Skip to content

Commit

Permalink
CSS modules support improvements for Server Components (#38536)
Browse files Browse the repository at this point in the history
Improve CSS modules support in server components.

## Bug

- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)


Co-authored-by: Jiachi Liu <4800338+huozhi@users.noreply.github.com>
  • Loading branch information
shuding and huozhi committed Jul 12, 2022
1 parent 9342a6c commit c2b40d0
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 78 deletions.
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;
}

0 comments on commit c2b40d0

Please sign in to comment.