基于 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.js
process.exitCode = 0; // 1. 正常执行返回
const runCommand = (command, args) => {}; // 2. 运行某个命令
const isInstalled = packageName => {}; // 3. 判断某个包是否安装
const CLIs = [..]; // 4. webpack 可用的 CLI: webpack-cli 或者 webpack-command
const 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):抽象语法树,是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。