Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS modules support improvements for Server Components #38536

Merged
merged 4 commits into from Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 35 additions & 15 deletions packages/next/build/webpack/config/blocks/css/index.ts
Expand Up @@ -249,22 +249,24 @@ export const css = curry(async function css(
})
)

// Throw an error for CSS Modules used outside their supported scope
fns.push(
loader({
oneOf: [
markRemovable({
test: [regexCssModules, regexSassModules],
use: {
loader: 'error-loader',
options: {
reason: getLocalModuleImportError(),
if (!ctx.experimental.appDir) {
// Throw an error for CSS Modules used outside their supported scope
fns.push(
loader({
oneOf: [
markRemovable({
test: [regexCssModules, regexSassModules],
use: {
loader: 'error-loader',
options: {
reason: getLocalModuleImportError(),
},
},
},
}),
],
})
)
}),
],
})
)
}

if (ctx.isServer) {
fns.push(
Expand Down Expand Up @@ -371,6 +373,24 @@ export const css = curry(async function css(
],
})
)
fns.push(
loader({
oneOf: [
markRemovable({
sideEffects: false,
test: regexCssModules,
issuer: {
or: [
{ and: [ctx.rootDirectory, /\.(js|mjs|jsx|ts|tsx)$/] },
// Also match the virtual client entry which doesn't have file path
(filePath) => !filePath,
],
},
use: getCssModuleLoader(ctx, lazyPostCSSInitializer),
}),
],
})
)
}
}

Expand Down
@@ -1,14 +1,15 @@
import { SERVER_RUNTIME } from '../../../lib/constants'

export default async function transformSource(this: any): Promise<string> {
let { modules, runtime, ssr } = this.getOptions()
let { modules, runtime, ssr, server } = this.getOptions()
if (!Array.isArray(modules)) {
modules = modules ? [modules] : []
}

const requests = modules as string[]
const code =
requests
.filter((request) => (server ? !request.endsWith('.css') : true))
.map((request) => `import(/* webpackMode: "eager" */ '${request}')`)
.join(';\n') +
`
Expand Down
15 changes: 11 additions & 4 deletions packages/next/build/webpack/plugins/client-entry-plugin.ts
Expand Up @@ -22,7 +22,7 @@ type Options = {
const PLUGIN_NAME = 'ClientEntryPlugin'

export const injectedClientEntries = new Map()
const regexCssGlobal = /(?<!\.module)\.css$/
const regexCSS = /\.css$/

export class ClientEntryPlugin {
dev: boolean = false
Expand Down Expand Up @@ -84,7 +84,7 @@ export class ClientEntryPlugin {

if (
clientComponentRegex.test(modRequest) ||
regexCssGlobal.test(modRequest)
regexCSS.test(modRequest)
) {
clientComponentImports.push(modRequest)
}
Expand Down Expand Up @@ -115,14 +115,21 @@ export class ClientEntryPlugin {
isDev: this.dev,
})

const clientLoader = `next-flight-client-entry-loader?${stringify({
const loaderOptions = {
modules: clientComponentImports,
runtime: this.isEdgeServer
? SERVER_RUNTIME.edge
: SERVER_RUNTIME.nodejs,
ssr: pageStaticInfo.ssr,
// Adding name here to make the entry key unique.
name,
}
const clientLoader = `next-flight-client-entry-loader?${stringify(
loaderOptions
)}!`
const clientSSRLoader = `next-flight-client-entry-loader?${stringify({
...loaderOptions,
server: true,
})}!`

const bundlePath = 'app' + normalizePagePath(routeInfo.page)
Expand Down Expand Up @@ -157,7 +164,7 @@ export class ClientEntryPlugin {
// Inject the entry to the server compiler (__sc_client__).
const clientComponentEntryDep = (
webpack as any
).EntryPlugin.createDependency(clientLoader, {
).EntryPlugin.createDependency(clientSSRLoader, {
name: name + NEXT_CLIENT_SSR_ENTRY_SUFFIX,
})
promises.push(
Expand Down
Expand Up @@ -93,7 +93,7 @@ export class FlightManifestPlugin {

if (
mod.request &&
/(?<!\.module)\.css$/.test(mod.request) &&
/\.css$/.test(mod.request) &&
(dev
? mod.loaders.some((item: any) =>
item.loader.includes('next-style-loader/index.js')
Expand Down
26 changes: 17 additions & 9 deletions packages/next/server/app-render.tsx
Expand Up @@ -16,7 +16,7 @@ import {
renderToInitialStream,
createBufferedTransformStream,
continueFromInitialStream,
createSuffixStream,
createPrefixStream,
} from './node-web-streams-helper'
import { isDynamicRoute } from '../shared/lib/router/utils'
import { tryGetPreviewData } from './api-utils/node'
Expand Down Expand Up @@ -113,7 +113,8 @@ function useFlightResponse(
writable: WritableStream<Uint8Array>,
cachePrefix: string,
req: ReadableStream<Uint8Array>,
serverComponentManifest: any
serverComponentManifest: any,
cssFlightData: string
) {
const id = cachePrefix + ',' + (React as any).useId()
let entry = rscCache.get(id)
Expand All @@ -125,7 +126,10 @@ function useFlightResponse(
rscCache.set(id, entry)

let bootstrapped = false
const forwardReader = forwardStream.getReader()
// We only attach CSS chunks to the inlined data.
const forwardReader = forwardStream
.pipeThrough(createPrefixStream(cssFlightData))
.getReader()
const writer = writable.getWriter()
function process() {
forwardReader.read().then(({ done, value }) => {
Expand Down Expand Up @@ -188,7 +192,7 @@ function createServerComponentRenderer(
globalThis.__next_chunk_load__ = () => Promise.resolve()
}

const cssFlight = getCssFlight(ComponentMod, serverComponentManifest)
const cssFlightData = getCssFlightData(ComponentMod, serverComponentManifest)

let RSCStream: ReadableStream<Uint8Array>
const createRSCStream = () => {
Expand All @@ -199,7 +203,7 @@ function createServerComponentRenderer(
{
context: serverContexts,
}
).pipeThrough(createSuffixStream(cssFlight))
)
}
return RSCStream
}
Expand All @@ -211,7 +215,8 @@ function createServerComponentRenderer(
writable,
cachePrefix,
reqStream,
serverComponentManifest
serverComponentManifest,
cssFlightData
)
const root = response.readRoot()
return root
Expand Down Expand Up @@ -322,7 +327,7 @@ function getSegmentParam(segment: string): {
return null
}

function getCssFlight(ComponentMod: any, serverComponentManifest: any) {
function getCssFlightData(ComponentMod: any, serverComponentManifest: any) {
const importedServerCSSFiles: string[] =
ComponentMod.__client__?.__next_rsc_css__ || []

Expand Down Expand Up @@ -760,7 +765,10 @@ export async function renderToHTML(
return [actualSegment]
}

const cssFlight = getCssFlight(ComponentMod, serverComponentManifest)
const cssFlightData = getCssFlightData(
ComponentMod,
serverComponentManifest
)
const flightData: FlightData = [
// TODO: change walk to output without ''
walkTreeWithFlightRouterState(tree, {}, providedFlightRouterState).slice(
Expand All @@ -770,7 +778,7 @@ export async function renderToHTML(

return new RenderResult(
renderToReadableStream(flightData, serverComponentManifest)
.pipeThrough(createSuffixStream(cssFlight))
.pipeThrough(createPrefixStream(cssFlightData))
.pipeThrough(createBufferedTransformStream())
)
}
Expand Down
12 changes: 12 additions & 0 deletions test/e2e/app-dir/app/app/css/css-nested/layout.client.js
@@ -0,0 +1,12 @@
import './style.css'
import styles from './style.module.css'

export default function ClientLayout({ children }) {
return (
<>
<div className={styles['client-css']}>Client Layout: CSS Modules</div>
<div className="client-css">Client Layout: Global CSS</div>
{children}
</>
)
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/css-nested/page.client.js
@@ -0,0 +1,3 @@
export default function Page() {
return null
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/css-nested/style.css
@@ -0,0 +1,3 @@
.client-css {
color: green;
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/css-nested/style.module.css
@@ -0,0 +1,3 @@
.client-css {
color: green;
}
14 changes: 14 additions & 0 deletions test/e2e/app-dir/app/app/css/layout.server.js
@@ -0,0 +1,14 @@
import './style.css'
import styles from './style.module.css'

export default function ServerLayout({ children }) {
return (
<>
<div id="server-cssm" className={styles['server-css']}>
Server Layout: CSS Modules
</div>
<div className="server-css">Server Layout: Global CSS</div>
{children}
</>
)
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/style.css
@@ -0,0 +1,3 @@
.server-css {
color: green;
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app/app/css/style.module.css
@@ -0,0 +1,3 @@
.server-css {
color: green;
}