Run command after webpack build
Asked Answered
C

8

88

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.

Christianity answered 18/5, 2015 at 20:58 Comment(0)
K
4

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

Knead answered 18/5, 2015 at 21:17 Comment(2)
Thank you, that was the missing link. I somehow was going in circles and completely missed this option.Christianity
An example would be welcome for those who are merely users of webpack.Bwana
B
172

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);
});
Blackleg answered 12/4, 2018 at 2:7 Comment(5)
Great, also works on watch builds, what didn't seem to be the case with Yair Tavor's solution.Zuckerman
Just a heads up, if you're looking for the before equivalent of the hook, you can try beforeCompile. Documentation on which hooks are available can be found hereSergius
Are you sure about "<path to your post-build script here>"? Docs says that it should be "the command to run": nodejs.org/api/… . When I have a path there it complaints about permission: "/bin/sh: tasks/postbuild.sh: Permission denied"Ferullo
@Ferullo you can supply a command to run, or a path to a script file with the execute permission set. To fix your problem try chmod +x /path/to/scriptBlackleg
Yes, but if webpack has child compilations, it will not workChloras
D
82

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.

Dasya answered 11/2, 2016 at 11:1 Comment(4)
If you need to access the bundle files it's better to use after-emit, since emit is triggered when Webpack starts emitting the files.Chladek
can u create a non e6 verion?Liverwurst
It's been packaged at: npmjs.com/package/webpack-shell-pluginNapolitano
I boiled this down to simply pass a single js function rather than a string command to exec. Works well.Hel
I
8

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'),
Inhibition answered 17/2, 2019 at 16:21 Comment(1)
I can see myself using this pattern a lot.Brainsick
M
7

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"']})
      ],
      ...
    }
Mottle answered 22/8, 2018 at 5:6 Comment(2)
Note: this is actually a plugin suggested by @Yair Tavor in his anwser.Pipkin
@Pipkin reducing the solution to what is strictly necessary is an actual contribution and deserves credit. Krishna you can enhance your answer by explaining the differences with Yair Tavor's answer. As far as I understand, Yair Tavor's solution has been packaged in the webpack-shell-plugin and what you propose is how to use it. It is worthwhile mentionning to help others understand the situation.Bwana
E
5

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
Exploratory answered 31/10, 2020 at 10:16 Comment(1)
works really great with webpack 4 also. Just remember to use for webpack 4 different version: webpack4 => "webpack-shell-plugin-next": "1.2.0" webpack5 => "webpack-shell-plugin-next": "2.2.2"Eustatius
K
4

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

Knead answered 18/5, 2015 at 21:17 Comment(2)
Thank you, that was the missing link. I somehow was going in circles and completely missed this option.Christianity
An example would be welcome for those who are merely users of webpack.Bwana
D
2

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

Detonate answered 6/2, 2018 at 20:26 Comment(0)
K
1

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.

Kea answered 7/9, 2021 at 5:22 Comment(2)
There are already plenty of answers solving the problem, please add some additional information how your answer differs.Indeliberate
The other answers point to webpack-shell-plugin or to custom implementations using webpack hooks. I couldn't get the shell plugin to work, and making a custom plugin seems way overkill. I found this other solution no one else pointed out. Hope it might helpKea

© 2022 - 2024 — McMap. All rights reserved.