基于 webpack4
从 webpack 命令行说起
- 通过 npm scripts 运行 webpack
 
- 开发环境 npm run dev
 - 生产环境 npm run build
 
- 通过 webpack 直接运行
 
- webpack entry.js bundle.js
 
在命令行运行以上命令后,npm  会让命令行工具进入 node_modules\.bin目录查找是否存在 webpack.sh 或者 webpack.cmd 文件。
如果存在,就执行。不存在就抛出错误
实际入口文件是:node_modules\webpack\bin\webpack.js
// webpack4 的 webpack.jsprocess.exitCode = 0; // 1. 正常执行返回const runCommand = (command, args) => {}; // 2. 运行某个命令const isInstalled = packageName => {}; // 3. 判断某个包是否安装const CLIs = [..]; // 4. webpack 可用的 CLI: webpack-cli 或者 webpack-commandconst installedClis = CLIs.filter(cli => cli.installed); // 5. 判断两个 CLI 是否有安装// 6. 根据安装数量进行处理if(installedClis.length == 0) {}else if(installedClis.length == 1) {}else {}
webpack-cli 和 webpack-command 有一个就可以运行 webpack。 webpack5 用的是 webpack-cli,它的功能更加丰富。
webpack-cli
webpack-cli 作用
- 引入 yargs,对命令行的进行定制
 - 分析命令行参数,对各个参数进行转换,组成编译配置项
 - 引用 webpack,根据配置项进行编译和构建
 
webpack-cli 对命令行的 args 的分析
webpack-cli 执行的结果 webpack-cli 对配置文件和命令行参数进行转换,最终生成配置选项参数 options 最后会更具配置参数实例化 webpack 对象,然后执行构建流程
webpack 的本质
webpack 可以将其理解是一种基于事件流的编程范例,一系列的插件运行。
Tapable
Tapable 是一个类似于 Node.js 的 EventEmitter 的库,主要是控制钩子函数的发布订阅,控制着 webpack 的插件系统。
Tapable 库保留了很多 Hook 类,为插件提供挂载的钩子。
Tapable 的使用
钩子的绑定和执行
Tapable 提供了同步 & 异步绑定钩子的方法,并且他们都有绑定事件和执行事件的对应方法。
| 绑定 | 执行 | |
|---|---|---|
| Async | tapAsync / tapPromise / tap | callAsync/ promise | 
| Sync | tap | call | 
const { SyncHook } = require('tapable');const hook = new SyncHook(['arg1', 'arg2', 'arg3']);hook.tap('hook1', (arg1, arg2, arg3) => {console.log(arg1, arg2, arg3);})hook.call(1, 2, 3)
示例

const { SyncHook, AsyncSeriesHook } = require('tapable');class Car {constructor() {this.hooks = {accelerator: new SyncHook(['newspeed']),brake: new SyncHook(),calculateRoutes: new AsyncSeriesHook(['source', 'target', 'routesList'])};}}const myCar = new Car();// 绑定事件myCar.hooks.brake.tap('WarningLampPlugin', () => {console.log('WarningLampPlugin');})myCar.hooks.accelerator.tap('LoggerPlugin', (newspeed) => {console.log('Accelerator', newspeed);})myCar.hooks.calculateRoutes.tapPromise('calculateRoutes Promise', (source, target, routesList) => {console.log('source', source);return new Promise((resolve, reject) => {setTimeout(() => {console.log('tapPromise to', source, target, routesList);resolve();}, 1000)})})// 执行事件myCar.hooks.brake.call();myCar.hooks.accelerator.call(200);// 开始计时,记录异步事件console.time('cost');myCar.hooks.calculateRoutes.promise('Async', 'hook', 'demo').then(() => console.timeEnd('cost')).catch(err => console.log(err))// 结果// WarningLampPlugin// Accelerator 200// source Async// tapPromise to Async hook demo// cost: 1.001s
webpack 的 hooks
class Compiler {constuctor(context) {this.hooks = Object.freeze({/** @type {SyncHook<[]>} */initialize: new SyncHook([]),/** @type {SyncBailHook<[Compilation], boolean>} */shouldEmit: new SyncBailHook(["compilation"]),/** @type {AsyncSeriesHook<[Stats]>} */done: new AsyncSeriesHook(["stats"]),/** @type {SyncHook<[Stats]>} */afterDone: new SyncHook(["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, AssetEmittedInfo]>} */assetEmitted: new AsyncSeriesHook(["file", "info"]),/** @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 {AsyncParallelHook<[Compilation]>} */finishMake: new AsyncSeriesHook(["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 | null, number]>} */invalid: new SyncHook(["filename", "changeTime"]),/** @type {SyncHook<[]>} */watchClose: new SyncHook([]),/** @type {AsyncSeriesHook<[]>} */shutdown: new AsyncSeriesHook([]),/** @type {SyncBailHook<[string, string, any[]], true>} */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], boolean>} */entryOption: new SyncBailHook(["context", "entry"])});}}
class Compilation {constructor(compiler, params) {this.hooks = Object.freeze({/** @type {SyncHook<[Module]>} */buildModule: new SyncHook(["module"]),/** @type {SyncHook<[Module]>} */rebuildModule: new SyncHook(["module"]),/** @type {SyncHook<[Module, WebpackError]>} */failedModule: new SyncHook(["module", "error"]),/** @type {SyncHook<[Module]>} */succeedModule: new SyncHook(["module"]),/** @type {SyncHook<[Module]>} */stillValidModule: new SyncHook(["module"]),/** @type {SyncHook<[Dependency, EntryOptions]>} */addEntry: new SyncHook(["entry", "options"]),/** @type {SyncHook<[Dependency, EntryOptions, Error]>} */failedEntry: new SyncHook(["entry", "options", "error"]),/** @type {SyncHook<[Dependency, EntryOptions, Module]>} */succeedEntry: new SyncHook(["entry", "options", "module"]),/** @type {SyncWaterfallHook<[(string[] | ReferencedExport)[], Dependency, RuntimeSpec]>} */dependencyReferencedExports: new SyncWaterfallHook(["referencedExports","dependency","runtime"]),/** @type {SyncHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */executeModule: new SyncHook(["options", "context"]),/** @type {AsyncParallelHook<[ExecuteModuleArgument, ExecuteModuleContext]>} */prepareModuleExecution: new AsyncParallelHook(["options", "context"]),/** @type {AsyncSeriesHook<[Iterable<Module>]>} */finishModules: new AsyncSeriesHook(["modules"]),/** @type {AsyncSeriesHook<[Module]>} */finishRebuildingModule: new AsyncSeriesHook(["module"]),/** @type {SyncHook<[]>} */unseal: new SyncHook([]),/** @type {SyncHook<[]>} */seal: new SyncHook([]),/** @type {SyncHook<[]>} */beforeChunks: new SyncHook([]),/** @type {SyncHook<[Iterable<Chunk>]>} */afterChunks: new SyncHook(["chunks"]),/** @type {SyncBailHook<[Iterable<Module>]>} */optimizeDependencies: new SyncBailHook(["modules"]),/** @type {SyncHook<[Iterable<Module>]>} */afterOptimizeDependencies: new SyncHook(["modules"]),/** @type {SyncHook<[]>} */optimize: new SyncHook([]),/** @type {SyncBailHook<[Iterable<Module>]>} */optimizeModules: new SyncBailHook(["modules"]),/** @type {SyncHook<[Iterable<Module>]>} */afterOptimizeModules: new SyncHook(["modules"]),/** @type {SyncBailHook<[Iterable<Chunk>, ChunkGroup[]]>} */optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]),/** @type {SyncHook<[Iterable<Chunk>, ChunkGroup[]]>} */afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]),/** @type {AsyncSeriesHook<[Iterable<Chunk>, Iterable<Module>]>} */optimizeTree: new AsyncSeriesHook(["chunks", "modules"]),/** @type {SyncHook<[Iterable<Chunk>, Iterable<Module>]>} */afterOptimizeTree: new SyncHook(["chunks", "modules"]),/** @type {AsyncSeriesBailHook<[Iterable<Chunk>, Iterable<Module>]>} */optimizeChunkModules: new AsyncSeriesBailHook(["chunks", "modules"]),/** @type {SyncHook<[Iterable<Chunk>, Iterable<Module>]>} */afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]),/** @type {SyncBailHook<[], boolean>} */shouldRecord: new SyncBailHook([]),/** @type {SyncHook<[Chunk, Set<string>, RuntimeRequirementsContext]>} */additionalChunkRuntimeRequirements: new SyncHook(["chunk","runtimeRequirements","context"]),/** @type {HookMap<SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext]>>} */runtimeRequirementInChunk: new HookMap(() => new SyncBailHook(["chunk", "runtimeRequirements", "context"])),/** @type {SyncHook<[Module, Set<string>, RuntimeRequirementsContext]>} */additionalModuleRuntimeRequirements: new SyncHook(["module","runtimeRequirements","context"]),/** @type {HookMap<SyncBailHook<[Module, Set<string>, RuntimeRequirementsContext]>>} */runtimeRequirementInModule: new HookMap(() => new SyncBailHook(["module", "runtimeRequirements", "context"])),/** @type {SyncHook<[Chunk, Set<string>, RuntimeRequirementsContext]>} */additionalTreeRuntimeRequirements: new SyncHook(["chunk","runtimeRequirements","context"]),/** @type {HookMap<SyncBailHook<[Chunk, Set<string>, RuntimeRequirementsContext]>>} */runtimeRequirementInTree: new HookMap(() => new SyncBailHook(["chunk", "runtimeRequirements", "context"])),/** @type {SyncHook<[RuntimeModule, Chunk]>} */runtimeModule: new SyncHook(["module", "chunk"]),/** @type {SyncHook<[Iterable<Module>, any]>} */reviveModules: new SyncHook(["modules", "records"]),/** @type {SyncHook<[Iterable<Module>]>} */beforeModuleIds: new SyncHook(["modules"]),/** @type {SyncHook<[Iterable<Module>]>} */moduleIds: new SyncHook(["modules"]),/** @type {SyncHook<[Iterable<Module>]>} */optimizeModuleIds: new SyncHook(["modules"]),/** @type {SyncHook<[Iterable<Module>]>} */afterOptimizeModuleIds: new SyncHook(["modules"]),/** @type {SyncHook<[Iterable<Chunk>, any]>} */reviveChunks: new SyncHook(["chunks", "records"]),/** @type {SyncHook<[Iterable<Chunk>]>} */beforeChunkIds: new SyncHook(["chunks"]),/** @type {SyncHook<[Iterable<Chunk>]>} */chunkIds: new SyncHook(["chunks"]),/** @type {SyncHook<[Iterable<Chunk>]>} */optimizeChunkIds: new SyncHook(["chunks"]),/** @type {SyncHook<[Iterable<Chunk>]>} */afterOptimizeChunkIds: new SyncHook(["chunks"]),/** @type {SyncHook<[Iterable<Module>, any]>} */recordModules: new SyncHook(["modules", "records"]),/** @type {SyncHook<[Iterable<Chunk>, any]>} */recordChunks: new SyncHook(["chunks", "records"]),/** @type {SyncHook<[Iterable<Module>]>} */optimizeCodeGeneration: new SyncHook(["modules"]),/** @type {SyncHook<[]>} */beforeModuleHash: new SyncHook([]),/** @type {SyncHook<[]>} */afterModuleHash: new SyncHook([]),/** @type {SyncHook<[]>} */beforeCodeGeneration: new SyncHook([]),/** @type {SyncHook<[]>} */afterCodeGeneration: new SyncHook([]),/** @type {SyncHook<[]>} */beforeRuntimeRequirements: new SyncHook([]),/** @type {SyncHook<[]>} */afterRuntimeRequirements: new SyncHook([]),/** @type {SyncHook<[]>} */beforeHash: new SyncHook([]),/** @type {SyncHook<[Chunk]>} */contentHash: new SyncHook(["chunk"]),/** @type {SyncHook<[]>} */afterHash: new SyncHook([]),/** @type {SyncHook<[any]>} */recordHash: new SyncHook(["records"]),/** @type {SyncHook<[Compilation, any]>} */record: new SyncHook(["compilation", "records"]),/** @type {SyncHook<[]>} */beforeModuleAssets: new SyncHook([]),/** @type {SyncBailHook<[], boolean>} */shouldGenerateChunkAssets: new SyncBailHook([]),/** @type {SyncHook<[]>} */beforeChunkAssets: new SyncHook([]),// TODO webpack 6 remove/** @deprecated */additionalChunkAssets: createProcessAssetsHook("additionalChunkAssets",Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,() => [this.chunks],"DEP_WEBPACK_COMPILATION_ADDITIONAL_CHUNK_ASSETS"),// TODO webpack 6 deprecate/** @deprecated */additionalAssets: createProcessAssetsHook("additionalAssets",Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,() => []),// TODO webpack 6 remove/** @deprecated */optimizeChunkAssets: createProcessAssetsHook("optimizeChunkAssets",Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,() => [this.chunks],"DEP_WEBPACK_COMPILATION_OPTIMIZE_CHUNK_ASSETS"),// TODO webpack 6 remove/** @deprecated */afterOptimizeChunkAssets: createProcessAssetsHook("afterOptimizeChunkAssets",Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE + 1,() => [this.chunks],"DEP_WEBPACK_COMPILATION_AFTER_OPTIMIZE_CHUNK_ASSETS"),// TODO webpack 6 deprecate/** @deprecated */optimizeAssets: processAssetsHook,// TODO webpack 6 deprecate/** @deprecated */afterOptimizeAssets: afterProcessAssetsHook,processAssets: processAssetsHook,afterProcessAssets: afterProcessAssetsHook,/** @type {AsyncSeriesHook<[CompilationAssets]>} */processAdditionalAssets: new AsyncSeriesHook(["assets"]),/** @type {SyncBailHook<[], boolean>} */needAdditionalSeal: new SyncBailHook([]),/** @type {AsyncSeriesHook<[]>} */afterSeal: new AsyncSeriesHook([]),/** @type {SyncWaterfallHook<[RenderManifestEntry[], RenderManifestOptions]>} */renderManifest: new SyncWaterfallHook(["result", "options"]),/** @type {SyncHook<[Hash]>} */fullHash: new SyncHook(["hash"]),/** @type {SyncHook<[Chunk, Hash, ChunkHashContext]>} */chunkHash: new SyncHook(["chunk", "chunkHash", "ChunkHashContext"]),/** @type {SyncHook<[Module, string]>} */moduleAsset: new SyncHook(["module", "filename"]),/** @type {SyncHook<[Chunk, string]>} */chunkAsset: new SyncHook(["chunk", "filename"]),/** @type {SyncWaterfallHook<[string, object, AssetInfo]>} */assetPath: new SyncWaterfallHook(["path", "options", "assetInfo"]),/** @type {SyncBailHook<[], boolean>} */needAdditionalPass: new SyncBailHook([]),/** @type {SyncHook<[Compiler, string, number]>} */childCompiler: new SyncHook(["childCompiler","compilerName","compilerIndex"]),/** @type {SyncBailHook<[string, LogEntry], true>} */log: new SyncBailHook(["origin", "logEntry"]),/** @type {SyncWaterfallHook<[WebpackError[]]>} */processWarnings: new SyncWaterfallHook(["warnings"]),/** @type {SyncWaterfallHook<[WebpackError[]]>} */processErrors: new SyncWaterfallHook(["errors"]),/** @type {HookMap<SyncHook<[Partial<NormalizedStatsOptions>, CreateStatsOptionsContext]>>} */statsPreset: new HookMap(() => new SyncHook(["options", "context"])),/** @type {SyncHook<[Partial<NormalizedStatsOptions>, CreateStatsOptionsContext]>} */statsNormalize: new SyncHook(["options", "context"]),/** @type {SyncHook<[StatsFactory, NormalizedStatsOptions]>} */statsFactory: new SyncHook(["statsFactory", "options"]),/** @type {SyncHook<[StatsPrinter, NormalizedStatsOptions]>} */statsPrinter: new SyncHook(["statsPrinter", "options"]),get normalModuleLoader() {return getNormalModuleLoader();}});}}
webpack 构建流程
准备阶段
Compiler Hooks
- 流程相关
- before-run、run
 - before-complie、complier、after-complie
 - make
 - emit、after-emit
 - done
 
 监听相关
output.library—> LibraryTemplatePluginexternals—>ExternalsPlugindevtool—>EvalDevtoolModulePlugin、SourceMapDevToolPluginAMDPlugin、CommonJSPlugin- 
Compilation
Compiler 调用 Compilation 生命周期方法
 addEntry —> addModuleChain
- finish(上报模块错误)
 - seal
模块构建阶段
模块构建阶段:make ——> seal
 
Compilation Hooks
- 模块相关
- build-module
 - failed-module
 - succeed-module
 
 - 资源生成相关
- seal、after-seal
 - optimize、optimize-modules-basic、optimize-modules-advanced
 - after-optimize-modules
 - after-optimize-chunks
 - after-optimize-tree
 - optimize-chunk-modules-basic、optimize-chunk-modules-advanced
 - after-optimize-chunk-modules
 - optimize-modules、chunk-order
 - before-module、chunk-ids
 - optimize-module、after-optimize-module、chunk-ids
 - before-hash、after-hash
 
 - 优化相关
ModuleFactory
Module

 
- NormalModule
 
构建
- webpack 先将 entry 中对应的 module 都生成一个新的 chunk
 - 遍历 module 的依赖列表,将依赖的 module 也加入到 chunk 中
 - 如果一个依赖 module 是动态引入的模块,那么就会根据这个 module 创建一个新的 chunk,继续遍历依赖
 - 重复上面的过程,直到得到所有的 chunks
 
文件生成阶段
seal 阶段 —> emit 阶段
实现一个简易的 webpack
可以实现 ES6 转 ES5
- 通过 babylon 生成 AST
 - 通过 babel-core、@babel/preset-env、babel-preset-env 将 AST 重新生成代码
 
可以分析模块之间的依赖关系
- 通过 babel-traverse 的 ImportDeclaration 方法获取依赖属性
 
前置知识
AST
AST(abstract syntax tree):抽象语法树,是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。
