Skip to content

Commit

Permalink
Reogranize the client component detection utils (#36135)
Browse files Browse the repository at this point in the history
Refactor:

- group client components detction util
- add next script as rsc client component
  • Loading branch information
huozhi committed Apr 13, 2022
1 parent 1e451ab commit f74b59c
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 30 deletions.
Expand Up @@ -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
Expand Down Expand Up @@ -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')
}

Expand Down Expand Up @@ -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 =
Expand Down
29 changes: 5 additions & 24 deletions 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}`
Expand Down
35 changes: 35 additions & 0 deletions 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) => {
Expand All @@ -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)
}
Expand Up @@ -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.
Expand Down
@@ -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'
Expand All @@ -10,6 +11,7 @@ export default function Index({ header }) {
<div>{'env:' + envVar}</div>
<div>{'header:' + header}</div>
<Nav />
<Script id="client-script">{`;`}</Script>
</div>
)
}
Expand Down
Expand Up @@ -14,11 +14,14 @@ export default function (context, { runtime, env }) {
},
})

const browser = await webdriver(context.appPort, '/')
const scriptTagContent = await browser.elementById('client-script').text()
// should have only 1 DOCTYPE
expect(homeHTML).toMatch(/^<!DOCTYPE html><html/)
expect(homeHTML).toContain('component:index.server')
expect(homeHTML).toContain('env:env_var_test')
expect(homeHTML).toContain('header:test-util')
expect(scriptTagContent).toBe(';')
})

it('should reuse the inline flight response without sending extra requests', async () => {
Expand Down

0 comments on commit f74b59c

Please sign in to comment.