# 一、什么是插件(介绍)

  1. 插件介绍
  • 扩展器,丰富 webpack 功能,针对 loader 结束后的打包过程
  • 不是直接操作文件,基于事件机制。监听打包过程的某些节点。
  • 与 loader 的区别 loader 是加载器,编译时转换文件。webpack 自身只支持 js、json 解析。其他文件需要借助 loader 将其转换为 cms 规范文件后,webpack 才能解析。
  1. 插件的特征
  • 是独立的模块
  • 对外暴露 js 文件
  • 函数原型(prototype)定义并注入complier对象的apply方法。

# tapable介绍

是一个小型的 lib,有点类似与 node 中的 event 库。基于事件机制。核心原理就是订阅发布模式,作用是提供插件接口

//广播
compiler.apply("eventName", params);
compilation.apply("eventName", params);
//监听
compiler.plugin("eventName", (params) => {});
compilation.plugin("eventName", (params) => {});

# 二、事件机制

# Tapable

function Tapable() {
  this._plugins = {};
}
//发布
Tapable.prototype.ApplyPlugin = function ApplyPlugin(name) {};
//订阅
Tapable.prototype.plugin = function plugin(name, fn) {
  if (this._plugin[name]) {
    this._plugins[name] = [fn];
  } else {
    this._plugins[name].push(fn);
  }
};
//给定一个插件数组,调用自身apply的注册插件
Tapable.prototype.apply = function () {
  for (let i = -0; i < arguments.length; i++) {
    arguments[i].apply.call(this);
  }
};

Tapable 为 webpack 提供 10 多种(钩子)类型的定义。

exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");

还保留 3 个方法给插件,用于注册不同类型的自定义构建行为

//同步
tap  注册同步和异步钩子
//异步回调
tapAsync
//异步promise
tapPromise

webpack中几个重要对象compilercompilationJavascriptParser都继承tapable.身上挂满钩子。

# 编写一个插件

class MyPlugin {
  constructor() {}
  apply(compiler) {
    compiler.hook.run.tap("run", () => {
      console.log("run");
    });
  }
}
//使用
plugins:[new MyPlugin()]

# compiler对象(负责编译)

compiler包括当前webpack运行的配置信息。entry output loaders等。在webpack启动被实例化。 常用的钩子

钩子 类型 什么时候调用
run AsyncSeriesHook 编译器开始读取记录
compile SyncHook 新的compilaton创建之前执行
compilation SyncHook 在一次compilation创建后执行
make AsyncParalleHook 完成一次编译前执行
emit AsyncSeriesHook 生成文件到output前,compilation
afferEmit AsyncSeriesHook 生成文件到output后
assetEmitted AsyncSeriesHook 生成文件的时候执行 回调 file info
done AsyncSeriesHook 一次编译完成后,回调参数stats

# compilation (表示一次构建)

compilation对象代表一次资源版本构建。简单来说就是将本次打包的内容存在内存里。

compilation对象也提供插件需要的自定义功能的回调。

简单来说,compilation的职责就是构建模块和chunk,并利用插件优化过程。

一个compilation表示当前的模块资源、编译生成资源、变化的文件、已经被跟踪依赖的状态信息。

compiler和compilation的区别

compiler:代表整个webpack从启动到关闭的生命周期

compilation:只是代表一次新的编译,只要有文件改动,compilation就会重新构建。

# 三、手写插件

输出时记录输出文件

class FileListPlugin {
  constructor(options) {
    // 获取插件配置项
    this.filename = options && options.filename ? options.filename : 'FILELIST.md'
  }

  apply(compiler) {
    // 注册 compiler 上的 emit 钩子
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
      // 通过 compilation.assets 获取文件数量
      let len = Object.keys(compilation.assets).length

      // 添加统计信息
      let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpack\n\n`

      // 通过 compilation.assets 获取文件名列表
      for (let filename in compilation.assets) {
        content += `- ${filename}\n`
      }

      // 往 compilation.assets 中添加清单文件
      compilation.assets[this.filename] = {
        // 写入新文件的内容
        source: function () {
          return content
        },
        // 新文件大小(给 webapck 输出展示用)
        size: function () {
          return content.length
        },
      }

      // 执行回调,让 webpack 继续执行
      cb()
    })
  }
}

module.exports = FileListPlugin

去除注释

  • 获取assets 拿到内容
  • 重新修改assets (正则replace删除注释)
class RemoveCommentPlugin
{
    constructor(options){this.options=options}
    apply(compiler) {
        const reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)|(\/\*\*\*\*\*\*\/)/g

      compiler.hooks.emit.tap('removeComment',function(compilation,cb){
          let assets = compilations.assets   
     		for(let key in assets) {
                let source = assets[key].source
               source = source.replace(reg, function (word) {
          // 去除注释后的文本
          return /^\/{2,}/.test(word) || /^\/\*!/.test(word) || /^\/\*{3,}\//.test(word) ? '' : word
        })
                //重新修改内容
                assets[key].source = source 
            }
      })  
    }
}    
module.exports = RemoveCommentPlugin