diff --git a/.eslintignore b/.eslintignore index 74003544d4f..a61e3d3d029 100644 --- a/.eslintignore +++ b/.eslintignore @@ -14,5 +14,5 @@ test/**/bin/ test/**/binary/ test/**/index.js test/typescript/webpack.config.ts -test/config/error/syntax-error.js -test/config/error-array/webpack.config.js +test/config/error-commonjs/syntax-error.js +test/config/error-mjs/syntax-error.mjs diff --git a/.prettierignore b/.prettierignore index 7050c6118d6..b9e12bb5f96 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ test/**/dist/ test/**/bin/ test/**/binary/ test/**/index.js -test/config/error/syntax-error.js +test/config/error-commonjs/syntax-error.js +test/config/error-mjs/syntax-error.mjs packages/webpack-cli/__tests__/test-assets/.yo-rc.json test/build-errors/stats.json diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index 97aecfda24f..bf3a46152a1 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -21,7 +21,7 @@ class WebpackCLI { constructor() {} async resolveConfig(args) { - const loadConfig = (configPath) => { + const loadConfig = async (configPath) => { const ext = extname(configPath); const interpreted = Object.keys(jsVariants).find((variant) => variant === ext); @@ -29,10 +29,32 @@ class WebpackCLI { rechoir.prepare(extensions, configPath); } - let options; + const { pathToFileURL } = require('url'); + + let importESM; try { - options = require(configPath); + importESM = new Function('id', 'return import(id);'); + } catch (e) { + importESM = null; + } + + let options; + try { + try { + options = require(configPath); + } catch (error) { + if (pathToFileURL && importESM && error.code === 'ERR_REQUIRE_ESM') { + const urlForConfig = pathToFileURL(configPath); + + options = await importESM(urlForConfig); + options = options.default; + + return { options, path: configPath }; + } + + throw error; + } } catch (error) { logger.error(`Failed to load '${configPath}'`); logger.error(error); @@ -90,7 +112,7 @@ class WebpackCLI { process.exit(2); } - const loadedConfig = loadConfig(configPath); + const loadedConfig = await loadConfig(configPath); return evaluateConfig(loadedConfig, args); }), @@ -135,7 +157,7 @@ class WebpackCLI { } if (foundDefaultConfigFile) { - const loadedConfig = loadConfig(foundDefaultConfigFile.path); + const loadedConfig = await loadConfig(foundDefaultConfigFile.path); const evaluatedConfig = await evaluateConfig(loadedConfig, args); config.options = evaluatedConfig.options; diff --git a/test/config-format/mjs/main.js b/test/config-format/mjs/main.js new file mode 100644 index 00000000000..a00a3125ea3 --- /dev/null +++ b/test/config-format/mjs/main.js @@ -0,0 +1 @@ +console.log('You know who'); diff --git a/test/config-format/mjs/mjs.test.js b/test/config-format/mjs/mjs.test.js new file mode 100644 index 00000000000..20b6c4764f0 --- /dev/null +++ b/test/config-format/mjs/mjs.test.js @@ -0,0 +1,18 @@ +const { run } = require('../../utils/test-utils'); + +describe('webpack cli', () => { + it('should support mjs config format', () => { + const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.mjs'], false, [], { DISABLE_V8_COMPILE_CACHE: true }); + + if (exitCode === 0) { + expect(exitCode).toBe(0); + expect(stderr).toContain('Compilation starting...'); + expect(stderr).toContain('Compilation finished'); + expect(stdout).toBeTruthy(); + } else { + expect(exitCode).toBe(2); + expect(/Cannot use import statement outside a module/.test(stderr) || /Unexpected token/.test(stderr)).toBe(true); + expect(stdout).toBeFalsy(); + } + }); +}); diff --git a/test/config-format/mjs/webpack.config.mjs b/test/config-format/mjs/webpack.config.mjs new file mode 100644 index 00000000000..6717d84042e --- /dev/null +++ b/test/config-format/mjs/webpack.config.mjs @@ -0,0 +1,11 @@ +import { fileURLToPath } from 'url'; +import path from 'path'; + +export default { + mode: 'production', + entry: './main.js', + output: { + path: path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'dist'), + filename: 'foo.bundle.js', + }, +}; diff --git a/test/config/defaults/mjs-config/default-mjs-config.test.js b/test/config/defaults/mjs-config/default-mjs-config.test.js new file mode 100644 index 00000000000..2c920c0e492 --- /dev/null +++ b/test/config/defaults/mjs-config/default-mjs-config.test.js @@ -0,0 +1,33 @@ +const fs = require('fs'); +const path = require('path'); +const { run, isWebpack5 } = require('../../../utils/test-utils'); + +describe('Default Config:', () => { + it('Should be able to pick mjs config by default', () => { + const { exitCode, stderr, stdout } = run(__dirname, [], false, [], { DISABLE_V8_COMPILE_CACHE: true }); + + if (exitCode === 0) { + expect(exitCode).toEqual(0); + expect(stderr).toContain('Compilation starting...'); + expect(stderr).toContain('Compilation finished'); + // default entry should be used + expect(stdout).toContain('./src/index.js'); + // should pick up the output path from config + expect(stdout).toContain('test-output'); + + if (!isWebpack5) { + expect(stdout).toContain('Hash'); + expect(stdout).toContain('Version'); + expect(stdout).toContain('Built at'); + expect(stdout).toContain('Time'); + } + + // check that the output file exists + expect(fs.existsSync(path.join(__dirname, '/dist/test-output.js'))).toBeTruthy(); + } else { + expect(exitCode).toEqual(2); + expect(stderr).toContain('Unexpected token'); + expect(stdout).toBeFalsy(); + } + }); +}); diff --git a/test/config/defaults/mjs-config/src/index.js b/test/config/defaults/mjs-config/src/index.js new file mode 100644 index 00000000000..278b015b7f7 --- /dev/null +++ b/test/config/defaults/mjs-config/src/index.js @@ -0,0 +1 @@ +console.log("Jotaro Kujo") diff --git a/test/config/defaults/mjs-config/webpack.config.mjs b/test/config/defaults/mjs-config/webpack.config.mjs new file mode 100644 index 00000000000..3c5c06d4449 --- /dev/null +++ b/test/config/defaults/mjs-config/webpack.config.mjs @@ -0,0 +1,6 @@ +export default { + mode: 'development', + output: { + filename: 'test-output.js', + }, +}; diff --git a/test/config/error/config-error.test.js b/test/config/error-commonjs/config-error.test.js similarity index 100% rename from test/config/error/config-error.test.js rename to test/config/error-commonjs/config-error.test.js diff --git a/test/config/error/src/index.js b/test/config/error-commonjs/src/index.js similarity index 100% rename from test/config/error/src/index.js rename to test/config/error-commonjs/src/index.js diff --git a/test/config/error/syntax-error.js b/test/config/error-commonjs/syntax-error.js similarity index 100% rename from test/config/error/syntax-error.js rename to test/config/error-commonjs/syntax-error.js diff --git a/test/config/error/webpack.config.js b/test/config/error-commonjs/webpack.config.js similarity index 100% rename from test/config/error/webpack.config.js rename to test/config/error-commonjs/webpack.config.js diff --git a/test/config/error-mjs/config-error.test.js b/test/config/error-mjs/config-error.test.js new file mode 100644 index 00000000000..3493cd2d472 --- /dev/null +++ b/test/config/error-mjs/config-error.test.js @@ -0,0 +1,25 @@ +'use strict'; +const { resolve } = require('path'); +const { run } = require('../../utils/test-utils'); + +describe('config error', () => { + it('should throw error with invalid configuration', () => { + const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.mjs')], false, [], { + DISABLE_V8_COMPILE_CACHE: true, + }); + + expect(exitCode).toBe(2); + expect(/Invalid configuration object/.test(stderr) || /Unexpected token/.test(stderr)).toBe(true); + expect(stdout).toBeFalsy(); + }); + + it('should throw syntax error and exit with non-zero exit code', () => { + const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'syntax-error.mjs')], false, [], { + DISABLE_V8_COMPILE_CACHE: true, + }); + + expect(exitCode).toBe(2); + expect(stderr).toContain('SyntaxError: Unexpected token'); + expect(stdout).toBeFalsy(); + }); +}); diff --git a/test/config/error-mjs/src/index.js b/test/config/error-mjs/src/index.js new file mode 100644 index 00000000000..97bfc742a9b --- /dev/null +++ b/test/config/error-mjs/src/index.js @@ -0,0 +1 @@ +console.log('config error test'); diff --git a/test/config/error-mjs/syntax-error.mjs b/test/config/error-mjs/syntax-error.mjs new file mode 100644 index 00000000000..fec871d7a2b --- /dev/null +++ b/test/config/error-mjs/syntax-error.mjs @@ -0,0 +1,5 @@ +export default { + name: 'config-error', + mode: 'development', + target: 'node'; //SyntaxError: Unexpected token ';' +}; diff --git a/test/config/error-mjs/webpack.config.mjs b/test/config/error-mjs/webpack.config.mjs new file mode 100644 index 00000000000..2f7dc4a7e49 --- /dev/null +++ b/test/config/error-mjs/webpack.config.mjs @@ -0,0 +1,5 @@ +export default { + name: 'config-error', + mode: 'unknown', //error + target: 'node', +};