Skip to content

Commit

Permalink
Transform client reference in middleware layer (#66294)
Browse files Browse the repository at this point in the history
### What

Use `next-flight-loader` to transform the client components into client
reference in middleware and instrumentation.
Add related required webpack aliases, such as alias for
`react-server-dom-webpack`

### Why

issue reported in
#65424 (comment)
  • Loading branch information
huozhi authored May 30, 2024
1 parent a95356a commit 92443b6
Show file tree
Hide file tree
Showing 15 changed files with 97 additions and 13 deletions.
3 changes: 1 addition & 2 deletions packages/next-swc/crates/next-core/src/next_import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,10 +583,9 @@ async fn insert_next_server_special_aliases(

rsc_aliases(import_map, project_path, ty, runtime, next_config).await?;
}
ServerContextType::Middleware => {
ServerContextType::Middleware | ServerContextType::Instrumentation => {
rsc_aliases(import_map, project_path, ty, runtime, next_config).await?;
}
ServerContextType::Instrumentation => {}
}

// see https://github.com/vercel/next.js/blob/8013ef7372fc545d49dbd060461224ceb563b454/packages/next/src/build/webpack-config.ts#L1449-L1531
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl ServerContextType {
| ServerContextType::AppRoute { .. }
| ServerContextType::PagesApi { .. }
| ServerContextType::Middleware { .. }
| ServerContextType::Instrumentation { .. }
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ impl AfterResolvePlugin for NextNodeSharedRuntimeResolvePlugin {
"next/dist/server/future/route-modules/{}/vendored/contexts/{}.js",
match self.context {
ServerContextType::Pages { .. } => "pages",
ServerContextType::AppRoute { .. } => "app-route",
ServerContextType::AppRoute { .. } | ServerContextType::Instrumentation { .. } =>
"app-route",
ServerContextType::AppSSR { .. } | ServerContextType::AppRSC { .. } => "app-page",
_ => "unknown",
},
Expand Down
15 changes: 11 additions & 4 deletions packages/next/src/build/create-compiler-aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,16 @@ export function createAppRouterApiAliases(isServerOnlyLayer: boolean) {
return aliasMap
}

export function createRSCRendererAliases(bundledReactChannel: string) {
return {
// react-server-dom-webpack alias
'react-server-dom-webpack/client$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client`,
'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client.edge`,
'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.edge`,
'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.node`,
}
}

export function createRSCAliases(
bundledReactChannel: string,
{
Expand Down Expand Up @@ -262,10 +272,7 @@ export function createRSCAliases(
'react-dom/server.edge$': `next/dist/compiled/react-dom${bundledReactChannel}/server.edge`,
'react-dom/server.browser$': `next/dist/compiled/react-dom${bundledReactChannel}/server.browser`,
// react-server-dom-webpack alias
'react-server-dom-webpack/client$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client`,
'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client.edge`,
'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.edge`,
'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.node`,
...createRSCRendererAliases(bundledReactChannel),
}

if (!isEdgeServer) {
Expand Down
7 changes: 7 additions & 0 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import {
createRSCAliases,
createNextApiEsmAliases,
createAppRouterApiAliases,
createRSCRendererAliases,
} from './create-compiler-aliases'
import { hasCustomExportOutput } from '../export/utils'
import { CssChunkingPlugin } from './webpack/plugins/css-chunking-plugin'
Expand Down Expand Up @@ -529,6 +530,7 @@ export default async function getBaseWebpackConfig(
: []

const instrumentLayerLoaders = [
'next-flight-loader',
// When using Babel, we will have to add the SWC loader
// as an additional pass to handle RSC correctly.
// This will cause some performance overhead but
Expand All @@ -538,6 +540,7 @@ export default async function getBaseWebpackConfig(
].filter(Boolean)

const middlewareLayerLoaders = [
'next-flight-loader',
// When using Babel, we will have to use SWC to do the optimization
// for middleware to tree shake the unused default optimized imports like "next/server".
// This will cause some performance overhead but
Expand Down Expand Up @@ -1470,6 +1473,8 @@ export default async function getBaseWebpackConfig(
use: middlewareLayerLoaders,
resolve: {
conditionNames: reactServerCondition,
// Always use default channels when use installed react
alias: createRSCRendererAliases(''),
},
},
{
Expand All @@ -1478,6 +1483,8 @@ export default async function getBaseWebpackConfig(
use: instrumentLayerLoaders,
resolve: {
conditionNames: reactServerCondition,
// Always use default channels when use installed react
alias: createRSCRendererAliases(''),
},
},
...(hasAppDir
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/module-layer/lib/mixed-lib/shared-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Link from 'next/link'

export const textValue = 'text-value'
export const TestLink = Link
2 changes: 1 addition & 1 deletion test/e2e/module-layer/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function middleware(request) {
throw new Error('React.useState should not be defined in server layer')
}

if (request.nextUrl.pathname === '/react-version') {
if (request.nextUrl.pathname === '/middleware') {
return Response.json({
React: Object.keys(ReactObject),
})
Expand Down
6 changes: 1 addition & 5 deletions test/e2e/module-layer/module-layer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import { getRedboxSource, hasRedbox, retry } from 'next-test-utils'
describe('module layer', () => {
const { next, isNextStart, isNextDev, isTurbopack } = nextTestSetup({
files: __dirname,
dependencies: {
react: '19.0.0-rc-915b914b3a-20240515',
'react-dom': '19.0.0-rc-915b914b3a-20240515',
},
})

function runTests() {
Expand Down Expand Up @@ -37,7 +33,7 @@ describe('module layer', () => {
}

it('should render installed react-server condition for middleware', async () => {
const json = await next.fetch('/react-version').then((res) => res.json())
const json = await next.fetch('/middleware').then((res) => res.json())
expect(json.React).toContain('version') // basic react-server export
expect(json.React).not.toContain('useEffect') // no client api export
})
Expand Down
7 changes: 7 additions & 0 deletions test/e2e/rsc-layers-transform/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Layout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
3 changes: 3 additions & 0 deletions test/e2e/rsc-layers-transform/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return 'page'
}
11 changes: 11 additions & 0 deletions test/e2e/rsc-layers-transform/instrumentation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
import { textValue } from './lib/shared-module'

export async function register() {
// TODO: support react-server condition for instrumentation hook in turbopack
if (!process.env.TURBOPACK && Object(React).useState) {
throw new Error('instrumentation is not working correctly in server layer')
}
console.log('instrumentation:register')
console.log('instrumentation:text:' + textValue)
}
4 changes: 4 additions & 0 deletions test/e2e/rsc-layers-transform/lib/shared-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Link from 'next/link'

export const textValue = 'text-value'
export const TestLink = Link
13 changes: 13 additions & 0 deletions test/e2e/rsc-layers-transform/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NextResponse } from 'next/server'
import { textValue, TestLink } from './lib/shared-module'

export function middleware(request) {
if (request.nextUrl.pathname === '/middleware') {
return Response.json({
clientReference: TestLink.$$typeof.toString(),
textValue,
})
}

return NextResponse.next()
}
5 changes: 5 additions & 0 deletions test/e2e/rsc-layers-transform/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
experimental: {
instrumentationHook: true,
},
}
26 changes: 26 additions & 0 deletions test/e2e/rsc-layers-transform/rsc-layers-transform.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { nextTestSetup } from 'e2e-utils'

// TODO: support react-server condition for instrumentation hook in turbopack
;(process.env.TURBOPACK ? describe.skip : describe)(
'rsc layers transform',
() => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should render installed react-server condition for middleware', async () => {
const json = await next.fetch('/middleware').then((res) => res.json())

expect(json).toEqual({
textValue: 'text-value',
clientReference: 'Symbol(react.client.reference)',
})
})

it('should call instrumentation hook without errors', async () => {
const output = next.cliOutput
expect(output).toContain('instrumentation:register')
expect(output).toContain('instrumentation:text:text-value')
})
}
)

0 comments on commit 92443b6

Please sign in to comment.