diff --git a/packages/next/build/webpack/loaders/next-flight-client-loader.ts b/packages/next/build/webpack/loaders/next-flight-client-loader.ts index 16016d1ac967..ccd4fab6c029 100644 --- a/packages/next/build/webpack/loaders/next-flight-client-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-client-loader.ts @@ -8,9 +8,7 @@ import { promisify } from 'util' import { parse } from '../../swc' -import { buildExports } from './utils' - -const IS_NEXT_CLIENT_BUILT_IN = /[\\/]next[\\/](link|image)\.js$/ +import { buildExports, isNextBuiltinClientComponent } from './utils' function addExportNames(names: string[], node: any) { if (!node) return @@ -59,7 +57,7 @@ async function collectExports( const names: string[] = [] // Next.js built-in client components - if (IS_NEXT_CLIENT_BUILT_IN.test(resourcePath)) { + if (isNextBuiltinClientComponent(resourcePath)) { names.push('default') } @@ -160,7 +158,7 @@ export default async function transformSource( const moduleRefDef = "const MODULE_REFERENCE = Symbol.for('react.module.reference');\n" - const isNextClientBuiltIn = IS_NEXT_CLIENT_BUILT_IN.test(resourcePath) + const isNextClientBuiltIn = isNextBuiltinClientComponent(resourcePath) const clientRefsExports = names.reduce((res: any, name) => { const moduleRef = diff --git a/packages/next/build/webpack/loaders/next-flight-server-loader.ts b/packages/next/build/webpack/loaders/next-flight-server-loader.ts index 8e37149a3f7e..4740a9197e19 100644 --- a/packages/next/build/webpack/loaders/next-flight-server-loader.ts +++ b/packages/next/build/webpack/loaders/next-flight-server-loader.ts @@ -1,30 +1,11 @@ import { builtinModules } from 'module' import { parse } from '../../swc' -import { buildExports } from './utils' - -const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif'] - -export const createClientComponentFilter = (extensions: string[]) => { - // Special cases for Next.js APIs that are considered as client components: - // - .client.[ext] - // - next built-in client components - // - .[imageExt] - const regex = new RegExp( - '(' + - `\\.client(\\.(${extensions.join('|')}))?|` + - `next/(link|image)(\\.js)?|` + - `\\.(${imageExtensions.join('|')})` + - ')$' - ) - - return (importSource: string) => regex.test(importSource) -} - -export const createServerComponentFilter = (extensions: string[]) => { - const regex = new RegExp(`\\.server(\\.(${extensions.join('|')}))?$`) - return (importSource: string) => regex.test(importSource) -} +import { + buildExports, + createClientComponentFilter, + createServerComponentFilter, +} from './utils' function createFlightServerRequest(request: string, options: object) { return `next-flight-server-loader?${JSON.stringify(options)}!${request}` diff --git a/packages/next/build/webpack/loaders/utils.ts b/packages/next/build/webpack/loaders/utils.ts index d771a714c277..7ac4ae406405 100644 --- a/packages/next/build/webpack/loaders/utils.ts +++ b/packages/next/build/webpack/loaders/utils.ts @@ -1,3 +1,15 @@ +const defaultJsFileExtensions = ['js', 'mjs', 'jsx', 'ts', 'tsx', 'json'] +const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif'] +const nextClientComponents = ['link', 'image', 'script'] + +const NEXT_BUILT_IN_CLIENT_RSC_REGEX = new RegExp( + `[\\\\/]next[\\\\/](${nextClientComponents.join('|')})\\.js$` +) + +export function isNextBuiltinClientComponent(resourcePath: string) { + return NEXT_BUILT_IN_CLIENT_RSC_REGEX.test(resourcePath) +} + export function buildExports(moduleExports: any, isESM: boolean) { let ret = '' Object.keys(moduleExports).forEach((key) => { @@ -11,3 +23,26 @@ export function buildExports(moduleExports: any, isESM: boolean) { }) return ret } + +export const createClientComponentFilter = ( + extensions: string[] = defaultJsFileExtensions +) => { + // Special cases for Next.js APIs that are considered as client components: + // - .client.[ext] + // - next built-in client components + // - .[imageExt] + const regex = new RegExp( + '(' + + `\\.client(\\.(${extensions.join('|')}))?|` + + `next/(${nextClientComponents.join('|')})(\\.js)?|` + + `\\.(${imageExtensions.join('|')})` + + ')$' + ) + + return (importSource: string) => regex.test(importSource) +} + +export const createServerComponentFilter = (extensions: string[]) => { + const regex = new RegExp(`\\.server(\\.(${extensions.join('|')}))?$`) + return (importSource: string) => regex.test(importSource) +} diff --git a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts index 59156b0c7579..d639b66ecf7e 100644 --- a/packages/next/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-manifest-plugin.ts @@ -7,7 +7,7 @@ import { webpack, sources } from 'next/dist/compiled/webpack/webpack' import { MIDDLEWARE_FLIGHT_MANIFEST } from '../../../shared/lib/constants' -import { createClientComponentFilter } from '../loaders/next-flight-server-loader' +import { createClientComponentFilter } from '../loaders/utils' // This is the module that will be used to anchor all client references to. // I.e. it will have all the client files as async deps from this point on. diff --git a/test/integration/react-streaming-and-server-components/app/pages/index.server.js b/test/integration/react-streaming-and-server-components/app/pages/index.server.js index c408b7ec05b0..aff83807b495 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/index.server.js +++ b/test/integration/react-streaming-and-server-components/app/pages/index.server.js @@ -1,4 +1,5 @@ import Nav from '../components/nav' +import Script from 'next/script' const envVar = process.env.ENV_VAR_TEST const headerKey = 'x-next-test-client' @@ -10,6 +11,7 @@ export default function Index({ header }) {
{'env:' + envVar}
{'header:' + header}