【前端必会】不知道webpack插件? webpack插件源码分析BannerPlugin

2022-09-29 09:10:12 作者:admin

背景

  1. 不知道webpack插件是怎么回事,除了官方的文档外,还有一个很直观的方式,就是看源码。
  2. 看源码是一个挖宝的行动,也是一次冒险,我们可以找一些代码量不是很大的源码
  3. 比如webpack插件,我们就可以通过BannerPlugin源码,来看下官方是如何实现一个插件的
  4. 希望对各位同学有所帮助,必要时可以通过源码进行一门技术的学习,加深理解

闲言少叙,直接上代码

https://github.com/webpack/webpack/blob/main/lib/BannerPlugin.js

配合文档api
https://webpack.docschina.org/api/compilation-object/#updateasset

代码分析已添加中文注释

/*MIT License http://www.opensource.org/licenses/mit-license.phpAuthor Tobias Koppers @sokra*/"use strict";const { ConcatSource } = require("webpack-sources");const Compilation = require("./Compilation");const ModuleFilenameHelpers = require("./ModuleFilenameHelpers");const Template = require("./Template");const createSchemaValidation = require("./util/create-schema-validation");/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginArgument} BannerPluginArgument *//** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginOptions} BannerPluginOptions *//** @typedef {import("./Compiler")} Compiler */// 创建一个验证const validate = createSchemaValidation(  require("../schemas/plugins/BannerPlugin.check.js"),  () => require("../schemas/plugins/BannerPlugin.json"),  {    name: "Banner Plugin",    baseDataPath: "options",  });//包装Banner文字const wrapComment = (str) => {  if (!str.includes("\n")) {    return Template.toComment(str);  }  return `/*!\n * ${str    .replace(/\*\//g, "* /")    .split("\n")    .join("\n * ")    .replace(/\s+\n/g, "\n")    .trimRight()}\n */`;};//插件类class BannerPlugin {  /**   * @param {BannerPluginArgument} options options object   * 初始化插件配置   */  constructor(options) {    if (typeof options === "string" || typeof options === "function") {      options = {        banner: options,      };    }    validate(options);    this.options = options;    const bannerOption = options.banner;    if (typeof bannerOption === "function") {      const getBanner = bannerOption;      this.banner = this.options.raw        ? getBanner        : (data) => wrapComment(getBanner(data));    } else {      const banner = this.options.raw        ? bannerOption        : wrapComment(bannerOption);      this.banner = () => banner;    }  }  /**   * Apply the plugin   * @param {Compiler} compiler the compiler instance   * @returns {void}   * 插件主方法   */  apply(compiler) {    const options = this.options;    const banner = this.banner;    const matchObject = ModuleFilenameHelpers.matchObject.bind(      undefined,      options    );    //创建一个Map,处理如果添加过的文件,不在添加    const cache = new WeakMap();    compiler.hooks.compilation.tap("BannerPlugin", (compilation) => {      //处理Assets的hook      compilation.hooks.processAssets.tap(        {          name: "BannerPlugin",          //PROCESS_ASSETS_STAGE_ADDITIONS — 为现有的 asset 添加额外的内容,例如 banner 或初始代码。          stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,        },        () => {          //遍历当前编译对象的chunks          for (const chunk of compilation.chunks) {            //如果配置标识只处理入口,但是当前chunk不是入口,直接进入下一次循环            if (options.entryOnly && !chunk.canBeInitial()) {              continue;            }            //否则,遍历chunk下的文件            for (const file of chunk.files) {              //根据配置匹配文件是否满足要求,如果不满足,直接进入下一次循环,处理下一个文件              if (!matchObject(file)) {                continue;              }              //否则,              const data = {                chunk,                filename: file,              };              //获取插值路径?https://webpack.docschina.org/api/compilation-object/#getpath              const comment = compilation.getPath(banner, data);              //修改Asset,https://webpack.docschina.org/api/compilation-object/#updateasset              compilation.updateAsset(file, (old) => {                //从缓存中获取                let cached = cache.get(old);                //如果缓存不存在 或者缓存的comment 不等于当前的comment                if (!cached || cached.comment !== comment) {                  //源文件追加到头部或者尾部                  const source = options.footer                    ? new ConcatSource(old, "\n", comment)                    : new ConcatSource(comment, "\n", old);                  //创建对象加到缓存                  cache.set(old, { source, comment });                  //返回修改后的源                  return source;                }                //返回缓存中的源                return cached.source;              });            }          }        }      );    });  }}module.exports = BannerPlugin;

总结

  1. 查看源码,查看源码,查看源码
  2. WeakMap可以深入了解下,应该是避免对象不释放导致内存问题。
  3. 插件里用到的很多工具方法可以继续深入,一遍自己开发插件时可以参考
在线咨询 拨打电话