webpack 自定义 plugin ?

plugin 的本质 类 (构造函数)

1 重要概念 tapable 类

const {
SyncHook, // 同步 执行
SyncBailHook, // 同步执行,可是一旦有返回值,就执行退出,再也不继续执行其余
AsyncParallelHook, // 异步 并行执行
AsyncSeriesBailHook, // 异步 串行执行
} = require('tapable');css

class Lesson {
constructor() {
// 初始化 hook容器
this.hooks = {
// 同步钩子,任务会依次执行
// go: new SyncHook(['address']),
go: new SyncBailHook(['address']), // 一旦有返回值,就不会再往下执行了
// 异步hooks
// AsyncParallelHook 异步并行
// leave: new AsyncParallelHook(['name', 'age']),
leave: new AsyncSeriesBailHook(['name', 'age']),
};
}webpack

tap() {
// 往hooks 容器中注册事件/ 添加回调函数
this.hooks.go.tap('class001', (address) => {
console.log('class001', address);
return 111;
});
this.hooks.go.tap('class002', (address) => {
console.log('class002', address);
});web

this.hooks.leave.tapAsync('class003', (name, age, cb) => {
  setTimeout(() => {
    console.log('class003', name, age);
    cb();
  }, 2000);
});
this.hooks.leave.tapPromise('class004', (name, age) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('class004', name, age);
      resolve();
    }, 1000);
  });
});

}json

start() {
this.hooks.go.call('c318');
this.hooks.leave.callAsync('zh', '18', () => {
// 表明全部leave容器中的函数触发完了,才触发
console.log('end');
});
}
}
const l = new Lesson();
l.tap();
l.start();数组

##### 2 complier 类
拥有各类钩子函数
```js
class Plugin1 {
  apply(complier) {
    complier.hooks.emit.tap('Plugin1', () => {
      console.log('emit 111111111111');
    });

    complier.hooks.afterEmit.tap('Plugin1', () => {
      console.log('emit after 111111111111');
    });
  }
}
module.exports = Plugin1;
3 compilation 类 (经过compiler.hooks.thisCompilation 获取),可对文件进行操做
const fs = require('fs');
const util = require('util');
const path = require('path');
const { RawSource } = require('webpack-sources');
// 将fs . readFile 的异步方法,改变成同步的写法
const readFile = util.promisify(fs.readFile);
class Plugin2 {
  apply(compiler) {
    // 初始化compilation 的钩子
    compiler.hooks.thisCompilation.tap('Plugin2', (compilation) => {
      // debugger;
      // console.log(compilation);
      compilation.hooks.additionalAssets.tapAsync('Plugin2', async (cb) => {
        const content = 'debug';
        // 往要输出资源中,添加一个a.txt
        compilation.assets['a.txt'] = {
          size() {
            return content.length;
          }, // 文件大小
          source() {
            return content;
          }, // 文件内容
        };
        const data = await readFile(path.resolve(__dirname, '../b.txt'));
        compilation.assets['b.txt'] = new RawSource(data);
        compilation.emitAsset('c.txt', new RawSource(data));
        cb();
      });
    });
  }
}
module.exports = Plugin2;

自定义插件 - CopyWebpackPlugin

const path = require('path');
const fs = require('fs');
const {promisify} = require('util')

const { validate } = require('schema-utils');
const globby = require('globby');
const webpack = require('webpack');

const schema = require('./schema.json');
const { Compilation } = require('webpack');

const readFile = promisify(fs.readFile);
const {RawSource} = webpack.sources

class CopyWebpackPlugin {
  constructor(options = {}) {
    // 验证options是否符合规范
    validate(schema, options, {
      name: 'CopyWebpackPlugin'
    })

    this.options = options;
  }

  apply(compiler) {
    // 初始化compilation
    compiler.hooks.thisCompilation.tap('CopyWebpackPlugin', (compilation) => {
      // 添加资源的hooks
      compilation.hooks.additionalAssets.tapAsync('CopyWebpackPlugin', async (cb) => {
        // 将from中的资源复制到to中,输出出去
        const { from, ignore } = this.options;
        const to = this.options.to ? this.options.to : '.';
        
        // context就是webpack配置
        // 运行指令的目录
        const context = compiler.options.context; // process.cwd()
        // 将输入路径变成绝对路径
        const absoluteFrom = path.isAbsolute(from) ? from : path.resolve(context, from);

        // 1. 过滤掉ignore的文件
        // globby(要处理的文件夹,options)
        const paths = await globby(absoluteFrom, { ignore });

        console.log(paths); // 全部要加载的文件路径数组

        // 2. 读取paths中全部资源
        const files = await Promise.all(
          paths.map(async (absolutePath) => {
            // 读取文件
            const data = await readFile(absolutePath);
            // basename获得最后的文件名称
            const relativePath = path.basename(absolutePath);
            // 和to属性结合
            // 没有to --> reset.css
            // 有to --> css/reset.css
            const filename = path.join(to, relativePath);

            return {
              // 文件数据
              data,
              // 文件名称
              filename
            }
          })
        )

        // 3. 生成webpack格式的资源
        const assets = files.map((file) => {
          const source = new RawSource(file.data);
          return {
            source,
            filename: file.filename
          }
        })
        
        // 4. 添加compilation中,输出出去
        assets.forEach((asset) => {
          compilation.emitAsset(asset.filename, asset.source);
        })

        cb();
      })
    })
  }

}

module.exports = CopyWebpackPlugin;