Run command after webpack build

JavascriptWebpack

Javascript 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

Use webpack-shell-plugin

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:

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 configuration file (webpack.config.js)
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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMonokaiView Question on Stackoverflow
Solution 1 - JavascriptjchookView Answer on Stackoverflow
Solution 2 - JavascriptYair TavorView Answer on Stackoverflow
Solution 3 - JavascriptSohailView Answer on Stackoverflow
Solution 4 - JavascriptKrishan KumarView Answer on Stackoverflow
Solution 5 - Javascriptjust-borisView Answer on Stackoverflow
Solution 6 - JavascriptSergey Vyacheslavovich BrunovView Answer on Stackoverflow
Solution 7 - JavascriptPistolpete .View Answer on Stackoverflow
Solution 8 - JavascriptDaniele MazzottaView Answer on Stackoverflow