From 55b0d2535f08a19963099fa80e691e21acfabdfc Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 12 Jul 2022 01:33:10 +0200 Subject: [PATCH 1/3] cssm in server components --- .../build/webpack/config/blocks/css/index.ts | 50 +++++++++++++------ .../next-flight-client-entry-loader.ts | 3 +- .../webpack/plugins/client-entry-plugin.ts | 15 ++++-- .../webpack/plugins/flight-manifest-plugin.ts | 2 +- packages/next/server/app-render.tsx | 26 ++++++---- .../app/app/css/css-nested/layout.client.js | 12 +++++ .../app/app/css/css-nested/page.client.js | 3 ++ .../app-dir/app/app/css/css-nested/style.css | 3 ++ .../app/app/css/css-nested/style.module.css | 3 ++ test/e2e/app-dir/app/app/css/layout.server.js | 12 +++++ test/e2e/app-dir/app/app/css/style.css | 3 ++ test/e2e/app-dir/app/app/css/style.module.css | 3 ++ 12 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 test/e2e/app-dir/app/app/css/css-nested/layout.client.js create mode 100644 test/e2e/app-dir/app/app/css/css-nested/page.client.js create mode 100644 test/e2e/app-dir/app/app/css/css-nested/style.css create mode 100644 test/e2e/app-dir/app/app/css/css-nested/style.module.css create mode 100644 test/e2e/app-dir/app/app/css/layout.server.js create mode 100644 test/e2e/app-dir/app/app/css/style.css create mode 100644 test/e2e/app-dir/app/app/css/style.module.css diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts index a674efec6527..5499e90cc227 100644 --- a/packages/next/build/webpack/config/blocks/css/index.ts +++ b/packages/next/build/webpack/config/blocks/css/index.ts @@ -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( @@ -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), + }), + ], + }) + ) } } diff --git a/packages/next/build/webpack/loaders/next-flight-client-entry-loader.ts b/packages/next/build/webpack/loaders/next-flight-client-entry-loader.ts index cd9f02d1210d..baec601211f5 100644 --- a/packages/next/build/webpack/loaders/next-flight-client-entry-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-client-entry-loader.ts @@ -1,7 +1,7 @@ import { SERVER_RUNTIME } from '../../../lib/constants' export default async function transformSource(this: any): Promise { - let { modules, runtime, ssr } = this.getOptions() + let { modules, runtime, ssr, server } = this.getOptions() if (!Array.isArray(modules)) { modules = modules ? [modules] : [] } @@ -9,6 +9,7 @@ export default async function transformSource(this: any): Promise { const requests = modules as string[] const code = requests + .filter((request) => (server ? !request.endsWith('.css') : true)) .map((request) => `import(/* webpackMode: "eager" */ '${request}')`) .join(';\n') + ` diff --git a/packages/next/build/webpack/plugins/client-entry-plugin.ts b/packages/next/build/webpack/plugins/client-entry-plugin.ts index 93f1cb29801a..46aef7ee4e82 100644 --- a/packages/next/build/webpack/plugins/client-entry-plugin.ts +++ b/packages/next/build/webpack/plugins/client-entry-plugin.ts @@ -22,7 +22,7 @@ type Options = { const PLUGIN_NAME = 'ClientEntryPlugin' export const injectedClientEntries = new Map() -const regexCssGlobal = /(? item.loader.includes('next-style-loader/index.js') diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index c1b1059af191..538adc025a8e 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -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' @@ -113,7 +113,8 @@ function useFlightResponse( writable: WritableStream, cachePrefix: string, req: ReadableStream, - serverComponentManifest: any + serverComponentManifest: any, + cssFlightData: string ) { const id = cachePrefix + ',' + (React as any).useId() let entry = rscCache.get(id) @@ -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 }) => { @@ -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 const createRSCStream = () => { @@ -199,7 +203,7 @@ function createServerComponentRenderer( { context: serverContexts, } - ).pipeThrough(createSuffixStream(cssFlight)) + ) } return RSCStream } @@ -211,7 +215,8 @@ function createServerComponentRenderer( writable, cachePrefix, reqStream, - serverComponentManifest + serverComponentManifest, + cssFlightData ) const root = response.readRoot() return root @@ -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__ || [] @@ -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( @@ -770,7 +778,7 @@ export async function renderToHTML( return new RenderResult( renderToReadableStream(flightData, serverComponentManifest) - .pipeThrough(createSuffixStream(cssFlight)) + .pipeThrough(createPrefixStream(cssFlightData)) .pipeThrough(createBufferedTransformStream()) ) } diff --git a/test/e2e/app-dir/app/app/css/css-nested/layout.client.js b/test/e2e/app-dir/app/app/css/css-nested/layout.client.js new file mode 100644 index 000000000000..8e132c3b51a9 --- /dev/null +++ b/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 ( + <> +
Client Layout: CSS Modules
+
Client Layout: Global CSS
+ {children} + + ) +} diff --git a/test/e2e/app-dir/app/app/css/css-nested/page.client.js b/test/e2e/app-dir/app/app/css/css-nested/page.client.js new file mode 100644 index 000000000000..c17431379f96 --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-nested/page.client.js @@ -0,0 +1,3 @@ +export default function Page() { + return null +} diff --git a/test/e2e/app-dir/app/app/css/css-nested/style.css b/test/e2e/app-dir/app/app/css/css-nested/style.css new file mode 100644 index 000000000000..3eeea957744d --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-nested/style.css @@ -0,0 +1,3 @@ +.client-css { + color: green; +} diff --git a/test/e2e/app-dir/app/app/css/css-nested/style.module.css b/test/e2e/app-dir/app/app/css/css-nested/style.module.css new file mode 100644 index 000000000000..3eeea957744d --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-nested/style.module.css @@ -0,0 +1,3 @@ +.client-css { + color: green; +} diff --git a/test/e2e/app-dir/app/app/css/layout.server.js b/test/e2e/app-dir/app/app/css/layout.server.js new file mode 100644 index 000000000000..edf63263f6dd --- /dev/null +++ b/test/e2e/app-dir/app/app/css/layout.server.js @@ -0,0 +1,12 @@ +import './style.css' +import styles from './style.module.css' + +export default function ServerLayout({ children }) { + return ( + <> +
Server Layout: CSS Modules
+
Server Layout: Global CSS
+ {children} + + ) +} diff --git a/test/e2e/app-dir/app/app/css/style.css b/test/e2e/app-dir/app/app/css/style.css new file mode 100644 index 000000000000..efd81fda0528 --- /dev/null +++ b/test/e2e/app-dir/app/app/css/style.css @@ -0,0 +1,3 @@ +.server-css { + color: green; +} diff --git a/test/e2e/app-dir/app/app/css/style.module.css b/test/e2e/app-dir/app/app/css/style.module.css new file mode 100644 index 000000000000..efd81fda0528 --- /dev/null +++ b/test/e2e/app-dir/app/app/css/style.module.css @@ -0,0 +1,3 @@ +.server-css { + color: green; +} From d3f833fe77217fb83b829f72d9e8e9f8d14e1626 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Tue, 12 Jul 2022 01:38:35 +0200 Subject: [PATCH 2/3] add test --- test/e2e/app-dir/app/app/css/layout.server.js | 4 +- test/e2e/app-dir/index.test.ts | 116 ++++++++++-------- 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/test/e2e/app-dir/app/app/css/layout.server.js b/test/e2e/app-dir/app/app/css/layout.server.js index edf63263f6dd..2107d1daf36c 100644 --- a/test/e2e/app-dir/app/app/css/layout.server.js +++ b/test/e2e/app-dir/app/app/css/layout.server.js @@ -4,7 +4,9 @@ import styles from './style.module.css' export default function ServerLayout({ children }) { return ( <> -
Server Layout: CSS Modules
+
+ Server Layout: CSS Modules +
Server Layout: Global CSS
{children} diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index 69ead33046f4..71580062cc74 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -393,66 +393,86 @@ describe('app dir', () => { }) describe('css support', () => { - it('should support global css inside server component layouts', async () => { - const browser = await webdriver(next.url, '/dashboard') + describe('server layouts', () => { + it('should support global css inside server layouts', async () => { + const browser = await webdriver(next.url, '/dashboard') + + // Should body text in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('.p')).color` + ) + ).toBe('rgb(255, 0, 0)') + + // Should inject global css for .green selectors + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('.green')).color` + ) + ).toBe('rgb(0, 128, 0)') + }) - // Should body text in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('.p')).color` - ) - ).toBe('rgb(255, 0, 0)') + it('should support css modules inside server layouts', async () => { + const browser = await webdriver(next.url, '/css/css-nested') + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('#server-cssm')).color` + ) + ).toBe('rgb(0, 255, 0)') + }) + }) - // Should inject global css for .green selectors - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('.green')).color` - ) - ).toBe('rgb(0, 128, 0)') + describe('server pages', () => { + it.todo('should support global css inside server pages', async () => {}) + it.todo('should support css modules inside server pages', async () => {}) }) - it('should support css modules inside client layouts', async () => { - const browser = await webdriver(next.url, '/client-nested') + describe('client layouts', () => { + it('should support css modules inside client layouts', async () => { + const browser = await webdriver(next.url, '/client-nested') - // Should render h1 in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('h1')).color` - ) - ).toBe('rgb(255, 0, 0)') - }) + // Should render h1 in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + ).toBe('rgb(255, 0, 0)') + }) - it('should support css modules inside client pages', async () => { - const browser = await webdriver(next.url, '/client-component-route') + it('should support global css inside client layouts', async () => { + const browser = await webdriver(next.url, '/client-nested') - // Should render p in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('p')).color` - ) - ).toBe('rgb(255, 0, 0)') + // Should render button in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('button')).color` + ) + ).toBe('rgb(255, 0, 0)') + }) }) - it('should support global css inside client layouts', async () => { - const browser = await webdriver(next.url, '/client-nested') + describe('client pages', () => { + it('should support css modules inside client pages', async () => { + const browser = await webdriver(next.url, '/client-component-route') - // Should render button in red - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('button')).color` - ) - ).toBe('rgb(255, 0, 0)') - }) + // Should render p in red + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('p')).color` + ) + ).toBe('rgb(255, 0, 0)') + }) - it('should support global css inside client pages', async () => { - const browser = await webdriver(next.url, '/client-component-route') + it('should support global css inside client pages', async () => { + const browser = await webdriver(next.url, '/client-component-route') - // Should render `b` in blue - expect( - await browser.eval( - `window.getComputedStyle(document.querySelector('b')).color` - ) - ).toBe('rgb(0, 0, 255)') + // Should render `b` in blue + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('b')).color` + ) + ).toBe('rgb(0, 0, 255)') + }) }) }) }) From 2691fd631fb415c8f3d17efe9d81d68eb0bd9fec Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 12 Jul 2022 11:04:38 +0200 Subject: [PATCH 3/3] fix tests --- test/e2e/app-dir/index.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index 71580062cc74..2bb7586f05f5 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -418,13 +418,13 @@ describe('app dir', () => { await browser.eval( `window.getComputedStyle(document.querySelector('#server-cssm')).color` ) - ).toBe('rgb(0, 255, 0)') + ).toBe('rgb(0, 128, 0)') }) }) - describe('server pages', () => { - it.todo('should support global css inside server pages', async () => {}) - it.todo('should support css modules inside server pages', async () => {}) + describe.skip('server pages', () => { + it('should support global css inside server pages', async () => {}) + it('should support css modules inside server pages', async () => {}) }) describe('client layouts', () => {