手搓一个 Webpack5 插件:让每一行代码都自带「回声」——BannerWebpackPlugin 从 0 到全球下载

59 阅读2分钟

手搓一个 Webpack5 插件:让每一行代码都自带「回声」——BannerWebpackPlugin 从 0 到全球下载

摘要:本文带你从 0 到 1 实现一个「BannerWebpackPlugin」——自动为 JS/CSS 插入版权 banner,并额外生成独立版权文件。过程中,我们将深入 Webpack5 的钩子宇宙、Source 家族与时间戳陷阱,最后附赠「发布 npm + CI 自动注入」一条龙方案。读完,你可以自信地在简历里写下:「写过 Webpack 插件,线上日下载 3k+」。


一、为什么需要「自动版权」?

  1. 法务合规:公司代码必须带版权头,人工粘贴容易漏。
  2. 品牌露出:开源库构建后无 banner,等于「裸奔」。
  3. CI/CD 友好:构建阶段一次性注入,不污染源码,不增加网络请求。

二、效果速览

构建前:

// src/index.js
console.log('hello');

构建后:

/*! Copyright (c) 2025 YourCompany */
console.log('hello');

同时生成:

dist/copyright.txt
----------------------------------------
Copyright by YourCompany
Generated at: 2025-06-25T08:00:00.000Z

三、核心原理:Webpack 的「processAssets」钩子

Webpack5 把「输出文件列表」抽象成一个内存级 Map —— compilation.assets
processAssets 阶段(官方叫 PROCESS_ASSETS_STAGE_ADDITIONS):

  • 所有 chunk 已编码完成,尚未写盘
  • 我们可以读取 / 修改 / 新增任意文件;
  • 改完后 webpack 会统一写磁盘,无需关心路径或文件名冲突

四、插件实现(TypeScript 友好版)

// plugins/banner-webpack-plugin.js
const { RawSource } = require('webpack-sources');

class BannerWebpackPlugin {
  constructor(options = {}) {
    this.banner = options.banner || '/*! (c) 2025 */\n';
  }

  apply(compiler) {
    const pluginName = 'BannerWebpackPlugin';

    compiler.hooks.compilation.tap(pluginName, (compilation) => {
      compilation.hooks.processAssets.tap(
        {
          name: pluginName,
          stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
        },
        (assets) => {
          // 1. 遍历所有待输出文件
          for (const name in assets) {
            if (!/\.(js|css)$/i.test(name)) continue;
            const oldSource = assets[name];
            const newSource = this.banner + oldSource.source().toString();
            assets[name] = new RawSource(newSource);
          }

          // 2. 额外生成版权清单
          const txt = `Copyright by YourCompany\nGenerated at: ${new Date().toISOString()}`;
          compilation.emitAsset('copyright.txt', new RawSource(txt));
        }
      );
    });
  }
}

module.exports = BannerWebpackPlugin;

五、踩坑记录:「时间戳」导致双文件?

现象:每次保存后 dist 里出现 两个 fileList-xxx.md
原因:processAssetswatch 模式下会触发多次 compilation,而时间戳文件名永远唯一。
解决:① 固定文件名;② 或先删除旧 fileList-*.md 再写入。


六、使用方式

// webpack.config.js
const BannerWebpackPlugin = require('./plugins/banner-webpack-plugin');

module.exports = {
  plugins: [
    new BannerWebpackPlugin({
      banner: '//! MIT License - (c) 2025 YourCompany\n',
    }),
  ],
};

构建:

npm run build

产物:

dist/
├── app.1234.js        // 顶部自带 banner
├── main.css
└── copyright.txt      // 独立版权文件

七、再进一步:发布到 npm + CI 自动注入

  1. 发包

    npm init -y
    npm publish --access public
    
  2. GitHub Actions 自动注入

    - name: Build
      run: |
        npm i your-copyright-plugin -D
        npm run build
    
  3. 徽章炫耀
    npm
    写进简历:「维护 xxx-webpack-plugin,周下载 3k+」


八、总结:插件思维模型

阶段钩子操作
编译开始compiler.hooks.compilation拿到 compilation 实例
资源已生成compilation.hooks.processAssets读写 assets Map
写盘前compilation.emitAsset新增文件
写盘后compiler.hooks.done打印日志

记住:**「钩子 → assets → RawSource → emitAsset」**四步曲,
任何「自动注入 / 自动生成 / 自动上传」需求都能套用这个模板。


现在,打开你的项目,敲下 npm run build
听——每一行代码都在回声:
「Copyright (c) 2025,这是我写下的痕迹。」

自动为 **JS/CSS** 顶部插入版权 banner,同时生成独立 `copyright.txt` 清单——**零配置、无侵入、构建即合规**
  • npm i copyright-webpack-plugin-qw