整体架构
compiler对象
compilation对象
compiler 算是核心实例,webpack的整个控制器。挂载webpack的整个配置,webpack编译等流程的执行,插件事件的触发和监听。
compilation 是一次构建
编译关键部分
Compiler
继承自Tapable
class Compiler extends Tapable {constructor(context) {super();this.hooks = {shouldEmit: new SyncBailHook(["compilation"]),done: new AsyncSeriesHook(["stats"]),additionalPass: new AsyncSeriesHook([]),beforeRun: new AsyncSeriesHook(["compilation"]),run: new AsyncSeriesHook(["compilation"]),emit: new AsyncSeriesHook(["compilation"]),afterEmit: new AsyncSeriesHook(["compilation"]),thisCompilation: new SyncHook(["compilation", "params"]),compilation: new SyncHook(["compilation", "params"]),...}}compile(callback) {const params = this.newCompilationParams();this.hooks.beforeCompile.callAsync(params, err => {if (err) return callback(err);this.hooks.compile.call(params);// 生成一个新的compilationconst compilation = this.newCompilation(params);// make事件this.hooks.make.callAsync(compilation, err => {if (err) return callback(err);compilation.finish();compilation.seal(err => {if (err) return callback(err);// 触发afterCompile事件this.hooks.afterCompile.callAsync(compilation, err => {if (err) return callback(err);return callback(null, compilation);});});});});}}属性hooks // Tabable对外的钩子,所有事件都可以在这里统一看到resolvers //recordscontext // 类似process.cwd()options方法constructorwatchruncreateChildCompilercreateCompilationnewCompilationcreateNormalModuleFactorycreateContextModuleFactorycompile
关键事件 & 编译过程
浅谈Webpack工作流程 | YoungZhang’s Blog 参见本篇文章,写的比较详细的编译过程和相应触发的事件
compile -> make -> build module -> after compile -> emit -> after emit
this.hooks.emit.callAsync(compilation, err => {if (err) return callback(err);outputPath = compilation.getPath(this.outputPath);this.outputFileSystem.mkdirp(outputPath, emitFiles);});
Compilation
class Compilation extends Tapable {constructor(compiler) {}}属性hooks //compiler // 初始化时传入外部的compilerentrieschunksmodulescacherecordsassetserrorswarningschildren方法getStatsaddModulefindModulebuildModuleaddModuleDependenciesaddEntryprefetchrebuildModulefinishunsealseal
关键事件
Tapable
webpack/tapable: Just a little module for plugins.
关于 tapable 你需要知道这些 - 知乎 非常简单的介绍清楚了Tapable的用户
webpack中特征,在某个事件会注册诸多的插件,插件机制在不同事件有不同的处理机制,因此有了类似EventEmitter,又比起增多了更多编排处理。
- SyncHook 类似EventEmitter
- SyncBailHook 注册的回调返回非undefiend时,就停止不再执行其他回调
SyncWaterfallHook 接受至少一个参数,上一个注册的回调返回值会作为下一个注册的回调的参数。
AsyncParallelHook 异步并发钩子,所有注册回调并发执行,当所有注册的异步回调并行执行完成后,再执行callAsync中的函数
- AsyncSeriesHook 顺序执行钩子,
- AsyncParallelBailHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
const {Tapable,SyncHook, //SyncBailHook,AsyncParallelHook,AsyncSeriesHook} = require("tapable");// 继承自Tabpleclass Compiler extends Tapable {constructor(context) {super();// hooks的初始化,定义是什么类型的hooks,hooks的参数this.hooks = {shouldEmit: new SyncBailHook(["compilation"]),done: new AsyncSeriesHook(["stats"]),additionalPass: new AsyncSeriesHook([]),beforeRun: new AsyncSeriesHook(["compilation"]),run: new AsyncSeriesHook(["compilation"]),emit: new AsyncSeriesHook(["compilation"]),compilation: new SyncHook(["compilation", "params"]),}}}// 同步的hooks调用if (this.hooks.shouldEmit.call(compilation) === false) {}// 异步的hooks调用this.hooks.done.callAsync(stats, err => {});
插件机制
如何编写插件?
- 编写一个类,constructor中处理初始化参数,
- apply的模板方法注入compiler对象,
- compiler对象可供做监听事件钩子
- 插件钩子提供一个回调函数,拿到编译时的compilation对象,可以做各种获取和修改(编译信息,文件内容,等等)
摘自 探寻 webpack 插件机制 的一段代码
class AnalyzeWebpackPlugin {constructor(opts = { filename: 'analyze.html' }) {this.opts = opts}apply(compiler) {const self = thiscompiler.plugin("emit", function (compilation, callback) {let stats = compilation.getStats().toJson({ chunkModules: true }) // 获取各个模块的状态let stringifiedStats = JSON.stringify(stats)// 服务端渲染let html = `<!doctype html><meta charset="UTF-8"><title>AnalyzeWebpackPlugin</title><style>${cssString}</style><div id="App"></div><script>window.stats = ${stringifiedStats};</script><script>${jsString}</script>`compilation.assets[`${self.opts.filename}`] = { // 生成文件路径source: () => html,size: () => html.length}callback()})}}
探寻 webpack 插件机制
WebPack 插件机制探索 – 滴滴云博客
webpack插件机制剖析 | 大专栏
Loader
阅读 vue-loader
vuejs/component-compiler-utils: Lower level utilities for compiling Vue single file components
vue-loader/lib/index.js
const loaderUtils = require('loader-utils') // 非常重要,官方提供的const { parse } = require('@vue/component-compiler-utils')//module.exports = function (source) {const loaderContext = this;const {target,request,minimize,sourceMap,rootContext,resourcePath,resourceQuery = ''} = loaderContextconst options = loaderUtils.getOptions(loaderContext) || {} // options.compilerconst descriptor = parse({source,compiler: options.compiler || loadTemplateCompiler(loaderContext),filename,sourceRoot,needMap: sourceMap})descriptor.styles // <style></style> 可以是多个标签descriptor.template // <template></template>标签包裹的内容descriptor.script // <script></script> 标签}// 大概转义的结果是// templatelet templateImport;const src = descriptor.template.src || resourcePath;const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`;const request = templateRequest = stringifyRequest(src + query);templateImport = `import { render, staticRenderFns } from ${request}`;// scriptconst request = stringifyRequest(src + query)scriptImport = (`import script from ${request}\n` +`export * from ${request}` // support named exports)let code = `${templateImport}${scriptImport}${stylesCode}/* normalize component */import normalizer from ${stringifyRequest(`!${componentNormalizerPath}`)}var component = normalizer(script,render,staticRenderFns,${hasFunctional ? `true` : `false`},${/injectStyles/.test(stylesCode) ? `injectStyles` : `null`},${hasScoped ? JSON.stringify(id) : `null`},${isServer ? JSON.stringify(hash(request)) : `null`}${isShadow ? `,true` : ``})`.trim() + `\n`
Plugin的作用?
实践 经常loader和plugin配置
很多的插件是如何loader和plugin配合完成任务,如vue-loader中包含vue-loader和VueLoaderPlugin
MiniCssExtractPlugin | webpack
const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = {plugins: [new MiniCssExtractPlugin()],module: {rules: [{test: /\.css$/i,use: [MiniCssExtractPlugin.loader, 'css-loader'],},],},};
vue-loader
实践
编译优化
主要是本地编译速度的优化
webpack 编译优化 · Issue #5 · jpone223/blog
性能优化
为最终线上运行production的优化
- long-term-caching
- chunks 拆包 (可按被引用的次数)
- webpack dll
- tree shaking
查漏补缺 面试题
一些想法
- 不要惧怕看源码,可能并不复杂,搞清楚核心模块,以及核心模块的关系就很好阅读
如webpack先看文章,
- webpack的核心部分还是非常非常清晰的,compiler,compilation
