背景
- 不知道webpack插件是怎么回事,除了官方的文档外,还有一个很直观的方式,就是看源码。
- 看源码是一个挖宝的行动,也是一次冒险,我们可以找一些代码量不是很大的源码
- 比如webpack插件,我们就可以通过BannerPlugin源码,来看下官方是如何实现一个插件的
- 希望对各位同学有所帮助,必要时可以通过源码进行一门技术的学习,加深理解
闲言少叙,直接上代码
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;
总结
- 查看源码,查看源码,查看源码
- WeakMap可以深入了解下,应该是避免对象不释放导致内存问题。
- 插件里用到的很多工具方法可以继续深入,一遍自己开发插件时可以参考