Fixing Cordova’s LiveReload when running Webpack

:: web, mobile, webpack, cordova

Want build an app with Cordova? Want to use a technology that builds best under Webpack? Finding that Cordova doesn’t detect changes with the --live-reload flag? Lets make a simple fix with a simple plugin and no dependencies.

Recently I started working on a little Cordova mobile app to pull some NOAA APIs and display solar weather. Wanting to mix ReasonML with Cordova, I setup my Webpack configuration and got to work. Quickly I realized that the output generated by Webpack does not automatically trigger a refresh when hosted using the Browser plugin. Searching found a few plugins you can add to Cordova projects to handle this issue, but that seemed a little too specific to fix the issue and most of the solutions were extremely out of date.

Reading through the webpack documentation I found that there is a way to add functionality to hooks throughout the build process. Before and after plugins are run, compiling, emitting files, logging, etc. There are a lot of places to inject additional code, with a very minor code footprint.

compiler.hooks.someHook.tap('MyPlugin', (params) => {
  /* Put your hook action here */
});

The specific hook we’d want to use is after the files have been emitted, afterEmit. At that point we know there are files that have been updated and it should trigger the Cordova build script to generate new output for our Browser plugin.

Node has a module child_process which can execute terminal commands as spawned child process (docs here). Providing the shell command to build our Cordova app we can now have webpack to watch for file changes, build and output files and then tell Cordova to rebuild.

const exec = require('child_process').exec;

compiler.hooks.afterEmit.tap('RebuildPlugin', () => {
  exec('cordova build');
});

The last step is to bundle this into a simple little plugin that we can reuse for other purposes. We can add in some fancy printing for the logging handlers, and make the hook that triggers our script an option.

// CustomActionPlugin.js
const exec = require('child_process').exec;

class CustomActionPlugin {
  constructor(name, hook, command) {
    this.name = name;
    this.hook = hook;
    this.command = command;
  }

  apply(compiler) {
    compiler.hooks[this.hook].tap(this.name, () => {
      exec(this.command, (err, stdout, stderr) => {
        if (err) throw err;
        if (stdout) process.stdout.write(this.name + ": " + stdout);
        if (stderr) process.stderr.write("ERROR - " + this.name + ": " + stderr);
      });
    });
  }
}

module.exports = CustomActionPlugin;

Last step is to add this plugin to our webpack.config.js file

const CustomActionPlugin = require('./CustomActionPlugin');

module.exports = {
  ...
  plugins: [
    new CustomActionPlugin('CordovaLiveReload', 'afterEmit', 'cordova build')
  ]
}

Now I can run my normal webpack process and cordova run browser. When I save my code it rebuilds and refreshing my browser shows the changes. Any messages or errors will be logged inside Webpack’s output.