diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index acd695ece19157b..f58536b2eb7f4c6 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -212,6 +212,7 @@ export function getEdgeServerEntry(opts: { stringifiedConfig: JSON.stringify(opts.config), pagesType: opts.pagesType, appDirLoader: Buffer.from(opts.appDirLoader || '').toString('base64'), + sriEnabled: !opts.isDev && !!opts.config.experimental.sri?.algorithm, } return { diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index fa114b46ef03be8..5e04a963c108d3b 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1082,13 +1082,13 @@ export async function isPageStatic({ getStaticProps: mod.getStaticProps, } } else { - componentsResult = await loadComponents( + componentsResult = await loadComponents({ distDir, - page, + pathname: page, serverless, - false, - false - ) + hasServerComponents: false, + isAppPath: false, + }) } const Comp = componentsResult.Component @@ -1214,13 +1214,13 @@ export async function hasCustomGetInitialProps( ): Promise { require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig) - const components = await loadComponents( + const components = await loadComponents({ distDir, - page, - isLikeServerless, - false, - false - ) + pathname: page, + serverless: isLikeServerless, + hasServerComponents: false, + isAppPath: false, + }) let mod = components.ComponentMod if (checkingApp) { @@ -1239,13 +1239,13 @@ export async function getNamedExports( runtimeEnvConfig: any ): Promise> { require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig) - const components = await loadComponents( + const components = await loadComponents({ distDir, - page, - isLikeServerless, - false, - false - ) + pathname: page, + serverless: isLikeServerless, + hasServerComponents: false, + isAppPath: false, + }) let mod = components.ComponentMod return Object.keys(mod) diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 295a0628e46235f..452a2afdc307b91 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1751,7 +1751,11 @@ export default async function getBaseWebpackConfig( }), // MiddlewarePlugin should be after DefinePlugin so NEXT_PUBLIC_* // replacement is done before its process.env.* handling - isEdgeServer && new MiddlewarePlugin({ dev }), + isEdgeServer && + new MiddlewarePlugin({ + dev, + sriEnabled: !dev && !!config.experimental.sri?.algorithm, + }), isClient && new BuildManifestPlugin({ buildId, @@ -1803,7 +1807,7 @@ export default async function getBaseWebpackConfig( })), !dev && isClient && - config.experimental.sri?.algorithm && + !!config.experimental.sri?.algorithm && new SubresourceIntegrityPlugin(config.experimental.sri.algorithm), !dev && isClient && diff --git a/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts b/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts index 527aae70d77dc26..cd47aadac3117d6 100644 --- a/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-edge-ssr-loader/index.ts @@ -14,6 +14,7 @@ export type EdgeSSRLoaderQuery = { stringifiedConfig: string appDirLoader?: string pagesType?: 'app' | 'pages' | 'root' + sriEnabled: boolean } export default async function edgeSSRLoader(this: any) { @@ -30,6 +31,7 @@ export default async function edgeSSRLoader(this: any) { stringifiedConfig, appDirLoader: appDirLoaderBase64, pagesType, + sriEnabled, } = this.getOptions() const appDirLoader = Buffer.from( @@ -94,6 +96,9 @@ export default async function edgeSSRLoader(this: any) { const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST const rscManifest = self.__RSC_MANIFEST const rscCssManifest = self.__RSC_CSS_MANIFEST + const subresourceIntegrityManifest = ${ + sriEnabled ? 'self.__SUBRESOURCE_INTEGRITY_MANIFEST' : 'undefined' + } const render = getRender({ dev: ${dev}, @@ -109,6 +114,7 @@ export default async function edgeSSRLoader(this: any) { reactLoadableManifest, serverComponentManifest: ${isServerComponent} ? rscManifest : null, serverCSSManifest: ${isServerComponent} ? rscCssManifest : null, + subresourceIntegrityManifest, config: ${stringifiedConfig}, buildId: ${JSON.stringify(buildId)}, }) diff --git a/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts index 2424b0cdffbe32a..49d335551858e78 100644 --- a/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -23,6 +23,7 @@ export function getRender({ appRenderToHTML, pagesRenderToHTML, serverComponentManifest, + subresourceIntegrityManifest, serverCSSManifest, config, buildId, @@ -38,6 +39,7 @@ export function getRender({ Document: DocumentType buildManifest: BuildManifest reactLoadableManifest: ReactLoadableManifest + subresourceIntegrityManifest?: Record serverComponentManifest: any serverCSSManifest: any appServerMod: any @@ -48,6 +50,7 @@ export function getRender({ dev, buildManifest, reactLoadableManifest, + subresourceIntegrityManifest, Document, App: appMod.default as AppType, } diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index 82b94f8b410105d..eed49538c317864 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -17,6 +17,7 @@ import { MIDDLEWARE_REACT_LOADABLE_MANIFEST, NEXT_CLIENT_SSR_ENTRY_SUFFIX, FLIGHT_SERVER_CSS_MANIFEST, + SUBRESOURCE_INTEGRITY_MANIFEST, } from '../../../shared/lib/constants' export interface EdgeFunctionDefinition { @@ -80,12 +81,19 @@ function isUsingIndirectEvalAndUsedByExports(args: { return false } -function getEntryFiles(entryFiles: string[], meta: EntryMetadata) { +function getEntryFiles( + entryFiles: string[], + meta: EntryMetadata, + opts: { sriEnabled: boolean } +) { const files: string[] = [] if (meta.edgeSSR) { if (meta.edgeSSR.isServerComponent) { files.push(`server/${FLIGHT_MANIFEST}.js`) files.push(`server/${FLIGHT_SERVER_CSS_MANIFEST}.js`) + if (opts.sriEnabled) { + files.push(`server/${SUBRESOURCE_INTEGRITY_MANIFEST}.js`) + } files.push( ...entryFiles .filter( @@ -118,8 +126,9 @@ function getEntryFiles(entryFiles: string[], meta: EntryMetadata) { function getCreateAssets(params: { compilation: webpack.Compilation metadataByEntry: Map + opts: { sriEnabled: boolean } }) { - const { compilation, metadataByEntry } = params + const { compilation, metadataByEntry, opts } = params return (assets: any) => { for (const entrypoint of compilation.entrypoints.values()) { if (!entrypoint.name) { @@ -145,7 +154,7 @@ function getCreateAssets(params: { const edgeFunctionDefinition: EdgeFunctionDefinition = { env: Array.from(metadata.env), - files: getEntryFiles(entrypoint.getFiles(), metadata), + files: getEntryFiles(entrypoint.getFiles(), metadata, opts), name: entrypoint.name, page: page, matchers, @@ -708,13 +717,15 @@ function getExtractMetadata(params: { } export default class MiddlewarePlugin { - dev: boolean + private readonly dev: boolean + private readonly sriEnabled: boolean - constructor({ dev }: { dev: boolean }) { + constructor({ dev, sriEnabled }: { dev: boolean; sriEnabled: boolean }) { this.dev = dev + this.sriEnabled = sriEnabled } - apply(compiler: webpack.Compiler) { + public apply(compiler: webpack.Compiler) { compiler.hooks.compilation.tap(NAME, (compilation, params) => { const { hooks } = params.normalModuleFactory /** @@ -751,7 +762,11 @@ export default class MiddlewarePlugin { name: 'NextJsMiddlewareManifest', stage: (webpack as any).Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, }, - getCreateAssets({ compilation, metadataByEntry }) + getCreateAssets({ + compilation, + metadataByEntry, + opts: { sriEnabled: this.sriEnabled }, + }) ) }) } diff --git a/packages/next/build/webpack/plugins/subresource-integrity-plugin.ts b/packages/next/build/webpack/plugins/subresource-integrity-plugin.ts index 63d6a260c650224..58f085f5ae21253 100644 --- a/packages/next/build/webpack/plugins/subresource-integrity-plugin.ts +++ b/packages/next/build/webpack/plugins/subresource-integrity-plugin.ts @@ -53,9 +53,17 @@ export class SubresourceIntegrityPlugin { } const json = JSON.stringify(hashes, null, 2) - assets[SUBRESOURCE_INTEGRITY_MANIFEST] = new sources.RawSource( + const file = 'server/' + SUBRESOURCE_INTEGRITY_MANIFEST + assets[file + '.js'] = new sources.RawSource( + 'self.__SUBRESOURCE_INTEGRITY_MANIFEST=' + json + // Work around webpack 4 type of RawSource being used + // TODO: use webpack 5 type by default + ) as unknown as webpack.sources.RawSource + assets[file + '.json'] = new sources.RawSource( json - ) as any + // Work around webpack 4 type of RawSource being used + // TODO: use webpack 5 type by default + ) as unknown as webpack.sources.RawSource } ) }) diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index 51655364d667927..66d43f35dc00272 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -290,13 +290,13 @@ export default async function exportPage({ getServerSideProps, getStaticProps, pageConfig, - } = await loadComponents( + } = await loadComponents({ distDir, - page, + pathname: page, serverless, - !!serverComponents, - isAppPath - ) + hasServerComponents: !!serverComponents, + isAppPath, + }) const ampState = { ampFirst: pageConfig?.amp === true, hasQuery: Boolean(query.amp), @@ -357,13 +357,13 @@ export default async function exportPage({ throw new Error(`Failed to render serverless page`) } } else { - const components = await loadComponents( + const components = await loadComponents({ distDir, - page, + pathname: page, serverless, - !!serverComponents, - isAppPath - ) + hasServerComponents: !!serverComponents, + isAppPath, + }) const ampState = { ampFirst: components.pageConfig?.amp === true, hasQuery: Boolean(query.amp), diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 22ee91bedc9d9ea..b71191b4b720ac4 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -421,18 +421,15 @@ function getScriptNonceFromHeader(cspHeaderValue: string): string | undefined { // First try to find the directive for the 'script-src', otherwise try to // fallback to the 'default-src'. - let directive = directives.find((dir) => dir.startsWith('script-src')) - if (!directive) { - directive = directives.find((dir) => dir.startsWith('default-src')) - } + const directive = + directives.find((dir) => dir.startsWith('script-src')) || + directives.find((dir) => dir.startsWith('default-src')) // If no directive could be found, then we're done. if (!directive) { return } - debugger - // Extract the nonce from the directive const nonce = directive .split(' ') diff --git a/packages/next/server/dev/static-paths-worker.ts b/packages/next/server/dev/static-paths-worker.ts index 185948d7f2d146b..e56d018b1f26720 100644 --- a/packages/next/server/dev/static-paths-worker.ts +++ b/packages/next/server/dev/static-paths-worker.ts @@ -38,13 +38,13 @@ export async function loadStaticPaths( require('../../shared/lib/runtime-config').setConfig(config) setHttpAgentOptions(httpAgentOptions) - const components = await loadComponents( + const components = await loadComponents({ distDir, pathname, serverless, - false, - false - ) + hasServerComponents: false, + isAppPath: false, + }) if (!components.getStaticPaths) { // we shouldn't get to this point since the worker should diff --git a/packages/next/server/load-components.ts b/packages/next/server/load-components.ts index 068a85ed8405159..8f9f5431ff9e894 100644 --- a/packages/next/server/load-components.ts +++ b/packages/next/server/load-components.ts @@ -13,7 +13,6 @@ import { BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, FLIGHT_MANIFEST, - SUBRESOURCE_INTEGRITY_MANIFEST, } from '../shared/lib/constants' import { join } from 'path' import { requirePage } from './require' @@ -61,14 +60,19 @@ export async function loadDefaultErrorComponents(distDir: string) { } } -export async function loadComponents( - distDir: string, - pathname: string, - serverless: boolean, - hasServerComponents: boolean, - isAppPath: boolean, - sriEnabled?: boolean -): Promise { +export async function loadComponents({ + distDir, + pathname, + serverless, + hasServerComponents, + isAppPath, +}: { + distDir: string + pathname: string + serverless: boolean + hasServerComponents: boolean + isAppPath: boolean +}): Promise { if (serverless) { const ComponentMod = await requirePage(pathname, distDir, serverless) if (typeof ComponentMod === 'string') { @@ -118,21 +122,14 @@ export async function loadComponents( requirePage(pathname, distDir, serverless, isAppPath) ) - const [ - buildManifest, - reactLoadableManifest, - serverComponentManifest, - subresourceIntegrityManifest, - ] = await Promise.all([ - require(join(distDir, BUILD_MANIFEST)), - require(join(distDir, REACT_LOADABLE_MANIFEST)), - hasServerComponents - ? require(join(distDir, 'server', FLIGHT_MANIFEST + '.json')) - : null, - sriEnabled - ? require(join(distDir, SUBRESOURCE_INTEGRITY_MANIFEST)) - : undefined, - ]) + const [buildManifest, reactLoadableManifest, serverComponentManifest] = + await Promise.all([ + require(join(distDir, BUILD_MANIFEST)), + require(join(distDir, REACT_LOADABLE_MANIFEST)), + hasServerComponents + ? require(join(distDir, 'server', FLIGHT_MANIFEST + '.json')) + : null, + ]) const Component = interopDefault(ComponentMod) const Document = interopDefault(DocumentMod) @@ -145,7 +142,6 @@ export async function loadComponents( Document, Component, buildManifest, - subresourceIntegrityManifest, reactLoadableManifest, pageConfig: ComponentMod.config || {}, ComponentMod, diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index 6e49d314dc078fa..746338e060ef774 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -248,20 +248,20 @@ export default class NextNodeServer extends BaseServer { if (!options.dev) { // pre-warm _document and _app as these will be // needed for most requests - loadComponents( - this.distDir, - '/_document', - this._isLikeServerless, - false, - false - ).catch(() => {}) - loadComponents( - this.distDir, - '/_app', - this._isLikeServerless, - false, - false - ).catch(() => {}) + loadComponents({ + distDir: this.distDir, + pathname: '/_document', + serverless: this._isLikeServerless, + hasServerComponents: false, + isAppPath: false, + }).catch(() => {}) + loadComponents({ + distDir: this.distDir, + pathname: '/_app', + serverless: this._isLikeServerless, + hasServerComponents: false, + isAppPath: false, + }).catch(() => {}) } } @@ -934,40 +934,37 @@ export default class NextNodeServer extends BaseServer { params: Params | null isAppPath: boolean }): Promise { - let paths = [ + const paths: string[] = [pathname] + if (query.amp) { // try serving a static AMP version first - query.amp - ? (isAppPath - ? normalizeAppPath(pathname) - : normalizePagePath(pathname)) + '.amp' - : null, - pathname, - ].filter(Boolean) + paths.unshift( + (isAppPath ? normalizeAppPath(pathname) : normalizePagePath(pathname)) + + '.amp' + ) + } if (query.__nextLocale) { - paths = [ + paths.unshift( ...paths.map( (path) => `/${query.__nextLocale}${path === '/' ? '' : path}` - ), - ...paths, - ] + ) + ) } for (const pagePath of paths) { try { - const components = await loadComponents( - this.distDir, - pagePath!, - !this.renderOpts.dev && this._isLikeServerless, - !!this.renderOpts.serverComponents, - isAppPath, - !!this.nextConfig.experimental.sri?.algorithm - ) + const components = await loadComponents({ + distDir: this.distDir, + pathname: pagePath, + serverless: !this.renderOpts.dev && this._isLikeServerless, + hasServerComponents: !!this.renderOpts.serverComponents, + isAppPath: !!this.nextConfig.experimental.appDir, + }) if ( query.__nextLocale && typeof components.Component === 'string' && - !pagePath?.startsWith(`/${query.__nextLocale}`) + !pagePath.startsWith(`/${query.__nextLocale}`) ) { // if loading an static HTML file the locale is required // to be present since all HTML files are output under their locale diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts index b3c0312165113b7..f17d05ba0f83924 100644 --- a/packages/next/shared/lib/constants.ts +++ b/packages/next/shared/lib/constants.ts @@ -26,8 +26,7 @@ export const APP_PATHS_MANIFEST = 'app-paths-manifest.json' export const APP_PATH_ROUTES_MANIFEST = 'app-path-routes-manifest.json' export const BUILD_MANIFEST = 'build-manifest.json' export const APP_BUILD_MANIFEST = 'app-build-manifest.json' -export const SUBRESOURCE_INTEGRITY_MANIFEST = - 'subresource-integrity-manifest.json' +export const SUBRESOURCE_INTEGRITY_MANIFEST = 'subresource-integrity-manifest' export const EXPORT_MARKER = 'export-marker.json' export const EXPORT_DETAIL = 'export-detail.json' export const PRERENDER_MANIFEST = 'prerender-manifest.json'