Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webpack plugin: How to dynamically add a loader to a module?

I'm writing a Webpack plugin. During module resolution, this plugin should dynamically add a specific loader for some modules, but not for all.

My idea right now is to tap into normal-module-factory's after-resolve event. There, I could modify the array data.loaders. Some debugging showed me that this array contains objects of the form { loader: string, options: object | undefined }.

I couldn't find any documentation on modifying per-module loaders on the fly. So I'm wondering:

  • Is this the right approach?
  • Some (but not all) existing loaders entries contain an additional property ident, looking like this: 'ref--0-0'. When should I set this property, and using what value?
like image 693
Daniel Wolf Avatar asked Feb 26 '18 15:02

Daniel Wolf


2 Answers

I'm not sure whether it's the best solution, but this code appears to work:

class MyPlugin {
  apply(compiler) {
    compiler.plugin('normal-module-factory', normalModuleFactory => {
      normalModuleFactory.plugin('after-resolve', (data, callback) => {
        data.loaders.push({
          loader: ..., // Path to loader
          options: {}
        });
        callback(null, data);
      });
    });
  }
}

module.exports = MyPlugin;

Regarding the extra ident property: Webpack needs to be able to compare loader options. By default, it does this by calling JSON.stringify() on the options object, then comparing the resulting strings. By adding an ident property, you can explicitly specify a string representation of the options object. For details, see https://stackoverflow.com/a/49006233/52041.

like image 58
Daniel Wolf Avatar answered Nov 02 '22 10:11

Daniel Wolf


I was attempting to solve a similar problem, and wanted to add a loader only for specific files identified elsewhere in a plugin. My solution goes as follows:

loader.js

module.exports = function loader(content) {
  return mySpecialTransformation(content);
};

plugin.js

const path = require('path');

class MyPluginName {
  apply(compiler) {
    compiler.hooks.compilation.tap('MyPluginName', compilation => {
      compilation.hooks.normalModuleLoader.tap('MyPluginName', (loaderContext, module) => {
        if (myModuleCondition(module)) {
          module.loaders.unshift({
            loader: path.resolve(__dirname, 'loader.js'), // Path to loader
            options: {}
          });
        }
      });
    });
  }
}

Note that I've used unshift rather than push to add the loader - I'm still in the development stage so I could be wrong, but my current understanding is that I would want my loader to run after all 'standard' loaders which may well be transpiling typescript, etc. I believe that loaders are then applied from right to left. Depending on what you're doing this may not be important.

like image 27
Abulafia Avatar answered Nov 02 '22 09:11

Abulafia