Skip to content

Commit aebdbbc

Browse files
feat: support ES module configuration format (#2381)
1 parent d004ded commit aebdbbc

25 files changed

+191
-85
lines changed

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"pretest": "yarn build && yarn lint && yarn prepsuite",
3838
"test": "jest --reporters=default",
3939
"test:smoketests": "nyc node smoketests",
40-
"test:coverage": "nyc jest --forceExit",
40+
"test:coverage": "nyc --require ts-node/register jest --forceExit",
4141
"test:cli": "jest test --reporters=default --forceExit",
4242
"test:packages": "jest packages/ --reporters=default --forceExit",
4343
"test:ci": "yarn test:cli && yarn test:packages",
@@ -64,6 +64,7 @@
6464
"@typescript-eslint/eslint-plugin": "^2.34.0",
6565
"@typescript-eslint/parser": "^2.34.0",
6666
"@webpack-cli/migrate": "^1.1.2",
67+
"coffeescript": "^2.5.1",
6768
"colorette": "^1.2.1",
6869
"commitlint": "^11.0.0",
6970
"commitlint-config-cz": "^0.13.2",
@@ -88,6 +89,7 @@
8889
"rimraf": "^3.0.2",
8990
"strip-ansi": "^6.0.0",
9091
"ts-jest": "^26.4.3",
92+
"ts-node": "^9.1.1",
9193
"typescript": "^4.1.3",
9294
"webpack": "^5.18.0",
9395
"webpack-bundle-analyzer": "^4.3.0",

packages/webpack-cli/bin/cli.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#!/usr/bin/env node
22

33
'use strict';
4+
5+
const Module = require('module');
6+
7+
const originalModuleCompile = Module.prototype._compile;
8+
49
require('v8-compile-cache');
510

611
const importLocal = require('import-local');
@@ -15,7 +20,7 @@ if (importLocal(__filename)) {
1520
process.title = 'webpack';
1621

1722
if (utils.packageExists('webpack')) {
18-
runCLI(process.argv);
23+
runCLI(process.argv, originalModuleCompile);
1924
} else {
2025
const { promptInstallation, logger, colors } = utils;
2126

@@ -25,7 +30,7 @@ if (utils.packageExists('webpack')) {
2530
.then(() => {
2631
logger.success(`${colors.bold('webpack')} was installed successfully.`);
2732

28-
runCLI(process.argv);
33+
runCLI(process.argv, originalModuleCompile);
2934
})
3035
.catch(() => {
3136
logger.error(`Action Interrupted, Please try once again or install ${colors.bold('webpack')} manually.`);

packages/webpack-cli/lib/bootstrap.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
const WebpackCLI = require('./webpack-cli');
22
const utils = require('./utils');
33

4-
const runCLI = async (args) => {
4+
const runCLI = async (args, originalModuleCompile) => {
55
try {
66
// Create a new instance of the CLI object
77
const cli = new WebpackCLI();
88

9+
cli._originalModuleCompile = originalModuleCompile;
10+
911
await cli.run(args);
1012
} catch (error) {
1113
utils.logger.error(error);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function dynamicImportLoader() {
2+
let importESM;
3+
4+
try {
5+
importESM = new Function('id', 'return import(id);');
6+
} catch (e) {
7+
importESM = null;
8+
}
9+
10+
return importESM;
11+
}
12+
13+
module.exports = dynamicImportLoader;

packages/webpack-cli/lib/utils/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ module.exports = {
1919
return require('./capitalize-first-letter');
2020
},
2121

22+
get dynamicImportLoader() {
23+
return require('./dynamic-import-loader');
24+
},
25+
2226
get getPackageManager() {
2327
return require('./get-package-manager');
2428
},

packages/webpack-cli/lib/webpack-cli.js

+23-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const fs = require('fs');
22
const path = require('path');
3+
const { pathToFileURL } = require('url');
4+
const Module = require('module');
35

46
const { program } = require('commander');
57
const utils = require('./utils');
@@ -1137,26 +1139,35 @@ class WebpackCLI {
11371139
}
11381140
}
11391141

1140-
const { pathToFileURL } = require('url');
1141-
1142-
let importESM;
1143-
1144-
try {
1145-
importESM = new Function('id', 'return import(id);');
1146-
} catch (e) {
1147-
importESM = null;
1148-
}
1149-
11501142
let options;
11511143

11521144
try {
11531145
try {
11541146
options = require(configPath);
11551147
} catch (error) {
1156-
if (pathToFileURL && importESM && error.code === 'ERR_REQUIRE_ESM') {
1148+
let previousModuleCompile;
1149+
1150+
// TODO Workaround https://github.com/zertosh/v8-compile-cache/issues/30
1151+
if (this._originalModuleCompile) {
1152+
previousModuleCompile = Module.prototype._compile;
1153+
1154+
Module.prototype._compile = this._originalModuleCompile;
1155+
}
1156+
1157+
const dynamicImportLoader = this.utils.dynamicImportLoader();
1158+
1159+
if (this._originalModuleCompile) {
1160+
Module.prototype._compile = previousModuleCompile;
1161+
}
1162+
1163+
if (
1164+
(error.code === 'ERR_REQUIRE_ESM' || process.env.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) &&
1165+
pathToFileURL &&
1166+
dynamicImportLoader
1167+
) {
11571168
const urlForConfig = pathToFileURL(configPath);
11581169

1159-
options = await importESM(urlForConfig);
1170+
options = await dynamicImportLoader(urlForConfig);
11601171
options = options.default;
11611172

11621173
return { options, path: configPath };

test/config-format/coffee/coffee.test.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// eslint-disable-next-line node/no-unpublished-require
21
const { run } = require('../../utils/test-utils');
32

43
describe('webpack cli', () => {

test/config-format/coffee/package.json

-5
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const { run } = require('../../utils/test-utils');
2+
3+
describe('webpack cli', () => {
4+
it('should support CommonJS file', () => {
5+
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.cjs'], false);
6+
7+
expect(exitCode).toBe(0);
8+
expect(stderr).toBeFalsy();
9+
expect(stdout).toBeTruthy();
10+
});
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('Hoshiumi');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const path = require('path');
2+
3+
const config = {
4+
mode: 'production',
5+
entry: './main.js',
6+
output: {
7+
path: path.resolve(__dirname, 'dist'),
8+
filename: 'foo.bundle.js',
9+
},
10+
};
11+
12+
module.exports.default = config;

test/config-format/failure/failure.test.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@ const path = require('path');
22

33
const { run } = require('../../utils/test-utils');
44

5-
describe('webpack cli', () => {
6-
it('should support mjs config format', () => {
7-
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.coffee']);
5+
describe('failure', () => {
6+
it('should log error on not installed registers', () => {
7+
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.iced']);
88

99
expect(exitCode).toBe(2);
10-
expect(stderr).toContain(`Unable load '${path.resolve(__dirname, './webpack.config.coffee')}'`);
11-
expect(stderr).toContain('Unable to use specified module loaders for ".coffee".');
12-
expect(stderr).toContain("Cannot find module 'coffeescript/register'");
13-
expect(stderr).toContain("Cannot find module 'coffee-script/register'");
14-
expect(stderr).toContain("Cannot find module 'coffeescript'");
15-
expect(stderr).toContain("Cannot find module 'coffee-script'");
10+
expect(stderr).toContain(`Unable load '${path.resolve(__dirname, './webpack.config.iced')}'`);
11+
expect(stderr).toContain('Unable to use specified module loaders for ".iced".');
12+
expect(stderr).toContain("Cannot find module 'iced-coffee-script/register'");
13+
expect(stderr).toContain("Cannot find module 'iced-coffee-script'");
1614
expect(stderr).toContain('Please install one of them');
1715
expect(stdout).toBeFalsy();
1816
});

test/config-format/mjs/mjs.test.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ const { run } = require('../../utils/test-utils');
22

33
describe('webpack cli', () => {
44
it('should support mjs config format', () => {
5-
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.mjs'], [], { DISABLE_V8_COMPILE_CACHE: true });
5+
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.mjs'], {
6+
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
7+
});
68

7-
if (exitCode === 0) {
9+
if (/Error: Not supported/.test(stderr)) {
10+
expect(exitCode).toBe(2);
11+
expect(stdout).toBeFalsy();
12+
} else {
813
expect(exitCode).toBe(0);
914
expect(stderr).toBeFalsy();
1015
expect(stdout).toBeTruthy();
11-
} else {
12-
expect(exitCode).toBe(2);
13-
expect(/Cannot use import statement outside a module/.test(stderr) || /Unexpected token/.test(stderr)).toBe(true);
14-
expect(stdout).toBeFalsy();
1516
}
1617
});
1718
});

test/config-format/typescript/package.json

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
/* eslint-disable node/no-unpublished-require */
2-
const { run, runInstall } = require('../../utils/test-utils');
1+
const { run } = require('../../utils/test-utils');
32
const { existsSync } = require('fs');
43
const { resolve } = require('path');
54

65
describe('webpack cli', () => {
7-
it.skip(
8-
'should support typescript file',
9-
async () => {
10-
await runInstall(__dirname);
11-
const { exitCode, stderr, stdout } = run(__dirname, ['-c', './webpack.config.ts']);
6+
it('should support typescript file', () => {
7+
const { exitCode, stderr, stdout } = run(__dirname, ['-c', './webpack.config.ts']);
128

13-
expect(stderr).toBeFalsy();
14-
expect(stdout).toBeTruthy();
15-
expect(exitCode).toBe(0);
16-
expect(existsSync(resolve(__dirname, 'bin/foo.bundle.js'))).toBeTruthy();
17-
},
18-
1000 * 60 * 5,
19-
);
9+
expect(stderr).toBeFalsy();
10+
expect(stdout).toBeTruthy();
11+
expect(exitCode).toBe(0);
12+
expect(existsSync(resolve(__dirname, 'dist/foo.bundle.js'))).toBeTruthy();
13+
});
2014
});

test/config-format/typescript/webpack.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ const config = {
1111
},
1212
};
1313

14-
export default config;
14+
export = config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { fileURLToPath } from 'url';
2+
import path from 'path';
3+
4+
export default {
5+
entry: './a.js',
6+
output: {
7+
path: path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'dist'),
8+
filename: 'a.bundle.js',
9+
},
10+
};

test/config-lookup/custom-name/custom-name.test.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,26 @@ const { resolve } = require('path');
44
const { run } = require('../../utils/test-utils');
55

66
describe('custom config file', () => {
7-
it('should work', () => {
8-
const { exitCode, stderr, stdout } = run(__dirname, ['--config', resolve(__dirname, 'config.webpack.js')], false);
7+
it('should work with cjs format', () => {
8+
const { exitCode, stderr, stdout } = run(__dirname, ['--config', resolve(__dirname, 'config.webpack.js')]);
99

1010
expect(exitCode).toBe(0);
1111
expect(stderr).toBeFalsy();
1212
expect(stdout).toBeTruthy();
1313
});
14+
15+
it('should work with esm format', () => {
16+
const { exitCode, stderr, stdout } = run(__dirname, ['--config', resolve(__dirname, 'config.webpack.mjs')], {
17+
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
18+
});
19+
20+
if (/Error: Not supported/.test(stderr)) {
21+
expect(exitCode).toBe(2);
22+
expect(stdout).toBeFalsy();
23+
} else {
24+
expect(exitCode).toBe(0);
25+
expect(stderr).toBeFalsy();
26+
expect(stdout).toBeTruthy();
27+
}
28+
});
1429
});

test/config/defaults/mjs-config/default-mjs-config.test.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ const { run, isWebpack5 } = require('../../../utils/test-utils');
44

55
describe('Default Config:', () => {
66
it('Should be able to pick mjs config by default', () => {
7-
const { exitCode, stderr, stdout } = run(__dirname, [], [], { DISABLE_V8_COMPILE_CACHE: true });
7+
const { exitCode, stderr, stdout } = run(__dirname, [], { env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true } });
88

9-
if (exitCode === 0) {
9+
if (/Error: Not supported/.test(stderr)) {
10+
expect(exitCode).toEqual(2);
11+
expect(stdout).toBeFalsy();
12+
} else {
1013
expect(exitCode).toEqual(0);
1114
expect(stderr).toBeFalsy();
1215
// default entry should be used
@@ -23,10 +26,6 @@ describe('Default Config:', () => {
2326

2427
// check that the output file exists
2528
expect(fs.existsSync(path.join(__dirname, '/dist/test-output.js'))).toBeTruthy();
26-
} else {
27-
expect(exitCode).toEqual(2);
28-
expect(stderr).toContain('Unexpected token');
29-
expect(stdout).toBeFalsy();
3029
}
3130
});
3231
});

test/config/error-mjs/config-error.test.js

+15-6
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,31 @@ const { run } = require('../../utils/test-utils');
44

55
describe('config error', () => {
66
it('should throw error with invalid configuration', () => {
7-
const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.mjs')], [], {
8-
DISABLE_V8_COMPILE_CACHE: true,
7+
const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.mjs')], {
8+
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
99
});
1010

1111
expect(exitCode).toBe(2);
12-
expect(/Invalid configuration object/.test(stderr) || /Unexpected token/.test(stderr)).toBe(true);
12+
13+
if (!/Error: Not supported/.test(stderr)) {
14+
expect(stderr).toContain('Invalid configuration object');
15+
expect(stderr).toContain(`"development" | "production" | "none"`);
16+
}
17+
1318
expect(stdout).toBeFalsy();
1419
});
1520

1621
it('should throw syntax error and exit with non-zero exit code', () => {
17-
const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'syntax-error.mjs')], [], {
18-
DISABLE_V8_COMPILE_CACHE: true,
22+
const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'syntax-error.mjs')], {
23+
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
1924
});
2025

2126
expect(exitCode).toBe(2);
22-
expect(stderr).toContain('SyntaxError: Unexpected token');
27+
28+
if (!/Error: Not supported/.test(stderr)) {
29+
expect(stderr).toContain('SyntaxError: Unexpected token');
30+
}
31+
2332
expect(stdout).toBeFalsy();
2433
});
2534
});

test/entry/scss/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"dependencies": {
3-
"css-loader": "^3.4.2",
4-
"style-loader": "^1.1.3",
5-
"sass-loader": "^8.0.2",
6-
"mini-css-extract-plugin": "^0.9.0",
7-
"node-sass": "^4.13.1"
3+
"css-loader": "^5.0.1",
4+
"style-loader": "^2.0.0",
5+
"sass-loader": "^10.1.1",
6+
"mini-css-extract-plugin": "^1.3.5",
7+
"node-sass": "^5.0.0"
88
}
99
}

0 commit comments

Comments
 (0)