Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support esm format for configurations #2381

Merged
merged 18 commits into from Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -38,7 +38,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 @@ -65,6 +65,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 @@ -90,6 +91,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"
}
}