Run command after webpack build
JavascriptWebpackJavascript Problem Overview
I'd like to run webpack in --watch
mode, and run a shell command after each build that synchronizes a folder to another one.
I found this plugin that triggers an event after each build. That works, but the last piece of the puzzle is to trigger a shell command (for syncing) from Javascript. Any pointers on how to achieve this are greatly appreciated.
Javascript Solutions
Solution 1 - Javascript
Webpack 4
As of today (April 11, 2018), most of the plugins I've tried use the deprecated API resulting in this warning:
DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
It pleased me to find that you can easily write an ad-hoc webpack plugin (docs).
In your webpack.config.js
file:
const exec = require('child_process').exec;
module.exports = {
// ... other config here ...
plugins: [
// ... other plugins here ...
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
exec('<path to your post-build script here>', (err, stdout, stderr) => {
if (stdout) process.stdout.write(stdout);
if (stderr) process.stderr.write(stderr);
});
});
}
}
]
};
If you'd rather use spawn
to get real-time or "live" data from your script, this illustrates the basic usage:
const spawn = require('child_process').spawn;
const child = spawn('<your script here>');
child.stdout.on('data', function (data) {
process.stdout.write(data);
});
child.stderr.on('data', function (data) {
process.stderr.write(data);
});
Solution 2 - Javascript
I also needed such a thing, so I compiled a super simple plugin to execute shell commands before and after each build.
'use strict';
var exec = require('child_process').exec;
function puts(error, stdout, stderr) {
console.log(stdout);
}
function WebpackShellPlugin(options) {
var defaultOptions = {
onBuildStart: [],
onBuildEnd: []
};
this.options = Object.assign(defaultOptions, options);
}
WebpackShellPlugin.prototype.apply = function(compiler) {
const options = this.options;
compiler.plugin("compilation", compilation => {
if(options.onBuildStart.length){
console.log("Executing pre-build scripts");
options.onBuildStart.forEach(script => exec(script, puts));
}
});
compiler.plugin("emit", (compilation, callback) => {
if(options.onBuildEnd.length){
console.log("Executing post-build scripts");
options.onBuildEnd.forEach(script => exec(script, puts));
}
callback();
});
};
module.exports = WebpackShellPlugin;
then in your webpack config:
plugins: [
new WebpackShellPlugin({
onBuildStart: ['echo "hello world"'],
onBuildEnd: ['echo "goodbye world"']
})
]
This is super basic, and do not support async scripts properly. but it works. feel free to modify however you see fit.
>Consider this code under MIT licence. > >Needs node 4.x and up to run, as I use some es6 features here.
Solution 3 - Javascript
Basically, you can hook into the compiler at various stages of the whole compilation to emitting resources stage etc and run your own script or code as you please.
I like to do it this way -
class CustomPlugin {
constructor(name, command, stage = 'afterEmit') {
this.name = name;
this.command = command;
this.stage = stage;
}
static execHandler(err, stdout, stderr) {
if (stdout) process.stdout.write(stdout);
if (stderr) process.stderr.write(stderr);
}
apply(compiler) {
compiler.hooks[this.stage].tap(this.name, () => {
exec(this.command, CustomPlugin.execHandler);
});
}
}
and then use it like so
new CustomPlugin('RunTest', 'jest', 'beforeRun'),
Solution 4 - Javascript
How to Use:
const WebpackShellPlugin = require('webpack-shell-plugin');
module.exports = {
...
...
plugins: [
new WebpackShellPlugin({onBuildStart:['echo "Webpack Start"'], onBuildEnd:['echo "Webpack End"']})
],
...
}
Solution 5 - Javascript
You can easily run any shell command with built-in child_process module. Also you can try some shell libraries for node.js, like Shell.js. It wraps most of default shell for more convenient usage
Solution 6 - Javascript
webpack-shell-plugin-next
plugin
There is the webpack-shell-plugin-next
plugin:
-
GitHub: s00d/webpack-shell-plugin-next: Run shell commands either before or after webpack 4 builds.
- Please, see the «Webpack compatibility» section.
Using the plugin
The onAfterDone
plugin API:
> onAfterDone
: configuration object for scripts that execute after done.
may be used to achieve the desired watch-related behaviour (in addition, please, see the important note below):
> I'd like to run webpack in --watch
mode, and run a shell command after each build that synchronizes a folder to another one.
Important note: the onAfterDone
plugin API will work for (affect) the normal build mode too (i.e. the webpack
command without the --watch
option).
Here is an additional reference to the related GitHub issue: onDoneWatch scripts executing before bundle written · Issue #16 · s00d/webpack-shell-plugin-next.
Example
Have just tried to use the plugin: it has worked fine.
devDependencies
(from package.json
)
"devDependencies": {
"webpack": "5.3.2",
"webpack-cli": "4.1.0",
"webpack-shell-plugin-next": "2.0.4"
}
watch
npm run script (from package.json
)
"scripts": {
"watch": "webpack --config webpack.config.js --watch"
}
webpack.config.js
)
Webpack configuration file (const WebpackShellPluginNext = require('webpack-shell-plugin-next');
module.exports = {
plugins: [
new WebpackShellPluginNext({
onAfterDone: {
scripts: ['echo "It works!"'],
blocking: true,
parallel: false
}
})
]
};
Command line to run Webpack in the watch mode
npm run watch
Solution 7 - Javascript
If you guya want to do it when a specific file gets changed you can use this little plugin I built: https://www.npmjs.com/package/webpack-noodle-plugin
Hope it can help
Solution 8 - Javascript
I had some issues with webpack-shell-plugin
and webpack-shell-plugin-next
: the scripts were executing before the new files were emitted, even though I was using onDoneWatch
.
That's when I found hook-shell-script-webpack-plugin. Works like a charm.