Skip to content

Commit

Permalink
feat: support ES module configuration format (#2381)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Feb 1, 2021
1 parent d004ded commit aebdbbc
Show file tree
Hide file tree
Showing 25 changed files with 191 additions and 85 deletions.
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -37,7 +37,7 @@
"pretest": "yarn build && yarn lint && yarn prepsuite",
"test": "jest --reporters=default",
"test:smoketests": "nyc node smoketests",
"test:coverage": "nyc jest --forceExit",
"test:coverage": "nyc --require ts-node/register jest --forceExit",
"test:cli": "jest test --reporters=default --forceExit",
"test:packages": "jest packages/ --reporters=default --forceExit",
"test:ci": "yarn test:cli && yarn test:packages",
Expand All @@ -64,6 +64,7 @@
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"@webpack-cli/migrate": "^1.1.2",
"coffeescript": "^2.5.1",
"colorette": "^1.2.1",
"commitlint": "^11.0.0",
"commitlint-config-cz": "^0.13.2",
Expand All @@ -88,6 +89,7 @@
"rimraf": "^3.0.2",
"strip-ansi": "^6.0.0",
"ts-jest": "^26.4.3",
"ts-node": "^9.1.1",
"typescript": "^4.1.3",
"webpack": "^5.18.0",
"webpack-bundle-analyzer": "^4.3.0",
Expand Down
9 changes: 7 additions & 2 deletions packages/webpack-cli/bin/cli.js
@@ -1,6 +1,11 @@
#!/usr/bin/env node

'use strict';

const Module = require('module');

const originalModuleCompile = Module.prototype._compile;

require('v8-compile-cache');

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

if (utils.packageExists('webpack')) {
runCLI(process.argv);
runCLI(process.argv, originalModuleCompile);
} else {
const { promptInstallation, logger, colors } = utils;

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

runCLI(process.argv);
runCLI(process.argv, originalModuleCompile);
})
.catch(() => {
logger.error(`Action Interrupted, Please try once again or install ${colors.bold('webpack')} manually.`);
Expand Down
4 changes: 3 additions & 1 deletion packages/webpack-cli/lib/bootstrap.js
@@ -1,11 +1,13 @@
const WebpackCLI = require('./webpack-cli');
const utils = require('./utils');

const runCLI = async (args) => {
const runCLI = async (args, originalModuleCompile) => {
try {
// Create a new instance of the CLI object
const cli = new WebpackCLI();

cli._originalModuleCompile = originalModuleCompile;

await cli.run(args);
} catch (error) {
utils.logger.error(error);
Expand Down
13 changes: 13 additions & 0 deletions packages/webpack-cli/lib/utils/dynamic-import-loader.js
@@ -0,0 +1,13 @@
function dynamicImportLoader() {
let importESM;

try {
importESM = new Function('id', 'return import(id);');
} catch (e) {
importESM = null;
}

return importESM;
}

module.exports = dynamicImportLoader;
4 changes: 4 additions & 0 deletions packages/webpack-cli/lib/utils/index.js
Expand Up @@ -19,6 +19,10 @@ module.exports = {
return require('./capitalize-first-letter');
},

get dynamicImportLoader() {
return require('./dynamic-import-loader');
},

get getPackageManager() {
return require('./get-package-manager');
},
Expand Down
35 changes: 23 additions & 12 deletions packages/webpack-cli/lib/webpack-cli.js
@@ -1,5 +1,7 @@
const fs = require('fs');
const path = require('path');
const { pathToFileURL } = require('url');
const Module = require('module');

const { program } = require('commander');
const utils = require('./utils');
Expand Down Expand Up @@ -1137,26 +1139,35 @@ class WebpackCLI {
}
}

const { pathToFileURL } = require('url');

let importESM;

try {
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') {
let previousModuleCompile;

// TODO Workaround https://github.com/zertosh/v8-compile-cache/issues/30
if (this._originalModuleCompile) {
previousModuleCompile = Module.prototype._compile;

Module.prototype._compile = this._originalModuleCompile;
}

const dynamicImportLoader = this.utils.dynamicImportLoader();

if (this._originalModuleCompile) {
Module.prototype._compile = previousModuleCompile;
}

if (
(error.code === 'ERR_REQUIRE_ESM' || process.env.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) &&
pathToFileURL &&
dynamicImportLoader
) {
const urlForConfig = pathToFileURL(configPath);

options = await importESM(urlForConfig);
options = await dynamicImportLoader(urlForConfig);
options = options.default;

return { options, path: configPath };
Expand Down
1 change: 0 additions & 1 deletion test/config-format/coffee/coffee.test.js
@@ -1,4 +1,3 @@
// eslint-disable-next-line node/no-unpublished-require
const { run } = require('../../utils/test-utils');

describe('webpack cli', () => {
Expand Down
5 changes: 0 additions & 5 deletions test/config-format/coffee/package.json

This file was deleted.

11 changes: 11 additions & 0 deletions test/config-format/commonjs-default/commonjs-default.test.js
@@ -0,0 +1,11 @@
const { run } = require('../../utils/test-utils');

describe('webpack cli', () => {
it('should support CommonJS file', () => {
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.cjs'], false);

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});
});
1 change: 1 addition & 0 deletions test/config-format/commonjs-default/main.js
@@ -0,0 +1 @@
console.log('Hoshiumi');
12 changes: 12 additions & 0 deletions test/config-format/commonjs-default/webpack.config.cjs
@@ -0,0 +1,12 @@
const path = require('path');

const config = {
mode: 'production',
entry: './main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'foo.bundle.js',
},
};

module.exports.default = config;
16 changes: 7 additions & 9 deletions test/config-format/failure/failure.test.js
Expand Up @@ -2,17 +2,15 @@ const path = require('path');

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

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

expect(exitCode).toBe(2);
expect(stderr).toContain(`Unable load '${path.resolve(__dirname, './webpack.config.coffee')}'`);
expect(stderr).toContain('Unable to use specified module loaders for ".coffee".');
expect(stderr).toContain("Cannot find module 'coffeescript/register'");
expect(stderr).toContain("Cannot find module 'coffee-script/register'");
expect(stderr).toContain("Cannot find module 'coffeescript'");
expect(stderr).toContain("Cannot find module 'coffee-script'");
expect(stderr).toContain(`Unable load '${path.resolve(__dirname, './webpack.config.iced')}'`);
expect(stderr).toContain('Unable to use specified module loaders for ".iced".');
expect(stderr).toContain("Cannot find module 'iced-coffee-script/register'");
expect(stderr).toContain("Cannot find module 'iced-coffee-script'");
expect(stderr).toContain('Please install one of them');
expect(stdout).toBeFalsy();
});
Expand Down
13 changes: 7 additions & 6 deletions test/config-format/mjs/mjs.test.js
Expand Up @@ -2,16 +2,17 @@ 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'], [], { DISABLE_V8_COMPILE_CACHE: true });
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.mjs'], {
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
});

if (exitCode === 0) {
if (/Error: Not supported/.test(stderr)) {
expect(exitCode).toBe(2);
expect(stdout).toBeFalsy();
} else {
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
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();
}
});
});
7 changes: 0 additions & 7 deletions test/config-format/typescript/package.json

This file was deleted.

22 changes: 8 additions & 14 deletions test/config-format/typescript/typescript.test.js
@@ -1,20 +1,14 @@
/* eslint-disable node/no-unpublished-require */
const { run, runInstall } = require('../../utils/test-utils');
const { run } = require('../../utils/test-utils');
const { existsSync } = require('fs');
const { resolve } = require('path');

describe('webpack cli', () => {
it.skip(
'should support typescript file',
async () => {
await runInstall(__dirname);
const { exitCode, stderr, stdout } = run(__dirname, ['-c', './webpack.config.ts']);
it('should support typescript file', () => {
const { exitCode, stderr, stdout } = run(__dirname, ['-c', './webpack.config.ts']);

expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
expect(exitCode).toBe(0);
expect(existsSync(resolve(__dirname, 'bin/foo.bundle.js'))).toBeTruthy();
},
1000 * 60 * 5,
);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
expect(exitCode).toBe(0);
expect(existsSync(resolve(__dirname, 'dist/foo.bundle.js'))).toBeTruthy();
});
});
2 changes: 1 addition & 1 deletion test/config-format/typescript/webpack.config.ts
Expand Up @@ -11,4 +11,4 @@ const config = {
},
};

export default config;
export = config;
10 changes: 10 additions & 0 deletions test/config-lookup/custom-name/config.webpack.mjs
@@ -0,0 +1,10 @@
import { fileURLToPath } from 'url';
import path from 'path';

export default {
entry: './a.js',
output: {
path: path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'dist'),
filename: 'a.bundle.js',
},
};
19 changes: 17 additions & 2 deletions test/config-lookup/custom-name/custom-name.test.js
Expand Up @@ -4,11 +4,26 @@ const { resolve } = require('path');
const { run } = require('../../utils/test-utils');

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

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it('should work with esm format', () => {
const { exitCode, stderr, stdout } = run(__dirname, ['--config', resolve(__dirname, 'config.webpack.mjs')], {
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
});

if (/Error: Not supported/.test(stderr)) {
expect(exitCode).toBe(2);
expect(stdout).toBeFalsy();
} else {
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
}
});
});
11 changes: 5 additions & 6 deletions test/config/defaults/mjs-config/default-mjs-config.test.js
Expand Up @@ -4,9 +4,12 @@ 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, [], [], { DISABLE_V8_COMPILE_CACHE: true });
const { exitCode, stderr, stdout } = run(__dirname, [], { env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true } });

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

// 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();
}
});
});
21 changes: 15 additions & 6 deletions test/config/error-mjs/config-error.test.js
Expand Up @@ -4,22 +4,31 @@ 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')], [], {
DISABLE_V8_COMPILE_CACHE: true,
const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'webpack.config.mjs')], {
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
});

expect(exitCode).toBe(2);
expect(/Invalid configuration object/.test(stderr) || /Unexpected token/.test(stderr)).toBe(true);

if (!/Error: Not supported/.test(stderr)) {
expect(stderr).toContain('Invalid configuration object');
expect(stderr).toContain(`"development" | "production" | "none"`);
}

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')], [], {
DISABLE_V8_COMPILE_CACHE: true,
const { exitCode, stderr, stdout } = run(__dirname, ['-c', resolve(__dirname, 'syntax-error.mjs')], {
env: { WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG: true },
});

expect(exitCode).toBe(2);
expect(stderr).toContain('SyntaxError: Unexpected token');

if (!/Error: Not supported/.test(stderr)) {
expect(stderr).toContain('SyntaxError: Unexpected token');
}

expect(stdout).toBeFalsy();
});
});
10 changes: 5 additions & 5 deletions test/entry/scss/package.json
@@ -1,9 +1,9 @@
{
"dependencies": {
"css-loader": "^3.4.2",
"style-loader": "^1.1.3",
"sass-loader": "^8.0.2",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.1"
"css-loader": "^5.0.1",
"style-loader": "^2.0.0",
"sass-loader": "^10.1.1",
"mini-css-extract-plugin": "^1.3.5",
"node-sass": "^5.0.0"
}
}

0 comments on commit aebdbbc

Please sign in to comment.