> 文章列表 > webpack plugin源码解析(三) banner-plugin

webpack plugin源码解析(三) banner-plugin

webpack plugin源码解析(三) banner-plugin

文章目录

  • 作用
  • 涉及 webpack API
    • 判断是否为入口 chunk
    • 判断文件名是否匹配 ModuleFilenameHelpers.matchObject
    • 创建注释内容 Template.toComment
    • 模版字符串生成 compilation.getPath
    • 合并文件内容
  • 实现
    • constructor
    • apply

作用

  • 在文件头部|尾部插入自定义的注释内容
new webpack.BannerPlugin({banner: 'hello world',raw: boolean, // true,不会变成注释entryOnly: boolean, // 只针对入口 chunktest: string | RegExp | [string, RegExp], // Include all modules that pass test assertion.include: string | RegExp | [string, RegExp], // Include all modules matching any of these conditions.exclude: string | RegExp | [string, RegExp], // Exclude all modules matching any of these conditions.footer?: boolean, // true,插在文件尾部
}),

涉及 webpack API

判断是否为入口 chunk

for (const chunk of compilation.chunks) {if(chunk.canBeInitial()){ // 判断入口 chunk// ...}
}

判断文件名是否匹配 ModuleFilenameHelpers.matchObject

ModuleFilenameHelpers.matchObject(optisn,filename) // filename: 'main.js'// 实现
ModuleFilenameHelpers.matchObject = (obj, str) => {if (obj.test) {if (!ModuleFilenameHelpers.matchPart(str, obj.test)) {return false;}}if (obj.include) {if (!ModuleFilenameHelpers.matchPart(str, obj.include)) {return false;}}if (obj.exclude) {if (ModuleFilenameHelpers.matchPart(str, obj.exclude)) {return false;}}return true;
};ModuleFilenameHelpers.matchPart = (str, test) => {if (!test) return true;test = asRegExp(test);if (Array.isArray(test)) {return test.map(asRegExp).some(regExp => regExp.test(str));} else {return test.test(str);}
};// 将字符串转换成正则表达式
const asRegExp = test => {if (typeof test === "string") {test = new RegExp("^" + test.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, "\\\\$&")); // 将字符串变成正则表达式,匹配给定字符串中的和正则相关的特殊字符,统一在前面加上\\进行转译// $&:表示前面正则匹配到的内容// \\\\:表示转译 \\,使得其不能转译 $,从而使 '*.' => '/\\*\\./'}return test;
};

创建注释内容 Template.toComment

Template.toComment

static toComment(str) {if (!str) return "";return `/*! ${str.replace(/\\*\\//g;, "* /")} */`;
}

模版字符串生成 compilation.getPath

let res=compilation.getPath((data)=>`dawdaa ${data.name}`,{name:'gg'})
// "dawdaa gg"

合并文件内容

const { ConcatSource } = require("webpack-sources");compilation.updateAsset(file, old => {let cached = cache.get(old);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;
});

实现

constructor

class BannerPlugin {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 // false 会将字符串变成注释? bannerOption: wrapComment(bannerOption);this.banner = () => banner;}}
}

wrapComment

  • 将字符串改造成注释
const wrapComment = str => {if (!str.includes("\\n")) {return Template.toComment(str);  '/*! hello world */'}return `/*!\\n * ${str  // 将一个字符串转换成一个多行注释的格式.replace(/\\*\\//g, "* /").split("\\n").join("\\n * ").replace(/\\s+\\n/g, "\\n").trimEnd()}\\n */`;// 比如://'Hello, world!\\nThis is a test.';/*!* Hello, world!* This is a test.*/};

apply

apply(compiler) {const options = this.options;const banner = this.banner;// 文件名匹配规则方法const matchObject = ModuleFilenameHelpers.matchObject.bind(undefined,options);const cache = new WeakMap(); // 从这里创建缓存可以看出 apply 只执行一次,后续重新编译只会执行 tap 注册的方法compiler.hooks.compilation.tap("BannerPlugin", compilation => {compilation.hooks.processAssets.tap({name: "BannerPlugin",stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS},() => {// 遍历生成的 chunkfor (const chunk of compilation.chunks) {if (options.entryOnly && !chunk.canBeInitial()) {continue;}// 拿到每一个 chunk 包含的文件for (const file of chunk.files) {// 匹配文件名和开发者传入的 test、include、exclude 配置if (!matchObject(file)) {continue;}// 要交给生成模版字符串的 dataconst data = {chunk,filename: file};// banner: () => 'hello world'// comment: "/*! hello world */"const comment = compilation.getPath(banner, data); // 更新文件资源,插入注释信息compilation.updateAsset(file, old => {// 先读取缓存信息let cached = cache.get(old);// 如果没有添加过或者注释内容改变了,重新生成资源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;});}}}
}