diff --git a/packages/webpack-cli/src/plugins/cli-plugin.ts b/packages/webpack-cli/src/plugins/cli-plugin.ts index 33f06e8aaf4..0f4796a3ea1 100644 --- a/packages/webpack-cli/src/plugins/cli-plugin.ts +++ b/packages/webpack-cli/src/plugins/cli-plugin.ts @@ -1,7 +1,7 @@ import { type Compiler } from "webpack"; import { type CLIPluginOptions } from "../types.js"; -export class CLIPlugin { +export default class CLIPlugin { logger!: ReturnType; options: CLIPluginOptions; @@ -149,5 +149,3 @@ export class CLIPlugin { this.setupHelpfulOutput(compiler); } } - -module.exports = CLIPlugin; diff --git a/packages/webpack-cli/src/types.ts b/packages/webpack-cli/src/types.ts index 67acb7de486..96048a629f4 100644 --- a/packages/webpack-cli/src/types.ts +++ b/packages/webpack-cli/src/types.ts @@ -1,4 +1,3 @@ -import { type stringifyChunked } from "@discoveryjs/json-ext"; import { type Command, type CommandOptions, type Option, type ParseOptions } from "commander"; import { type prepare } from "rechoir"; import { @@ -32,6 +31,7 @@ declare interface WebpackCallback { } // TODO remove me in the next major release, we don't need extra interface +// TODO also revisit all methods - remove unused or make private interface IWebpackCLI { colors: WebpackCLIColors; logger: WebpackCLILogger; @@ -287,10 +287,6 @@ interface Rechoir { prepare: typeof prepare; } -interface JsonExt { - stringifyChunked: typeof stringifyChunked; -} - interface RechoirError extends Error { failures: RechoirError[]; error: Error; @@ -318,7 +314,6 @@ export { type IWebpackCLI, type ImportLoaderError, type Instantiable, - type JsonExt, type LoadableWebpackConfiguration, type ModuleName, type PackageInstallOptions, diff --git a/packages/webpack-cli/src/webpack-cli.ts b/packages/webpack-cli/src/webpack-cli.ts index ae26dff3317..ac7431f79ed 100644 --- a/packages/webpack-cli/src/webpack-cli.ts +++ b/packages/webpack-cli/src/webpack-cli.ts @@ -10,14 +10,11 @@ import { type WebpackError, default as webpack, } from "webpack"; -import type webpackMerge from "webpack-merge"; -import { type CLIPlugin as CLIPluginClass } from "./plugins/cli-plugin.js"; import { type Argument, type Argv, type BasicPrimitive, - type CLIPluginOptions, type CallableWebpackConfiguration, type CommandAction, type DynamicImport, @@ -26,7 +23,6 @@ import { type IWebpackCLI, type ImportLoaderError, type Instantiable, - type JsonExt, type LoadableWebpackConfiguration, type ModuleName, type PackageInstallOptions, @@ -73,6 +69,11 @@ const WEBPACK_DEV_SERVER_PACKAGE = WEBPACK_DEV_SERVER_PACKAGE_IS_CUSTOM : "webpack-dev-server"; const EXIT_SIGNALS = ["SIGINT", "SIGTERM"]; +const DEFAULT_CONFIGURATION_FILES = [ + "webpack.config", + ".webpack/webpack.config", + ".webpack/webpackfile", +]; interface Information { Binaries?: string[]; @@ -83,6 +84,25 @@ interface Information { npmPackages?: string | string[]; } +type LoadConfigOption = PotentialPromise; + +class ConfigurationLoadingError extends Error { + name = "ConfigurationLoadingError"; + + constructor(errors: [unknown, unknown]) { + const message1 = errors[0] instanceof Error ? errors[0].message : String(errors[0]); + const message2 = util.stripVTControlCharacters( + errors[1] instanceof Error ? errors[1].message : String(errors[1]), + ); + const message = + `▶ ESM (\`import\`) failed:\n ${message1.split("\n").join("\n ")}\n\n▶ CJS (\`require\`) failed:\n ${message2.split("\n").join("\n ")}`.trim(); + + super(message); + + this.stack = ""; + } +} + class WebpackCLI implements IWebpackCLI { colors: WebpackCLIColors; @@ -348,7 +368,7 @@ class WebpackCLI implements IWebpackCLI { } if (needInstall) { - const { sync } = require("cross-spawn"); + const { sync } = await import("cross-spawn"); try { sync(packageManager, commandArguments, { stdio: "inherit" }); @@ -364,6 +384,7 @@ class WebpackCLI implements IWebpackCLI { process.exit(2); } + // TODO remove me in the next major release async tryRequireThenImport( module: ModuleName, handleError = true, @@ -539,7 +560,7 @@ class WebpackCLI implements IWebpackCLI { defaultInformation.npmPackages = `{${defaultPackages.map((item) => `*${item}*`).join(",")}}`; - const envinfo = await this.tryRequireThenImport("envinfo", false); + const envinfo = await import("envinfo"); let info = await envinfo.run(defaultInformation, envinfoConfig); @@ -1094,8 +1115,8 @@ class WebpackCLI implements IWebpackCLI { return options; } - async loadWebpack(handleError = true) { - return this.tryRequireThenImport(WEBPACK_PACKAGE, handleError); + async loadWebpack(): Promise { + return require(WEBPACK_PACKAGE); } async run(args: Parameters[0], parseOptions: ParseOptions) { @@ -1287,7 +1308,7 @@ class WebpackCLI implements IWebpackCLI { }; // Register own exit - this.program.exitOverride(async (error) => { + this.program.exitOverride((error) => { if (error.exitCode === 0) { process.exit(0); } @@ -1325,10 +1346,10 @@ class WebpackCLI implements IWebpackCLI { process.exit(2); } - const levenshtein = require("fastest-levenshtein"); + const { distance } = require("fastest-levenshtein"); for (const option of (command as WebpackCLICommand).options) { - if (!option.hidden && levenshtein.distance(name, option.long?.slice(2)) < 3) { + if (!option.hidden && distance(name, option.long?.slice(2) as string) < 3) { this.logger.error(`Did you mean '--${option.name()}'?`); } } @@ -1761,11 +1782,10 @@ class WebpackCLI implements IWebpackCLI { } else { this.logger.error(`Unknown command or entry '${operand}'`); - const levenshtein = require("fastest-levenshtein"); + const { distance } = await import("fastest-levenshtein"); const found = knownCommands.find( - (commandOptions) => - levenshtein.distance(operand, getCommandName(commandOptions.name)) < 3, + (commandOptions) => distance(operand, getCommandName(commandOptions.name)) < 3, ); if (found) { @@ -1789,30 +1809,41 @@ class WebpackCLI implements IWebpackCLI { await this.program.parseAsync(args, parseOptions); } - async loadConfig(options: Partial) { - const disableInterpret = - typeof options.disableInterpret !== "undefined" && options.disableInterpret; + async #loadConfigurationFile( + configPath: string, + disableInterpret = false, + ): Promise { + let pkg: LoadConfigOption | LoadConfigOption[] | undefined; - const interpret = require("interpret"); + let loadingError; - const loadConfigByPath = async ( - configPath: string, - argv: Argv = {}, - ): Promise<{ options: Configuration | Configuration[]; path: string }> => { + try { + // eslint-disable-next-line no-eval + pkg = (await eval(`import("${pathToFileURL(configPath)}")`)).default; + } catch (err) { + if (this.isValidationError(err) || process.env?.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) { + throw err; + } + + loadingError = err; + } + + // Fallback logic when we can't use `import(...)` + if (loadingError) { + const { jsVariants, extensions } = await import("interpret"); const ext = path.extname(configPath).toLowerCase(); - let interpreted = Object.keys(interpret.jsVariants).find((variant) => variant === ext); - // Fallback `.cts` to `.ts` - // TODO implement good `.mts` support after https://github.com/gulpjs/rechoir/issues/43 - // For ESM and `.mts` you need to use: 'NODE_OPTIONS="--loader ts-node/esm" webpack-cli --config ./webpack.config.mts' + + let interpreted = Object.keys(jsVariants).find((variant) => variant === ext); + if (!interpreted && ext.endsWith(".cts")) { - interpreted = interpret.jsVariants[".ts"]; + interpreted = jsVariants[".ts"] as string; } if (interpreted && !disableInterpret) { - const rechoir: Rechoir = require("rechoir"); + const rechoir: Rechoir = (await import("rechoir")).default; try { - rechoir.prepare(interpret.extensions, configPath); + rechoir.prepare(extensions, configPath); } catch (error) { if ((error as RechoirError)?.failures) { this.logger.error(`Unable load '${configPath}'`); @@ -1823,52 +1854,59 @@ class WebpackCLI implements IWebpackCLI { this.logger.error("Please install one of them"); process.exit(2); } - this.logger.error(error); process.exit(2); } } - let options: LoadableWebpackConfiguration | LoadableWebpackConfiguration[]; + try { + pkg = require(configPath); + } catch (err) { + if (this.isValidationError(err)) { + throw err; + } + + throw new ConfigurationLoadingError([loadingError, err]); + } + } + + // To handle `babel`/`module.exports.default = {};` + if (pkg && typeof pkg === "object" && "default" in pkg) { + pkg = pkg.default as LoadConfigOption | LoadConfigOption[] | undefined; + } - type LoadConfigOption = PotentialPromise; + if (!pkg) { + this.logger.warn( + `Default export is missing or nullish at (from ${configPath}). Webpack will run with an empty configuration. Please double-check that this is what you want. If you want to run webpack with an empty config, \`export {}\`/\`module.exports = {};\` to remove this warning.`, + ); + } - let moduleType: "unknown" | "commonjs" | "esm" = "unknown"; + return pkg || {}; + } - switch (ext) { - case ".cjs": - case ".cts": - moduleType = "commonjs"; - break; - case ".mjs": - case ".mts": - moduleType = "esm"; - break; - } + async loadConfig(options: Partial) { + const disableInterpret = + typeof options.disableInterpret !== "undefined" && options.disableInterpret; + + const loadConfigByPath = async ( + configPath: string, + argv: Argv = {}, + ): Promise<{ options: Configuration | Configuration[]; path: string }> => { + let options: LoadableWebpackConfiguration | LoadableWebpackConfiguration[] | undefined; try { - options = await this.tryRequireThenImport( - configPath, - false, - moduleType, - ); + options = await this.#loadConfigurationFile(configPath, disableInterpret); } catch (error) { - this.logger.error(`Failed to load '${configPath}' config`); - - if (this.isValidationError(error)) { - this.logger.error(error.message); + if (error instanceof ConfigurationLoadingError) { + this.logger.error(`Failed to load '${configPath}' config\n${error.message}`); } else { + this.logger.error(`Failed to load '${configPath}' config`); this.logger.error(error); } process.exit(2); } - if (!options) { - this.logger.error(`Failed to load '${configPath}' config. Unable to find default export.`); - process.exit(2); - } - if (Array.isArray(options)) { // reassign the value to assert type const optionsArray: LoadableWebpackConfiguration[] = options; @@ -1950,6 +1988,7 @@ class WebpackCLI implements IWebpackCLI { } } } else { + const interpret = await import("interpret"); // Prioritize popular extensions first to avoid unnecessary fs calls const extensions = new Set([ ".js", @@ -1962,7 +2001,7 @@ class WebpackCLI implements IWebpackCLI { ]); // Order defines the priority, in decreasing order const defaultConfigFiles = new Set( - ["webpack.config", ".webpack/webpack.config", ".webpack/webpackfile"].flatMap((filename) => + DEFAULT_CONFIGURATION_FILES.flatMap((filename) => [...extensions].map((ext) => path.resolve(filename + ext)), ), ); @@ -2035,7 +2074,7 @@ class WebpackCLI implements IWebpackCLI { ), ); - const merge = await this.tryRequireThenImport("webpack-merge"); + const { merge } = await import("webpack-merge"); const loadedOptions = loadedConfigs.flatMap((config) => config.options); if (loadedOptions.length > 0) { @@ -2108,7 +2147,7 @@ class WebpackCLI implements IWebpackCLI { } if (options.merge) { - const merge = await this.tryRequireThenImport("webpack-merge"); + const { merge } = await import("webpack-merge"); // we can only merge when there are multiple configurations // either by passing multiple configs by flags or passing a @@ -2161,10 +2200,7 @@ class WebpackCLI implements IWebpackCLI { process.exit(2); } - const CLIPlugin = - await this.tryRequireThenImport>( - "./plugins/cli-plugin", - ); + const CLIPlugin = (await import("./plugins/cli-plugin.js")).default; const internalBuildConfig = (item: Configuration) => { const originalWatchValue = item.watch; @@ -2407,9 +2443,9 @@ class WebpackCLI implements IWebpackCLI { let createStringifyChunked: typeof stringifyChunked; if (options.json) { - const jsonExt = await this.tryRequireThenImport("@discoveryjs/json-ext"); + const { stringifyChunked } = await import("@discoveryjs/json-ext"); - createStringifyChunked = jsonExt.stringifyChunked; + createStringifyChunked = stringifyChunked; } const callback: WebpackCallback = (error, stats): void => { diff --git a/test/build/config-format/typescript-cjs-using-nodejs/typescript.test.mjs b/test/build/config-format/typescript-cjs-using-nodejs/typescript.test.mjs index f521bc851f3..6cf4dd1c201 100644 --- a/test/build/config-format/typescript-cjs-using-nodejs/typescript.test.mjs +++ b/test/build/config-format/typescript-cjs-using-nodejs/typescript.test.mjs @@ -13,11 +13,6 @@ describe("webpack cli", () => { __dirname, ["-c", "./webpack.config.ts", "--disable-interpret"], { - env: { - NODE_NO_WARNINGS: 1, - // Due nyc logic - WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true, - }, // Fallback to `ts-node/esm` for old Node.js versions nodeOptions: major >= 24 ? [] : ["--require=ts-node/register"], }, diff --git a/test/build/config-format/typescript-mjs-using-nodejs/typescript.test.mjs b/test/build/config-format/typescript-mjs-using-nodejs/typescript.test.mjs index 62d0fdc5a58..33638220420 100644 --- a/test/build/config-format/typescript-mjs-using-nodejs/typescript.test.mjs +++ b/test/build/config-format/typescript-mjs-using-nodejs/typescript.test.mjs @@ -15,8 +15,6 @@ describe("webpack cli", () => { { env: { NODE_NO_WARNINGS: 1, - // Due nyc logic - WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true, }, // Fallback to `ts-node/esm` for old Node.js versions nodeOptions: major >= 24 ? [] : ["--experimental-loader=ts-node/esm"], diff --git a/test/build/config-format/typescript-ts-node-loader/typescript.test.mjs b/test/build/config-format/typescript-ts-node-loader/typescript.test.mjs index 89462548c7e..8653ad72ae8 100644 --- a/test/build/config-format/typescript-ts-node-loader/typescript.test.mjs +++ b/test/build/config-format/typescript-ts-node-loader/typescript.test.mjs @@ -12,8 +12,6 @@ describe("webpack cli", () => { const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "./webpack.config.ts"], { env: { NODE_NO_WARNINGS: 1, - // Due nyc logic - WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true, }, nodeOptions: major >= 22 && minor >= 6 diff --git a/test/build/config-format/typescript-using-nodejs/typescript.test.mjs b/test/build/config-format/typescript-using-nodejs/typescript.test.mjs index 62d0fdc5a58..33638220420 100644 --- a/test/build/config-format/typescript-using-nodejs/typescript.test.mjs +++ b/test/build/config-format/typescript-using-nodejs/typescript.test.mjs @@ -15,8 +15,6 @@ describe("webpack cli", () => { { env: { NODE_NO_WARNINGS: 1, - // Due nyc logic - WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true, }, // Fallback to `ts-node/esm` for old Node.js versions nodeOptions: major >= 24 ? [] : ["--experimental-loader=ts-node/esm"], diff --git a/test/build/config-lookup/custom-name/custom-name.test.js b/test/build/config-lookup/custom-name/custom-name.test.js index 7f83bed5f1d..617d16fc9ae 100644 --- a/test/build/config-lookup/custom-name/custom-name.test.js +++ b/test/build/config-lookup/custom-name/custom-name.test.js @@ -16,21 +16,13 @@ describe("custom config file", () => { }); it("should work with esm format", async () => { - const { exitCode, stderr, stdout } = await run( - __dirname, - ["--config", resolve(__dirname, "config.webpack.mjs")], - { - env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true }, - }, - ); + const { exitCode, stderr, stdout } = await run(__dirname, [ + "--config", + resolve(__dirname, "config.webpack.mjs"), + ]); - if (/Error: Not supported/.test(stderr)) { - expect(exitCode).toBe(2); - expect(stdout).toBeFalsy(); - } else { - expect(exitCode).toBe(0); - expect(stderr).toBeFalsy(); - expect(stdout).toBeTruthy(); - } + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + expect(stdout).toBeTruthy(); }); }); diff --git a/test/build/config/defaults/mjs-config/default-mjs-config.test.js b/test/build/config/defaults/mjs-config/default-mjs-config.test.js index b1842c87628..7bd86af72dd 100644 --- a/test/build/config/defaults/mjs-config/default-mjs-config.test.js +++ b/test/build/config/defaults/mjs-config/default-mjs-config.test.js @@ -4,23 +4,16 @@ const { run } = require("../../../../utils/test-utils"); describe("default config with mjs extension", () => { it("should build and not throw error with mjs config by default", async () => { - const { exitCode, stderr, stdout } = await run(__dirname, [], { - env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true }, - }); + const { exitCode, stderr, stdout } = await run(__dirname, []); - if (/Error: Not supported/.test(stderr)) { - expect(exitCode).toBe(2); - expect(stdout).toBeFalsy(); - } else { - expect(exitCode).toBe(0); - expect(stderr).toBeFalsy(); - // default entry should be used - expect(stdout).toContain("./src/index.js"); - // should pick up the output path from config - expect(stdout).toContain("test-output"); - expect(stdout).toContain("compiled successfully"); - // check that the output file exists - expect(fs.existsSync(path.join(__dirname, "/dist/test-output.js"))).toBeTruthy(); - } + expect(exitCode).toBe(0); + expect(stderr).toBeFalsy(); + // default entry should be used + expect(stdout).toContain("./src/index.js"); + // should pick up the output path from config + expect(stdout).toContain("test-output"); + expect(stdout).toContain("compiled successfully"); + // check that the output file exists + expect(fs.existsSync(path.join(__dirname, "/dist/test-output.js"))).toBeTruthy(); }); }); diff --git a/test/build/config/error-array/__snapshots__/config-array-error.test.js.snap.webpack5 b/test/build/config/error-array/__snapshots__/config-array-error.test.js.snap.webpack5 new file mode 100644 index 00000000000..837aa696909 --- /dev/null +++ b/test/build/config/error-array/__snapshots__/config-array-error.test.js.snap.webpack5 @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`config with invalid array syntax should throw syntax error and exit with non-zero exit code when even 1 object has syntax error 1`] = ` +"[webpack-cli] Failed to load '/test/build/config/error-array/webpack.config.js' config +▶ ESM (\`import\`) failed: + Unexpected token ';' + +▶ CJS (\`require\`) failed: + Unexpected token ';'" +`; diff --git a/test/build/config/error-array/config-array-error.test.js b/test/build/config/error-array/config-array-error.test.js index c609231aeac..286b76ee0db 100644 --- a/test/build/config/error-array/config-array-error.test.js +++ b/test/build/config/error-array/config-array-error.test.js @@ -1,12 +1,13 @@ "use strict"; -const { run } = require("../../../utils/test-utils"); +const { normalizeStderr, run } = require("../../../utils/test-utils"); describe("config with invalid array syntax", () => { it("should throw syntax error and exit with non-zero exit code when even 1 object has syntax error", async () => { const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "./webpack.config.js"]); + expect(exitCode).toBe(2); - expect(stderr).toContain("SyntaxError: Unexpected token"); + expect(normalizeStderr(stderr)).toMatchSnapshot(); expect(stdout).toBeFalsy(); }); }); diff --git a/test/build/config/error-commonjs/__snapshots__/config-error.test.js.snap.webpack5 b/test/build/config/error-commonjs/__snapshots__/config-error.test.js.snap.webpack5 new file mode 100644 index 00000000000..cfa64d5bab4 --- /dev/null +++ b/test/build/config/error-commonjs/__snapshots__/config-error.test.js.snap.webpack5 @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`config with errors should throw syntax error and exit with non-zero exit code 1`] = ` +"[webpack-cli] Failed to load '/test/build/config/error-commonjs/syntax-error.js' config +▶ ESM (\`import\`) failed: + Unexpected token ';' + +▶ CJS (\`require\`) failed: + Unexpected token ';'" +`; diff --git a/test/build/config/error-commonjs/config-error.test.js b/test/build/config/error-commonjs/config-error.test.js index a4d4df2f420..f207df15881 100644 --- a/test/build/config/error-commonjs/config-error.test.js +++ b/test/build/config/error-commonjs/config-error.test.js @@ -1,7 +1,7 @@ "use strict"; const { resolve } = require("node:path"); -const { run } = require("../../../utils/test-utils"); +const { normalizeStderr, run } = require("../../../utils/test-utils"); describe("config with errors", () => { it("should throw error with invalid configuration", async () => { @@ -23,7 +23,7 @@ describe("config with errors", () => { ]); expect(exitCode).toBe(2); - expect(stderr).toContain("SyntaxError: Unexpected token"); + expect(normalizeStderr(stderr)).toMatchSnapshot(); expect(stdout).toBeFalsy(); }); }); diff --git a/test/build/config/error-mjs/config-error.test.js b/test/build/config/error-mjs/config-error.test.js index c2ae7507281..3f98b4047bf 100644 --- a/test/build/config/error-mjs/config-error.test.js +++ b/test/build/config/error-mjs/config-error.test.js @@ -5,25 +5,29 @@ const { run } = require("../../../utils/test-utils"); describe("config error", () => { it("should throw error with invalid configuration", async () => { - const { exitCode, stderr, stdout } = await run( - __dirname, - ["-c", resolve(__dirname, "webpack.config.mjs")], - { - env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true }, - }, - ); + const { exitCode, stderr, stdout } = await run(__dirname, [ + "-c", + resolve(__dirname, "webpack.config.mjs"), + ]); expect(exitCode).toBe(2); + expect(stderr).toContain("Invalid configuration object"); + expect(stderr).toContain('"development" | "production" | "none"'); + expect(stdout).toBeFalsy(); + }); - if (!/Error: Not supported/.test(stderr)) { - expect(stderr).toContain("Invalid configuration object"); - expect(stderr).toContain('"development" | "production" | "none"'); - } + it("should throw syntax error and exit with non-zero exit code", async () => { + const { exitCode, stderr, stdout } = await run(__dirname, [ + "-c", + resolve(__dirname, "syntax-error.mjs"), + ]); + expect(exitCode).toBe(2); + expect(stderr).toContain("Unexpected token"); expect(stdout).toBeFalsy(); }); - it("should throw syntax error and exit with non-zero exit code", async () => { + it("should throw syntax error and exit with non-zero exit code (force ESM loading)", async () => { const { exitCode, stderr, stdout } = await run( __dirname, ["-c", resolve(__dirname, "syntax-error.mjs")], @@ -33,11 +37,7 @@ describe("config error", () => { ); expect(exitCode).toBe(2); - - if (!/Error: Not supported/.test(stderr)) { - expect(stderr).toContain("SyntaxError: Unexpected token"); - } - + expect(stderr).toContain("Unexpected token"); expect(stdout).toBeFalsy(); }); }); diff --git a/test/build/config/named-export/mjs.test.js b/test/build/config/named-export/mjs.test.js index ab4336a35fc..d9e5578d5e0 100644 --- a/test/build/config/named-export/mjs.test.js +++ b/test/build/config/named-export/mjs.test.js @@ -2,15 +2,11 @@ const { run } = require("../../../utils/test-utils"); describe("webpack cli", () => { it("should support mjs config format", async () => { - const { exitCode, stderr } = await run(__dirname, ["-c", "webpack.config.mjs"], { - env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true }, - }); + const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.mjs"]); - if (/Error: Not supported/.test(stderr)) { - expect(exitCode).toBe(2); - } else { - expect(exitCode).toBe(2); - expect(stderr).toMatch(/Unable to find default export./); - } + // Exit `1` because entry was not found + expect(exitCode).toBe(1); + expect(stderr).toMatch(/Default export is missing or nullish at/); + expect(stdout).toBeTruthy(); }); }); diff --git a/test/build/config/undefined-default/undefined-default.test.js b/test/build/config/undefined-default/undefined-default.test.js index 53330b739a5..c398abbcf08 100644 --- a/test/build/config/undefined-default/undefined-default.test.js +++ b/test/build/config/undefined-default/undefined-default.test.js @@ -11,7 +11,7 @@ describe("config flag with undefined default export config file", () => { ]); expect(exitCode).toBe(0); - expect(stderr).toBeFalsy(); + expect(stderr).toMatch(/Default export is missing or nullish at/); expect(stdout).toBeTruthy(); }); }); diff --git a/test/build/config/undefined/undefined.test.js b/test/build/config/undefined/undefined.test.js index aa9f43244cf..98274a96473 100644 --- a/test/build/config/undefined/undefined.test.js +++ b/test/build/config/undefined/undefined.test.js @@ -11,7 +11,7 @@ describe("config flag with undefined export config file", () => { ]); expect(exitCode).toBe(0); - expect(stderr).toBeFalsy(); + expect(stderr).toMatch(/Default export is missing or nullish at/); expect(stdout).toBeTruthy(); }); }); diff --git a/test/configtest/with-config-path/__snapshots__/with-config-path.test.js.snap.webpack5 b/test/configtest/with-config-path/__snapshots__/with-config-path.test.js.snap.webpack5 index dec64f56516..8f64293cc3c 100644 --- a/test/configtest/with-config-path/__snapshots__/with-config-path.test.js.snap.webpack5 +++ b/test/configtest/with-config-path/__snapshots__/with-config-path.test.js.snap.webpack5 @@ -6,12 +6,11 @@ exports[`'configtest' command with the configuration path option should throw er exports[`'configtest' command with the configuration path option should throw syntax error: stderr 1`] = ` "[webpack-cli] Failed to load '/test/configtest/with-config-path/syntax-error.config.js' config -[webpack-cli] /test/configtest/with-config-path/syntax-error.config.js:5 - target: 'node'; - ^ +▶ ESM (\`import\`) failed: + Unexpected token ';' -SyntaxError: - at stack" +▶ CJS (\`require\`) failed: + Unexpected token ';'" `; exports[`'configtest' command with the configuration path option should throw syntax error: stdout 1`] = `""`;