Webpack插件:如何在编译后修改和重新解析模块?

时间:2016-01-29 19:07:59

标签: webpack

我正在开发一个webpack插件,无法弄清楚如何在构建期间修改模块。我想做什么:

  • 通过自定义加载程序收集数据(精细)
  • 加载所有模块后,收集我的装载机收集的数据(精)
  • 将我生成的代码插入构建中的现有模块中(如下所述执行此操作,不确定它是否是最佳方式)
  • '更新'该模块,以便我添加的代码得到解析并将其'require'变为webpack require调用(无法弄清楚如何正确执行此操作)

目前我正在编译器上加入'this-compilation',然后在编译中加入'additional-chunk-assets'。抓住第一个块(目前唯一的一个,因为我还在开发中),遍历该块中的模块以找到我想要修改的块。然后:

  • 将我生成的源附加到模块的_cachedSource.source._source._value(我也尝试附加到模块的._source._value)
  • 将._cachedSource.hash设置为空字符串(因为这似乎是下一步工作所必需的)
  • 我将模块传递给.rebuildModule()

看起来rebuildModule应该重新解析源代码,重新建立依赖关系等等,但它不会解析我的require语句并将它们更改为webpack需要。构建的文件包含我修改过的源代码,但require('...')语句未经修改。

如何让我修改的模块'更新',以便webpack将我添加的源视为与原始解析的源相同?除了rebuildModule()之外我还需要做些什么吗?我在构建过程中做得太晚了吗?或者我是以错误的方式去做的?

3 个答案:

答案 0 :(得分:17)

我想出了如何以一种非常轻松的方式做到这一点。

我错了:

  • 可能挂得太晚了?最早的插件,你可以完成这个是编译的'密封'插件。尽管有这个名字,但这个插件钩子作为密封功能的第一行执行,所以还没有发生密封。此时所有模块都已加载。
  • rebuildModule()不是一个好主意,因为这会从头开始重新加载模块:文件的源被加载并传递给任何适用的加载器,以及{{1}的_source属性该进程完成后,最终会重新分配对象。
    • 如果有一种方法可以修改模块源,因为在此调用中加载模块源(即动态分配仅用于此重建的加载器函数),此时使用module实际上会很棒。然后我们就可以利用加载模块源时发生的sourceMap行为(见下文)

我是如何运作的:

  • 挂钩到rebuildModule()的'seal'插件,遍历编译的compilation并找到你想要的那个
  • 修改模块的来源,例如module
  • 重新解析模块:

    module._source._value += extraCode;

解析来自NormalModule的module.parse.parse(module._source.source(), { current: module, module.module, compilation: compilation, options: compilation.options }); 方法,该方法在正常模块构建过程中加载源后会立即调用。

此实现将修改后的解析源转换为我的最终输出。由于NormalModuleMixin的build方法中存在一些sourceMap内容,并且由于我在调用这些函数后添加到源代码,因此我假设sourceMap现在会搞乱。因此,下一步是让sourceMap反映代码添加。不确定是否尝试手动更新sourceMap或查看上面的想法,尝试动态应用加载器并调用rebuildModule()而不是解析。

如果您知道更好的方法,请告诉我们!

答案 1 :(得分:1)

我想到了另一个想法。如果在webpack编译之前做一些预处理怎么办?

  1. 利用babel +自定义插件,甚至是基于babylon的自定义代码解析器来收集您需要的数据,然后保存到临时文件。
  2. 与此临时文件一起运行webpack build。
  3. 此临时文件可以是虚拟文件。可以参考https://github.com/rmarscher/virtual-module-webpack-plugin/

      

    ...如果您在构建时生成文件内容需要由源代码作为模块使用,但您不想将此文件写入磁盘。

答案 2 :(得分:1)

基于研究Webpack的官方插件(例如DefinePlugin)如何修改模块代码的方法,我相信做到这一点的最佳方法是:

  1. 创建自定义“依赖项”类和相应的“模板”类。
  2. 将依赖项类的实例附加到每个模块,例如响应buildModule,并以module.addDependency()
  3. compilation.dependencyTemplates.set()注册依赖项模板。
  4. 在模板的apply方法中,使用source.replace()source.insert()进行修改(其中source是第二个参数)—请参阅ReplaceSource文档。

就编译挂钩而言,模板在beforeChunkAssets之后立即被调用。以这种方式修改源将保留SourceMap。

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));
      });
    });
  }
};