Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack Child Compiler Change Configuration

Tags:

webpack

I would like the output of my webpack build, to be defined as a variable while compiling my service worker.

I would like to use the child compilation functionality to compile a service worker that is put into a different path. I need the output emitted from the webpack compilation to properly compile the service worker.

My initial play was to use the same strategy as offline-plugin where you create a child compiler, but I need to be able to change the output path for the service worker. Service workers path is important because it defines scope.

I am wondering how to accomplish what I have below using the child compiler API to prevent having this sort of side-effect of my build (and hopefully it would give me webpack-dev-server support.

var webpack = require('webpack');

function ServiceWorkerPlugin(options) {
    this.options = options;
}

ServiceWorkerPlugin.prototype.apply = function(compiler) {
    compiler.plugin('emit', (compilation, callback) => {
    const stats = compilation.getStats().toJson();
    const child = webpack(this.options);

    child.apply(
    new webpack.DefinePlugin({
    assets: stats.assets
  })
);

child.run((err, swStats) => {
     if (err) {
         callback(err);
     }
     const swStatsJson = swStats.toJson();

     if(swStatsJson.errors.length > 0) {
         console.log(swStatsJson.errors);
     }

     if(swStatsJson.warnings.length > 0) {
         console.log(swStatsJson.warnings);
     }

     callback();
});

module.exports = ServiceWorkerPlugin;
like image 348
Merrick Christensen Avatar asked Jul 08 '16 22:07

Merrick Christensen


People also ask

Where is the webpack config file?

To answer your specific question, the webpack configuration is stored wherever your global node_modules are installed; on Windows this is typically %AppData%\Roaming\npm\node_modules\powerbi-visuals-tools\lib\webpack. config. js.

Is it mandatory to use a configuration file when setting up a webpack project?

Out of the box, webpack won't require you to use a configuration file. However, it will assume the entry point of your project is src/index.js and will output the result in dist/main.js minified and optimized for production.

What is difference between production and development mode in webpack?

In development mode, the bundle will be more verbose with comments. In production mode, Webpack does everything to use the bundle for production. It includes minification of the bundle file and other optimizations. Default value of mode is production .


1 Answers

First of all, everything you have described is in offline-plugin implementation, so now I am going to show you how I do it.

Everything becomes a bit tricky in webpack when you need to use child compilation and compilation.assets in it. Problem is that child compilation has to be created on complier.plugin('make') event, but compilation.assets are available only on compiler.plugin('emit') event which is fired almost in the end of a compilation.

Here is kind of a boilerplate for child compilation implementation:

(note: code is in the ES2015 version)

import SingleEntryPlugin from 'webpack/lib/SingleEntryPlugin';

export default class AwesomePlugin {
  constructor() {
    // Define compilation name and output name
    this.childCompilerName = 'awesome-plugin-compilation';
    this.outputFileName = 'custom-file.js';
    // To make child compiler work, you have to have a entry in the file system
    this.compilationEntry = 'entry-file.js';
  }

  apply(compiler) {
    // Listen to `make` event
    compiler.plugin('make', (compilation, callback) => {
      // Creating child compiler with params
      const childCompiler = compilation.createChildCompiler(this.childCompilerName, {
        filename: this.outputFileName
      });

      // Everyone plugin does this, I don't know why
      childCompiler.context = compiler.context;

      // Add SingleEntryPlugin to make all this work
      childCompiler.apply(new SingleEntryPlugin(compiler.context, this.compilationEntry, this.outputFileName));

      // Needed for HMR. Even if your plugin don't support HMR,
      // this code seems to be always needed just in case to prevent possible errors
      childCompiler.plugin('compilation', (compilation) => {
        if (compilation.cache) {
          if (!compilation.cache[name]) {
            compilation.cache[name] = {};
          }

          compilation.cache = compilation.cache[name];
        }
      });

      // Run child compilation
      childCompiler.runAsChild((err, entries, childCompilation) => {
        callback(err);
      });
    });
  }
}

This will make your entry compile into separate file which you can name how you want. Next, you need to do some hacky manipulations with compilation.assets on 'emit' event:

compiler.plugin('emit', function(compilation, callback) {
  // Get our output asset
  const asset = compilation.assets[this.outputFileName];

  // Delete delete our asset from output
  delete compilation.assets[this.outputFileName];

  // Collect all output assets
  const assets = Object.keys(compilation.assets);

  // Combine collected assets and child compilation output into new source.
  // Note: `globalAssets` is global variable
  let source = `
    var globalAssets = ${ JSON.stringify(assets) }

    ${ asset.source() }
  `;

  // Add out asset back to the output
  compilation.assets[this.outputFileName] = {
    source() {
      return source;
    },
    size() {
      return Buffer.byteLength(source, 'utf8');
    }
  };
});

EDIT: You probably can have some special place in the entry where you want to insert assets list. But be careful, if you will use regular template syntax, then JS loader won't be able to parse your file. So you may place something like this __INSERT_WEBPACK_ASSETS_DATA__ and then use String#replace to replace it with with the actual data.

This is basically it. Now you should be able to inject variable with compilation.assets into your child compilation output. Note that in the offline-plugin, I use fake compilation name when I create it and then rename it on 'emit' event to the real file name. I don't remember exact reasons why, but I remember that I had them. So, you probably will need to experiment with it by yourself.

Here is the full code of this boilerplate (with both 'make' and 'emit' events): https://gist.github.com/NekR/f85d297fe4f1ea3c168827b305c13844

like image 53
NekR Avatar answered Sep 29 '22 07:09

NekR