前言
- 这篇讲plugin。
- 官网链接
Compiler与Compilation
-
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
-
compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
-
神图:
-
webpack目录的lib目录下的compiler文件里面的compiler对象上写的hook
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<string, string, any[]>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])
};
- 这个就跟第一篇的tapable连起来了,不然都不知道这些钩子是什么钩子。
DonePlugin
- 先制作个简单的plugin:
class DonePlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.done.tap('DonePlugin', (stats) => {
console.log(stats)
})
}
}
module.exports = DonePlugin
- 都会有apply方法,然后收到compiler,在compiler里的hooks就是上面那个hook,可以拿到它的钩子。
- 可以看见前面那个done是new的asyncSeriesHook,就是串行异步执行钩子:
let hook = new AsyncSeriesHook(['name', 'age'])
hook.tapAsync('1', (name, age, callback) => {
setTimeout(() => {
console.log(1)
callback()
}, 1000);
})
hook.tapAsync('2', (data, age, callback) => {
setTimeout(() => {
console.log(2)
callback()
}, 1000);
})
hook.tapAsync('3', (data, age, callback) => {
setTimeout(() => {
console.log(3)
callback()
}, 2000);
})
hook.callAsync('yehuozhili', 111, err => {
console.log('执行完成')
})
- 插件直接在webpack里面new就可以打印出来了,可以发现是个stats对象。
- 这个对象东西太多太多了,主要含有modules、chunks和assets三个属性值的对象。
- 可以通过下面命令把stats对象输出出来:
npx webpack --profile --json > stats.json
- 上面那个async钩子其实应该异步tap,异步tap会有回调函数,必须要调用:
class DonePlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.done.tapAsync('DonePlugin', (stats, callback) => {
console.log(stats)
callback()
})
}
}
module.exports = DonePlugin
AssetsPlugin
- 同样一个简单的plugin,就是打印下资源由哪个chunk生成哪个filename:
class AssestPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.compilation.tap('yehuozhili', (compliation, params) => {
compliation.hooks.chunkAsset.tap('yehuozhili', (chunk, filename) => {
console.log(`通过${chunk}生成了${filename}`)
})
})
}
}
module.exports = AssestPlugin
ZipPlugin
- 写个插件用来把打包后的文件做成压缩包,便于后面部署用。
const jszip = require('jszip')
const { RawSource } = require('webpack-sources')
class ZipPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
let that = this
compiler.hooks.emit.tapAsync('yehuozhili', (compiltion, callback) => {
let zip = new jszip
for (let filename in compiltion.assets) {
let source = compiltion.assets[filename].source()
zip.file(filename, source)
}
zip.generateAsync({ type: 'nodebuffer' }).then(content => {
compiltion.assets[that.options.filename] = new RawSource(content)
callback()
})
})
}
}
module.exports = ZipPlugin
- 这个是在emit阶段,也就是快最后生成阶段,通过compiltion.assets获取文件列表,文件以键值对形式,通过值里的source方法获取内容,然后通过jszip进行打包。
- 打包完生成文件,需要输出,给其加上option里配置的filename,然后使用webpack的RawSource输出,其实也可以配个对象,只要包含souce和size就行,具体可以点到rawSource里面看这个类的属性。
new ZipPlugin({
filename: Date.now() + '.zip'
})
- 先就写这么多,基本上这里算是初步把webpack基础功能搞完了,plugin要写的复杂需要对webpack流程有很深认识,所以后面得从webpack流程写起。