1. 介绍tapable
1.1 Webpack和Tapable
-
webpack有两个非常重要的类:Compiler和Compilation
- 他们通过注入插件的方式,来监听webpack的所有生命周期
- 插件的注入离不开各种各样的Hook,而他们的Hook其实是创建了Tapable库中的各种Hook的实例
-
Tapable
- Tapable是官方编写和维护的一个库
- Tapable管理着需要的Hook,这些Hook可以被应用到我们的插件中
1.2 Hooks的使用
-
hooks分类
- 同步和异步的:
- 以sync开头的,是同步的Hook
- 以async开头的,两个事件处理回调,不会等待上一次处理回调结束后再执行下一次回调
- 其他的类别
- bail:当有返回值时,就不会执行后续的事件触发了
- Loop:当返回值为true,就会反复执行该事件,当返回值为undefined或者不返回内容,就退出事件
- Waterfall:当返回值不为undefined时,会将这次返回的结果作为下次事件的第一个参数
- Parallel:并行,不会等到上一个事件回调执行结束,才执行下一次事件处理回调
- Series:串行,会等待上一次是异步的Hook
- 同步和异步的:
-
hook的使用过程
- 基本使用
const { SyncHook } = require('tapable') class YSCompiler { constructor() { this.hooks = { syncHook: new SyncHook(['name', 'age']) } // 用hooks监听事件 this.hooks.syncHook.tap('event1', (name, age) => { console.log('event1事件监听执行了', name, age) }) this.hooks.syncHook.tap('event2', (name, age) => { console.log('event2事件监听执行了', name, age) }) } } const compiler = new YSCompiler() // 触发事件 setTimeout(() => { compiler.hooks.syncHook.call('lucy', 17) }, 2000);
- bail使用
const { SyncBailHook } = require('tapable') class YSCompiler { constructor() { // 创建hooks // bail的特点: 如果有返回值, 那么可以阻断后续事件继续执行 this.hooks = { bailHook: new SyncBailHook(['name', 'age']) } // 用hooks监听事件 this.hooks.bailHook.tap('event1', (name, age) => { console.log('event1事件监听执行了', name, age) return 123 }) this.hooks.bailHook.tap('event2', (name, age) => { console.log('event2事件监听执行了', name, age) }) } } const compiler = new YSCompiler() // 触发事件 setTimeout(() => { compiler.hooks.bailHook.call('lucy', 17) }, 2000);
- loop使用
const { SyncLoopHook } = require('tapable') let count = 0 class YSCompiler { constructor() { // 创建hooks this.hooks = { loopHook: new SyncLoopHook(['name', 'age']) } // 用hooks监听事件 this.hooks.loopHook.tap('event1', (name, age) => { if(count < 5) { console.log('event1事件监听执行了', name, age) count++ return true } }) this.hooks.loopHook.tap('event2', (name, age) => { console.log('event2事件监听执行了', name, age) }) } } const compiler = new YSCompiler() // 触发事件 setTimeout(() => { compiler.hooks.loopHook.call('lucy', 17) }, 2000);
- waterfall使用
const { SyncWaterfallHook } = require('tapable') class YSCompiler { constructor() { // 创建hooks this.hooks = { waterfallHook: new SyncWaterfallHook(['name', 'age']) } // 用hooks监听事件 this.hooks.waterfallHook.tap('event1', (name, age) => { console.log('event1事件监听执行了', name, age) // 返回结果作为下次事件的第一个参数 return { x: 'xx', y: 'yy' } }) this.hooks.waterfallHook.tap('event2', (name, age) => { console.log('event2事件监听执行了', name, age) }) } } const compiler = new YSCompiler() // 触发事件 setTimeout(() => { compiler.hooks.waterfallHook.call('lucy', 17) }, 2000);
- parallel使用
const { AsyncParallelHook } = require('tapable') class YSCompiler { constructor() { // 创建hooks this.hooks = { parallelHook: new AsyncParallelHook(['name', 'age']) } // 用hooks监听事件 this.hooks.parallelHook.tapAsync('event1', (name, age) => { setTimeout(() => { console.log('event1事件监听执行了', name, age) }, 3000) }) this.hooks.parallelHook.tapAsync('event2', (name, age) => { setTimeout(() => { console.log('event2事件监听执行了', name, age) }, 3000) }) } } const compiler = new YSCompiler() // 触发事件 setTimeout(() => { compiler.hooks.parallelHook.callAsync('lucy', 17) }, 1000)
- series使用:串行传入回调函数决定是否结束
const { AsyncSeriesHook } = require('tapable') class YSCompiler { constructor() { // 创建hooks this.hooks = { seriesHook: new AsyncSeriesHook(['name', 'age']) } // 用hooks监听事件 this.hooks.seriesHook.tapAsync('event1', (name, age, callback) => { setTimeout(() => { console.log('event1事件监听执行了', name, age) callback() }, 3000) }) this.hooks.seriesHook.tapAsync('event2', (name, age, callback) => { setTimeout(() => { console.log('event2事件监听执行了', name, age) callback() }, 3000) }) } } const compiler = new YSCompiler() // 触发事件 setTimeout(() => { compiler.hooks.seriesHook.callAsync('lucy', 17, () => { console.log('所有任务都执行完成~') }) }, 0)
2. 自定义Plugin
2.1 插件如何注册?
- 第一:在webpack函数的createCompiler方法中,注册了所有的插件
- 第二:在注册插件时,会调用插件函数或者插件对象的apply方法
- 第三:插件方法会接收compiler对象,我们可以通过compiler对象来注册Hook的事件
- 第四:某些插件也会传入一个compilation的对象,我们也可以监听compilation的Hook事件
2.2 自定义插件
-
功能:将静态文件自动上传服务器中
-
自定义插件的过程:
- 创建AutoUploadWebpackPlugin类
- 编写apply方法
- 通过ssh连接服务器
- 删除服务器原来的文件夹
- 上传文件夹中的内容
- 在webpack的plugins中,使用AutoUploadWebpackPlugin类
-
AutoUploadWebpackPlugin
const { NodeSSH } = require('node-ssh') class AutoUploadWebpackPlugin { constructor(options) { this.ssh = new NodeSSH() this.options = options } apply(compiler) { // console.log("AutoUploadWebpackPlugin被注册:") // 完成的事情: 注册hooks监听事件 // 等到assets已经输出到output目录上时, 完成自动上传的功能 compiler.hooks.afterEmit.tapAsync("AutoPlugin", async (compilation, callback) => { // 1.获取输出文件夹路径(其中资源) const outputPath = compilation.outputOptions.path // 2.连接远程服务器 SSH await this.connectServer() // 3.删除原有的文件夹中内容 const remotePath = this.options.remotePath this.ssh.execCommand(`rm -rf ${remotePath}/*`) // 4.将文件夹中资源上传到服务器中 await this.uploadFiles(outputPath, remotePath) // 5.关闭ssh连接 this.ssh.dispose() // 完成所有的操作后, 调用callback() callback() }) } async connectServer() { await this.ssh.connect({ host: this.options.host, username: this.options.username, password: this.options.password }) console.log('服务器连接成功') } async uploadFiles(localPath, remotePath) { const status = await this.ssh.putDirectory(localPath, remotePath, { recursive: true, concurrency: 10 }) if (status) { console.log("文件上传服务器成功~") } } } module.exports = AutoUploadWebpackPlugin module.exports.AutoUploadWebpackPlugin = AutoUploadWebpackPlugin
-
webpack配置
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const AutoUploadWebpackPlugin = require('./plugins/AutoUploadWebpackPlugin') const { PASSWORD } = require('./plugins/config') module.exports = { mode: 'production', entry: './src/main.js', output: { clean: true, path: path.resolve(__dirname, './build'), filename: 'bundle.js' }, plugins: [ new HtmlWebpackPlugin(), new AutoUploadWebpackPlugin({ host: 'xxx.xx.xx.xx', username: 'root', password: PASSWORD, remotePath: "/root/test" }) ] }