3

【前端必会】不知道webpack插件? webpack插件源码分析BannerPlugin - 李同学的教室

 1 year ago
source link: https://www.cnblogs.com/lee35/p/16740049.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  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. 插件里用到的很多工具方法可以继续深入,一遍自己开发插件时可以参考

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK