一、Webpack中的loader

所谓loader只是一个导出为函数的Javascript模块。它接收上一个loader产生的结果或者资源文件作为参数。它也可以由多个函数组成loader chain。

compiler需要得到最后一个loader产生的的处理结果。这个结果应该是一个String或者Buffer(转成String)。

1、loader整体运行流程

  • 初始化参数:从配置文件或者shell命令行中读取合并参数,得出最终的参数。
  • 开始编译:上一步得到的参数初始化compiler对象,加载所有的配置插件,执行compiler对象的run方法开始执行编译。确定入口:根据配置中方的entry找出入口文件。
  • 编译模块:从入口文件出发,调用所有配置的loader对模块进行编译,在找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过本步骤的处理。
  • 完成编译:在经过上面步骤使用loader完成翻译所有模块后,得到了每个模块被翻译后的最终内容以及他们之间的依赖关系。
  • 输出资源:根据入口和模块之间的关系,组成一个个包含多个模块的chunk,在把每个chunk转成一个单独的文件加入到输出列表中。
  • 写入文件:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

    在以上过程中,Webpack会在特定的时间广播出特定的事件,插件在监听到感兴趣的事件后执行特定的逻辑,并且插件可以调用Webpack提供的API改变Webpack的运行结果。

    2、loader的使用方式

  • 在配置文件中使用。

  • 在行内使用,在引入文件的前面使用。
    loader的执行顺序是post(后置) + inline(内联) + normal(正常) + pre(前置)
    以下是行内的特殊配置:
    | 符号 | 变量 | 含义 | | —- | —- | —- | | -! | noPreAutoLoaders | 不要前置和普通loader | | ! | noAutoLoaders | 不要普通loader | | !! | noPrePostAutoLoaders | 不要前置、后置和普通loader,只要内联loader |

3、pitch函数

  • 例如a!b!c!module,正常的调用顺序应该是c、b、a,当时真正的调用顺序是a(pitch)、b(pitch)、c(pitch)、读取目标文件、c(normal)、b(normal)、a(normal),如果其中一个pitch loader有返回值就相当于在它以及右边的loader都执行完成了。
  • 比如b的pitch loader返回了一个字符串’result b’,接下来只有a的normal loader会被系统执行,且a的normal loader接收的参数是’result b’。

    在style-loader中为什么要使用pitch函数呢?style-loader的作用是将css-loader返回的字符串转成css样式,然后插入到html中。按照正常执行的顺序,style-loader只能拿到这些字符串,所以我们需要在style-loader中执行require(css-loader!resource),这样就能拿到css-loader转化后的文件,能获取到真正的CSS样式。

    pitch函数与normal函数本身方法的执行顺序图如下:

    image.png

    二、Webpack中的plugin

    插件想第三方提供了webpack引擎中完成的能力。使用阶段式的构建回调,开发者可以引入他们自己的行为到webpack构建流程中。需要了解一些webpack底层的内部特性来做响应的钩子。

    1、Compiler和Compliation

    在插件开发中最重要的两个资源就是compiler和compilation对象。理解它们的角色是扩展webpack引擎的第一步和最终的一步。
  • compiler对象代表了完整的webpack环境。这个对象在启动webpack时被一次性建立,并设置好所有可操作的配置,包括options,loader和plugins。当在webpack环境应用一个插件时,插件将收到此compiler对象的应用,可以使插件来访问webpack的主环境。

  • compilation对象代表了一次资源版本的构建。当运行webpack的watch模式时,每当检测到一个文件的变化,就会创建一个新的compilation,从而生成一组新的编译资源。一个compilation对象就代表了当前的模块资源、编译生成资源、变化的文件等状态。compilation对象也提供了很多钩子函数,以便插件调用。

    2、基本插件架构

  • 插件是由一个具备apply方法的prototype对象所实例化出来的。

  • 这个apply方法在安装插件时,会被webpack的compiler调用一次。
  • apply方法可以接收一个compiler对象的引用,从而可以在回调函数中访问到compiler对象。

    3、自定义插件

    1、自定义compiler插件

    1. const { RawSource } = require("webpack-sources");
    2. const JSZip = require("jszip");
    3. class ZipPlugin {
    4. constructor(options) {
    5. this.options = options;
    6. }
    7. apply(compiler) {
    8. const { filename } = this.options;
    9. compiler.hooks.emit.tapAsync('zip', function(compilation, callback) {
    10. var zip = new JSZip();
    11. for (let filename in compilation.assets) {
    12. const source = compilation.assets[filename].source();
    13. zip.file(filename, source);
    14. }
    15. zip.generateAsync({ type: "nodebuffer" }).then((content) => {
    16. compilation.assets[filename] = new RawSource(content);
    17. callback();
    18. });
    19. })
    20. }
    21. }
    22. module.exports = ZipPlugin;

    2、自定义compilation插件

    使用 compiler 对象时,你可以绑定提供了编译 compilation 引用的回调函数,然后拿到每次新的 compilation 对象。这些 compilation 对象提供了一些钩子函数,来钩入到构建流程的很多步骤中。
    1. class AssetPlugin {
    2. constructor(options) {
    3. this.options = options;
    4. }
    5. apply(compiler) {
    6. compiler.hooks.compilation.tap("AssetPlugin", function (compilation) {
    7. compilation.hooks.chunkAsset.tap("AssetPlugin", function (
    8. chunk,
    9. filename
    10. ) {
    11. console.log("filename=", filename);
    12. });
    13. });
    14. }
    15. }
    16. module.exports = AssetPlugin;