From b81c398931c41aa1ac37ad93b9e027346c1ed130 Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Wed, 15 Mar 2023 18:07:19 -0400 Subject: [PATCH] fix(nextjs): refactor how webpack config is loaded with nextjs (#15650) Co-authored-by: FrozenPandaz (cherry picked from commit be81405a84777df7916d5a0979a5bd32a4d63cd7) --- nx-dev/nx-dev/next.config.js | 2 +- packages/next/plugins/with-nx.spec.ts | 6 +- packages/next/plugins/with-nx.ts | 139 ++++++++++++++++++ .../next/src/executors/build/build.impl.ts | 33 +---- .../next/src/executors/export/export.impl.ts | 42 +++--- .../next/src/executors/server/server.impl.ts | 39 +---- packages/next/src/utils/config.spec.ts | 107 ++------------ packages/next/src/utils/config.ts | 115 ++++----------- packages/next/src/utils/types.ts | 1 - .../executors/dev-server/dev-server.impl.ts | 2 +- .../src/executors/webpack/webpack.impl.ts | 4 +- 11 files changed, 218 insertions(+), 272 deletions(-) diff --git a/nx-dev/nx-dev/next.config.js b/nx-dev/nx-dev/next.config.js index 8e16595629457..e6c98a5019b36 100644 --- a/nx-dev/nx-dev/next.config.js +++ b/nx-dev/nx-dev/next.config.js @@ -1,5 +1,5 @@ // nx-ignore-next-line -const withNx = require('@nrwl/next/plugins/with-nx'); +const { withNx } = require('@nrwl/next/plugins/with-nx'); const { copySync } = require('fs-extra'); const path = require('path'); const redirectRules = require('./redirect-rules.config'); diff --git a/packages/next/plugins/with-nx.spec.ts b/packages/next/plugins/with-nx.spec.ts index 37369146349d0..f0dfcf7c17e5b 100644 --- a/packages/next/plugins/with-nx.spec.ts +++ b/packages/next/plugins/with-nx.spec.ts @@ -1,10 +1,10 @@ import { NextConfigComplete } from 'next/dist/server/config-shared'; -import { withNx } from './with-nx'; +import { getNextConfig } from './with-nx'; describe('withNx', () => { describe('svgr', () => { it('should be used by default', () => { - const config = withNx({}); + const config = getNextConfig(); const result = config.webpack( { @@ -32,7 +32,7 @@ describe('withNx', () => { }); it('should not be used when disabled', () => { - const config = withNx({ + const config = getNextConfig({ nx: { svgr: false, }, diff --git a/packages/next/plugins/with-nx.ts b/packages/next/plugins/with-nx.ts index d750a6f13f117..0e6e3ecbb224a 100644 --- a/packages/next/plugins/with-nx.ts +++ b/packages/next/plugins/with-nx.ts @@ -1,5 +1,22 @@ +import { + createProjectGraphAsync, + joinPathFragments, + offsetFromRoot, + parseTargetString, + ProjectGraph, + ProjectGraphProjectNode, + Target, + workspaceRoot, +} from '@nrwl/devkit'; +import { + calculateProjectDependencies, + DependentBuildableProjectNode, +} from '@nrwl/js/src/utils/buildable-libs-utils'; import type { NextConfig } from 'next'; +import path = require('path'); +import { createWebpackConfig } from '../src/utils/config'; +import { NextBuildBuilderOptions } from '@nrwl/next'; export interface WithNxOptions extends NextConfig { nx?: { svgr?: boolean; @@ -34,7 +51,128 @@ function getWithNxContext(): WithNxContext { }; } +function getTargetConfig(graph: ProjectGraph, target: Target) { + const projectNode = graph.nodes[target.project]; + return projectNode.data.targets[target.target]; +} + +function getOptions(graph: ProjectGraph, target: Target) { + const targetConfig = getTargetConfig(graph, target); + const options = targetConfig.options; + if (target.configuration) { + Object.assign(options, targetConfig.configurations[target.configuration]); + } + + return options; +} + +function getNxContext( + graph: ProjectGraph, + target: Target +): { + node: ProjectGraphProjectNode; + options: NextBuildBuilderOptions; + projectName: string; + targetName: string; + configurationName?: string; +} { + const targetConfig = getTargetConfig(graph, target); + + if ('@nrwl/next:build' === targetConfig.executor) { + return { + node: graph.nodes[target.project], + options: getOptions(graph, target), + projectName: target.project, + targetName: target.target, + configurationName: target.configuration, + }; + } + + const targetOptions = getOptions(graph, target); + + // If we are running serve or export pull the options from the dependent target first (ex. build) + if (targetOptions.devServerTarget) { + const devServerTarget = parseTargetString( + targetOptions.devServerTarget, + graph + ); + + return getNxContext(graph, devServerTarget); + } else if ( + ['@nrwl/next:server', '@nrwl/next:export'].includes(targetConfig.executor) + ) { + const buildTarget = parseTargetString(targetOptions.buildTarget, graph); + return getNxContext(graph, buildTarget); + } else { + throw new Error( + 'Could not determine the config for this Next application.' + ); + } +} + export function withNx( + _nextConfig = {} as WithNxOptions, + context: WithNxContext = getWithNxContext() +): () => Promise { + return async () => { + let dependencies: DependentBuildableProjectNode[] = []; + + const graph = await createProjectGraphAsync(); + + const originalTarget = { + project: process.env.NX_TASK_TARGET_PROJECT, + target: process.env.NX_TASK_TARGET_TARGET, + configuration: process.env.NX_TASK_TARGET_CONFIGURATION, + }; + + const { + node: projectNode, + options, + projectName: project, + targetName, + configurationName, + } = getNxContext(graph, originalTarget); + const projectDirectory = projectNode.data.root; + + if (!options.buildLibsFromSource && targetName) { + const result = calculateProjectDependencies( + graph, + workspaceRoot, + project, + targetName, + configurationName + ); + dependencies = result.dependencies; + } + + // Get next config + const nextConfig = getNextConfig(_nextConfig, context); + + const outputDir = `${offsetFromRoot(projectDirectory)}${ + options.outputPath + }`; + nextConfig.distDir = + nextConfig.distDir && nextConfig.distDir !== '.next' + ? joinPathFragments(outputDir, nextConfig.distDir) + : joinPathFragments(outputDir, '.next'); + + const userWebpackConfig = nextConfig.webpack; + + nextConfig.webpack = (a, b) => + createWebpackConfig( + workspaceRoot, + options.root, + options.fileReplacements, + options.assets, + dependencies, + path.join(workspaceRoot, context.libsDir) + )(userWebpackConfig ? userWebpackConfig(a, b) : a, b); + + return nextConfig; + }; +} + +export function getNextConfig( nextConfig = {} as WithNxOptions, context: WithNxContext = getWithNxContext() ): NextConfig { @@ -223,3 +361,4 @@ function addNxEnvVariables(config: any) { module.exports = withNx; // Support for newer generated code: `const { withNx } = require(...);` module.exports.withNx = withNx; +module.exports.getNextConfig = getNextConfig; diff --git a/packages/next/src/executors/build/build.impl.ts b/packages/next/src/executors/build/build.impl.ts index e47f38ea5bff7..bb21136506895 100644 --- a/packages/next/src/executors/build/build.impl.ts +++ b/packages/next/src/executors/build/build.impl.ts @@ -2,7 +2,6 @@ import 'dotenv/config'; import { ExecutorContext, readJsonFile, - workspaceLayout, workspaceRoot, writeJsonFile, } from '@nrwl/devkit'; @@ -12,18 +11,13 @@ import { join, resolve } from 'path'; import { copySync, existsSync, mkdir, writeFileSync } from 'fs-extra'; import { gte } from 'semver'; import { directoryExists } from '@nrwl/workspace/src/utilities/fileutils'; -import { - calculateProjectDependencies, - DependentBuildableProjectNode, -} from '@nrwl/workspace/src/utilities/buildable-libs-utils'; -import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils'; +import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utils/version-utils'; -import { prepareConfig } from '../../utils/config'; import { updatePackageJson } from './lib/update-package-json'; import { createNextConfigFile } from './lib/create-next-config-file'; import { checkPublicDirectory } from './lib/check-project'; import { NextBuildBuilderOptions } from '../../utils/types'; -import { PHASE_PRODUCTION_BUILD } from '../../utils/constants'; + import { getLockFileName } from 'nx/src/lock-file/lock-file'; export default async function buildExecutor( @@ -33,23 +27,10 @@ export default async function buildExecutor( // Cast to any to overwrite NODE_ENV (process.env as any).NODE_ENV ||= 'production'; - let dependencies: DependentBuildableProjectNode[] = []; const root = resolve(context.root, options.root); - const libsDir = join(context.root, workspaceLayout().libsDir); checkPublicDirectory(root); - if (!options.buildLibsFromSource && context.targetName) { - const result = calculateProjectDependencies( - context.projectGraph, - context.root, - context.projectName, - context.targetName, - context.configurationName - ); - dependencies = result.dependencies; - } - // Set `__NEXT_REACT_ROOT` based on installed ReactDOM version const packageJsonPath = join(root, 'package.json'); const packageJson = existsSync(packageJsonPath) @@ -66,15 +47,7 @@ export default async function buildExecutor( (process.env as any).__NEXT_REACT_ROOT ||= 'true'; } - const config = await prepareConfig( - PHASE_PRODUCTION_BUILD, - options, - context, - dependencies, - libsDir - ); - - await build(root, config as any); + await build(root); if (!directoryExists(options.outputPath)) { mkdir(options.outputPath); diff --git a/packages/next/src/executors/export/export.impl.ts b/packages/next/src/executors/export/export.impl.ts index d838836c1d1c6..88bfc37470538 100644 --- a/packages/next/src/executors/export/export.impl.ts +++ b/packages/next/src/executors/export/export.impl.ts @@ -3,7 +3,7 @@ import { ExecutorContext, parseTargetString, readTargetOptions, - runExecutor, + workspaceLayout, } from '@nrwl/devkit'; import exportApp from 'next/dist/export'; import { join, resolve } from 'path'; @@ -11,15 +11,19 @@ import { calculateProjectDependencies, DependentBuildableProjectNode, } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; -import { workspaceLayout } from '@nrwl/devkit'; -import { prepareConfig } from '../../utils/config'; import { NextBuildBuilderOptions, NextExportBuilderOptions, } from '../../utils/types'; import { PHASE_EXPORT } from '../../utils/constants'; import nextTrace = require('next/dist/trace'); +import { platform } from 'os'; +import { execFileSync } from 'child_process'; +import * as chalk from 'chalk'; + +// platform specific command name +const pmCmd = platform() === 'win32' ? `npx.cmd` : 'npx'; export default async function exportExecutor( options: NextExportBuilderOptions, @@ -38,13 +42,18 @@ export default async function exportExecutor( } const libsDir = join(context.root, workspaceLayout().libsDir); - const buildTarget = parseTargetString(options.buildTarget); - const build = await runExecutor(buildTarget, {}, context); + const buildTarget = parseTargetString( + options.buildTarget, + context.projectGraph + ); - for await (const result of build) { - if (!result.success) { - return result; - } + try { + const args = getBuildTargetCommand(options); + execFileSync(pmCmd, args, { + stdio: [0, 1, 2], + }); + } catch { + throw new Error(`Build target failed: ${chalk.bold(options.buildTarget)}`); } const buildOptions = readTargetOptions( @@ -52,13 +61,6 @@ export default async function exportExecutor( context ); const root = resolve(context.root, buildOptions.root); - const config = await prepareConfig( - PHASE_EXPORT, - buildOptions, - context, - dependencies, - libsDir - ); // Taken from: // https://github.com/vercel/next.js/blob/ead56eaab68409e96c19f7d9139747bac1197aa9/packages/next/cli/next-export.ts#L13 @@ -72,9 +74,13 @@ export default async function exportExecutor( threads: options.threads, outdir: `${buildOptions.outputPath}/exported`, } as any, - nextExportCliSpan, - config + nextExportCliSpan ); return { success: true }; } + +function getBuildTargetCommand(options: NextExportBuilderOptions) { + const cmd = ['nx', 'run', options.buildTarget]; + return cmd; +} diff --git a/packages/next/src/executors/server/server.impl.ts b/packages/next/src/executors/server/server.impl.ts index d2a18c6fe6f4c..3b3216692e66b 100644 --- a/packages/next/src/executors/server/server.impl.ts +++ b/packages/next/src/executors/server/server.impl.ts @@ -5,14 +5,11 @@ import { parseTargetString, readTargetOptions, runExecutor, - workspaceLayout, } from '@nrwl/devkit'; import * as chalk from 'chalk'; import { existsSync } from 'fs'; import { join, resolve } from 'path'; -import { calculateProjectDependencies } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; -import { prepareConfig } from '../../utils/config'; import { NextBuildBuilderOptions, NextServeBuilderOptions, @@ -22,10 +19,6 @@ import { } from '../../utils/types'; import { customServer } from './lib/custom-server'; import { defaultServer } from './lib/default-server'; -import { - PHASE_DEVELOPMENT_SERVER, - PHASE_PRODUCTION_SERVER, -} from '../../utils/constants'; const infoPrefix = `[ ${chalk.dim(chalk.cyan('info'))} ] `; @@ -48,42 +41,16 @@ export default async function* serveExecutor( context ); const root = resolve(context.root, buildOptions.root); - const config = await prepareConfig( - options.dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER, - buildOptions, - context, - getDependencies(options, context), - join(context.root, workspaceLayout().libsDir) - ); if (options.customServerTarget) { - yield* runCustomServer(root, config, options, buildOptions, context); - } else { - yield* runNextDevServer(root, config, options, buildOptions, context); - } -} - -function getDependencies( - options: NextServeBuilderOptions, - context: ExecutorContext -) { - if (options.buildLibsFromSource) { - return []; + yield* runCustomServer(root, options, buildOptions, context); } else { - const result = calculateProjectDependencies( - context.projectGraph, - context.root, - context.projectName, - 'build', // should be generalized - context.configurationName - ); - return result.dependencies; + yield* runNextDevServer(root, options, buildOptions, context); } } async function* runNextDevServer( root: string, - config: ReturnType, options: NextServeBuilderOptions, buildOptions: NextBuildBuilderOptions, context: ExecutorContext @@ -94,7 +61,6 @@ async function* runNextDevServer( dir: root, staticMarkup: options.staticMarkup, quiet: options.quiet, - conf: config, port: options.port, customServer: !!options.customServerTarget, hostname: options.hostname || 'localhost', @@ -144,7 +110,6 @@ async function* runNextDevServer( async function* runCustomServer( root: string, - config: ReturnType, options: NextServeBuilderOptions, buildOptions: NextBuildBuilderOptions, context: ExecutorContext diff --git a/packages/next/src/utils/config.spec.ts b/packages/next/src/utils/config.spec.ts index 79b9a20cfcc31..ebf903a44add4 100644 --- a/packages/next/src/utils/config.spec.ts +++ b/packages/next/src/utils/config.spec.ts @@ -1,14 +1,8 @@ import 'nx/src/utils/testing/mock-fs'; -import { createWebpackConfig, prepareConfig } from './config'; -import { NextBuildBuilderOptions } from '@nrwl/next'; -import { dirname } from 'path'; +import { createWebpackConfig } from './config'; import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; -import { PHASE_PRODUCTION_BUILD } from './constants'; - -jest.mock('@nrwl/webpack', () => ({ - createCopyPlugin: () => {}, -})); +jest.mock('@nrwl/webpack', () => ({})); jest.mock('tsconfig-paths-webpack-plugin'); jest.mock('next/dist/server/config', () => ({ __esModule: true, @@ -57,97 +51,28 @@ describe('Next.js webpack config builder', () => { }); }); - it('should set the rules', () => { + it('should add rules for ts', () => { const webpackConfig = createWebpackConfig('/root', 'apps/wibble', []); const config = webpackConfig( - { resolve: { alias: {} }, module: { rules: [] }, plugins: [] }, + { + resolve: { alias: {} }, + module: { + rules: [ + { + test: /\.*.ts/, + loader: 'some-ts-loader', + }, + ], + }, + plugins: [], + }, { defaultLoaders: {} } ); // not much value in checking what they are // just check they get added - expect(config.module.rules.length).toBe(1); - }); - }); - - describe('prepareConfig', () => { - it('should set the dist directory', async () => { - const config = await prepareConfig( - PHASE_PRODUCTION_BUILD, - { - root: 'apps/wibble', - outputPath: 'dist/apps/wibble', - fileReplacements: [], - }, - { root: '/root' } as any, - [], - '' - ); - - expect(config).toEqual( - expect.objectContaining({ - distDir: '../../dist/apps/wibble/.next', - }) - ); - }); - - it('should support nextConfig option to customize the config', async () => { - const fullPath = require.resolve('./config.fixture'); - const rootPath = dirname(fullPath); - const config = await prepareConfig( - PHASE_PRODUCTION_BUILD, - { - root: 'apps/wibble', - outputPath: 'dist/apps/wibble', - fileReplacements: [], - nextConfig: 'config.fixture', - customValue: 'test', - } as NextBuildBuilderOptions, - { root: rootPath } as any, - [], - '' - ); - - expect(config).toMatchObject({ - myCustomValue: 'test', - }); - }); - - it('should provide error message when nextConfig path is invalid', async () => { - await expect(() => - prepareConfig( - PHASE_PRODUCTION_BUILD, - { - root: 'apps/wibble', - outputPath: 'dist/apps/wibble', - fileReplacements: [], - nextConfig: 'config-does-not-exist.fixture', - customValue: 'test', - } as NextBuildBuilderOptions, - { root: '/root' } as any, - [], - '' - ) - ).rejects.toThrow(/Could not find file/); - }); - - it('should provide error message when nextConfig does not export a function', async () => { - await expect(() => - prepareConfig( - PHASE_PRODUCTION_BUILD, - { - root: 'apps/wibble', - outputPath: 'dist/apps/wibble', - fileReplacements: [], - nextConfig: require.resolve('./config-not-a-function.fixture'), - customValue: 'test', - } as NextBuildBuilderOptions, - { root: '/root' } as any, - [], - '' - ) - ).rejects.toThrow(/option does not export a function/); + expect(config.module.rules.length).toBe(2); }); }); }); diff --git a/packages/next/src/utils/config.ts b/packages/next/src/utils/config.ts index 7c80f52c619d8..e08309f1bc507 100644 --- a/packages/next/src/utils/config.ts +++ b/packages/next/src/utils/config.ts @@ -1,31 +1,13 @@ -import { - ExecutorContext, - joinPathFragments, - offsetFromRoot, -} from '@nrwl/devkit'; -// ignoring while we support both Next 11.1.0 and versions before it -// @ts-ignore -import type { NextConfig } from 'next/dist/server/config-shared'; -// @ts-ignore -import type { - PHASE_DEVELOPMENT_SERVER, - PHASE_EXPORT, - PHASE_PRODUCTION_BUILD, - PHASE_PRODUCTION_SERVER, -} from 'next/dist/shared/lib/constants'; import { join, resolve } from 'path'; import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; -import { Configuration } from 'webpack'; -import { FileReplacement, NextBuildBuilderOptions } from './types'; +import { Configuration, RuleSetRule } from 'webpack'; +import { FileReplacement } from './types'; import { createCopyPlugin, normalizeAssets } from '@nrwl/webpack'; -import { WithNxOptions } from '../../plugins/with-nx'; import { createTmpTsConfig, DependentBuildableProjectNode, } from '@nrwl/workspace/src/utilities/buildable-libs-utils'; -const loadConfig = require('next/dist/server/config').default; - export function createWebpackConfig( workspaceRoot: string, projectRoot: string, @@ -38,12 +20,10 @@ export function createWebpackConfig( config: Configuration, { isServer, - defaultLoaders, }: { buildId: string; dev: boolean; isServer: boolean; - defaultLoaders: any; } ): Configuration { const mainFields = ['es2015', 'module', 'main']; @@ -76,12 +56,24 @@ export function createWebpackConfig( return alias; }, config.resolve.alias); - config.module.rules.push({ - test: /\.([jt])sx?$/, - include: [libsDir], - exclude: /node_modules/, - use: [defaultLoaders.babel], - }); + // Apply any rules that work on ts files to the libsDir as well + const rulesToAdd = []; + for (const r of config.module.rules) { + if (typeof r === 'string') { + continue; + } + if (isTsRule(r)) { + rulesToAdd.push({ ...r, include: [libsDir] }); + } else if (r.oneOf && r.oneOf.find(isTsRule)) { + rulesToAdd.push({ + ...r, + oneOf: r.oneOf + .filter(isTsRule) + .map((subRule) => ({ ...subRule, include: [libsDir] })), + }); + } + } + config.module.rules.push(...rulesToAdd); // Copy (shared) assets to `public` folder during client-side compilation if (!isServer && Array.isArray(assets) && assets.length > 0) { @@ -99,68 +91,13 @@ export function createWebpackConfig( }; } -export async function prepareConfig( - phase: - | typeof PHASE_PRODUCTION_BUILD - | typeof PHASE_EXPORT - | typeof PHASE_DEVELOPMENT_SERVER - | typeof PHASE_PRODUCTION_SERVER, - options: NextBuildBuilderOptions, - context: ExecutorContext, - dependencies: DependentBuildableProjectNode[], - libsDir: string -) { - const config = (await loadConfig(phase, options.root, null)) as NextConfig & - WithNxOptions; - - const userWebpack = config.webpack; - const userNextConfig = getConfigEnhancer(options.nextConfig, context.root); - // Yes, these do have different capitalisation... - const outputDir = `${offsetFromRoot(options.root)}${options.outputPath}`; - config.distDir = - config.distDir && config.distDir !== '.next' - ? joinPathFragments(outputDir, config.distDir) - : joinPathFragments(outputDir, '.next'); - config.webpack = (a, b) => - createWebpackConfig( - context.root, - options.root, - options.fileReplacements, - options.assets, - dependencies, - libsDir - )(userWebpack ? userWebpack(a, b) : a, b); - - if (typeof userNextConfig !== 'function') { - throw new Error( - `Module specified by 'nextConfig' option does not export a function. It should be of form 'module.exports = (phase, config, options) => config;'` - ); - } - - return userNextConfig(phase, config, { options }); -} - -function getConfigEnhancer( - pluginPath: undefined | string, - workspaceRoot: string -) { - if (!pluginPath) { - return (_, x) => x; +function isTsRule(r: RuleSetRule): boolean { + if (typeof r === 'string') { + return false; } - - let fullPath: string; - - try { - fullPath = require.resolve(pluginPath); - } catch { - fullPath = join(workspaceRoot, pluginPath); + if (!(r.test instanceof RegExp)) { + return false; } - try { - return require(fullPath); - } catch { - throw new Error( - `Could not find file specified by 'nextConfig' option: ${fullPath}` - ); - } + return r.test.test('a.ts'); } diff --git a/packages/next/src/utils/types.ts b/packages/next/src/utils/types.ts index 350fd620ca411..8d6bb0b7e3282 100644 --- a/packages/next/src/utils/types.ts +++ b/packages/next/src/utils/types.ts @@ -17,7 +17,6 @@ export interface NextServerOptions { dir: string; staticMarkup: boolean; quiet: boolean; - conf: any; port: number; path: string; hostname: string; diff --git a/packages/webpack/src/executors/dev-server/dev-server.impl.ts b/packages/webpack/src/executors/dev-server/dev-server.impl.ts index 0b2722e62456a..22751b40a8e1c 100644 --- a/packages/webpack/src/executors/dev-server/dev-server.impl.ts +++ b/packages/webpack/src/executors/dev-server/dev-server.impl.ts @@ -25,7 +25,7 @@ export async function* devServerExecutor( context: ExecutorContext ) { // Default to dev mode so builds are faster and HMR mode works better. - process.env.NODE_ENV ??= 'development'; + (process.env as any).NODE_ENV ??= 'development'; const { root: projectRoot, sourceRoot } = context.projectsConfigurations.projects[context.projectName]; diff --git a/packages/webpack/src/executors/webpack/webpack.impl.ts b/packages/webpack/src/executors/webpack/webpack.impl.ts index 20463e8aa2b35..c05143075774d 100644 --- a/packages/webpack/src/executors/webpack/webpack.impl.ts +++ b/packages/webpack/src/executors/webpack/webpack.impl.ts @@ -96,7 +96,9 @@ export async function* webpackExecutor( ? options.optimization.scripts : false; - process.env.NODE_ENV ||= isScriptOptimizeOn ? 'production' : 'development'; + (process.env as any).NODE_ENV ||= isScriptOptimizeOn + ? 'production' + : 'development'; if (options.compiler === 'swc') { try {