Plugin的渡劫指南

27 阅读3分钟

文/ 天机阁首席炼器宗师

(云海深处,紫袍道人端坐八卦炉前,三十六道灵力锁链连接虚空)

"昨日尔等习得Loader采气之术,今日当参Plugin造化之功。须知Loader如单兵剑诀,Plugin乃排兵布阵之法!且看这周天星斗大阵——"


第一章:Plugin的渡劫境界

凝气期Plugin(基础道纹)

// 记录构建时间的青铜日晷
class TimePlugin {
  apply(compiler) {
    compiler.hooks.done.tap('TimePlugin', () => {
      console.log(`🕰️ 本次修炼耗时:${Date.now() - startTime}ms`);
    });
  }
}

筑基期Plugin(因果干涉)

// 修改功德榜(manifest)的生死簿
class ManifestPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap('Manifest', (compilation) => {
      const manifest = {};
      for (const name of Object.keys(compilation.assets)) {
        manifest[name] = name.split('').reverse().join(''); // 倒转文件名
      }
      compilation.assets['manifest.json'] = {
        source: () => JSON.stringify(manifest),
        size: () => JSON.stringify(manifest).length
      };
    });
  }
}

元婴期Plugin(时空逆转)

// 在平行宇宙生成镜像分身(多线程压缩)
const { Compilation } = require('webpack');

class ParallelPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('Parallel', (compilation) => {
      compilation.hooks.processAssets.tapPromise({
        name: 'Parallel',
        stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
      }, async (assets) => {
        await Promise.all(Object.keys(assets).map(async (name) => {
          // 每个文件开启独立时空裂隙
          const content = assets[name].source();
          assets[name] = await compressInParallel(content); 
        }));
      });
    });
  }
}

第二章:周天星斗大阵——七步炼器法

第一步:引地脉灵气(项目初始化)

mkdir celestial-plugin
cd celestial-plugin
npm init -y
npm install tapable webpack-sources --save

第二步:铸器胚核心(基础结构)

// celestial-plugin.js
const { SyncHook } = require('tapable');

class CelestialPlugin {
  constructor(options = { stars: 9 }) {
    this.starCount = options.stars;
  }

  apply(compiler) {
    // 注入星辰之力
    compiler.hooks = {
      ...compiler.hooks,
      celestial: new SyncHook(['starArray'])
    };
  }
}

第三步:刻周天星斗(生命周期接入)

apply(compiler) {
  // 在environment阶段布阵
  compiler.hooks.environment.tap('Celestial', () => {
    this.starArray = Array.from({length: this.starCount}, 
      (_,i) => `★第${i+1}天枢星`);
  });

  // 在emit阶段释放星力
  compiler.hooks.emit.tapAsync('Celestial', (compilation, callback) => {
    compilation.assets['star-map.txt'] = {
      source: () => this.starArray.join('\n'),
      size: () => this.starArray.join('\n').length
    };
    callback();
  });
}

第四步:引四象之力(异步事件处理)

compiler.hooks.make.tapPromise('Celestial', async (compilation) => {
  await new Promise(resolve => {
    // 青龙位注入木系灵气
    setTimeout(() => {
      compilation.errors.push(new Error('⚠️ 东方青龙苏醒'));
      resolve();
    }, 1000);
  });
});

第五步:抗九天雷劫(单元测试)

const webpack = require('webpack');

describe('渡劫测试', () => {
  it('应生成正确的星图', (done) => {
    const config = {
      plugins: [new CelestialPlugin({ stars: 3 })]
    };
    
    webpack(config, (err, stats) => {
      const output = fs.readFileSync('dist/star-map.txt', 'utf8');
      expect(output).toMatch(/第1天枢星/);
      done();
    });
  });
});

第六步:融器灵神识(类型定义)

// celestial-plugin.d.ts
declare module 'celestial-plugin' {
  import { Plugin } from 'webpack';

  interface Options {
    stars?: number;
  }

  class CelestialPlugin extends Plugin {
    constructor(options?: Options);
  }

  export = CelestialPlugin;
}

第七步:成先天至宝(发布与集成)

npm login --registry=https://registry.npmjs.org/
npm version major
npm publish --access public

第三章:天道法则

"Plugin修炼五戒:
1️⃣ 勿直接触碰compilation真身(使用Tapable接口)
2️⃣ 异步操作需结太极印(正确处理callback/promise)
3️⃣ 修改assets要留时空印记(维护sourceMap)
4️⃣ 慎用SyncHook以免阻塞天地灵气(避免同步阻塞)
5️⃣ 多插件协同需遵五行相生顺序(注意hook阶段)"

第四章:诛仙剑阵实战

基础阵图(webpack.config.js)

const CelestialPlugin = require('celestial-plugin');

module.exports = {
  plugins: [
    new CelestialPlugin({
      stars: 28, // 对应二十八星宿
      phases: {
        // 在特定阶段注入星力
        beforeRun: (compiler) => {/*...*/},
        afterEmit: (compilation) => {/*...*/}
      }
    })
  ]
};

合击秘术(多插件协作)

// 四象封印大阵
const plugins = [
  new青龙Plugin({ position: '东方' }),
  new白虎Plugin({ attack: 9000 }),
  new朱雀Plugin({ rebirth: true }),
  new玄武Plugin({ defense: '∞' }),
  new四象中枢Plugin() // 控制插件执行顺序
];

第五章:域外天魔入侵

魔道案例:

class DemonPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('Demon', (compilation) => {
      // 魔染所有资源(错误:直接修改原始assets)
      Object.keys(compilation.assets).forEach(name => {
        compilation.assets[name] = {
          source: () => '天魔夺舍',
          size: () => 666
        };
      });
    });
  }
}

正道解法:

class PurePlugin {
  apply(compiler) {
    compiler.hooks.emit.tap('Pure', (compilation) => {
      // 正道:通过中间件形式处理
      const { RawSource } = require('webpack-sources');
      
      compilation.hooks.processAssets.tap({
        name: 'Pure',
        stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
      }, (assets) => {
        for (const [name, source] of Object.entries(assets)) {
          compilation.updateAsset(name, new RawSource(
            `/* 正道封印 */\n${source.source()}`
          ));
        }
      });
    });
  }
}

(突然,天空裂开紫色缝隙,无数webpack配置从天而降)

弟子:"师尊!我的Plugin让构建内存暴涨!"

道人:"痴儿!定是未在合适的hook阶段释放缓存,就像炼丹炉要定时泄压。且看这招——"

道人剑指划出玄奥轨迹,虚空中浮现代码:

compiler.hooks.thisCompilation.tap('Cache', (compilation) => {
  compilation.hooks.finishModules.tap('Cache', (modules) => {
    // 清理模块缓存
    modules.forEach(mod => mod.cleanup());
  });
});

飞升天象:

当Plugin修炼至大乘期,可:

  • 创造新的Tapable法则(自定义hook)
  • 干涉Loader的剑道轨迹(修改moduleGraph)
  • 在compiler与compilation间架设虹桥
  • 通过stats对象窥探天道轮回

(道人化作万千星光,虚空中浮现最后谜题)

"记住,Plugin之道在于'顺势而为'。如同观星者不会强行改变星辰轨迹,而是通过设置hook节点让万法自然运行......"

(八卦炉轰然打开,飞出金色卷轴:《Loader与Plugin合击秘术——从入门到渡劫》)