【问题标题】:Webpack plugin: how can I modify and re-parse a module after compilation?Webpack 插件:编译后如何修改和重新解析模块?
【发布时间】:2016-05-07 15:22:21
【问题描述】:

我正在开发一个 webpack 插件,但不知道如何在构建过程中修改模块。我正在尝试做的事情:

  • 通过自定义加载器收集数据(很好)
  • 加载所有模块后,收集我的加载器收集的数据(很好)
  • 将我生成的代码插入到构建中的现有模块中(按如下所述执行此操作,不确定是否是最佳方式)
  • “更新”该模块,以便解析我添加的代码并将其“要求”转换为 webpack 要求调用(无法弄清楚如何正确执行此操作)

目前我在编译器上连接到“this-compilation”,然后在编译上连接到“additional-chunk-assets”。抓取第一个块(目前唯一一个,因为我仍在开发中),遍历该块中的模块以找到我想要修改的模块。那么:

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

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

如何使我修改过的模块“更新”,以便 webpack 将我添加的源与最初解析的源相同?除了rebuildModule() 之外我还需要做些什么吗?我在构建过程中做这项工作是否为时已晚?还是我走错路了?

【问题讨论】:

    标签: webpack


    【解决方案1】:

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

    我做错了什么:

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

    我是如何工作的:

    • 挂钩compilation的'seal'插件,遍历编译的modules并找到你想要的
    • 修改模块的源代码,例如module._source._value += extraCode;
    • 重新解析模块:

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

    解析取自 NormalModule 的 build 方法,在正常模块构建过程中加载源代码后或多或少会立即调用该方法。

    此实现将修改和解析的源代码放入我的最终输出中。由于 NormalModuleMixin 的 doBuild 方法中有一些 sourceMap 的东西,而且我是在调用这些函数之后添加到源中的,所以我认为 sourceMap 现在会搞砸了。因此,下一步是获取 sourceMap 以反映代码添加。不确定是否尝试手动更新 sourceMap 或查看上面的想法,尝试动态应用加载器并调用 rebuildModule() 而不是解析。

    如果您知道执行上述任何操作的更好方法,请告诉我!

    【讨论】:

    • 所以上述方法有很大的局限性。如果您需要将任意代码注入模块的源代码,它可以正常工作。然而,解析似乎并不适合已经编译的模块,例如它不会识别已在捆绑依赖项中的模块的require。因此,除非(可能)新代码不使用 webpack 的功能,否则这不是在构建中实现更改的好解决方案。
    • 有趣的方法。但是为什么不使用 Webpack 的 BannerPlugin 呢?
    • @EviSong 没有过多地研究 BannerPlugin,但是用例是在构建过程中派生一些新的源代码,然后在解析完所有模块等之后将其插入到构建中。所以代码在编译之后,直到构建时间才知道插入。
    • 我明白了。 BannerPlugin 只处理静态内容。您的场景有点像 extract-text-webpack-plugin 但更复杂。
    • 有什么消息要分享吗?
    【解决方案2】:

    基于查看Webpack的官方插件(如DefinePlugin)如何修改模块代码,我认为最好的方法是:

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

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

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

    【讨论】:

      【解决方案3】:

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

      1. 利用 babel + 自定义插件,甚至是基于 babylon 的自定义代码解析器,收集您需要的数据,然后保存到临时文件。
      2. 与这个临时文件一起运行 webpack 构建。

      这个临时文件可能是一个虚拟文件。可参考https://github.com/rmarscher/virtual-module-webpack-plugin/

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

      【讨论】:

      • 这不是一个坏主意;它可能适用于某些情况。在我们的案例中,我认为我们负担不起运行额外的构建,即使我们可以为该预构建去掉一些加载器、插件等——我们的应用程序很大,并且(即使进行了一些优化)到位)需要几分钟才能完成。
      猜你喜欢
      • 2018-01-20
      • 1970-01-01
      • 1970-01-01
      • 2017-08-07
      • 2023-01-25
      • 2015-09-14
      • 2019-08-25
      • 2012-11-25
      • 1970-01-01
      相关资源
      最近更新 更多