Skip to content

Commit

Permalink
fix: support esm format for configurations
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Jan 28, 2021
1 parent f9ce1d3 commit c4a50db
Show file tree
Hide file tree
Showing 25 changed files with 172 additions and 96 deletions.
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -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 @@ -91,6 +92,7 @@
"strip-ansi": "^6.0.0",
"ts-jest": "^26.4.3",
"typescript": "^4.1.3",
"ts-node": "^9.1.1",
"webpack": "^5.18.0",
"webpack-bundle-analyzer": "^4.3.0",
"webpack-dev-server": "^3.11.1",
Expand Down
8 changes: 6 additions & 2 deletions packages/webpack-cli/bin/cli.js
Expand Up @@ -2,6 +2,10 @@

'use strict';

const Module = require('module');

const originalModuleCompile = Module.prototype._compile;

require('v8-compile-cache');

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

if (packageExists('webpack')) {
runCLI(process.argv);
runCLI(process.argv, originalModuleCompile);
} else {
promptInstallation('webpack', () => {
error(`It looks like ${yellow('webpack')} is not installed.`);
})
.then(() => {
success(`${yellow('webpack')} was installed successfully.`);

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

process.title = 'webpack-cli';

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) {
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;
31 changes: 19 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 getPkg = require('./utils/package-exists');
Expand Down Expand Up @@ -1143,26 +1145,31 @@ 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 = require('./utils/dynamic-import-loader')();

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

if (error.code === 'ERR_REQUIRE_ESM' && 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.

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
12 changes: 3 additions & 9 deletions test/config-format/mjs/mjs.test.js
Expand Up @@ -4,14 +4,8 @@ describe('webpack cli', () => {
it('should support mjs config format', () => {
const { exitCode, stderr, stdout } = run(__dirname, ['-c', 'webpack.config.mjs'], [], { DISABLE_V8_COMPILE_CACHE: true });

if (exitCode === 0) {
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();
}
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});
});
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,14 @@
const { run } = require('../../utils/test-utils');
const { existsSync } = require('fs');
const { resolve } = require('path');

describe('webpack cli', () => {
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, 'dist/foo.bundle.js'))).toBeTruthy();
});
});
1 change: 1 addition & 0 deletions test/config-format/typescript-commonjs/main.ts
@@ -0,0 +1 @@
console.log('Main typescript file');
5 changes: 5 additions & 0 deletions test/config-format/typescript-commonjs/tsconfig.json
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"module": "commonjs"
}
}
14 changes: 14 additions & 0 deletions test/config-format/typescript-commonjs/typescript-commonjs.test.js
@@ -0,0 +1,14 @@
const { run } = require('../../utils/test-utils');
const { existsSync } = require('fs');
const { resolve } = require('path');

describe('webpack cli', () => {
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, 'dist/foo.bundle.js'))).toBeTruthy();
});
});
14 changes: 14 additions & 0 deletions test/config-format/typescript-commonjs/webpack.config.ts
@@ -0,0 +1,14 @@
/* eslint-disable node/no-unsupported-features/es-syntax */
/** eslint-disable **/
import * as path from 'path';

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

export = config;
7 changes: 0 additions & 7 deletions test/config-format/typescript/package.json

This file was deleted.

20 changes: 0 additions & 20 deletions test/config-format/typescript/typescript.test.js

This file was deleted.

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',
},
};
10 changes: 9 additions & 1 deletion test/config-lookup/custom-name/custom-name.test.js
Expand Up @@ -4,11 +4,19 @@ const { resolve } = require('path');
const { run } = require('../../utils/test-utils');

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

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')], false);

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});
});
34 changes: 14 additions & 20 deletions test/config/defaults/mjs-config/default-mjs-config.test.js
Expand Up @@ -6,27 +6,21 @@ describe('Default Config:', () => {
it('Should be able to pick mjs config by default', () => {
const { exitCode, stderr, stdout } = run(__dirname, [], [], { DISABLE_V8_COMPILE_CACHE: true });

if (exitCode === 0) {
expect(exitCode).toEqual(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(exitCode).toEqual(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');

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();
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();
});
});
11 changes: 4 additions & 7 deletions test/config/error-mjs/config-error.test.js
Expand Up @@ -4,19 +4,16 @@ 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')]);

expect(exitCode).toBe(2);
expect(/Invalid configuration object/.test(stderr) || /Unexpected token/.test(stderr)).toBe(true);
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')]);

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

0 comments on commit c4a50db

Please sign in to comment.