diff --git a/packages/webpack-cli/lib/plugins/WebpackCLIPlugin.js b/packages/webpack-cli/lib/plugins/WebpackCLIPlugin.js new file mode 100644 index 00000000000..c79c8654a95 --- /dev/null +++ b/packages/webpack-cli/lib/plugins/WebpackCLIPlugin.js @@ -0,0 +1,53 @@ +const { packageExists } = require('../utils/package-exists'); +const webpack = packageExists('webpack') ? require('webpack') : undefined; +const logger = require('../utils/logger'); + +const PluginName = 'webpack-cli'; + +class WebpackCLIPlugin { + constructor(options) { + this.options = options; + } + async apply(compiler) { + const compilers = compiler.compilers || [compiler]; + + for (const compiler of compilers) { + if (this.options.progress) { + const { ProgressPlugin } = compiler.webpack || webpack; + + let progressPluginExists; + + if (compiler.options.plugins) { + progressPluginExists = Boolean(compiler.options.plugins.find((e) => e instanceof ProgressPlugin)); + } + + if (!progressPluginExists) { + new ProgressPlugin().apply(compiler); + } + } + } + + const compilationName = (compilation) => (compilation.name ? ` ${compilation.name}` : ''); + + compiler.hooks.watchRun.tap(PluginName, (compilation) => { + const { bail, watch } = compilation.options; + if (bail && watch) { + logger.warn('You are using "bail" with "watch". "bail" will still exit webpack when the first error is found.'); + } + + logger.success(`Compilation${compilationName(compilation)} starting...`); + }); + + compiler.hooks.done.tap(PluginName, (compilation) => { + logger.success(`Compilation${compilationName(compilation)} finished`); + + process.nextTick(() => { + if (compiler.watchMode) { + logger.success('watching files for updates...'); + } + }); + }); + } +} + +module.exports = WebpackCLIPlugin; diff --git a/packages/webpack-cli/lib/webpack-cli.js b/packages/webpack-cli/lib/webpack-cli.js index 07bdf950c40..9ae5c1ae010 100644 --- a/packages/webpack-cli/lib/webpack-cli.js +++ b/packages/webpack-cli/lib/webpack-cli.js @@ -10,6 +10,7 @@ const { toKebabCase } = require('./utils/helpers'); const assignFlagDefaults = require('./utils/flag-defaults'); const { writeFileSync } = require('fs'); const { options: coloretteOptions } = require('colorette'); +const WebpackCLIPlugin = require('./plugins/WebpackCLIPlugin'); // CLI arg resolvers const handleConfigResolution = require('./groups/ConfigGroup'); @@ -212,24 +213,27 @@ class WebpackCLI extends GroupHelper { return this.runOptionGroups(args); } - createCompiler(options) { + handleError(error) { + // https://github.com/webpack/webpack/blob/master/lib/index.js#L267 + // https://github.com/webpack/webpack/blob/v4.44.2/lib/webpack.js#L90 + const ValidationError = webpack.ValidationError || webpack.WebpackOptionsValidationError; + + // In case of schema errors print and exit process + // For webpack@4 and webpack@5 + if (error instanceof ValidationError) { + logger.error(error.message); + } else { + logger.error(error); + } + } + + createCompiler(options, callback) { let compiler; try { - compiler = webpack(options); + compiler = webpack(options, callback); } catch (error) { - // https://github.com/webpack/webpack/blob/master/lib/index.js#L267 - // https://github.com/webpack/webpack/blob/v4.44.2/lib/webpack.js#L90 - const ValidationError = webpack.ValidationError ? webpack.ValidationError : webpack.WebpackOptionsValidationError; - - // In case of schema errors print and exit process - // For webpack@4 and webpack@5 - if (error instanceof ValidationError) { - logger.error(error.message); - } else { - logger.error(error); - } - + this.handleError(error); process.exit(2); } @@ -245,54 +249,35 @@ class WebpackCLI extends GroupHelper { async run(args, cliOptions) { await this.processArgs(args, cliOptions); - const compiler = this.createCompiler(this.compilerConfiguration); - - const options = this.compilerConfiguration; - const outputOptions = this.outputConfiguration; - - if (outputOptions.interactive) { - const interactive = require('./utils/interactive'); + let compiler; - return interactive(compiler, options, outputOptions); - } + let options = this.compilerConfiguration; + let outputOptions = this.outputConfiguration; - const compilers = compiler.compilers ? compiler.compilers : [compiler]; - const isWatchMode = Boolean(compilers.find((compiler) => compiler.options.watch)); const isRawOutput = typeof outputOptions.json === 'undefined'; if (isRawOutput) { - for (const compiler of compilers) { - if (outputOptions.progress) { - const { ProgressPlugin } = webpack; - - let progressPluginExists; - - if (compiler.options.plugins) { - progressPluginExists = Boolean(compiler.options.plugins.find((e) => e instanceof ProgressPlugin)); - } + const webpackCLIPlugin = new WebpackCLIPlugin({ + progress: outputOptions.progress, + }); - if (!progressPluginExists) { - new ProgressPlugin().apply(compiler); - } + const addPlugin = (options) => { + if (!options.plugins) { + options.plugins = []; } + options.plugins.unshift(webpackCLIPlugin); + }; + if (Array.isArray(options)) { + options.forEach(addPlugin); + } else { + addPlugin(options); } - - compiler.hooks.watchRun.tap('watchInfo', (compilation) => { - if (compilation.options.bail && isWatchMode) { - logger.warn('You are using "bail" with "watch". "bail" will still exit webpack when the first error is found.'); - } - - logger.success(`Compilation${compilation.name ? `${compilation.name}` : ''} starting...`); - }); - compiler.hooks.done.tap('watchInfo', (compilation) => { - logger.success(`Compilation${compilation.name ? `${compilation.name}` : ''} finished`); - }); } const callback = (error, stats) => { if (error) { - logger.error(error); - process.exit(1); + this.handleError(error); + process.exit(2); } if (stats.hasErrors()) { @@ -314,9 +299,11 @@ class WebpackCLI extends GroupHelper { return stats; }; + const getStatsOptionsFromCompiler = (compiler) => getStatsOptions(compiler.options ? compiler.options.stats : undefined); + const foundStats = compiler.compilers - ? { children: compiler.compilers.map((compiler) => getStatsOptions(compiler.options.stats)) } - : getStatsOptions(compiler.options.stats); + ? { children: compiler.compilers.map(getStatsOptionsFromCompiler) } + : getStatsOptionsFromCompiler(compiler); if (outputOptions.json === true) { process.stdout.write(JSON.stringify(stats.toJson(foundStats), null, 2) + '\n'); @@ -335,46 +322,17 @@ class WebpackCLI extends GroupHelper { } else { logger.raw(`${stats.toString(foundStats)}`); } - - if (isWatchMode) { - logger.success('watching files for updates...'); - } }; - if (isWatchMode) { - const watchOptions = (compiler.options && compiler.options.watchOptions) || {}; + compiler = this.createCompiler(options, callback); - if (watchOptions.stdin) { - process.stdin.on('end', function () { - process.exit(); - }); - process.stdin.resume(); - } - - return new Promise((resolve) => { - compiler.watch(watchOptions, (error, stats) => { - callback(error, stats); + if (compiler && outputOptions.interactive) { + const interactive = require('./utils/interactive'); - resolve(); - }); - }); - } else { - return new Promise((resolve) => { - compiler.run((error, stats) => { - if (compiler.close) { - compiler.close(() => { - callback(error, stats); - - resolve(); - }); - } else { - callback(error, stats); - - resolve(); - } - }); - }); + interactive(compiler, options, outputOptions); } + + return Promise.resolve(); } } diff --git a/test/core-flags/cache-flags.test.js b/test/core-flags/cache-flags.test.js index 8a2cb8ccbb8..57a1f0338ac 100644 --- a/test/core-flags/cache-flags.test.js +++ b/test/core-flags/cache-flags.test.js @@ -1,10 +1,18 @@ 'use strict'; +const path = require('path'); +const rimraf = require('rimraf'); const { run, isWindows } = require('../utils/test-utils'); const { existsSync, writeFileSync, unlinkSync } = require('fs'); const { resolve } = require('path'); describe('cache related flags from core', () => { + beforeEach((done) => { + rimraf(path.join(__dirname, '../../node_modules/.cache/webpack/*'), () => { + done(); + }); + }); + it('should be successful with --cache ', () => { const { stderr, stdout } = run(__dirname, ['--cache']); expect(stderr).toBeFalsy(); diff --git a/test/utils/cli-plugin-test/plugin.test.js b/test/utils/cli-plugin-test/plugin.test.js index 96a5ff20c27..14e4810a3fa 100644 --- a/test/utils/cli-plugin-test/plugin.test.js +++ b/test/utils/cli-plugin-test/plugin.test.js @@ -10,6 +10,6 @@ describe('webpack-cli-test-plugin Test', () => { if (typeof cli !== 'undefined') { expect(stdout).toContain(`alias: { alias: [ 'alias1', 'alias2' ] }`); } - expect(stdout).toContain('plugins: [ WebpackCLITestPlugin { opts: [Array], showAll: true } ]'); + expect(stdout).toContain(` WebpackCLITestPlugin { opts: [Array], showAll: true }`); }); });