Webpack的事件流和插件机制
前面2篇我们已经讲了webpack的运行原理和loader的原理,本篇我们来讲一下webpack的事件流和插件的机制。本系列一共分为三篇
- Webpack5的打包分析
- Webpack的loader的实现
- Webpack的事件流和插件的原理
webpack的事件流
什么是webpack的事件流,这里引入深入浅出的webpack里面的一段话。
Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 Webpack 通过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 --吴浩麟《深入浅出webpack》
webpack的运行机制
前面第一篇我们自己实现了一个简单的webpack, 我们通过一个图片来了解一下webpack从开始启动到打包资源后的一个整体运行机制。 总体分为这几个关键步骤
- 当我们运行webpack的时候,它会读取你的传入的配置文件(webpack.config.js), 然后运行
compile.run
来初始化本质构建的参数(入口文件等),然后开始分析模块 (environment钩子函数) - 接下来进入了entryOption阶段,webpack开始读取配置的入口entry,然后开始递归的遍历所有的入口文件 (
entryOption
钩子) - webpack进入入口文件后,开始分析依赖项,进行
compilation
过程。 (compilation
钩子函数)- 使用配置好的loader对文件内容进行编译(
buildModule
), 我们可以从传入的事件回调的参数module上拿到模块的resource(资源路径) (buildModule
钩子函数)
compiler.hooks.compilation.tap('compilation', (compilation , compilationParams) => { compilation.hooks.buildModule.tap('sourceMap', (module) => { console.log(module.resource) // 资源路径 }) })
- 将经过loader处理后的文件,通过acorn解析生成ast语法树(normalModuleLoader)分析文件的依赖关系,然后形成依赖数组,重复上面的模块分析过程 (webpack5 已经废弃了
normalModuleLoader
这个钩子)
- 使用配置好的loader对文件内容进行编译(
- emit阶段: 所有文件的编译及转化都已经完成,包含了最终输出的资源,我们可以在传入事件回调的compilation.assets 上拿到所需数据,其中包括即将输出的资源、代码块Chunk等等信息。
下面这张图是整体的运行机制和主要插件的图的对比 还有一张图是基于webpack基于模块的更加详细的运行流程图(虚线代表重复的执行)
webpack的插件开发
我们主要实战2个插件
- 使用md模式列出文件打包资源的列表
- 通过插件分析文件依赖的组件
首先我们需要知道webpack的插件需要是怎么开发的。它需要2个条件
- 必须是一个类
- 要暴露一个apply方法,webpack会在初始化的时候执行这个方法, 并传入一个
compiler
, 源码里面是这样的
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
列表打包资源插件
从上面的图我们可以看出,我们可以在资源打包后对打包后的资源进行获取,然后新增一个文件。
- 通过emit 钩子函数,我们可以在回调函数中的的
compilation
中的参数拿到assets
资源列表 - 然后新增一项资源,通过webpack进行流程处理
class FileListPlugin {
constructor({ filename = 'index.md' }) {
this.filename = filename;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
let assets = compilation.assets;
let content = '## 文件 资源大小\r\n';
Object.entries(assets).forEach(([key, value]) => {
content += `${key} ${value.size()}\r\n`;
});
assets[this.filename] = {
size() {
return content.length;
},
source() {
return content;
}
};
cb();
});
}
}
module.exports = FileListPlugin;
组件被使用的列表
在现在一切皆组件的思想下,一个组件可能被多个页面使用,有时候我们修改一个组件,都不知道会不会影响到其他的页面,所有这里开发了一个插件,用来统计组件被使用的页面情况。在webpack4和webpack5中的使用不一样,我们现在是基于webpack5
进行分析。
- 从上面的流程图中我们可以看到,每一个资源(asset) 都会模块处理阶段
NormalModuleFactory
, 所有我们可以在模块经过loader处理完成后的afterResolve
的钩子来分析模块 - 在分析完模块的依赖后,可以通过
done
钩子等将分析的内容输出
class NormalModuleFactory {
constructor() {
this.dependencies = {}
this.entry = ''
this.workDictory = ""
}
apply(compiler) {
compiler.hooks.normalModuleFactory.tap('NormalModuleFactory', (nmf) => {
nmf.hooks.afterResolve.tapAsync('nmf', (result, callback) => {
let { request, contextInfo, context } = result
if (!request.includes('node_modules')) {
if (!contextInfo.issuer) {
console.log('entry', path.normalize(request))
this.entry = request
this.workDictory = context
} else if(!['react', 'react-dom'].includes(request)){
// 有依赖项 contextInfo 是请求资源的上下文
const requestPath = path.relative(this.workDictory, contextInfo.issuer)
// './component/Hello requestPath: src/index.js
if(!requestPath.includes("node_modules")) {
const resourcePath = path.join(context, request)
console.log(requestPath, request)
request = path.relative(this.workDictory, resourcePath)
if(!this.dependencies[request]) {
this.dependencies[request] = [requestPath]
} else {
this.dependencies[request].push(requestPath)
}
}
}
}
callback()
})
})
compiler.hooks.done.tap('NormalModuleFactory', () => {
console.log(this.dependencies)
})
}
}
module.exports = NormalModuleFactory
执行npx webpack
后我们可以得到这个内容, key为被使用的组件的路径,value为使用该组件的页面或者组件,可以根据项目内容再进行路径匹配分析具体的样式和组件依赖。
{
'src\\base\\a.js': [ 'src\\index.js' ],
'src\\component\\Hcc': [ 'src\\index.js' ],
'src\\component\\Hello': [ 'src\\index.js', 'src\\component\\Hcc.jsx' ],
'src\\index.less': [ 'src\\index.js' ],
'src\\base\\b': [ 'src\\base\\a.js' ],
'src\\pic\\hcc.jpg': [ 'src\\index.less' ]
}
参考文章
- webpack插件机制探索
- 揭秘webpack插件工作流程和原理
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!