Skip to content

Commit

Permalink
fix: watch mode and options (#1931)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed Oct 13, 2020
1 parent 6f95b26 commit 258219a
Show file tree
Hide file tree
Showing 18 changed files with 550 additions and 912 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -89,7 +89,7 @@
"strip-ansi": "^6.0.0",
"ts-jest": "^25.5.1",
"typescript": "^3.9.7",
"webpack": "^4.44.2",
"webpack": "^5.0.0",
"webpack-bundle-analyzer": "^3.9.0",
"webpack-dev-server": "3.10.3",
"yeoman-test": "^2.7.0"
Expand Down
229 changes: 100 additions & 129 deletions packages/webpack-cli/lib/utils/Compiler.js
Expand Up @@ -2,134 +2,29 @@ const { packageExists } = require('./package-exists');
const webpack = packageExists('webpack') ? require('webpack') : undefined;
const logger = require('./logger');
const { writeFileSync } = require('fs');
const bailAndWatchWarning = require('./warnings/bailAndWatchWarning');

const assignWatchHooks = (compiler) => {
compiler.hooks.watchRun.tap('watchInfo', (compilation) => {
const compilationName = compilation.name || '';
logger.raw(`\nCompilation ${compilationName} starting…\n`);
});
compiler.hooks.done.tap('watchInfo', (compilation) => {
const compilationName = compilation.name || '';
logger.raw(`\nCompilation ${compilationName} finished\n`);
});
};

const watchInfo = (compiler) => {
if (compiler.compilers) {
compiler.compilers.map((comp) => {
assignWatchHooks(comp);
});
} else {
assignWatchHooks(compiler);
}
};

class Compiler {
constructor() {
this.compilerOptions = {};
}
setUpHookForCompilation(compilation, outputOptions, options) {
const { ProgressPlugin } = webpack;
let progressPluginExists;
if (options.plugins) {
progressPluginExists = options.plugins.find((e) => e instanceof ProgressPlugin);
}

compilation.hooks.beforeRun.tap('webpackProgress', () => {
if (outputOptions.progress) {
if (!progressPluginExists) {
new ProgressPlugin().apply(compilation);
} else {
if (!progressPluginExists.handler) {
options.plugins = options.plugins.filter((e) => e !== progressPluginExists);
Object.keys(progressPluginExists).map((opt) => {
ProgressPlugin.defaultOptions[opt] = progressPluginExists[opt];
});
new ProgressPlugin().apply(compilation);
} else {
progressPluginExists.apply(compilation);
}
}
}
});
}

compilerCallback(error, stats, lastHash, options, outputOptions) {
if (error) {
lastHash = null;
logger.error(error);
process.exit(1);
}

if (!outputOptions.watch && stats.hasErrors()) {
process.exitCode = 1;
}

if (stats.hash !== lastHash) {
lastHash = stats.hash;

if (outputOptions.json === true) {
process.stdout.write(JSON.stringify(stats.toJson(outputOptions), null, 2) + '\n');
} else if (typeof outputOptions.json === 'string') {
const JSONStats = JSON.stringify(stats.toJson(outputOptions), null, 2);

try {
writeFileSync(outputOptions.json, JSONStats);
logger.success(`stats are successfully stored as json to ${outputOptions.json}`);
} catch (err) {
logger.error(err);
}
} else {
logger.raw(`${stats.toString(this.compilerOptions.stats)}\n`);
}

if (outputOptions.watch) {
logger.info('watching files for updates...');
}
}
}

async invokeCompilerInstance(lastHash, options, outputOptions) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve) => {
await this.compiler.run((err, stats) => {
if (this.compiler.close) {
this.compiler.close(() => {
this.compilerCallback(err, stats, lastHash, options, outputOptions);

resolve();
});
} else {
this.compilerCallback(err, stats, lastHash, options, outputOptions);

resolve();
}
});
});
}

async invokeWatchInstance(lastHash, options, outputOptions, watchOptions) {
return this.compiler.watch(watchOptions, (err, stats) => {
this.compilerCallback(err, stats, lastHash, options, outputOptions);
});
}

async createCompiler(options) {
try {
this.compiler = await webpack(options);
this.compilerOptions = options;
} catch (err) {
} 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 (err instanceof ValidationError) {
logger.error(`\n${err.message}`);
if (error instanceof ValidationError) {
logger.error(error.message);
} else {
logger.error(`\n${err}`);
logger.error(error);
}

process.exit(2);
}
}
Expand All @@ -140,39 +35,115 @@ class Compiler {

async webpackInstance(opts) {
const { outputOptions, options } = opts;
const lastHash = null;

const { ProgressPlugin } = webpack;
if (options.plugins) {
options.plugins = options.plugins.filter((e) => e instanceof ProgressPlugin);
}

if (outputOptions.interactive) {
const interactive = require('./interactive');

return interactive(options, outputOptions);
}
if (this.compiler.compilers) {
this.compiler.compilers.forEach((comp, idx) => {
bailAndWatchWarning(comp); //warn the user if bail and watch both are used together
this.setUpHookForCompilation(comp, outputOptions, options[idx]);

const compilers = this.compiler.compilers ? this.compiler.compilers : [this.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));
}

if (!progressPluginExists) {
new ProgressPlugin().apply(compiler);
}
}
}

this.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...`);
});
this.compiler.hooks.done.tap('watchInfo', (compilation) => {
logger.success(`Compilation${compilation.name ? `${compilation.name}` : ''} finished`);
});
} else {
bailAndWatchWarning(this.compiler);
this.setUpHookForCompilation(this.compiler, outputOptions, options);
}

if (outputOptions.watch) {
const watchOptions = outputOptions.watchOptions || {};
const callback = (error, stats) => {
if (error) {
logger.error(error);
process.exit(1);
}

if (stats.hasErrors()) {
process.exitCode = 1;
}

const foundStats = this.compiler.compilers
? { children: this.compiler.compilers.map((compiler) => compiler.options.stats) }
: this.compiler.options.stats;

if (outputOptions.json === true) {
process.stdout.write(JSON.stringify(stats.toJson(foundStats), null, 2) + '\n');
} else if (typeof outputOptions.json === 'string') {
const JSONStats = JSON.stringify(stats.toJson(foundStats), null, 2);

try {
writeFileSync(outputOptions.json, JSONStats);
logger.success(`stats are successfully stored as json to ${outputOptions.json}`);
} catch (error) {
logger.error(error);

process.exit(2);
}
} else {
logger.raw(`${stats.toString(foundStats)}`);
}

if (isWatchMode) {
logger.success('watching files for updates...');
}
};

if (isWatchMode) {
const watchOptions = (this.compiler.options && this.compiler.options.watchOptions) || {};

if (watchOptions.stdin) {
process.stdin.on('end', function () {
process.exit();
});
process.stdin.resume();
}
watchInfo(this.compiler);
await this.invokeWatchInstance(lastHash, options, outputOptions, watchOptions);

return new Promise((resolve) => {
this.compiler.watch(watchOptions, (error, stats) => {
callback(error, stats);

resolve();
});
});
} else {
return await this.invokeCompilerInstance(lastHash, options, outputOptions);
return new Promise((resolve) => {
this.compiler.run((error, stats) => {
if (this.compiler.close) {
this.compiler.close(() => {
callback(error, stats);

resolve();
});
} else {
callback(error, stats);

resolve();
}
});
});
}
}
}
Expand Down
14 changes: 0 additions & 14 deletions packages/webpack-cli/lib/utils/warnings/bailAndWatchWarning.js

This file was deleted.

19 changes: 6 additions & 13 deletions test/analyze/analyze-flag.test.js
@@ -1,26 +1,19 @@
'use strict';

const { runAndGetWatchProc } = require('../utils/test-utils');
const { writeFileSync } = require('fs');
const { resolve } = require('path');

describe('--analyze flag', () => {
it('should load webpack-bundle-analyzer plugin with --analyze flag', (done) => {
const proc = runAndGetWatchProc(__dirname, ['--analyze'], false, '', true);
let semaphore = 1;
const proc = runAndGetWatchProc(__dirname, ['--analyze', '--watch'], false, '', true);

proc.stdout.on('data', (chunk) => {
const data = chunk.toString();
if (semaphore === 1 && data.includes('BundleAnalyzerPlugin')) {
writeFileSync(resolve(__dirname, './src/main.js'), `console.log('analyze flag test');`);
semaphore--;
return;
}
if (semaphore === 0) {
expect(data).toContain('Webpack Bundle Analyzer is started at http://127.0.0.1:8888');
semaphore--;

if (data.includes('Webpack Bundle Analyzer is started at')) {
expect(data).toContain('Webpack Bundle Analyzer is started at');

proc.kill();
done();
return;
}
});
});
Expand Down
26 changes: 0 additions & 26 deletions test/bail/bail-and-watch-warning.test.js

This file was deleted.

File renamed without changes.
19 changes: 19 additions & 0 deletions test/bail/bail-multi-webpack.config.js
@@ -0,0 +1,19 @@
module.exports = [
{
output: {
filename: './dist-first.js',
},
name: 'first',
entry: './src/first.js',
mode: 'development',
bail: true,
},
{
output: {
filename: './dist-second.js',
},
name: 'second',
entry: './src/second.js',
mode: 'production',
},
];
File renamed without changes.

0 comments on commit 258219a

Please sign in to comment.