必须知道的plugin基础知识

175 阅读3分钟

必须知道的plugin基础知识,流程:

image.png

plugin

plugin是流水线,在执行过程中不同时间段执行扩展功能

Tapable 注册 tap,tapAsync,tabPromise

  • tab 可以注册同步或异步钩子

  • tabAsync 回调方式注册异步钩子

  • tabPromise Promise方式注册异步钩子,返回Promise

Compiler 和 Compilation

  • compiler 对象保持着完整的webpack环境配置,可以访问到loader,plugin等

  • 主要使用以下属性:

1、compiler.options 可以访问本次启动的webpack的所有配置文件,包括但不限于 loaders,entry,plugin等完整配置信息

2、compiler.inputFileSystem 和 compiler.outputFileSystem 可以进行文件操作,相当于nodejs中 fs

3、compiler.hooks 可以注册 tabpable 的不同种类Hooks,从而可以在compiler生命周期中注入不同逻辑
  • compilation 代表一次资源构建,compilation实例能够访问所有模块和他的依赖

  • 主要使用以下属性:

1、compilation.modules 可以访问所有模块,打包的每一个文件都是一个模块

2、compilation.chunks chunk即是多个modules组成而来的一个代码块,入口文件引入的资源组成一个chunk,通过代码分割的模块又是另一个chunk

3、compilation.assets 可以访问本次打包生成的所有文件结果

4、compilation.hooks 可以注册tabpale的不同种类hooks,用于在compilation编译模块阶段进行逻辑添加以及修改

node 调试方式

package.json 中

{
    "scripts":{
        "debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
    }
}

--inspect-brk 在第一行停下来方便调试,直接命令行运行 npm run debug

打开浏览器,控制台就看到 nodejs 调试按钮了

image.png

image.png

例子

BannerWebpackPlugin 最终在js和css文件头加上注释

class BannerWebpackPlugin {
  constructor(options={}) {
    this.options = options
  }

  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      "BannerWebpackPlugin",
      (compilation, callback) => {
        // 1、获取即将输出的资源:compilation.assets
        // 2、过滤只保留js和css文件,图片字体等跳过
        // 3、遍历剩下资源,添加注释

        const assets = Object.keys(compilation.assets).filter((assetPath) =>
          /\.(css|js)/.test(assetPath)
        );

        assets.forEach((asset) => {
          const source = compilation.assets[asset];
          const content = "/*添加的前缀*/" + source;

          compilation.assets[asset] = {
            // 最终资源输出时,调用source方法,返回值就是最终资源具体内容
            source() {
              return content;
            },
            // 资源大小
            size() {
              return content.length;
            },
          };

        });
        callback();
      }
    );
  }
}

module.exports = BannerWebpackPlugin;

CleanWebpackPlugin 清空目标文件夹

class CleanWebpackPlugin {
  constructor() {}
  apply(compiler) {
    // 1、在打包输出前注册 emit
    // 2、获取打包目录
    // 3、通过fs删除目标目录
    const outputPath = compiler.options.output.path;
    const fs = compiler.outputFileSystem;
    compiler.hooks.emit.tap("CleanWebpackPlugin", (compilation) => {
      this.removeFiles(fs, outputPath);
    });
  }
  removeFiles(fs, filePath) {
    // 删除目录下所有文件
    //  1、读取当前目录下所有资源
    //  2、遍历一个一个删除
    // 删除空目录

    const files = fs.readdirSync(filePath);
    files.forEach((file) => {
      const fullPath = `${filePath}/${file}`;
      const fileStat = fs.statSync(fullPath);
      if (fileStat.isDirectory()) {
        this.removeFiles(fs, fullPath);
        fs.rmdirSync(fullPath);
      } else {
        fs.unlinkSync(fullPath);
      }
    });
  }
}

module.exports = CleanWebpackPlugin;

AnalyzeWebpackPlugin 统计打包文件大小

class AnalyzeWebpackPlugin {
  constructor() {}

  apply(compiler) {
    compiler.hooks.emit.tap("AnalyzeWebpackPlugin", (compilation) => {
      const assets = Object.entries(compilation.assets);

      let content = `| 资源名称 | 资源大小 |
| --- | --- |`;
      assets.forEach(([filename, file]) => {
        content += `\n|${filename}|${Math.ceil(file.size() / 1024)}|`;
      });
      compilation.assets["analyze.md"] = {
        source() {
          return content;
        },
        size() {
          return content.length;
        },
      };
    });
  }
}

module.exports = AnalyzeWebpackPlugin;

InlineHtmlWebpackPack 将runtime文件删除,直接在index.html文件中内联

const HtmlWebpackPlugin = require("safe-require")("html-webpack-plugin");
class InlineChunkWebpackPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(
      "InlineChunkWebpackPlugin",
      (compilation) => {
        // 1、获取html-webpack-plugin的hooks
        const hooks = HtmlWebpackPlugin.getHooks(compilation);
        // 2、注册 html-webpack-plugin 的hooks -> alterAssetTagGroups
        hooks.alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => {
          // 3、从里面将script的runtime文件,变成inline script
          assets.headTags = this.getInlineChunk(
            assets.headTags,
            compilation.assets
          );
          assets.bodyTags = this.getInlineChunk(
            assets.bodyTags,
            compilation.assets
          );
        });
        // 4、删除runtime文件
        hooks.afterEmit.tap("InlineChunkWebpackPlugin", () => {
          Object.keys(compilation.assets).forEach((filepath) => {
            if (/runtime(.*)\.js$/.test(filepath)) {
              delete compilation.assets[filepath];
            }
          });
        });
      }
    );
  }
  getInlineChunk(tags, assets) {
    /**
     目前:
     [
        {
            tagName: 'script',
            voidTag: false,
            meta: { plugin: 'html-webpack-plugin'},
            attributes: {defer: true, type: undefined, src: 'js/runtime~main.js'}
        }
     ]
     修改为:
     [
        {
            tagName: 'script',
            innerHTML : 'runtime文件内容',
            closeTag : true
        }
     ]
     */

    return tags.map((tag) => {
      if (tag.tagName !== "script") return tag;
      // 获取文件路径
      const filepath = tag.attributes.src;
      if (!filepath) return tag;

      if (!/runtime(.*)\.js$/g.test(filepath)) return tag;

      return {
        tagName: "script",
        innerHTMl: assets[filepath].source(),
        closeTag: true,
      };
    });
  }
}

module.exports = InlineChunkWebpackPlugin;