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:
Currently I'm hooking into 'this-compilation' on the compiler, then 'additional-chunk-assets' on the compilation. Grabbing the first chunk (the only one, currently, as I'm still in development), iterating through the modules in that chunk to find the one I want to modify. Then:
It looks like rebuildModule should re-parse the source, re-establish dependencies, etc. etc., but it's not parsing my require statements and changing them to webpack requires. The built file includes my modified source but the require('...') statements are unmodified.
How can I make the module I modified 'update' so that webpack will treat my added source the same as the originally parsed source? Is there something I need to do in addition to rebuildModule()? Am I doing this work too late in the build process? Or am I going about it the wrong way?
I figured out how to do this in a pretty painless fashion.
Things I had wrong:
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. 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:
compilation
's 'seal' plugin, iterate through the compilation's module
s and find the one you wantmodule._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!
Based on looking at how Webpack's official plugins (such as DefinePlugin
) modify module code, I believe the best way to do this is:
buildModule
, with module.addDependency()
.compilation.dependencyTemplates.set()
.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.
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)); }); }); } };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With