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: watch mode and options #1931

Merged
merged 9 commits into from Oct 13, 2020
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
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.

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.