Webpack plugin: how can I modify and re-parse a module after compilation?

后端 未结 3 1142
粉色の甜心
粉色の甜心 2020-12-13 19:05

I\'m working on a webpack plugin and can\'t figure out how to modify a module during the build. What I\'m trying to do:

  • Collect data via a custom loader (fine)
相关标签:
3条回答
  • 2020-12-13 19:47

    Another idea comes to my mind. What if do some preprocessing before webpack compilation?

    1. Leverage babel + a custom plugin, or even a custom code parser based on babylon, to collect data you need, and then save to temp file.
    2. Run webpack build together with this temp file.

    This temp file could be a virtual file. May refer to https://github.com/rmarscher/virtual-module-webpack-plugin/ .

    ... be used if you generated file contents at build time that needs to be consumed as a module by your source code, but you don't want to write this file to disk.

    0 讨论(0)
  • 2020-12-13 19:53

    Based on looking at how Webpack's official plugins (such as DefinePlugin) modify module code, I believe the best way to do this is:

    1. Create a custom "dependency" class and a corresponding "template" class.
    2. Attach an instance of the dependency class to each module, e.g. in response to buildModule, with module.addDependency().
    3. Register the dependency template with compilation.dependencyTemplates.set().
    4. In the template's apply method, use source.replace() or source.insert() to make your modifications (where source is the second argument)—see the ReplaceSource docs.

    In terms of compilation hooks, the templates are invoked immediately after beforeChunkAssets. Modifying the source in this way preserves SourceMaps.

    Example for Webpack 4

    const Dependency = require('webpack/lib/Dependency');
    
    class MyDependency extends Dependency {
      // Use the constructor to save any information you need for later
      constructor(module) {
        super();
        this.module = module;
      }
    }
    
    MyDependency.Template = class MyDependencyTemplate {
      apply(dep, source) {
        // dep is the MyDependency instance, so the module is dep.module
        source.insert(0, 'console.log("Hello, plugin world!");');
      }
    };
    
    module.exports = class MyPlugin {
      apply(compiler) {
        compiler.hooks.compilation.tap('MyPluginName', compilation => {
          compilation.dependencyTemplates.set(
            MyDependency,
            new MyDependency.Template()
          );
          compilation.hooks.buildModule.tap('MyPluginName', module => {
            module.addDependency(new MyDependency(module));
          });
        });
      }
    };
    
    0 讨论(0)
  • 2020-12-13 20:01

    I figured out how to do this in a pretty painless fashion.

    Things I had wrong:

    • probably hooking in too late? the earliest plugin where you can accomplish this is the compilation's 'seal' plugin. Despite the name, this plugin hook executes as the very first line in the seal function, so no sealing has yet occurred. At this point all the modules have been loaded.
    • rebuildModule() isn't a good idea, because this re-loads the module from scratch: the file's source is loaded and passed through any applicable loaders, and the _source property of the module object is eventually reassigned when that process is finished.
      • Using rebuildModule() at this point would actually be great if there were a way to modify the module source as it was being loaded in this call (i.e. dynamically assign a loader function that's only used on this rebuild). We'd then be able to take advantage of the sourceMap behavior that happens when a module's source is loaded (see below)

    How I got it working:

    • hook into compilation's 'seal' plugin, iterate through the compilation's modules and find the one you want
    • modify the module's source, e.g. module._source._value += extraCode;
    • reparse the module:

      module.parse.parse(module._source.source(), {
        current: module, 
        module.module,
        compilation: compilation,
        options: compilation.options
      });
      

    The parsing is taken from NormalModule's build method, which is called more or or less immediately after the source has been loaded during the normal module build process.

    This implementation gets the modified and parsed source into my final output. Since there's some sourceMap stuff in NormalModuleMixin's doBuild method, and since I'm adding to the source after those functions have been called, I assume the sourceMap will be messed up now. So, next step is getting the sourceMap to reflect the code addition. Not sure whether to try and manually update the sourceMap or look into the idea above, trying to dynamically apply a loader and call rebuildModule() instead of parsing.

    If you know a better way of doing any of the above, please let me know!

    0 讨论(0)
提交回复
热议问题