From 3ac4dd3dcc872e9322510e5ebe14d8b8c8505e59 Mon Sep 17 00:00:00 2001 From: Jude Gao Date: Tue, 19 Nov 2024 15:00:10 -0500 Subject: [PATCH 1/9] v1 --- .../webpack/plugins/flight-manifest-plugin.ts | 16 ++++++- packages/next/src/export/index.ts | 1 + .../next/src/server/app-render/app-render.tsx | 3 +- .../create-component-styles-and-scripts.tsx | 12 +++-- .../app-render/create-component-tree.tsx | 5 ++- .../app-render/get-css-inlined-link-tags.tsx | 11 +++-- .../server/app-render/get-layer-assets.tsx | 44 ++++++++++++++----- packages/next/src/server/app-render/types.ts | 2 + .../walk-tree-with-flight-router-state.tsx | 3 +- packages/next/src/server/base-server.ts | 2 + packages/next/src/server/config-schema.ts | 1 + packages/next/src/server/config-shared.ts | 6 +++ .../e2e/app-dir/app-inline-css/app/global.css | 3 ++ .../e2e/app-dir/app-inline-css/app/layout.tsx | 9 ++++ test/e2e/app-dir/app-inline-css/app/page.tsx | 3 ++ test/e2e/app-dir/app-inline-css/index.test.ts | 17 +++++++ .../e2e/app-dir/app-inline-css/next.config.js | 5 +++ 17 files changed, 119 insertions(+), 24 deletions(-) create mode 100644 test/e2e/app-dir/app-inline-css/app/global.css create mode 100644 test/e2e/app-dir/app-inline-css/app/layout.tsx create mode 100644 test/e2e/app-dir/app-inline-css/app/page.tsx create mode 100644 test/e2e/app-dir/app-inline-css/index.test.ts create mode 100644 test/e2e/app-dir/app-inline-css/next.config.js diff --git a/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts index 55664a13e537c..3acf5352aae71 100644 --- a/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts @@ -83,6 +83,11 @@ export interface ClientReferenceManifestForRsc { } } +export interface EntryCssFile { + path: string + content: string +} + export interface ClientReferenceManifest extends ClientReferenceManifestForRsc { readonly moduleLoading: { prefix: string @@ -95,7 +100,7 @@ export interface ClientReferenceManifest extends ClientReferenceManifestForRsc { [moduleId: string]: ManifestNode } entryCSSFiles: { - [entry: string]: string[] + [entry: string]: EntryCssFile[] } entryJSFiles?: { [entry: string]: string[] @@ -296,9 +301,18 @@ export class ClientReferenceManifestPlugin { /[\\/]/g, path.sep ) + manifest.entryCSSFiles[chunkEntryName] = entrypoint .getFiles() .filter((f) => !f.startsWith('static/css/pages/') && f.endsWith('.css')) + .map((file) => { + const source = compilation.assets[file].source() + // TODO: only do this when inlineCss is enabled + return { + path: file, + content: typeof source === 'string' ? source : source.toString(), + } + }) const requiredChunks = getAppPathRequiredChunks(entrypoint, rootMainFiles) const recordModule = (modId: ModuleId, mod: webpack.NormalModule) => { diff --git a/packages/next/src/export/index.ts b/packages/next/src/export/index.ts index 66bd9af69eab7..7d346dc174d4a 100644 --- a/packages/next/src/export/index.ts +++ b/packages/next/src/export/index.ts @@ -358,6 +358,7 @@ async function exportAppImpl( expireTime: nextConfig.expireTime, after: nextConfig.experimental.after ?? false, dynamicIO: nextConfig.experimental.dynamicIO ?? false, + inlineCss: nextConfig.experimental.inlineCss ?? false, }, reactMaxHeadersLength: nextConfig.reactMaxHeadersLength, } diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index afcdd59e2ed59..e83a90867a764 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -21,6 +21,7 @@ import type { LoaderTree } from '../lib/app-dir-module' import type { AppPageModule } from '../route-modules/app-page/module' import type { ClientReferenceManifest, + EntryCssFile, ManifestNode, } from '../../build/webpack/plugins/flight-manifest-plugin' import type { DeepReadonly } from '../../shared/lib/deep-readonly' @@ -695,7 +696,7 @@ async function getRSCPayload( ctx: AppRenderContext, is404: boolean ): Promise { - const injectedCSS = new Set() + const injectedCSS = new Set() const injectedJS = new Set() const injectedFontPreloadTags = new Set() let missingSlots: Set | undefined diff --git a/packages/next/src/server/app-render/create-component-styles-and-scripts.tsx b/packages/next/src/server/app-render/create-component-styles-and-scripts.tsx index b3273c97b2cdb..c70124bc3a892 100644 --- a/packages/next/src/server/app-render/create-component-styles-and-scripts.tsx +++ b/packages/next/src/server/app-render/create-component-styles-and-scripts.tsx @@ -4,7 +4,9 @@ import { getLinkAndScriptTags } from './get-css-inlined-link-tags' import type { AppRenderContext } from './app-render' import { getAssetQueryString } from './get-asset-query-string' import { encodeURIPath } from '../../shared/lib/encode-uri-path' +import type { EntryCssFile } from '../../build/webpack/plugins/flight-manifest-plugin' +// [STEP 1] TODO: consolidate this with get-layer-assets.tsx export async function createComponentStylesAndScripts({ filePath, getComponent, @@ -14,7 +16,7 @@ export async function createComponentStylesAndScripts({ }: { filePath: string getComponent: () => any - injectedCSS: Set + injectedCSS: Set injectedJS: Set ctx: AppRenderContext }): Promise<[React.ComponentType, React.ReactNode, React.ReactNode]> { @@ -26,9 +28,9 @@ export async function createComponentStylesAndScripts({ ) const styles = cssHrefs - ? cssHrefs.map((href, index) => { + ? cssHrefs.map((entryCssFile, index) => { const fullHref = `${ctx.assetPrefix}/_next/${encodeURIPath( - href + entryCssFile.path )}${getAssetQueryString(ctx, true)}` // `Precedence` is an opt-in signal for React to handle resource @@ -38,7 +40,9 @@ export async function createComponentStylesAndScripts({ // for different stylesheets, so their order will be kept. // https://github.com/facebook/react/pull/25060 const precedence = - process.env.NODE_ENV === 'development' ? 'next_' + href : 'next' + process.env.NODE_ENV === 'development' + ? 'next_' + entryCssFile.path + : 'next' return ( + injectedCSS: Set injectedJS: Set injectedFontPreloadTags: Set getMetadataReady: () => Promise @@ -80,7 +81,7 @@ async function createComponentTreeInternal({ parentParams: Params rootLayoutIncluded: boolean firstItem?: boolean - injectedCSS: Set + injectedCSS: Set injectedJS: Set injectedFontPreloadTags: Set getMetadataReady: () => Promise diff --git a/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx b/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx index b177883e371fa..506d2887814d1 100644 --- a/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx +++ b/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx @@ -1,4 +1,7 @@ -import type { ClientReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin' +import type { + ClientReferenceManifest, + EntryCssFile, +} from '../../build/webpack/plugins/flight-manifest-plugin' import type { DeepReadonly } from '../../shared/lib/deep-readonly' /** @@ -7,12 +10,12 @@ import type { DeepReadonly } from '../../shared/lib/deep-readonly' export function getLinkAndScriptTags( clientReferenceManifest: DeepReadonly, filePath: string, - injectedCSS: Set, + injectedCSS: Set, injectedScripts: Set, collectNewImports?: boolean -): { styles: string[]; scripts: string[] } { +): { styles: EntryCssFile[]; scripts: string[] } { const filePathWithoutExt = filePath.replace(/\.[^.]+$/, '') - const cssChunks = new Set() + const cssChunks = new Set() const jsChunks = new Set() const entryCSSFiles = diff --git a/packages/next/src/server/app-render/get-layer-assets.tsx b/packages/next/src/server/app-render/get-layer-assets.tsx index b69483e01bf60..9d922338b90cf 100644 --- a/packages/next/src/server/app-render/get-layer-assets.tsx +++ b/packages/next/src/server/app-render/get-layer-assets.tsx @@ -5,6 +5,7 @@ import type { AppRenderContext } from './app-render' import { getAssetQueryString } from './get-asset-query-string' import { encodeURIPath } from '../../shared/lib/encode-uri-path' import type { PreloadCallbacks } from './types' +import type { EntryCssFile } from '../../build/webpack/plugins/flight-manifest-plugin' export function getLayerAssets({ ctx, @@ -15,7 +16,7 @@ export function getLayerAssets({ preloadCallbacks, }: { layoutOrPagePath: string | undefined - injectedCSS: Set + injectedCSS: Set injectedJS: Set injectedFontPreloadTags: Set ctx: AppRenderContext @@ -73,7 +74,18 @@ export function getLayerAssets({ } const styles = styleTags - ? styleTags.map((href, index) => { + ? styleTags.map((entryCssFile, index) => { + // `Precedence` is an opt-in signal for React to handle resource + // loading and deduplication, etc. It's also used as the key to sort + // resources so they will be injected in the correct order. + // During HMR, it's critical to use different `precedence` values + // for different stylesheets, so their order will be kept. + // https://github.com/facebook/react/pull/25060 + const precedence = + process.env.NODE_ENV === 'development' + ? 'next_' + entryCssFile.path + : 'next' + // In dev, Safari and Firefox will cache the resource during HMR: // - https://github.com/vercel/next.js/issues/5860 // - https://bugs.webkit.org/show_bug.cgi?id=187726 @@ -81,17 +93,26 @@ export function getLayerAssets({ // development. We need to also make sure that the number is always // increasing. const fullHref = `${ctx.assetPrefix}/_next/${encodeURIPath( - href + entryCssFile.path )}${getAssetQueryString(ctx, true)}` - // `Precedence` is an opt-in signal for React to handle resource - // loading and deduplication, etc. It's also used as the key to sort - // resources so they will be injected in the correct order. - // During HMR, it's critical to use different `precedence` values - // for different stylesheets, so their order will be kept. - // https://github.com/facebook/react/pull/25060 - const precedence = - process.env.NODE_ENV === 'development' ? 'next_' + href : 'next' + if ( + process.env.NEXT_RUNTIME !== 'edge' && + ctx.renderOpts.experimental.inlineCss + ) { + return ( + ) } From 4cbdddd04219ea653df32cdadf9992afcaef7ce1 Mon Sep 17 00:00:00 2001 From: Jude Gao Date: Wed, 20 Nov 2024 14:41:27 -0500 Subject: [PATCH 8/9] disable inline at source --- crates/next-api/src/app.rs | 1 + .../src/next_manifests/client_reference_manifest.rs | 5 ++++- .../src/build/webpack/plugins/flight-manifest-plugin.ts | 7 ++++++- .../next/src/server/app-render/render-css-resource.tsx | 7 +------ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/next-api/src/app.rs b/crates/next-api/src/app.rs index 8f52c25f35926..df2876bea5f48 100644 --- a/crates/next-api/src/app.rs +++ b/crates/next-api/src/app.rs @@ -1190,6 +1190,7 @@ impl AppEndpoint { ssr_chunking_context, this.app_project.project().next_config(), runtime, + this.app_project.project().next_mode(), ) .to_resolved() .await?; diff --git a/crates/next-core/src/next_manifests/client_reference_manifest.rs b/crates/next-core/src/next_manifests/client_reference_manifest.rs index 692e05bc33a0e..3ad123c852dcf 100644 --- a/crates/next-core/src/next_manifests/client_reference_manifest.rs +++ b/crates/next-core/src/next_manifests/client_reference_manifest.rs @@ -16,6 +16,7 @@ use turbopack_ecmascript::utils::StringifyJs; use super::{ClientReferenceManifest, CssResource, ManifestNode, ManifestNodeEntry, ModuleId}; use crate::{ + mode::NextMode, next_app::ClientReferencesChunks, next_client_reference::{ClientReferenceGraphResult, ClientReferenceType}, next_config::NextConfig, @@ -37,6 +38,7 @@ impl ClientReferenceManifest { ssr_chunking_context: Option>>, next_config: Vc, runtime: NextRuntime, + mode: Vc, ) -> Result>> { let mut entry_manifest: ClientReferenceManifest = Default::default(); let mut references = FxIndexSet::default(); @@ -271,7 +273,8 @@ impl ClientReferenceManifest { } } - let inlined = next_config.await?.experimental.inline_css.unwrap_or(false); + let inlined = next_config.await?.experimental.inline_css.unwrap_or(false) + && mode.await?.is_production(); let entry_css_files_vec = entry_css_files_with_chunk .into_iter() .map(|(path, chunk)| async { diff --git a/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts index ebdc43c1987f9..81f7f944329f7 100644 --- a/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts @@ -318,7 +318,12 @@ export class ClientReferenceManifestPlugin { .filter((f) => !f.startsWith('static/css/pages/') && f.endsWith('.css')) .map((file) => { const source = compilation.assets[file].source() - if (this.experimentalInlineCss) { + if ( + this.experimentalInlineCss && + // Inline CSS currently does not work in Turbopack HMR, so we only inline + // CSS in production. + process.env.NODE_ENV === 'production' + ) { return { inlined: true, path: file, diff --git a/packages/next/src/server/app-render/render-css-resource.tsx b/packages/next/src/server/app-render/render-css-resource.tsx index cf175d6b3ed94..9ed009fa6c991 100644 --- a/packages/next/src/server/app-render/render-css-resource.tsx +++ b/packages/next/src/server/app-render/render-css-resource.tsx @@ -36,12 +36,7 @@ export function renderCssResource( entryCssFile.path )}${getAssetQueryString(ctx, true)}` - if ( - entryCssFile.inlined && - // Inline CSS currently does not work in Turbopack HMR, so we only inline - // CSS in production. - process.env.NODE_ENV === 'production' - ) { + if (entryCssFile.inlined) { return (