From d5e767b20d06dfd625f70267b713af52917e5897 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 26 Apr 2022 19:54:28 +0200 Subject: [PATCH] Add process env NEXT_RUNTIME (#36383) * To help determine the current running environment is in server nodejs / edge runtime * Remove usage of `process.browser` --- errors/env-key-not-allowed.md | 4 +-- packages/next/bin/next.ts | 1 + packages/next/build/webpack-config.ts | 24 +++++++++++---- packages/next/lib/chalk.ts | 6 ++-- packages/next/pages/_document.tsx | 2 +- packages/next/server/base-server.ts | 9 ++++-- packages/next/server/render.tsx | 30 ++++++++++++------- packages/next/server/web/sandbox/context.ts | 4 ++- packages/next/shared/lib/isomorphic/path.ts | 7 +++-- .../middleware/core/test/index.test.js | 2 ++ .../production-config/next.config.js | 5 ++++ .../production-config/test/index.test.js | 10 +++++++ .../app/pages/runtime.js | 2 +- .../index.test.ts | 1 + 14 files changed, 77 insertions(+), 30 deletions(-) diff --git a/errors/env-key-not-allowed.md b/errors/env-key-not-allowed.md index cb365e1217fd..ae45e9e0cffa 100644 --- a/errors/env-key-not-allowed.md +++ b/errors/env-key-not-allowed.md @@ -2,8 +2,8 @@ #### Why This Error Occurred -Next.js configures internal variables for replacement itself. These start with `__` or `NODE_`, for this reason they are not allowed as values for `env` in `next.config.js` +Next.js configures internal variables for replacement itself. `NEXT_RUNTIME` along with variables starting with `__` or `NODE_` are currently internal, for this reason they are not allowed as values for `env` in `next.config.js` #### Possible Ways to Fix It -Rename the specified key so that it does not start with `__` or `NODE_`. +Rename the specified key so that it does not start with `__` or `NODE_`, or pick other another name for `NEXT_RUNTIME`. diff --git a/packages/next/bin/next.ts b/packages/next/bin/next.ts index dead0052077f..d6442c51b732 100755 --- a/packages/next/bin/next.ts +++ b/packages/next/bin/next.ts @@ -104,6 +104,7 @@ if (process.env.NODE_ENV) { } ;(process.env as any).NODE_ENV = process.env.NODE_ENV || defaultEnv +;(process.env as any).NEXT_RUNTIME = 'nodejs' // x-ref: https://github.com/vercel/next.js/pull/34688#issuecomment-1047994505 if (process.versions.pnp === '3') { diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 6ee0cf5ed3b3..53fd9eebccdb 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1354,11 +1354,7 @@ export default async function getBaseWebpackConfig( {} ), ...Object.keys(config.env).reduce((acc, key) => { - if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) { - throw new Error( - `The key "${key}" under "env" in ${config.configFileName} is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed` - ) - } + errorIfEnvConflicted(config, key) return { ...acc, @@ -1369,11 +1365,16 @@ export default async function getBaseWebpackConfig( 'process.env.NODE_ENV': JSON.stringify( dev ? 'development' : 'production' ), + ...(isServer && { + 'process.env.NEXT_RUNTIME': JSON.stringify( + isEdgeRuntime ? 'edge' : 'nodejs' + ), + }), 'process.env.__NEXT_NEW_LINK_BEHAVIOR': JSON.stringify( config.experimental.newNextLinkBehavior ), 'process.env.__NEXT_CROSS_ORIGIN': JSON.stringify(crossOrigin), - 'process.browser': JSON.stringify(targetWeb), + 'process.browser': JSON.stringify(!isServer), 'process.env.__NEXT_TEST_MODE': JSON.stringify( process.env.__NEXT_TEST_MODE ), @@ -2182,3 +2183,14 @@ export default async function getBaseWebpackConfig( return webpackConfig } + +function errorIfEnvConflicted(config: NextConfigComplete, key: string) { + const isPrivateKey = /^(?:NODE_.+)|^(?:__.+)$/i.test(key) + const hasNextRuntimeKey = key === 'NEXT_RUNTIME' + + if (isPrivateKey || hasNextRuntimeKey) { + throw new Error( + `The key "${key}" under "env" in ${config.configFileName} is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed` + ) + } +} diff --git a/packages/next/lib/chalk.ts b/packages/next/lib/chalk.ts index f25f56ec3522..8e40472953f8 100644 --- a/packages/next/lib/chalk.ts +++ b/packages/next/lib/chalk.ts @@ -1,9 +1,9 @@ let chalk: typeof import('next/dist/compiled/chalk') -if (!process.browser) { - chalk = require('next/dist/compiled/chalk') -} else { +if (process.env.NEXT_RUNTIME === 'edge') { chalk = require('./web/chalk').default +} else { + chalk = require('next/dist/compiled/chalk') } export default chalk diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx index b989817a6a7e..4d0c3ec539fe 100644 --- a/packages/next/pages/_document.tsx +++ b/packages/next/pages/_document.tsx @@ -80,7 +80,7 @@ function getPreNextWorkerScripts(context: HtmlProps, props: OriginProps) { const { assetPrefix, scriptLoader, crossOrigin, nextScriptWorkers } = context // disable `nextScriptWorkers` in edge runtime - if (!nextScriptWorkers || process.browser) return null + if (!nextScriptWorkers || process.env.NEXT_RUNTIME === 'edge') return null try { let { diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index 94a2d24cad13..3bde9a187d47 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -1155,7 +1155,7 @@ export default abstract class Server { (isServerComponent && !hasServerProps && !hasGetInitialProps && - !process.browser) + process.env.NEXT_RUNTIME !== 'edge') // Toggle whether or not this is a Data request const isDataReq = @@ -1240,7 +1240,7 @@ export default abstract class Server { if (hasServerProps || isSSG) { // For the edge runtime, we don't support preview mode in SSG. - if (!process.browser) { + if (process.env.NEXT_RUNTIME !== 'edge') { const { tryGetPreviewData } = require('./api-utils/node') as typeof import('./api-utils/node') previewData = tryGetPreviewData(req, res, this.renderOpts.previewProps) @@ -1758,7 +1758,10 @@ export default abstract class Server { ) if (!isWrappedError) { - if ((this.minimalMode && !process.browser) || this.renderOpts.dev) { + if ( + (this.minimalMode && process.env.NEXT_RUNTIME !== 'edge') || + this.renderOpts.dev + ) { if (isError(err)) err.page = page throw err } diff --git a/packages/next/server/render.tsx b/packages/next/server/render.tsx index 8439d0b823d7..9d9b053562ae 100644 --- a/packages/next/server/render.tsx +++ b/packages/next/server/render.tsx @@ -89,7 +89,7 @@ let postProcess: typeof import('../shared/lib/post-process').default const DOCTYPE = '' -if (!process.browser) { +if (process.env.NEXT_RUNTIME !== 'edge') { require('./node-polyfill-web-streams') optimizeAmp = require('./optimize-amp').default getFontDefinitionFromManifest = @@ -518,7 +518,9 @@ export async function renderToHTML( Uint8Array, Uint8Array > | null = - isServerComponent && !process.browser ? new TransformStream() : null + isServerComponent && process.env.NEXT_RUNTIME !== 'edge' + ? new TransformStream() + : null if (isServerComponent) { serverComponentsInlinedTransformStream = new TransformStream() @@ -700,7 +702,11 @@ export async function renderToHTML( let isPreview let previewData: PreviewData - if ((isSSG || getServerSideProps) && !isFallback && !process.browser) { + if ( + (isSSG || getServerSideProps) && + !isFallback && + process.env.NEXT_RUNTIME !== 'edge' + ) { // Reads of this are cached on the `req` object, so this should resolve // instantly. There's no need to pass this data down from a previous // invoke, where we'd have to consider server & serverless. @@ -775,7 +781,7 @@ export async function renderToHTML( } // Disable AMP under the web environment - const inAmpMode = !process.browser && isInAmpMode(ampState) + const inAmpMode = process.env.NEXT_RUNTIME !== 'edge' && isInAmpMode(ampState) const reactLoadableModules: string[] = [] @@ -1313,7 +1319,7 @@ export async function renderToHTML( | typeof Document | undefined - if (process.browser && Document.getInitialProps) { + if (process.env.NEXT_RUNTIME === 'edge' && Document.getInitialProps) { // In the Edge runtime, `Document.getInitialProps` isn't supported. // We throw an error here if it's customized. if (!builtinDocument) { @@ -1323,7 +1329,10 @@ export async function renderToHTML( } } - if ((isServerComponent || process.browser) && Document.getInitialProps) { + if ( + (isServerComponent || process.env.NEXT_RUNTIME === 'edge') && + Document.getInitialProps + ) { if (builtinDocument) { Document = builtinDocument } else { @@ -1541,7 +1550,7 @@ export async function renderToHTML( const hasDocumentGetInitialProps = !( isServerComponent || - process.browser || + process.env.NEXT_RUNTIME === 'edge' || !Document.getInitialProps ) @@ -1560,7 +1569,7 @@ export async function renderToHTML( const { docProps } = (documentInitialPropsRes as any) || {} const documentElement = () => { - if (isServerComponent || process.browser) { + if (isServerComponent || process.env.NEXT_RUNTIME === 'edge') { return (Document as any)() } @@ -1751,7 +1760,8 @@ export async function renderToHTML( return html } : null, - !process.browser && process.env.__NEXT_OPTIMIZE_FONTS + process.env.NEXT_RUNTIME !== 'edge' && + process.env.__NEXT_OPTIMIZE_FONTS ? async (html: string) => { return await postProcess( html, @@ -1762,7 +1772,7 @@ export async function renderToHTML( ) } : null, - !process.browser && renderOpts.optimizeCss + process.env.NEXT_RUNTIME !== 'edge' && renderOpts.optimizeCss ? async (html: string) => { // eslint-disable-next-line import/no-extraneous-dependencies const Critters = require('critters') diff --git a/packages/next/server/web/sandbox/context.ts b/packages/next/server/web/sandbox/context.ts index 292b5fa87bfb..9e96005bbccb 100644 --- a/packages/next/server/web/sandbox/context.ts +++ b/packages/next/server/web/sandbox/context.ts @@ -265,7 +265,9 @@ function buildEnvironmentVariablesFrom( keys: string[] ): Record { const pairs = keys.map((key) => [key, process.env[key]]) - return Object.fromEntries(pairs) + const env = Object.fromEntries(pairs) + env.NEXT_RUNTIME = 'edge' + return env } async function loadWasm( diff --git a/packages/next/shared/lib/isomorphic/path.ts b/packages/next/shared/lib/isomorphic/path.ts index b074412ddefb..bf4e2197fd80 100644 --- a/packages/next/shared/lib/isomorphic/path.ts +++ b/packages/next/shared/lib/isomorphic/path.ts @@ -1,5 +1,6 @@ -const path = process.browser - ? require('next/dist/compiled/path-browserify') - : require('path') +const path = + process.env.NEXT_RUNTIME === 'edge' + ? require('next/dist/compiled/path-browserify') + : require('path') export default path diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index e5761b96ba24..a5e44c99ce90 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -119,6 +119,7 @@ describe('Middleware base tests', () => { context.app = await launchApp(context.appDir, context.appPort, { env: { MIDDLEWARE_TEST: 'asdf', + NEXT_RUNTIME: 'edge', }, }) }) @@ -130,6 +131,7 @@ describe('Middleware base tests', () => { process: { env: { MIDDLEWARE_TEST: 'asdf', + NEXT_RUNTIME: 'edge', }, // it's poflyfilled since there is the "process" module // as a devDepencies of the next package diff --git a/test/integration/production-config/next.config.js b/test/integration/production-config/next.config.js index a2120be4a6ee..c72c2361ba07 100644 --- a/test/integration/production-config/next.config.js +++ b/test/integration/production-config/next.config.js @@ -15,6 +15,11 @@ module.exports = { SOME__ENV__VAR: '123', } : {}), + ...(process.env.ENABLE_ENV_NEXT_PRESERVED + ? { + NEXT_RUNTIME: 'nodejs', + } + : {}), }, onDemandEntries: { // Make sure entries are not getting disposed. diff --git a/test/integration/production-config/test/index.test.js b/test/integration/production-config/test/index.test.js index 53e57cb5c749..53696974199f 100644 --- a/test/integration/production-config/test/index.test.js +++ b/test/integration/production-config/test/index.test.js @@ -56,6 +56,16 @@ describe('Production Config Usage', () => { expect(result.stderr).toMatch(/The key "NODE_ENV" under/) }) + it('should fail with NEXT_RUNTIME in env key', async () => { + const result = await runNextCommand(['build', appDir], { + env: { ENABLE_ENV_NEXT_PRESERVED: true }, + stdout: true, + stderr: true, + }) + + expect(result.stderr).toMatch(/The key "NEXT_RUNTIME" under/) + }) + it('should allow __ within env key', async () => { const result = await runNextCommand(['build', appDir], { env: { ENABLE_ENV_WITH_UNDERSCORES: true }, diff --git a/test/integration/react-streaming-and-server-components/app/pages/runtime.js b/test/integration/react-streaming-and-server-components/app/pages/runtime.js index 05a3bd70a5af..4d0cde67a02f 100644 --- a/test/integration/react-streaming-and-server-components/app/pages/runtime.js +++ b/test/integration/react-streaming-and-server-components/app/pages/runtime.js @@ -1,5 +1,5 @@ export default function () { - return process.version + return process.env.NEXT_RUNTIME === 'nodejs' ? `Runtime: Node.js ${process.version}` : 'Runtime: Edge/Browser' } diff --git a/test/production/middleware-environment-variables-in-node-server-reflect-the-usage-inference/index.test.ts b/test/production/middleware-environment-variables-in-node-server-reflect-the-usage-inference/index.test.ts index f18fc371cf08..7aa6ddb7577b 100644 --- a/test/production/middleware-environment-variables-in-node-server-reflect-the-usage-inference/index.test.ts +++ b/test/production/middleware-environment-variables-in-node-server-reflect-the-usage-inference/index.test.ts @@ -44,6 +44,7 @@ describe('middleware environment variables in node server reflect the usage infe rest: { CAN_BE_INFERRED: 'can-be-inferred', X_CUSTOM_HEADER: 'x-custom-header', + NEXT_RUNTIME: 'edge', }, }) })