diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 09714824c9f24b..0d6c071d3eb789 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -1906,6 +1906,7 @@ export default async function getBaseWebpackConfig( ? new ClientReferenceManifestPlugin({ dev, appDir, + experimentalInlineCss: !!config.experimental.inlineCss, }) : new FlightClientEntryPlugin({ appDir, 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 3acf5352aae719..ebdc43c1987f92 100644 --- a/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts @@ -32,6 +32,7 @@ import type { ModuleInfo } from './flight-client-entry-plugin' interface Options { dev: boolean appDir: string + experimentalInlineCss: boolean } /** @@ -83,11 +84,19 @@ export interface ClientReferenceManifestForRsc { } } -export interface EntryCssFile { +export type CssResource = InlinedCssFile | UninlinedCssFile + +interface InlinedCssFile { path: string + inlined: true content: string } +interface UninlinedCssFile { + path: string + inlined: false +} + export interface ClientReferenceManifest extends ClientReferenceManifestForRsc { readonly moduleLoading: { prefix: string @@ -100,7 +109,7 @@ export interface ClientReferenceManifest extends ClientReferenceManifestForRsc { [moduleId: string]: ManifestNode } entryCSSFiles: { - [entry: string]: EntryCssFile[] + [entry: string]: CssResource[] } entryJSFiles?: { [entry: string]: string[] @@ -205,11 +214,13 @@ export class ClientReferenceManifestPlugin { dev: Options['dev'] = false appDir: Options['appDir'] appDirBase: string + experimentalInlineCss: Options['experimentalInlineCss'] constructor(options: Options) { this.dev = options.dev this.appDir = options.appDir this.appDirBase = path.dirname(this.appDir) + path.sep + this.experimentalInlineCss = options.experimentalInlineCss } apply(compiler: webpack.Compiler) { @@ -307,10 +318,16 @@ export class ClientReferenceManifestPlugin { .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 + if (this.experimentalInlineCss) { + return { + inlined: true, + path: file, + content: typeof source === 'string' ? source : source.toString(), + } + } return { + inlined: false, path: file, - content: typeof source === 'string' ? source : source.toString(), } }) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index d5e774d426b063..08d73ac5ee5760 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -21,7 +21,7 @@ import type { LoaderTree } from '../lib/app-dir-module' import type { AppPageModule } from '../route-modules/app-page/module' import type { ClientReferenceManifest, - EntryCssFile, + CssResource, ManifestNode, } from '../../build/webpack/plugins/flight-manifest-plugin' import type { DeepReadonly } from '../../shared/lib/deep-readonly' @@ -691,7 +691,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 c70124bc3a892c..ea296f709cb0e9 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,9 +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' +import type { CssResource } from '../../build/webpack/plugins/flight-manifest-plugin' +import { renderCssResource } from './render-css-resource' -// [STEP 1] TODO: consolidate this with get-layer-assets.tsx export async function createComponentStylesAndScripts({ filePath, getComponent, @@ -16,46 +16,18 @@ export async function createComponentStylesAndScripts({ }: { filePath: string getComponent: () => any - injectedCSS: Set + injectedCSS: Set injectedJS: Set ctx: AppRenderContext }): Promise<[React.ComponentType, React.ReactNode, React.ReactNode]> { - const { styles: cssHrefs, scripts: jsHrefs } = getLinkAndScriptTags( + const { styles: entryCssFiles, scripts: jsHrefs } = getLinkAndScriptTags( ctx.clientReferenceManifest, filePath, injectedCSS, injectedJS ) - const styles = cssHrefs - ? cssHrefs.map((entryCssFile, index) => { - const fullHref = `${ctx.assetPrefix}/_next/${encodeURIPath( - 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_' + entryCssFile.path - : 'next' - - return ( - - ) - }) - : null + const styles = renderCssResource(entryCssFiles, ctx) const scripts = jsHrefs ? jsHrefs.map((href, index) => ( diff --git a/packages/next/src/server/app-render/create-component-tree.tsx b/packages/next/src/server/app-render/create-component-tree.tsx index bc5022da9fa13a..2dbba9725e4080 100644 --- a/packages/next/src/server/app-render/create-component-tree.tsx +++ b/packages/next/src/server/app-render/create-component-tree.tsx @@ -22,7 +22,7 @@ import type { LoadingModuleData } from '../../shared/lib/app-router-context.shar import type { Params } from '../request/params' import { workUnitAsyncStorage } from './work-unit-async-storage.external' import { OUTLET_BOUNDARY_NAME } from '../../lib/metadata/metadata-constants' -import type { EntryCssFile } from '../../build/webpack/plugins/flight-manifest-plugin' +import type { CssResource } from '../../build/webpack/plugins/flight-manifest-plugin' /** * Use the provided loader tree to create the React Component tree. @@ -33,7 +33,7 @@ export function createComponentTree(props: { parentParams: Params rootLayoutIncluded: boolean firstItem?: boolean - injectedCSS: Set + injectedCSS: Set injectedJS: Set injectedFontPreloadTags: Set getMetadataReady: () => Promise @@ -81,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 506d2887814d1b..b8136413812b86 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,6 +1,6 @@ import type { ClientReferenceManifest, - EntryCssFile, + CssResource, } from '../../build/webpack/plugins/flight-manifest-plugin' import type { DeepReadonly } from '../../shared/lib/deep-readonly' @@ -10,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: EntryCssFile[]; scripts: string[] } { +): { styles: CssResource[]; 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 9d922338b90cfe..b6e4324b23199a 100644 --- a/packages/next/src/server/app-render/get-layer-assets.tsx +++ b/packages/next/src/server/app-render/get-layer-assets.tsx @@ -5,7 +5,8 @@ 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' +import type { CssResource } from '../../build/webpack/plugins/flight-manifest-plugin' +import { renderCssResource } from './render-css-resource' export function getLayerAssets({ ctx, @@ -16,7 +17,7 @@ export function getLayerAssets({ preloadCallbacks, }: { layoutOrPagePath: string | undefined - injectedCSS: Set + injectedCSS: Set injectedJS: Set injectedFontPreloadTags: Set ctx: AppRenderContext @@ -73,68 +74,7 @@ export function getLayerAssets({ } } - const styles = styleTags - ? 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 - // Because of this, we add a `?v=` query to bypass the cache during - // development. We need to also make sure that the number is always - // increasing. - const fullHref = `${ctx.assetPrefix}/_next/${encodeURIPath( - entryCssFile.path - )}${getAssetQueryString(ctx, true)}` - - if ( - process.env.NEXT_RUNTIME !== 'edge' && - ctx.renderOpts.experimental.inlineCss - ) { - return ( -