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

背景

  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.php 	Author 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. 插件里用到的很多工具方法可以继续深入,一遍自己开发插件时可以参考
发表评论

相关文章