loader
loader就是webpack的代码转换器,因为webpack最开始只支持js文件的编译,所以我们在打包项目时,其他格式的文件需要通过loader来转换。
loade本质是一个函数,接受初始资源为参数,返回转化后的内容,所以函数内部一般会结合AST和字符串拼接对代码进行转换和加工,只能在编译阶段调用。
plugin
plugin就是webpack的插件,可以扩展webpack的功能,能在任何阶段调用。
// myPlugin.jsclass MyPlugin {apply(compiler) {compiler.hooks.run.tap("myPlugin", compiler=> {console.log("我写的插件正在运行!!!");});}}module.exports = { MyPlugin };//webpack.config.jsconst { MyPlugin } = require("./plugin/MyPlugin");module.exports = {entry: {app: "./src/index.js"},plugins: [new MyPlugin()]};
插件中的apply方法可以与webpack内部运行接轨,插件从apply这个函数中拿到webpack的compiler对象,他是webpack编译器实例(后面介绍),包含整个编译周期的内容,通过compiler.hooks来访问各个周期钩子(比如run),通过tap方法将插件中自定义的回调函数,注册到该钩子上。
除了run这个钩子,还有compilation。是webpack编译之后触发的钩子:
const { ConcatSource } = require("webpack-sources");class MyPlugin {apply(compiler) {//使用compilation hook 编译(compilation)创建之后,执行插件。compiler.hooks.compilation.tap("BannerPlugin", compilation => {//优化所有 chunk 资源(asset)。资源(asset)会被存储在 compilation.assets。// 每个 Chunk 都有一个 files 属性,指向这个 chunk 创建的所有文件。//附加资源(asset)被存储在 compilation.additionalChunkAssets 中。compilation.hooks.optimizeChunkAssets.tap("BannerPlugin", chunks => {for (const chunk of chunks) {for (const file of chunk.files) {compilation.updateAsset(file, old => {return new ConcatSource("/*!我在这里加入一行注释*/","\n", old);});}}});});}}module.exports = { MyPlugin };
工作流程
webpack的工作流程是串行的。分为初始化、编译、输出三个阶段。
初始化阶段:拿到配置启动编译,读取和合并参数(shell和config),加载所有plugin(外部和内置),实例化compiler,它是整个周期的指挥者,负责整个编译构建过程,包含了webpack所有的环境信息,接着注册所有的Hooks钩子。
编译阶段:从入口entry开始,对每一个模块调用对应的loader去编译内容,在找到模块依赖的模块,去递归进行编译,形成依赖图集moduleGraph(利用Parser(acorn)将入口文件转化为AST,分析导入语句,将依赖加入到dependencies中)。
如果entry是数组,说明是多入口,则会循环遍历每一个入口处理,如果是函数,说明是异步加载入口,则使用异步加载plugin处理。初始化compilation,它是单一一次编译构建的内容,每编译一次都会初始化一次。
编译好的内容存储在内存中(compilation.modules)。
输出阶段:将编译好的模块组合成chunk,然后转换成文件,这里会将AST转换回JS代码。
事件流机制
webpack声明周期中的各个hook钩子是通过Tapable实现的,类似EventCenter(观察者模式)。
