webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
webpack编译总览
下图为流程图:
一、执行webpack()
如果向webpack中传入回调函数,回调函数会在 webpack compiler 运行时被执行。
如果你不向 webpack 传入可执行的回调函数, 它会返回一个 webpack Compiler 实例。 你可以通过手动执行它或者为它的构建时添加一个监听器, 就像 CLI 类似。Compiler 实例提供了以下方法:
.run(callback).watch(watchOptions, handler)
/* webpack.js */
const webpack = (options, callback) => {
/* 省略…… */
if (callback) {
try {
// 生成一个编译器compliler
const { compiler, watch, watchOptions } = create();
if (watch) {
// 监听并执行
compiler.watch(watchOptions, callback);
} else {
// 执行
compiler.run((err, stats) => {
// 关闭
compiler.close(err2 => {
callback(err || err2, stats);
});
});
}
return compiler;
} catch (err) {
process.nextTick(() => callback(err));
return null;
}
} else {
const { compiler, watch } = create();
if (watch) {
util.deprecate(
() => { },
"A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
"DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
)();
}
return compiler;
}
}
create()
webpack内部首先定义create方法并调用,用于返回compiler、watch、watchOptions。
/* webpack.js */
const webpack = (options, callback) => {
const create = () => {
/* 省略…… */
let compiler;
let watch = false;
let watchOptions;
// 判断当前webpack是否有多配置
if (Array.isArray(options)) {
compiler = createMultiCompiler(options);
watch = options.some(options => options.watch);
watchOptions = options.map(options => options.watchOptions || {});
} else {
const webpackOptions = (options);
compiler = createCompiler(webpackOptions);
watch = webpackOptions.watch;
watchOptions = webpackOptions.watchOptions || {};
}
return { compiler, watch, watchOptions };
};
/* 省略…… */
// 生成一个编译器compliler
const { compiler, watch, watchOptions } = create();
/* 省略…… */
}
二、创建compiler
createCompiler(webpackOptions)
create方法中定义了两个方法(createMultiCompiler、createCompiler)用于创建并返回compiler,并执行一些后续相关操作。
createCompiler内部工作:
- 处理用于NodeEnvironmentPlugin插件的infrastructureLogging配置;
- 创建一个Compiler实例;
- 执行NodeEnvironmentPlugin插件;
- 设置了node文件监听系统
- compiler.watchFileSystem = new NodeWatchFileSystem(compiler.inputFileSystem);
- 设置了node文件监听系统
- 执行外部配置插件;
- 处理webpack其余配置项;
- 在编译器准备环境时,配置文件中初始化插件后,触发compiler的environment钩子;
- 编译器环境设置完成后,触发compiler的afterEnvironment钩子;
- 根据webpack配置项执行各种内部操作;
- 处理webpack选项中的entry;
- new EntryOptionPlugin().apply(compiler);
- entry被处理后触发钩子;
- compiler.hooks.entryOption.call(options.context, options.entry);
- 初始化内部插件集合完成设置后触发钩子;
- compiler.hooks.afterPlugins.call(compiler);
- 设置resolver;
- resolver设置完成后触发钩子;
- compiler.hooks.afterResolvers.call(compiler);
- 处理webpack选项中的entry;
- 编译器对象被初始化时,触发compiler的initialize钩子;
- 返回compiler;
webpack使用WebpackOptionsDefaulter和WebpackOptionsApply来配置Compiler实例以及所有内置插件。
/* webpack.js */
/* createCompiler */
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options); // 处理用于NodeEnvironmentPlugin插件的infrastructureLogging配置
const compiler = new Compiler(options.context, options);
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (Array.isArray(options.plugins)) {
// 执行外部插件
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options); // 处理webpack其余的配置项
// 在编译器准备环境时调用,时机就在配置文件中初始化插件之后。
compiler.hooks.environment.call();
// 当编译器环境设置完成后,在 environment hook 后直接调用。
compiler.hooks.afterEnvironment.call();
// 执行webpack内部设置的所有选项
new WebpackOptionsApply().process(options, compiler);
// 当编译器对象被初始化时调用。
compiler.hooks.initialize.call();
return compiler;
};
通常情况下,仅会创建一个主要
Compiler实例, 虽然可以创建一些子 compiler 来代理到特定任务。Compiler基本上只是执行最低限度的功能,以维持生命周期运行的功能。 它将所有的加载、打包和写入工作, 都委托到注册过的插件上。
/* webpack.js */
/* createMultiCompiler */
const createMultiCompiler = (childOptions, options) => {
const compilers = childOptions.map(options => createCompiler(options));
const compiler = new MultiCompiler(compilers, options);
for (const childCompiler of compilers) {
if (childCompiler.options.dependencies) {
compiler.setDependencies(
childCompiler,
childCompiler.options.dependencies
);
}
}
return compiler;
};
new Compiler(options.context, options)
Compiler模块是 webpack 的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自Tapable类,用来注册和调用插件。 大多数面向用户的插件会首先在Compiler上注册。
Compiler实例上的hooks属性,用于将一个插件注册 到Compiler的生命周期中的所有钩子事件上。
/* compiler.js */
const {
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook
} = require("tapable");
class Compiler {
/**
* @param {string} context the compilation path
* @param {WebpackOptions} options options
*/
constructor(context, options = /** @type {WebpackOptions} */ ({})) {
this.hooks = Object.freeze({
initialize: new SyncHook([]),
shouldEmit: new SyncBailHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
afterDone: new SyncHook(["stats"]),
/* 省略…… 具体内容看源码 */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
environment: new SyncHook([]),
afterEnvironment: new SyncHook([]),
afterPlugins: new SyncHook(["compiler"]),
afterResolvers: new SyncHook(["compiler"]),
entryOption: new SyncBailHook(["context", "entry"])
});
this.webpack = webpack;
this.name = undefined;
this.parentCompilation = undefined;
this.root = this;
this.outputPath = "";
this.watching = undefined;
this.resolverFactory = new ResolverFactory();
/* 省略…… 具体内容看源码 */
this.running = false;
this.idle = false;
this._assetEmittingSourceCache = new WeakMap();
this._assetEmittingWrittenFiles = new Map();
this._assetEmittingPreviousFiles = new Set();
}
}
三、启动构建
compiler创建完毕后,启动监听并执行编译:compiler.watch()。(也可不启动监听直接执行编译:compiler.run()),通常这两种模式分别用在开发环境和生产环境。
/* webpack.js */
const webpack = (options, callback) => {
/* 省略…… */
if (watch) {
// 监听并执行
compiler.watch(watchOptions, callback);
} else {
// 执行
compiler.run((err, stats) => {
// 关闭
compiler.close(err2 => {
callback(err || err2, stats);
});
});
}
return compiler;
}
Compiler支持可以监控文件系统的 监听(watching) 机制,并且在文件修改时重新编译。 当处于监听模式(watch mode)时, compiler 会触发诸如watchRun,watchClose和invalid等额外的事件。 通常在 开发环境 中使用, 也常常会在webpack-dev-server这些工具的底层调用, 由此开发人员无须每次都使用手动方式重新编译。 还可以通过 CLI 进入监听模式。
1、开发构建
① compiler.watch(watchOptions, callback)
compiler内部定义watch方法,用于启动webpack的监听模式编译流程。
/* compiler.js */
class Compiler {
/* 省略…… */
watch(watchOptions, handler) {
if (this.running) {
return handler(new ConcurrentCompilationError());
}
this.running = true;
this.watchMode = true;
this.watching = new Watching(this, watchOptions, handler);
return this.watching;
}
/* 省略…… */
}
调用
watch方法会触发 webpack 执行,但之后会监听变更(很像 CLI 命令:webpack --watch), 一旦 webpack 检测到文件变更,就会重新执行编译。 该方法返回一个Watching实例。
② new Watching(compiler, watchOptions, callback)
/* Watching.js */
class Watching {
constructor(compiler, watchOptions, handler) {
/* 省略…… */
process.nextTick(() => {
// 初次执行
if (this._initial) this._invalidate();
});
}
}
_invalidate()对构建条件进行判断,在合适的时机发送构建请求,调用_go:
/* Watching.js */
_invalidate(
fileTimeInfoEntries,
contextTimeInfoEntries,
changedFiles,
removedFiles
) {
if (this.suspended || (this._isBlocked() && (this.blocked = true))) {
this._mergeWithCollected(changedFiles, removedFiles);
return;
}
// 如果正在执行,则先将变动收集
if (this.running) {
this._mergeWithCollected(changedFiles, removedFiles);
this.invalid = true;
} else {
this._go(
fileTimeInfoEntries,
contextTimeInfoEntries,
changedFiles,
removedFiles
);
}
}
③ compiler.compile(onCompiled)
_go内部定义run方法,run内部会执行compiler.compile(onCompiled):
/* Watching.js */
_go(fileTimeInfoEntries, contextTimeInfoEntries, changedFiles, removedFiles) {
this.running = true; // 设置执行状态
// 用_mergeWithCollected合并or收集变更的文件内容
/* 省略代码…… */
this.compiler.modifiedFiles = this._collectedChangedFiles;
this._collectedChangedFiles = undefined;
this.compiler.removedFiles = this._collectedRemovedFiles;
this._collectedRemovedFiles = undefined;
const run = () => {
/* 省略…… */
this.invalid = false;
this._invalidReported = false;
// 在监听模式下,一个新的 compilation 触发之后,但在 compilation 实际开始之前执行。
this.compiler.hooks.watchRun.callAsync(this.compiler, err => {
if (err) return this._done(err);
const onCompiled = (err, compilation) => {
/* 省略…… */
};
this.compiler.compile(onCompiled);
});
};
// 构建
run();
}
接下来看compiler.compile方法:
compile方法内部会生成一个compilation对象。
/* compiler.js */
compile(callback) {
const params = this.newCompilationParams();
// 在创建 compilation parameter 之后执行。
this.hooks.beforeCompile.callAsync(params, err => {
// beforeCompile 之后立即调用,但在一个新的 compilation 创建之前。
this.hooks.compile.call(params);
// 创建一个Compilation模块
const compilation = this.newCompilation(params);
// compilation 结束之前执行。
this.hooks.make.callAsync(compilation, err => {
this.hooks.finishMake.callAsync(compilation, err => {
process.nextTick(() => {
// 完成编译并调用给定的回调。
compilation.finish(err => {
// 封闭编译。
compilation.seal(err => {
// compilation 结束和封印之后执行。
this.hooks.afterCompile.callAsync(compilation, err => {
// 执行传入的回调:即为下文中的onComplied方法。
return callback(null, compilation);
});
});
});
});
});
});
});
}
onComplied方法:
/* Watching.js */
const onCompiled = (err, compilation) => {
// 在输出 asset 之前调用。返回一个布尔值,告知是否输出。
if (this.compiler.hooks.shouldEmit.call(compilation) === false) {
return this._done(null, compilation);
}
process.nextTick(() => {
this.compiler.emitAssets(compilation, err => {
this.compiler.emitRecords(err => {
if (compilation.hooks.needAdditionalPass.call()) {
// 在 compilation 完成时执行。
this.compiler.hooks.done.callAsync(stats, err => {
// This hook allows you to do a one more additional pass of the build.
this.compiler.hooks.additionalPass.callAsync(err => {
this.compiler.compile(onCompiled);
});
});
return;
}
return this._done(null, compilation);
});
});
});
};
④ watching.watch(files, dirs, missing)
onCompiled回调的最后,内部会开启一个文件监听。
/* Watching.js */
_done(err, compilation) {
this.running = false;
/* 省略代码 */
const cbs = this.callbacks;
this.callbacks = [];
// 在 compilation 完成时执行。
this.compiler.hooks.done.callAsync(stats, err => {
this.compiler.cache.storeBuildDependencies(
compilation.buildDependencies,
err => {
process.nextTick(() => {
if (!this.closed) {
// 开启文件监听
this.watch(
compilation.fileDependencies,
compilation.contextDependencies,
compilation.missingDependencies
);
}
});
for (const cb of cbs) cb(null);
this.compiler.hooks.afterDone.call(stats);
}
);
});
}
/* 省略…… */
watch(files, dirs, missing) {
this.pausedWatcher = null;
// 向监听系统中传入监听回调
this.watcher = this.compiler.watchFileSystem.watch(
files,
dirs,
missing,
this.lastWatcherStartTime,
this.watchOptions,
(
err,
fileTimeInfoEntries,
contextTimeInfoEntries,
changedFiles,
removedFiles
) => {
if (err) {
/* 省略…… 错误处理 */
}
// 监听到文件变更后执行
this._invalidate(
fileTimeInfoEntries,
contextTimeInfoEntries,
changedFiles,
removedFiles
);
this._onChange();
},
(fileName, changeTime) => {
/* 省略…… compilation不可用处理 */
}
);
}
2、生产构建
compiler.run(callback)
使用
run方法启动所有编译工作。 完成之后,执行传入的的callback函数。 最终记录下来的概括信息(stats)和错误(errors),都应在这个 callback 函数中获取。
/* webpack.js */
compiler.run((err, stats) => {
// 关闭
compiler.close(err2 => {
callback(err || err2, stats);
});
});
/* Compiler.js */
class Compiler {
run(callback) {
if (this.running) {
return callback(new ConcurrentCompilationError());
}
this.running = true;
const onCompiled = (err, compilation) => {
// 在输出 asset 之前调用。返回一个布尔值,告知是否输出。
if (this.hooks.shouldEmit.call(compilation) === false) {
// 在 compilation 完成时执行。
this.hooks.done.callAsync(stats, err => {
return finalCallback(null, stats);
});
return;
}
process.nextTick(() => {
this.emitAssets(compilation, err => {
if (compilation.hooks.needAdditionalPass.call()) {
// 在 compilation 完成时执行。
this.hooks.done.callAsync(stats, err => {
// This hook allows you to do a one more additional pass of the build.
this.hooks.additionalPass.callAsync(err => {
this.compile(onCompiled);
});
});
return;
}
this.emitRecords(err => {
// 在 compilation 完成时执行。
this.hooks.done.callAsync(stats, err => {
this.cache.storeBuildDependencies(
compilation.buildDependencies,
err => {
return finalCallback(null, stats);
}
);
});
});
});
});
};
const run = () => {
// 在开始执行一次构建之前调用,compiler.run 方法开始执行后立刻进行调用。
this.hooks.beforeRun.callAsync(this, err => {
// 在开始读取 records 之前调用。
this.hooks.run.callAsync(this, err => {
this.readRecords(err => {
this.compile(onCompiled);
});
});
});
};
if (this.idle) {
this.cache.endIdle(err => {
this.idle = false;
run();
});
} else {
run();
}
}
}
四、执行构建
compiler.compile(onCompiled)
compiler.compile内部工作:
- 设置compilation的parameter;
- 实例normalModuleFactory并触发compiler的normalModuleFactory钩子;
- 实例contextModuleFactory并触发compiler的contextModuleFactory钩子;
- 触发compiler的beforeCompiler钩子;
- 触发compiler的compile钩子;
- 创建compilation;
- 触发compiler的thisCompilation钩子;
- 触发compiler的compilation钩子;
- 触发compiler的make钩子(compilation结束之前执行);
- EntryPlugin插件:执行compilation的addEntry方法,为编译添加入口:
- compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {compilation.addEntry(context, dep, options, err => {}})
- PrefetchPlugin插件:
- ① compilation.addModuleChain(context, dependency, callback)
- ② compilation.addModuleTree({ context, dependency }, callback)
- ③ compilation.handleModuleCreation(options, callback)
- ④ compilation.factorizeModule(options, (err, factoryResult) => {this.addModule(factoryResult.module, () => { this._handleModuleBuildAndDependencies()})})
- ⑤ compilation.factorizeQueue.add(options, callback)(入参同上)
- ⑥ compilation._factorizeModule(options, (e, r) => {this.handleResult(entry, e, r)} )
- ⑦ normalModuleFactory.create(options, callback) (该实例在compiler.conpile中创建,在PrefetchPlugin中设置)
- ⑧ create内部触发normalModuleFactory的factorize钩子(初始化解析之前调用)
- ⑨ foctorize钩子内部触发normalModuleFactory的resolve钩子(请求被解析前调用)
- ⑩ 执行resolver.resolve()
- 解析得到resolvedResource、resolvedResourceResolveData和loaders
- ①① 创建Parser
- normalModuleFactory.hooks.createParser.for(type).call(parserOptions)钩子去通知各插件创建对应类型的parser
- ①② 触发normalModuleFactory的afterResolve钩子(解析后调用)
- ①③ 触发normalModuleFactory的createModule钩子(创建normalModule实例之前调用)
- ①④ 创建Module
- createModule = new NormalModule(normalModuleCreateData)
- ①⑤ 触发normalModuleFactory的module钩子(创建normalModule实例之后调用)
- ①⑥ factorizeQueue.handleResult(entry, null, factoryResult),其中factoryResult.module = createModule
- ①⑦ compilation.addModule(factoryResult.module, callback)
- ①⑧ 编译module
- normalModuleFactory.buildModule()
- ①⑨ compilation.processModuleDependencies()
- 重复步骤③~①⑨
- EntryPlugin插件:执行compilation的addEntry方法,为编译添加入口:
- 触发compiler的finishMake钩子;
- 依次执行compilation的finish、seal方法;
- 触发compiler的afterCompile方法。
/* Compiler.js */
compile(callback) {
const params = this.newCompilationParams();
// 在创建 compilation parameter 之后执行。
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
// beforeCompile 之后立即调用,但在一个新的 compilation 创建之前。
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
// compilation 结束之前执行。
this.hooks.make.callAsync(compilation, err => {
this.hooks.finishMake.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
compilation.seal(err => {
this.hooks.afterCompile.callAsync(compilation, err => {
return callback(null, compilation);
})
})
})
})
})
})
/* 省略…… */
});
}
Compiler使用NormalModuleFactory模块生成各类模块。从入口点开始,此模块会分解每个请求,解析文件内容以查找进一步的请求,然后通过分解所有请求以及解析新的文件来爬取全部文件。在最后阶段,每个依赖项都会成为一个模块实例。
Compiler使用ContextModuleFactory模块从 webpack 独特的 require.context API 生成依赖关系。它会解析请求的目录,为每个文件生成请求,并依据传递来的 regExp 进行过滤。最后匹配成功的依赖关系将被传入 NormalModuleFactory。
/* Compiler.js */
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory()
};
return params;
}
newCompilation(params) {
const compilation = this.createCompilation(params);
compilation.name = this.name;
compilation.records = this.records;
// 初始化 compilation 时调用,在触发 compilation 事件之前调用。
this.hooks.thisCompilation.call(compilation, params);
// compilation 创建之后执行。
this.hooks.compilation.call(compilation, params);
return compilation;
}
createCompilation(params) {
this._cleanupLastCompilation();
return (this._lastCompilation = new Compilation(this, params));
}
每执行一次compiler的compile方法都会创建一个compilation对象。
五、创建compilation
new Compilation(compiler, params)
Compilation模块会被Compiler用来创建新的 compilation 对象(或新的 build 对象)。compilation实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。
class Compilation {
/* 省略代码…… */
}
六、执行resolver
解析器是使用
enhanced-resolve库创建的。Resolver类 拓展了tapable类,并使用tapable来提供了一些钩子。enhanced-resolve可以直接用于创建新的解析器, 但是,任何compiler实例 都有一些解析器实例,可以 被tap进去。
resolverFactory对象在创建compiler实例时生成,并传入normalModuleFactory中。
/* Compiler.js */
class Compiler {
constructor() {
this.resolverFactory = new ResolverFactory();
}
}
createNormalModuleFactory() {
this._cleanupLastNormalModuleFactory();
const normalModuleFactory = new NormalModuleFactory({
context: this.options.context,
fs: this.inputFileSystem,
resolverFactory: this.resolverFactory,
options: this.options.module,
associatedObjectForCache: this.root,
layers: this.options.experiments.layers
});
this._lastNormalModuleFactory = normalModuleFactory;
// NormalModuleFactory 创建之后调用。
this.hooks.normalModuleFactory.call(normalModuleFactory);
return normalModuleFactory;
}
resolveResource
/* NormoalModuleFactory.js */
resolveResource(
contextInfo,
context,
unresolvedResource,
resolver,
resolveContext,
callback
) {
resolver.resolve(
contextInfo,
context,
unresolvedResource,
resolveContext,
(err, resolvedResource, resolvedResourceResolveData) => {
if (err) {
/* 省略代码…… */
}
callback(err, resolvedResource, resolvedResourceResolveData);
}
);
}
resolveLoader
/* NormoalModuleFactory.js */
resolveRequestArray(
contextInfo,
context,
array,
resolver,
resolveContext,
callback
) {
if (array.length === 0) return callback(null, array);
asyncLib.map(
array,
(item, callback) => {
resolver.resolve(
contextInfo,
context,
item.loader,
resolveContext,
(err, result) => {
if (
err &&
/^[^/]*$/.test(item.loader) &&
!/-loader$/.test(item.loader)
) {
/* 省略代码 */
}
if (err) return callback(err);
const parsedResult = this._parseResourceWithoutFragment(result);
const resolved = {
loader: parsedResult.path,
options:
item.options === undefined
? parsedResult.query
? parsedResult.query.slice(1)
: undefined
: item.options,
ident: item.options === undefined ? undefined : item.ident
};
return callback(null, resolved);
}
);
},
callback
);
}
七、创建对应的parser
type不同创建的parser类型不同。
/* NormalFactory.js */
createParser(type, parserOptions = {}) {
parserOptions = mergeGlobalOptions(
this._globalParserOptions,
type,
parserOptions
);
// 在 Parser 实例创建前调用。parserOptions 是 module.parser 中对应标识符或空对象的选项。
const parser = this.hooks.createParser.for(type).call(parserOptions);
if (!parser) {
throw new Error(`No parser registered for ${type}`);
}
// 在创建 Parser 实例后触发。
this.hooks.parser.for(type).call(parser, parserOptions);
return parser;
}
/* AssetModulesPlugin.js */
normalModuleFactory.hooks.createParser
.for("asset/inline")
.tap(plugin, parserOptions => {
const AssetParser = getAssetParser();
return new AssetParser(true);
});
/* JavascriptModulesPlugin.js */
normalModuleFactory.hooks.createParser
.for("javascript/auto")
.tap("JavascriptModulesPlugin", options => {
return new JavascriptParser("auto");
});
normalModuleFactory.hooks.createParser
.for("javascript/dynamic")
.tap("JavascriptModulesPlugin", options => {
return new JavascriptParser("script");
});
normalModuleFactory.hooks.createParser
.for("javascript/esm")
.tap("JavascriptModulesPlugin", options => {
return new JavascriptParser("module");
});
合并resolveData
将解析到的loader、resource和parser添加到resolveData中,之后创建module时会将此数据传入到moduleFactory中。
/* NormoalModuleFactory.js */
try { // 处理resolveData,添加loader、parser等
Object.assign(data.createData, {
layer:
layer === undefined ? contextInfo.issuerLayer || null : layer,
request: stringifyLoadersAndResource(
allLoaders,
resourceData.resource
),
userRequest,
rawRequest: request,
loaders: allLoaders,
resource: resourceData.resource,
context:
resourceData.context || getContext(resourceData.resource),
matchResource: matchResourceData
? matchResourceData.resource
: undefined,
resourceResolveData: resourceData.data,
settings,
type,
parser: this.getParser(type, settings.parser),
parserOptions: settings.parser,
generator: this.getGenerator(type, settings.generator),
generatorOptions: settings.generator,
resolveOptions
});
} catch (e) {
return callback(e);
}
八、创建并编译module
new normalModule()
/* NormoalModuleFactory.js */
class NormalModuleFactory extends ModuleFactory {
custructor() {
// 在初始化解析之前调用。它应该返回 undefined 以继续。
this.hooks.factorize.tapAsync(
{
name: "NormalModuleFactory",
stage: 100
},
(resolveData, callback) => {
// 在请求被解析之前调用。可以通过返回 false 来忽略依赖项。返回一个模块实例将结束进程。否则,返回 undefined 以继续。
this.hooks.resolve.callAsync(resolveData, (err, result) => {
this.hooks.afterResolve.callAsync(resolveData, (err, result) => {
const createData = resolveData.createData;
// 在创建 NormalModule 实例之前调用。
this.hooks.createModule.callAsync(
createData,
resolveData,
(err, createdModule) => {
if (!createdModule) {
if (!resolveData.request) {
return callback(new Error("Empty dependency (no request)"));
}
createdModule = new NormalModule((createData));
}
// 在创建 NormalModule 实例后调用。
createdModule = this.hooks.module.call(
createdModule,
createData,
resolveData
);
return callback(null, createdModule);
}
);
});
});
}
);
}
}
compilation.buildModule()
/* Compilation.js */
_buildModule(module, callback) {
/* 省略代码…… */
module.needBuild(
{
compilation: this,
fileSystemInfo: this.fileSystemInfo,
valueCacheVersions: this.valueCacheVersions
},
(err, needBuild) => {
if (!needBuild) {
/* 省略代码 */
}
this.hooks.buildModule.call(module);
this.builtModules.add(module);
module.build(
this.options,
this,
this.resolverFactory.get("normal", module.resolveOptions),
this.inputFileSystem,
err => {
/* 省略代码 */
}
);
}
);
}
九、依次编译所有模块
compilation.processModuleDependencies()
/* Compilation.js */
_processModuleDependencies(module, callback) {
try {
/** @type {DependenciesBlock[]} */
const queue = [module];
do {
const block = queue.pop();
if (block.dependencies) {
currentBlock = block;
let i = 0;
for (const dep of block.dependencies) processDependency(dep, i++);
}
if (block.blocks) {
for (const b of block.blocks) queue.push(b);
}
} while (queue.length !== 0);
} catch (e) {
return callback(e);
}
}
processModuleDependencies方法会根据依赖关系依次编译每个模块。将所有模块都处理完毕后,根据依赖关系图以及前面各个步骤得到的模块信息,再组织和拼装对应的模板,再把相应的内容填充到模板中,最后“渲染”出目标代码。