webpack 只支持JS模块,全部其余类型的模块,好比图片,css等,都须要经过对应的loader转成JS模块。因此在webpack中不管任何类型的资源,本质上都被当成JS模块处理。css
Webpack 源码是一个插件的架构,他的不少功能都是经过诸多的内置插件实现的。node
从配置文件读取 entry 开始,到最后输出 bundle.js 的过程,就是主线react
应该关心以下几点:webpack
webpack 的编译过程主要有哪些阶段?(生命周期)git
webpack 是如何 从 entry 开始解析出整个依赖树的?github
loaders 是在什么时候被调用的?web
最终是如何知道要生成几个文件,以及每一个文件的内容的?json
而其余一些不重要的问题咱们尽可能忽略,好比如何解析配置,如何处理错误,HASH 规则等。数组
编译源码分为如下几步:promise
根据咱们的webpack配置注册号对应的插件;
调用 compile.run 进入编译阶段,
在编译的第一阶段是 compilation,他会注册好不一样类型的module对应的 factory,否则后面碰到了就不知道如何处理了
进入 make 阶段,会从 entry 开始进行两步操做:
第一步是调用 loaders 对模块的原始代码进行编译,转换成标准的JS代码
第二步是调用 acorn 对JS代码进行语法分析,而后收集其中的依赖关系。每一个模块都会记录本身的依赖关系,从而造成一颗关系树
最后调用 compilation.seal 进入 render 阶段,根据以前收集的依赖,决定生成多少文件,每一个文件的内容是什么
当咱们const webpack = require('webpack')
时:
会从webpack包的package.json
文件的main
字段找到入口文件, 这里是:
"main": "lib/index.js",
复制代码
咱们找到lib/index.js
文件: 发现它经过module.exports对外暴露了webpack
及其余一些方法。
这里有个有意思的函数:
const exportPlugins = (obj, mappings) => {
for (const name of Object.keys(mappings)) {
Object.defineProperty(obj, name, {
configurable: false,
enumerable: true,
get: mappings[name]
});
}
};
exportPlugins((module.exports.cache = {}), {
MemoryCachePlugin: () => require("./cache/MemoryCachePlugin")
});
复制代码
经过这个函数, 实现了往module.exports
这个对象上添加属性, 并设置属性的configurable
, enumerable
, get
等特性。
咱们顺着index.js找到webpack.js
。
const webpack = (options, callback) => {
if (Array.isArray(options)) {
compiler = createMultiCompiler(options);
} else {
compiler = createCompiler(options);
}
if (callback) {}
return compiler;
};
module.exports = webpack;
复制代码
简化一下发现webpack是一个方法, 支持两个参数, callback是一个可选的回调。 options支持数组形式,这里咱们暂时按只传一个非数组的options往下走(options就是咱们在项目里配置的webpack.config.js
文件)
这里进入 createCompiler
方法, 发现了compiler原来是一个构造函数的实例,
const createCompiler = options => {
options = new WebpackOptionsDefaulter().process(options); //针对webpack的默认设置,主要功能内容都在原型上面
const compiler = new Compiler(options.context); //根据配置的option生成compiler实例, 此时的options.context是process.cwd() 方法返回 Node.js 进程的当前工做目录。
compiler.options = options;
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (Array.isArray(options.plugins)) { ///这里会解析webpack.config.js的plugins
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
compiler.options = new WebpackOptionsApply().process(options, compiler); ////这里会解析webpack.config.js的entry
return compiler;
};
复制代码
咱们进到compiler.js
中来看这个Compiler构造函数。 当咱们执行编译时, 我从create-react-app
的cli上发现会执行compiler实例的run
方法。
run (callback) { //compiler实例编译要执行的run方法
this.cache.endIdle(err => {
if (err) return finalCallback(err);
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
});
}
复制代码
发现这里出现了hooks
, 在Compiler不难找到hooks在constructor中已经注册, 来自tapable
这个库。
beforerun, run都是来自这个库的AsyncSeriesHook
的实例 compile是来自这个库的SyncHook
的实例
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
compile: new SyncHook(["params"]),
复制代码
为了下一步的解读, 咱们首先就要去弄清tapable
这个库都有些什么东西。
Webpack为此专门本身写一个插件系统,叫 Tapable , 主要提供了注册和调用插件的功能。
咱们先弄清楚这里须要用到的 SyncHook
和AsyncSeriesHook
。
SyncHook
为串行同步执行,不关心事件处理函数的返回值,在触发事件以后,会按照事件注册的前后顺序执行全部的事件处理函数。
在 tapable 解构的 SyncHook 是一个类,注册事件需先建立实例,建立实例时支持传入一个数组,数组内存储事件触发时传入的参数,实例的 tap 方法用于注册事件,支持传入两个参数,第一个参数为事件名称,在 Webpack 中通常用于存储事件对应的插件名称(名字随意,只是起到注释做用), 第二个参数为事件处理函数,函数参数为执行 call 方法触发事件时所传入的参数的形参。
AsyncSeriesHook 为异步串行执行,经过 tapAsync 注册的事件,经过 callAsync 触发,经过 tapPromise 注册的事件,经过 promise 触发,能够调用 then 方法。
注: 异步串行是指,事件处理函数内三个定时器的异步执行时间分别为 1s、2s 和 3s,而三个事件处理函数执行完总共用时接近 6s,因此三个事件处理函数执行是须要排队的,必须一个一个执行,当前事件处理函数执行完才能执行下一个。
AsyncSeriesHook 的 next 执行机制更像 Express 和 Koa 中的中间件,在注册事件的回调中若是不调用 next,则在触发事件时会在没有调用 next 的事件处理函数的位置 “卡死”,即不会继续执行后面的事件处理函数,只有都调用 next 才能继续,而最后一个事件处理函数中调用 next 决定是否调用 callAsync 的回调。
在 tapable 源码中,注册事件的方法 tab、tapSync、tapPromise 和触发事件的方法 call、callAsync、promise 都是经过 compile 方法快速编译出来的,咱们本文中这些方法的实现只是遵守了 tapable 库这些 “钩子” 的事件处理机制进行了模拟,以方便咱们了解 tapable,为学习 Webpack 原理作了一个铺垫,在 Webpack 中,这些 “钩子” 的真正做用就是将经过配置文件读取的插件与插件、加载器与加载器之间进行链接,“并行” 或 “串行” 执行。
咱们对tapable
的hooks有了必定理解, 继续往下走:
若是没有出错, 咱们会执行下面这个方法
this.compile(onCompiled);
复制代码
这个compile不是上面的对象实例compile
, 而是在Compiler中定义的compile方法
代码中的new Compilation(params)
, Compilation
是一个2000多行代码的构造函数, 获取到的compilation实例,它就是咱们须要的编译对象.
compilation 会存储编译一个 entry 的全部信息,包括他的依赖,对应的配置等
compile (callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params); //最主要的在这里, 获取compliation实例
const logger = compilation.getLogger("webpack.Compiler");
logger.time("make hook");
this.hooks.make.callAsync(compilation, err => {
logger.timeEnd("make hook");
if (err) return callback(err);
process.nextTick(() => { //将 callback 添加到下一个时间点的队列。 在 JavaScript 堆栈上的当前操做运行完成以后以及容许事件循环继续以前,此队列会被彻底耗尽。
logger.time("finish compilation");
compilation.finish(err => { //下面的finish方法
logger.timeEnd("finish compilation");
if (err) return callback(err);
logger.time("seal compilation");
compilation.seal(err => {
logger.timeEnd("seal compilation");
if (err) return callback(err);
logger.time("afterCompile hook");
this.hooks.afterCompile.callAsync(compilation, err => {
logger.timeEnd("afterCompile hook");
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});
}
复制代码
收集依赖
finish(callback) {
const { moduleGraph, modules } = this;
for (const module of modules) {
moduleGraph.finishModule(module);
}
this.hooks.finishModules.callAsync(modules, err => {
if (err) return callback(err);
// extract warnings and errors from modules
for (const module of modules) {
this.reportDependencyErrorsAndWarnings(module, [module]);
const errors = module.getErrors();
if (errors !== undefined) {
if (module.isOptional(this.moduleGraph)) {
for (const error of errors) {
if (!error.module) {
error.module = module;
}
this.warnings.push(error);
}
} else {
for (const error of errors) {
if (!error.module) {
error.module = module;
}
this.errors.push(error);
}
}
}
const warnings = module.getWarnings();
if (warnings !== undefined) {
for (const warning of warnings) {
if (!warning.module) {
warning.module = module;
}
this.warnings.push(warning);
}
}
}
callback();
});
}
复制代码
把全部依赖的模块都经过对应的模板 render 出一个拼接好的字符串
seal(callback) {
const chunkGraph = new ChunkGraph(this.moduleGraph);
this.chunkGraph = chunkGraph;
for (const module of this.modules) {
ChunkGraph.setChunkGraphForModule(module, chunkGraph);
}
this.hooks.seal.call();
while (this.hooks.optimizeDependencies.call(this.modules)) {
/* empty */
}
this.hooks.afterOptimizeDependencies.call(this.modules);
this.hooks.beforeChunks.call();
for (const [name, dependencies] of this.entryDependencies) {
const chunk = this.addChunk(name);
chunk.name = name;
const entrypoint = new Entrypoint(name);
entrypoint.setRuntimeChunk(chunk);
this.namedChunkGroups.set(name, entrypoint);
this.entrypoints.set(name, entrypoint);
this.chunkGroups.push(entrypoint);
connectChunkGroupAndChunk(entrypoint, chunk);
for (const dep of dependencies) {
entrypoint.addOrigin(null, { name }, dep.request);
const module = this.moduleGraph.getModule(dep);
if (module) {
chunkGraph.connectChunkAndModule(chunk, module);
chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
this.assignDepth(module);
}
}
}
buildChunkGraph(
this,
/** @type {Entrypoint[]} */ (this.chunkGroups.slice())
);
this.hooks.afterChunks.call(this.chunks);
this.hooks.optimize.call();
while (this.hooks.optimizeModules.call(this.modules)) {
/* empty */
}
this.hooks.afterOptimizeModules.call(this.modules);
while (this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups)) {
/* empty */
}
this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
if (err) {
return callback(
makeWebpackError(err, "Compilation.hooks.optimizeTree")
);
}
this.hooks.afterOptimizeTree.call(this.chunks, this.modules);
while (this.hooks.optimizeChunkModules.call(this.chunks, this.modules)) {
/* empty */
}
this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules);
const shouldRecord = this.hooks.shouldRecord.call() !== false;
this.hooks.reviveModules.call(this.modules, this.records);
this.hooks.beforeModuleIds.call(this.modules);
this.hooks.moduleIds.call(this.modules);
this.hooks.optimizeModuleIds.call(this.modules);
this.hooks.afterOptimizeModuleIds.call(this.modules);
this.hooks.reviveChunks.call(this.chunks, this.records);
this.hooks.beforeChunkIds.call(this.chunks);
this.hooks.chunkIds.call(this.chunks);
this.hooks.optimizeChunkIds.call(this.chunks);
this.hooks.afterOptimizeChunkIds.call(this.chunks);
this.sortItemsWithChunkIds();
if (shouldRecord) {
this.hooks.recordModules.call(this.modules, this.records);
this.hooks.recordChunks.call(this.chunks, this.records);
}
this.hooks.optimizeCodeGeneration.call(this.modules);
this.hooks.beforeModuleHash.call();
this.createModuleHashes();
this.hooks.afterModuleHash.call();
this.hooks.beforeCodeGeneration.call();
this.codeGenerationResults = this.codeGeneration();
this.hooks.afterCodeGeneration.call();
this.hooks.beforeRuntimeRequirements.call();
this.processRuntimeRequirements(this.entrypoints.values());
this.hooks.afterRuntimeRequirements.call();
this.hooks.beforeHash.call();
this.createHash();
this.hooks.afterHash.call();
if (shouldRecord) {
this.hooks.recordHash.call(this.records);
}
this.clearAssets();
this.hooks.beforeModuleAssets.call();
this.createModuleAssets();
const cont = () => {
this.hooks.additionalChunkAssets.call(this.chunks);
this.summarizeDependencies();
if (shouldRecord) {
this.hooks.record.call(this, this.records);
}
this.hooks.additionalAssets.callAsync(err => {
if (err) {
return callback(
makeWebpackError(err, "Compilation.hooks.additionalAssets")
);
}
this.hooks.optimizeChunkAssets.callAsync(this.chunks, err => {
if (err) {
return callback(
makeWebpackError(err, "Compilation.hooks.optimizeChunkAssets")
);
}
this.hooks.afterOptimizeChunkAssets.call(this.chunks);
this.hooks.optimizeAssets.callAsync(this.assets, err => {
if (err) {
return callback(
makeWebpackError(err, "Compilation.hooks.optimizeAssets")
);
}
this.hooks.afterOptimizeAssets.call(this.assets);
if (this.hooks.needAdditionalSeal.call()) {
this.unseal();
return this.seal(callback);
}
this.hooks.finishAssets.callAsync(this.assets, err => {
if (err) {
return callback(
makeWebpackError(err, "Compilation.hooks.finishAssets")
);
}
this.hooks.afterFinishAssets.call(this.assets);
this.cache.storeBuildDependencies(
this.buildDependencies,
err => {
if (err) {
return callback(err);
}
return this.hooks.afterSeal.callAsync(callback);
}
);
});
});
});
});
};
if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
this.hooks.beforeChunkAssets.call();
this.createChunkAssets(err => {
if (err) {
return callback(err);
}
cont();
});
} else {
cont();
}
});
}
复制代码
createNormalModuleFactory () {
const normalModuleFactory = new NormalModuleFactory({
context: this.options.context, //node工做进程的文件目录
fs: this.inputFileSystem,
resolverFactory: this.resolverFactory,
options: this.options.module || {} //这里就是webpack.config.js的module对象
});
this.hooks.normalModuleFactory.call(normalModuleFactory);
return normalModuleFactory;
}
createContextModuleFactory () {
const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
this.hooks.contextModuleFactory.call(contextModuleFactory);
return contextModuleFactory;
}
const params = {
normalModuleFactory: this.createNormalModuleFactory(), //这里会解析webpack.config.js的loaders, 并经过loaders将它们转换成js代码
contextModuleFactory: this.createContextModuleFactory()
};
return params;
复制代码
const onCompiled = (err, compilation) => {
if (err) return finalCallback(err);
if (this.hooks.shouldEmit.call(compilation) === false) {
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
return;
}
process.nextTick(() => {
logger = compilation.getLogger("webpack.Compiler");
logger.time("emitAssets");
this.emitAssets(compilation, err => {
logger.timeEnd("emitAssets");
if (err) return finalCallback(err);
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true;
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
logger.time("done hook");
this.hooks.done.callAsync(stats, err => {
logger.timeEnd("done hook");
if (err) return finalCallback(err);
this.hooks.additionalPass.callAsync(err => {
if (err) return finalCallback(err);
this.compile(onCompiled); //递归调用对下一层进行编译
});
});
return;
}
logger.time("emitRecords");
this.emitRecords(err => {
logger.timeEnd("emitRecords");
if (err) return finalCallback(err);
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
logger.time("done hook");
this.hooks.done.callAsync(stats, err => {
logger.timeEnd("done hook");
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
});
});
});
};
复制代码
大概的理了理webpack编译的主要思路。 事实上webpack上还有不少其余的配置, 感受还有不少东西没搞明白是作什么用的。
参考: