Skip to content

Commit

Permalink
Pick esm main fields and condition names first for RSC server layer (#…
Browse files Browse the repository at this point in the history
…50548)

For RSC server layer so far we bundle all dependencies, ESM format is the better one rather than CJS to analyze and tree-shake out the unused parts. This PR changes pick the condition names that are in ESM format first for server layer.

Also fixes the misorder of condition names of edge runtime, `conditionNames` should only contain either ESM or CJS, previously the main fields are mixed with conditon names which is not expected for webpack, we separate them now.

Since we're picking ESM instead CJS now, the error of require `exports * from` doesn't exist anymore, but if you're using a CJS dependency which require a ESM package, it will error. This is the existing behavior for our webpack configuration but could happen on server layer bundling

Other related changes:

* Imports are hoisted in ESM, so migrate`enhanceGlobals` to a imported module
* Use `...` to pick the proper imports by import expression, and prefer the `react-server` / `edge-light` condition names for corresponding cases
* Remove edge SSR duplicated `middleware` export checking
  • Loading branch information
huozhi committed Jun 8, 2023
1 parent ea63e92 commit a035224
Show file tree
Hide file tree
Showing 29 changed files with 989 additions and 789 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
"semver": "7.3.7",
"shell-quote": "1.7.3",
"strip-ansi": "6.0.0",
"styled-components": "6.0.0-beta.5",
"styled-components": "6.0.0-rc.3",
"styled-jsx": "5.1.1",
"styled-jsx-plugin-postcss": "3.0.2",
"swr": "^2.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { adapter, enhanceGlobals } from 'next/dist/server/web/adapter'
import 'next/dist/server/web/globals'
import { adapter } from 'next/dist/server/web/adapter'
import { NAME, PAGE } from 'BOOTSTRAP_CONFIG'

enhanceGlobals()

var mod = require('ENTRY')
var handler = mod.middleware || mod.default

Expand Down
30 changes: 16 additions & 14 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import crypto from 'crypto'
import { webpack } from 'next/dist/compiled/webpack/webpack'
import path from 'path'
import semver from 'next/dist/compiled/semver'

import { escapeStringRegexp } from '../shared/lib/escape-regexp'
import {
DOT_NEXT_ALIAS,
Expand Down Expand Up @@ -98,15 +99,23 @@ const reactPackagesRegex = /^(react|react-dom|react-server-dom-webpack)($|\/)/
const asyncStoragesRegex =
/next[\\/]dist[\\/]client[\\/]components[\\/](static-generation-async-storage|action-async-storage|request-async-storage)/

// exports.<conditionName>
const edgeConditionNames = [
'edge-light',
'worker',
// inherits the default conditions
'...',
]

// packageJson.<mainField>
const mainFieldsPerCompiler: Record<CompilerNameValues, string[]> = {
[COMPILER_NAMES.server]: ['main', 'module'],
[COMPILER_NAMES.client]: ['browser', 'module', 'main'],
[COMPILER_NAMES.edgeServer]: [
'edge-light',
'worker',
'browser',
'module',
'main',
// inherits the default conditions
'...',
],
}

Expand Down Expand Up @@ -915,12 +924,9 @@ export default async function getBaseWebpackConfig(

const reactServerCondition = [
'react-server',
...mainFieldsPerCompiler[
isEdgeServer ? COMPILER_NAMES.edgeServer : COMPILER_NAMES.server
],
'node',
'import',
'require',
...(isEdgeServer ? edgeConditionNames : []),
// inherits the default conditions
'...',
]

const clientEntries = isClient
Expand Down Expand Up @@ -1166,11 +1172,7 @@ export default async function getBaseWebpackConfig(
: undefined),
mainFields: mainFieldsPerCompiler[compilerType],
...(isEdgeServer && {
conditionNames: [
...mainFieldsPerCompiler[COMPILER_NAMES.edgeServer],
'import',
'node',
],
conditionNames: edgeConditionNames,
}),
plugins: [],
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,11 @@ const nextEdgeFunctionLoader: webpack.LoaderDefinitionFunction<EdgeFunctionLoade
buildInfo.rootDir = rootDir

return `
import { adapter, enhanceGlobals } from 'next/dist/esm/server/web/adapter'
import 'next/dist/esm/server/web/globals'
import { adapter } from 'next/dist/esm/server/web/adapter'
import { IncrementalCache } from 'next/dist/esm/server/lib/incremental-cache'
enhanceGlobals()
const mod = require(${stringifiedPagePath})
const handler = mod.middleware || mod.default
import handler from ${stringifiedPagePath}
if (typeof handler !== 'function') {
throw new Error('The Edge Function "pages${page}" must export a \`default\` function');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,11 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
)}${isAppDir ? `?${WEBPACK_RESOURCE_QUERIES.edgeSSREntry}` : ''}`

const transformed = `
import { adapter, enhanceGlobals } from 'next/dist/esm/server/web/adapter'
import 'next/dist/esm/server/web/globals'
import { adapter } from 'next/dist/esm/server/web/adapter'
import { getRender } from 'next/dist/esm/build/webpack/loaders/next-edge-ssr-loader/render'
import { IncrementalCache } from 'next/dist/esm/server/lib/incremental-cache'
enhanceGlobals()
const pagesType = ${JSON.stringify(pagesType)}
${
isAppDir
Expand Down Expand Up @@ -132,10 +131,10 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
`
}
const incrementalCacheHandler = ${
${
incrementalCacheHandlerPath
? `require("${incrementalCacheHandlerPath}")`
: 'null'
? `import incrementalCacheHandler from "${incrementalCacheHandlerPath}"`
: 'const incrementalCacheHandler = null'
}
const buildManifest = self.__BUILD_MANIFEST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,21 @@ export default function middlewareLoader(this: any) {
buildInfo.rootDir = rootDir

return `
import { adapter, enhanceGlobals } from 'next/dist/esm/server/web/adapter'
import 'next/dist/esm/server/web/globals'
import { adapter } from 'next/dist/esm/server/web/adapter'
import * as mod from ${stringifiedPagePath}
enhanceGlobals()
var mod = require(${stringifiedPagePath})
var handler = mod.middleware || mod.default;
const handler = mod.middleware || mod.default
if (typeof handler !== 'function') {
throw new Error('The Middleware "pages${page}" must export a \`middleware\` or a \`default\` function');
}
export default function (opts) {
return adapter({
...opts,
page: ${JSON.stringify(page)},
handler,
...opts,
page: ${JSON.stringify(page)},
handler,
})
}
`
Expand Down
32 changes: 16 additions & 16 deletions packages/next/src/compiled/babel-packages/packages-bundle.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/next/src/server/dev/next-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,10 +1330,12 @@ export default class DevServer extends Server {
if (isError(err) && err.stack) {
try {
const frames = parseStack(err.stack!)
// Filter out internal edge related runtime stack
const frame = frames.find(
({ file }) =>
!file?.startsWith('eval') &&
!file?.includes('web/adapter') &&
!file?.includes('web/globals') &&
!file?.includes('sandbox/context') &&
!file?.includes('<anonymous>')
)
Expand Down
5 changes: 3 additions & 2 deletions packages/next/src/server/require-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// This module will only be loaded once per process.

const { dirname } = require('path')
const mod = require('module')
const resolveFilename = mod._resolveFilename
const hookPropertyMap = new Map()
Expand All @@ -20,9 +21,9 @@ export function addHookAliases(aliases: [string, string][] = []) {
addHookAliases([
// Use `require.resolve` explicitly to make them statically analyzable
// styled-jsx needs to be resolved as the external dependency.
['styled-jsx', require.resolve('styled-jsx')],
['styled-jsx/style', require.resolve('styled-jsx/style')],
['styled-jsx', dirname(require.resolve('styled-jsx/package.json'))],
['styled-jsx/style', require.resolve('styled-jsx/style')],
['zod', dirname(require.resolve('zod/package.json'))],
])

// Override built-in React packages if necessary
Expand Down
73 changes: 1 addition & 72 deletions packages/next/src/server/web/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import {
RSC,
} from '../../client/components/app-router-headers'
import { NEXT_QUERY_PARAM_PREFIX } from '../../lib/constants'

declare const _ENTRIES: any
import { ensureInstrumentationRegistered } from './globals'

class NextRequestHint extends NextRequest {
sourcePage: string
Expand Down Expand Up @@ -59,29 +58,6 @@ export type AdapterOptions = {
IncrementalCache?: typeof import('../lib/incremental-cache').IncrementalCache
}

async function registerInstrumentation() {
if (
'_ENTRIES' in globalThis &&
_ENTRIES.middleware_instrumentation &&
_ENTRIES.middleware_instrumentation.register
) {
try {
await _ENTRIES.middleware_instrumentation.register()
} catch (err: any) {
err.message = `An error occurred while loading instrumentation hook: ${err.message}`
throw err
}
}
}

let registerInstrumentationPromise: Promise<void> | null = null
function ensureInstrumentationRegistered() {
if (!registerInstrumentationPromise) {
registerInstrumentationPromise = registerInstrumentation()
}
return registerInstrumentationPromise
}

export async function adapter(
params: AdapterOptions
): Promise<FetchEventResult> {
Expand Down Expand Up @@ -318,50 +294,3 @@ export async function adapter(
waitUntil: Promise.all(event[waitUntilSymbol]),
}
}

function getUnsupportedModuleErrorMessage(module: string) {
// warning: if you change these messages, you must adjust how react-dev-overlay's middleware detects modules not found
return `The edge runtime does not support Node.js '${module}' module.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`
}

function __import_unsupported(moduleName: string) {
const proxy: any = new Proxy(function () {}, {
get(_obj, prop) {
if (prop === 'then') {
return {}
}
throw new Error(getUnsupportedModuleErrorMessage(moduleName))
},
construct() {
throw new Error(getUnsupportedModuleErrorMessage(moduleName))
},
apply(_target, _this, args) {
if (typeof args[0] === 'function') {
return args[0](proxy)
}
throw new Error(getUnsupportedModuleErrorMessage(moduleName))
},
})
return new Proxy({}, { get: () => proxy })
}

export function enhanceGlobals() {
// The condition is true when the "process" module is provided
if (process !== global.process) {
// prefer local process but global.process has correct "env"
process.env = global.process.env
global.process = process
}

// to allow building code that import but does not use node.js modules,
// webpack will expect this function to exist in global scope
Object.defineProperty(globalThis, '__import_unsupported', {
value: __import_unsupported,
enumerable: false,
configurable: false,
})

// Eagerly fire instrumentation hook to make the startup faster.
void ensureInstrumentationRegistered()
}
4 changes: 2 additions & 2 deletions packages/next/src/server/web/edge-route-module-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { RouteDefinition } from '../future/route-definitions/route-definiti
import type { RouteModule } from '../future/route-modules/route-module'
import type { NextRequest } from './spec-extension/request'

import { adapter, enhanceGlobals, type AdapterOptions } from './adapter'
import './globals'
import { adapter, type AdapterOptions } from './adapter'
import { IncrementalCache } from '../lib/incremental-cache'
enhanceGlobals()

import { removeTrailingSlash } from '../../shared/lib/router/utils/remove-trailing-slash'
import { RouteMatcher } from '../future/route-matchers/route-matcher'
Expand Down
73 changes: 73 additions & 0 deletions packages/next/src/server/web/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
declare const _ENTRIES: any

async function registerInstrumentation() {
if (
'_ENTRIES' in globalThis &&
_ENTRIES.middleware_instrumentation &&
_ENTRIES.middleware_instrumentation.register
) {
try {
await _ENTRIES.middleware_instrumentation.register()
} catch (err: any) {
err.message = `An error occurred while loading instrumentation hook: ${err.message}`
throw err
}
}
}

let registerInstrumentationPromise: Promise<void> | null = null
export function ensureInstrumentationRegistered() {
if (!registerInstrumentationPromise) {
registerInstrumentationPromise = registerInstrumentation()
}
return registerInstrumentationPromise
}

function getUnsupportedModuleErrorMessage(module: string) {
// warning: if you change these messages, you must adjust how react-dev-overlay's middleware detects modules not found
return `The edge runtime does not support Node.js '${module}' module.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`
}

function __import_unsupported(moduleName: string) {
const proxy: any = new Proxy(function () {}, {
get(_obj, prop) {
if (prop === 'then') {
return {}
}
throw new Error(getUnsupportedModuleErrorMessage(moduleName))
},
construct() {
throw new Error(getUnsupportedModuleErrorMessage(moduleName))
},
apply(_target, _this, args) {
if (typeof args[0] === 'function') {
return args[0](proxy)
}
throw new Error(getUnsupportedModuleErrorMessage(moduleName))
},
})
return new Proxy({}, { get: () => proxy })
}

function enhanceGlobals() {
// The condition is true when the "process" module is provided
if (process !== global.process) {
// prefer local process but global.process has correct "env"
process.env = global.process.env
global.process = process
}

// to allow building code that import but does not use node.js modules,
// webpack will expect this function to exist in global scope
Object.defineProperty(globalThis, '__import_unsupported', {
value: __import_unsupported,
enumerable: false,
configurable: false,
})

// Eagerly fire instrumentation hook to make the startup faster.
void ensureInstrumentationRegistered()
}

enhanceGlobals()
6 changes: 3 additions & 3 deletions packages/next/src/server/web/sandbox/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ function withTaggedErrors(fn: RunnerFn): RunnerFn {
})
}

export const getRuntimeContext = async (params: {
export async function getRuntimeContext(params: {
name: string
onWarning?: any
useCache: boolean
edgeFunctionEntry: any
distDir: string
paths: string[]
incrementalCache?: any
}): Promise<EdgeRuntime<any>> => {
}): Promise<EdgeRuntime<any>> {
const { runtime, evaluateInContext } = await getModuleContext({
moduleName: params.name,
onWarning: params.onWarning ?? (() => {}),
Expand All @@ -71,7 +71,7 @@ export const getRuntimeContext = async (params: {
return runtime
}

export const run = withTaggedErrors(async (params) => {
export const run = withTaggedErrors(async function runWithTaggedErrors(params) {
const runtime = await getRuntimeContext(params)
const subreq = params.request.headers[`x-middleware-subrequest`]
const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
Expand Down

0 comments on commit a035224

Please sign in to comment.