Appearance
make 流程
make 流程是 webpack 的一个核心流程,其主要分为下面几个步骤
- 入口
根据前面加载 SingleEntryPlugin
,MultiEntryPlugin
两个插件的 apply 时,注册了一个 compiler.hooks.make 的钩子函数
js
compiler.hooks.make.tapAsync("SingleEntryPlugin", (compilation, callback) => {
const { entry, name, context } = this
const dep = SingleEntryPlugin.createDependency(entry, name)
compilation.addEntry(context, dep, name, callback)
})
然后我们看compilation.addEntry()
其
js
/**
*
* @param {string} context context path for entry
* @param {Dependency} entry entry dependency being created
* @param {string} name name of entry
* @param {ModuleCallback} callback callback function
* @returns {void} returns
*/
addEntry(context, entry, name, callback) {
const slot = {
name: name,
// TODO webpack 5 remove `request`
// 入口文件的路径 地址
request: null,
module: null
};
// 保存路径地址
if (entry instanceof ModuleDependency) {
slot.request = entry.request;
}
// TODO webpack 5: merge modules instead when multiple entry modules are supported
// 在 _preparedEntrypoints 上保存当前入口地址对象
const idx = this._preparedEntrypoints.findIndex(slot => slot.name === name);
if (idx >= 0) {
// Overwrite existing entrypoint
// 如果找到 当前入口地址 就覆盖
this._preparedEntrypoints[idx] = slot;
} else {
this._preparedEntrypoints.push(slot);
}
// 真正的去处理module
this._addModuleChain(
context,
entry,
module => {
this.entries.push(module);
},
(err, module) => {
if (err) {
return callback(err);
}
if (module) {
slot.module = module;
} else {
const idx = this._preparedEntrypoints.indexOf(slot);
if (idx >= 0) {
this._preparedEntrypoints.splice(idx, 1);
}
}
return callback(null, module);
}
);
}
其主要还是调用 this._addModuleChain() 去处理入口模块
js
/**
*
* @param {string} context context string path 项目路径 d:\guzhangh\webpack\webpack\hash
* @param {Dependency} dependency dependency used to create Module chain
* @param {OnModuleCallback} onModule function invoked on modules creation
* @param {ModuleChainCallback} callback callback for when module chain is complete
* @returns {void} will throw if dependency instance is not a valid Dependency
*/
_addModuleChain(context, dependency, onModule, callback) {
const start = this.profile && Date.now();
const currentProfile = this.profile && {};
const errorAndCallback = this.bail
? err => {
callback(err);
}
: err => {
err.dependencies = [dependency];
this.errors.push(err);
callback();
};
if (
typeof dependency !== "object" ||
dependency === null ||
!dependency.constructor
) {
throw new Error("Parameter 'dependency' must be a Dependency");
}
const Dep = /** @type {DepConstructor} */ (dependency.constructor);
// 获取当前module类型的工厂方法
const moduleFactory = this.dependencyFactories.get(Dep);
if (!moduleFactory) {
throw new Error(
`No dependency factory available for this dependency type: ${
dependency.constructor.name
}`
);
}
/**
* 通过信号机的方式 维持当前处理 module的个数不能超过当前设置的运行最大并行的数目 `new Semaphore(options.parallelism || 100);`
*
* @type {Object}
*/
this.semaphore.acquire(() => {
// 调用module工厂函数的创建方法
moduleFactory.create(
{
contextInfo: {
issuer: "",
compiler: this.compiler.name
},
context: context,
dependencies: [dependency]
},
(err, module) => {
if (err) {
this.semaphore.release();
return errorAndCallback(new EntryModuleNotFoundError(err));
}
let afterFactory;
if (currentProfile) {
afterFactory = Date.now();
currentProfile.factory = afterFactory - start;
}
// 添加当前 Model 主要是缓存
const addModuleResult = this.addModule(module);
module = addModuleResult.module;
// 执行每一个NormalModule 的回调 如入口Module缓存到 this.entry
onModule(module);
dependency.module = module;
module.addReason(null, dependency);
const afterBuild = () => {
if (currentProfile) {
const afterBuilding = Date.now();
currentProfile.building = afterBuilding - afterFactory;
}
if (addModuleResult.dependencies) {
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
} else {
return callback(null, module);
}
};
if (addModuleResult.issuer) {
if (currentProfile) {
module.profile = currentProfile;
}
}
if (addModuleResult.build) {
// 真正的开始 编译module
this.buildModule(module, false, null, null, err => {
if (err) {
this.semaphore.release();
return errorAndCallback(err);
}
if (currentProfile) {
const afterBuilding = Date.now();
currentProfile.building = afterBuilding - afterFactory;
}
this.semaphore.release();
afterBuild();
});
} else {
this.semaphore.release();
this.waitForBuildingFinished(module, afterBuild);
}
}
);
});
}
在 _addModuleChain 中主要是做的什么那?
上面我们在 SingleEntryPlugin
中 const dep = SingleEntryPlugin.createDependency(entry, name);
创建了一个 SingleEntryDependency 的实例对象,那么在 const Dep = /** @type {DepConstructor} */ (dependency.constructor);
指向的就是我们的 SingleEntryDependency
构造函数对象,然后通过 const moduleFactory = this.dependencyFactories.get(Dep);
获取当前SingleEntryDependency
对应的 module 工厂实例对象。
我们看一个 this.dependencyFactories 中保存了所有类型的 module 对于的工厂函数实例对象
当我们获取到这个 module 对应的工厂函数以后通过 this.semaphore.acquire(() => {})
一个信号机的方式去处理这个模块,有关于 Semaphore
可以看这一篇 Semaphore,这个主要的作用就是 当我们处理模块的时候可能会同时有非常大的 module 一起执行,机子带不动的,那么这时候就可以通过 Semaphore 去控制同时执行的 module 的个数,当超过允许个数的时候只有通过this.semaphore.release();
释放一个机会,才会从队列中执行下一个 module。
然后开始通过 moduleFactory.create()
去处理 module,module 的类型不同其工厂函数也不同,我们这边以 NormalModuleFactory
为例子。
下面我们看 NormalModuleFactory.create()
js
class NormalModuleFactory extends Tapable {
constructor(context, resolverFactory, options) {
super()
this.hooks = {
resolver: new SyncWaterfallHook(["resolver"]),
factory: new SyncWaterfallHook(["factory"]),
parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
}
this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
// 执行当前中 定义的 resolver 函数 其实跟 factory 一样 都是通过钩子函数去放回一个 回调函数
let resolver = this.hooks.resolver.call(null)
// Ignored
if (!resolver) return callback()
// 执行获取的 resolver 的回调函数 就是下面的 `this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {}) `
resolver(result, (err, data) => {
if (err) return callback(err)
// Ignored
if (!data) return callback()
// direct module
if (typeof data.source === "function") return callback(null, data)
this.hooks.afterResolve.callAsync(data, (err, result) => {
if (err) return callback(err)
// Ignored
if (!result) return callback()
let createdModule = this.hooks.createModule.call(result)
if (!createdModule) {
if (!result.request) {
return callback(new Error("Empty dependency (no request)"))
}
createdModule = new NormalModule(result)
}
// 执行当前 normalModuleFactory 中创建 module 的钩子函数,传入的是 createdModule(当前默认的创建 NormalModule 实例对象)
//
createdModule = this.hooks.module.call(createdModule, result)
return callback(null, createdModule)
})
})
})
this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {
const contextInfo = data.contextInfo
// 工程路径
const context = data.context
// module的路径
const request = data.request
// 获取当前Module对于的 loader
const loaderResolver = this.getResolver("loader")
// 获取通过的resolver
const normalResolver = this.getResolver("normal", data.resolveOptions)
let matchResource = undefined
let requestWithoutMatchResource = request
const matchResourceMatch = MATCH_RESOURCE_REGEX.exec(request)
if (matchResourceMatch) {
matchResource = matchResourceMatch[1]
if (/^\.\.?\//.test(matchResource)) {
matchResource = path.join(context, matchResource)
}
requestWithoutMatchResource = request.substr(matchResourceMatch[0].length)
}
const noPreAutoLoaders = requestWithoutMatchResource.startsWith("-!")
const noAutoLoaders = noPreAutoLoaders || requestWithoutMatchResource.startsWith("!")
const noPrePostAutoLoaders = requestWithoutMatchResource.startsWith("!!")
let elements = requestWithoutMatchResource.replace(/^-?!+/, "").replace(/!!+/g, "!").split("!")
let resource = elements.pop()
elements = elements.map(identToLoaderRequest)
asyncLib.parallel(
[
callback => this.resolveRequestArray(contextInfo, context, elements, loaderResolver, callback),
callback => {
if (resource === "" || resource[0] === "?") {
return callback(null, {
resource,
})
}
normalResolver.resolve(contextInfo, context, resource, {}, (err, resource, resourceResolveData) => {
if (err) return callback(err)
callback(null, {
resourceResolveData,
resource,
})
})
},
],
(err, results) => {
if (err) return callback(err)
let loaders = results[0]
const resourceResolveData = results[1].resourceResolveData
resource = results[1].resource
// translate option idents
try {
for (const item of loaders) {
if (typeof item.options === "string" && item.options[0] === "?") {
const ident = item.options.substr(1)
item.options = this.ruleSet.findOptionsByIdent(ident)
item.ident = ident
}
}
} catch (e) {
return callback(e)
}
if (resource === false) {
// ignored
return callback(
null,
new RawModule("/* (ignored) */", `ignored ${context} ${request}`, `${request} (ignored)`)
)
}
const userRequest =
(matchResource !== undefined ? `${matchResource}!=!` : "") +
loaders.map(loaderToIdent).concat([resource]).join("!")
let resourcePath = matchResource !== undefined ? matchResource : resource
let resourceQuery = ""
const queryIndex = resourcePath.indexOf("?")
if (queryIndex >= 0) {
resourceQuery = resourcePath.substr(queryIndex)
resourcePath = resourcePath.substr(0, queryIndex)
}
// 获取匹配的 rule
const result = this.ruleSet.exec({
resource: resourcePath,
realResource: matchResource !== undefined ? resource.replace(/\?.*/, "") : resourcePath,
resourceQuery,
issuer: contextInfo.issuer,
compiler: contextInfo.compiler,
})
const settings = {}
const useLoadersPost = []
const useLoaders = []
const useLoadersPre = []
for (const r of result) {
if (r.type === "use") {
if (r.enforce === "post" && !noPrePostAutoLoaders) {
useLoadersPost.push(r.value)
} else if (r.enforce === "pre" && !noPreAutoLoaders && !noPrePostAutoLoaders) {
useLoadersPre.push(r.value)
} else if (!r.enforce && !noAutoLoaders && !noPrePostAutoLoaders) {
useLoaders.push(r.value)
}
} else if (
typeof r.value === "object" &&
r.value !== null &&
typeof settings[r.type] === "object" &&
settings[r.type] !== null
) {
settings[r.type] = cachedMerge(settings[r.type], r.value)
} else {
settings[r.type] = r.value
}
}
asyncLib.parallel(
[
this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPost, loaderResolver),
this.resolveRequestArray.bind(this, contextInfo, this.context, useLoaders, loaderResolver),
this.resolveRequestArray.bind(this, contextInfo, this.context, useLoadersPre, loaderResolver),
],
(err, results) => {
if (err) return callback(err)
loaders = results[0].concat(loaders, results[1], results[2])
process.nextTick(() => {
const type = settings.type
const resolveOptions = settings.resolve
callback(null, {
context: context,
request: loaders.map(loaderToIdent).concat([resource]).join("!"),
dependencies: data.dependencies,
userRequest,
rawRequest: request,
loaders,
resource,
matchResource,
resourceResolveData,
settings,
type,
parser: this.getParser(type, settings.parser),
generator: this.getGenerator(type, settings.generator),
resolveOptions,
})
})
}
)
}
)
})
}
/**
* 创建一个当前NormalModuleFactory工厂函数的module
* @param {[type]} data [description]
* - context
* - contextInfo: {
issuer: "",
compiler: this.compiler.name
},
- dependencies: [dependency]
* @param {Function} callback [description]
* @return {[type]} [description]
*/
create(data, callback) {
// 获取当前module的依赖
const dependencies = data.dependencies
//
const cacheEntry = dependencyCache.get(dependencies[0])
if (cacheEntry) return callback(null, cacheEntry)
// 工程路径
const context = data.context || this.context
const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS
const request = dependencies[0].request
const contextInfo = data.contextInfo || {}
this.hooks.beforeResolve.callAsync(
{
contextInfo,
resolveOptions,
context,
request,
dependencies,
},
(err, result) => {
if (err) return callback(err)
// Ignored
if (!result) return callback()
// 获取当前 NormalModuleFactory 上定义的 factory 的钩子函数 详情在 NormalModuleFactory constructor中定义的一个 回调函数
const factory = this.hooks.factory.call(null)
// Ignored
if (!factory) return callback()
// 执行当前 NormalModuleFactory 上定义的 factory 的钩子函数
factory(result, (err, module) => {
if (err) return callback(err)
if (module && this.cachePredicate(module)) {
for (const d of dependencies) {
dependencyCache.set(d, module)
}
}
callback(null, module)
})
}
)
}
}
从 NormalModuleFactory.build() 开始其主要执行下面的流程
其起初是一个以下的对象数据
js
let module = {
contextInfo :{
compiler:undefined
issuer:""
},
resolveOptions : {},
// 工程路径
context : 'd:\guzhangh\webpack\webpack\hash',
// module的路径
request : "d:\guzhangh\webpack\webpack\hash\public\js\main.js",
// dependecies
dependencies:[SingleEntryDependency]
}
每一个类型的 ModuleFactory 都通过 build 执行以下的流程
resolve
loader
rules
获取对于的 module
1. resolve
通过
NormalModuleFactory.hooks.beforeResolve
执行 beforeResolve 的钩子函数NormalModuleFactory.hooks.factory.call(null);
获取对于的 factory 实例对象
factory()
this.hooks.resolver.call(null);
获取当前工厂函数对应的 resolve 解析的钩子函数
其通过 constructor 在创建的时候 this.hooks.resolver.tap("NormalModuleFactory", () => (data, callback) => {})
添加了一个 resolver 钩子处理函数
执行 resolver()
获取 loaderResolver、 normalResolver
获取对应的 loader
最后生成一个
js
let obj = {
// 工程路径
context: "d:\\guzhangh\\webpack\\webpack\\hash",
// 文件的路径
request: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
// 一开始传入的module
dependencies: [
{
module: null,
weak: false,
optional: false,
loc: { name: "main" },
request: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
userRequest: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
},
],
// 文件的路径
userRequest: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
// 文件的路径
rawRequest: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
loaders: [],
resource: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
// 当前资源对应的 resole信息
resourceResolveData: {
context: { issuer: "" },
path: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
query: "",
module: false,
file: false,
descriptionFilePath: "d:\\guzhangh\\webpack\\webpack\\hash\\package.json",
// package.json的内容
descriptionFileData: {},
descriptionFileRoot: "d:\\guzhangh\\webpack\\webpack\\hash",
relativePath: "./public/js/main.js",
__innerRequest_relativePath: "./public/js/main.js",
__innerRequest: "./public/js/main.js",
},
settings: { type: "javascript/auto", resolve: {} },
// 文件类型
type: "javascript/auto",
// 对于的 Parser 实例对象
parser: {
_pluginCompat: {},
comments: undefined,
hooks: {},
options: {},
scope: undefined,
options: {},
sourceType: "auto",
},
generator: {},
resolveOptions: {},
}
其中最重要的是通过 loader 获取到当前资源 module 对于的 Parser 实例对象
this.hooks.createModule.call(result);
createModule 钩子函数去创建上面数据对应的 Module 实例对象
如果没有自定义createModule
那么就使用此钩子函数默认的createdModule = new NormalModule(result);
去创建对于的 Module 对象
这时候我们再看 module 对象的内容
js
let module = {
dependencies: [],
blocks: [],
variables: [],
type: "javascript/auto",
context: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js",
debugId: 1000,
resolveOptions: {},
factoryMeta: {},
warnings: [],
errors: [],
reasons: [],
_chunks: { _lastActiveSortFn: null },
id: null,
index: null,
index2: null,
depth: null,
issuer: null,
prefetched: false,
built: false,
used: null,
usedExports: null,
optimizationBailout: [],
useSourceMap: false,
_source: null,
request: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
userRequest: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
rawRequest: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
binary: false,
parser: {
_pluginCompat: {
_args: ["options"],
taps: [
{ type: "sync", name: "Parser" },
{ type: "sync", name: "Tapable camelCase", stage: 100 },
{ type: "sync", name: "Tapable this.hooks", stage: 200 },
],
interceptors: [],
},
hooks: {},
options: {},
sourceType: "auto",
},
generator: {},
resource: "d:\\guzhangh\\webpack\\webpack\\hash\\public\\js\\main.js",
loaders: [],
error: null,
_buildHash: "",
_cachedSources: {},
lineToLine: false,
_lastSuccessfulBuildMeta: {},
}
发现暂时其主要的信息还是 Parser 对象
createdModule = this.hooks.module.call(createdModule, result);
module 钩子函数去处理 module 对象
一个 module 对象就通过 createModule -> module 两个钩子函数的蹂躏
- 终于处理好 module 回到
NormalModuleFactory.create() 的factory的回调函数中去了
这时候通过
js
if (module && this.cachePredicate(module)) {
for (const d of dependencies) {
dependencyCache.set(d, module)
}
}
缓存当前 module 在对于的 dependencyCache 中
继续返回回调到
moduleFactory.create()
的回调中this.addModule(module)
缓存 moduleonModule(module);
如果是入口函数 就缓存到this.entries.push(module);
module.addReason(null, dependency);
this.buildModule()
终于开始编译 module
js
/**
* Builds the module object
*
* @param {Module} module 编译的module对象
* @param {boolean} optional optional flag
* @param {Module=} origin origin module this module build was requested from
* @param {Dependency[]=} dependencies optional dependencies from the module to be built
* @param {TODO} thisCallback the callback
* @returns {TODO} returns the callback function with results
*/
buildModule(module, optional, origin, dependencies, thisCallback) {
let callbackList = this._buildingModules.get(module);
if (callbackList) {
callbackList.push(thisCallback);
return;
}
this._buildingModules.set(module, (callbackList = [thisCallback]));
const callback = err => {
this._buildingModules.delete(module);
for (const cb of callbackList) {
cb(err);
}
};
// 调用编译module开始的时候的钩子函数 如
// - ProgressPlugin 在控制台上输出编译当前module的进度条
this.hooks.buildModule.call(module);
// 调用每一个Module的build方法 如NormalModule的build
module.build(
this.options,
this,
this.resolverFactory.get("normal", module.resolveOptions),
this.inputFileSystem,
error => {
const errors = module.errors;
for (let indexError = 0; indexError < errors.length; indexError++) {
const err = errors[indexError];
err.origin = origin;
err.dependencies = dependencies;
if (optional) {
this.warnings.push(err);
} else {
this.errors.push(err);
}
}
const warnings = module.warnings;
for (
let indexWarning = 0;
indexWarning < warnings.length;
indexWarning++
) {
const war = warnings[indexWarning];
war.origin = origin;
war.dependencies = dependencies;
this.warnings.push(war);
}
module.dependencies.sort((a, b) => compareLocations(a.loc, b.loc));
if (error) {
this.hooks.failedModule.call(module, error);
return callback(error);
}
this.hooks.succeedModule.call(module);
return callback();
}
);
}
这里面又经历了
this._buildingModules
缓存当前正在编译的 modulethis.hooks.buildModule.call(module);
调用 buildModule 钩子函数
在这里面主要有下面几个插件
- ProgressPlugin 在控制台上输出编译当前 module 的进度条
- 调用真正的每一个 Module 对于的 build 方法
这时候我们又回到 NormalModule.js
js
class NormalModule extends Module {
build(options, compilation, resolver, fs, callback) {
this.buildTimestamp = Date.now()
this.built = true
this._source = null
this._ast = null
this._buildHash = ""
this.error = null
this.errors.length = 0
this.warnings.length = 0
this.buildMeta = {}
this.buildInfo = {
cacheable: false,
fileDependencies: new Set(),
contextDependencies: new Set(),
}
return this.doBuild(options, compilation, resolver, fs, err => {
this._cachedSources.clear()
// if we have an error mark module as failed and exit
if (err) {
this.markModuleAsErrored(err)
this._initBuildHash(compilation)
return callback()
}
// check if this module should !not! be parsed.
// if so, exit here;
const noParseRule = options.module && options.module.noParse
if (this.shouldPreventParsing(noParseRule, this.request)) {
this._initBuildHash(compilation)
return callback()
}
const handleParseError = e => {
const source = this._source.source()
const error = new ModuleParseError(this, source, e)
this.markModuleAsErrored(error)
this._initBuildHash(compilation)
return callback()
}
const handleParseResult = result => {
this._lastSuccessfulBuildMeta = this.buildMeta
this._initBuildHash(compilation)
return callback()
}
try {
const result = this.parser.parse(
this._ast || this._source.source(),
{
current: this,
module: this,
compilation: compilation,
options: options,
},
(err, result) => {
if (err) {
handleParseError(err)
} else {
handleParseResult(result)
}
}
)
if (result !== undefined) {
// parse is sync
handleParseResult(result)
}
} catch (e) {
handleParseError(e)
}
})
}
doBuild(options, compilation, resolver, fs, callback) {
const loaderContext = this.createLoaderContext(resolver, options, compilation, fs)
runLoaders(
{
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs),
},
(err, result) => {
if (result) {
this.buildInfo.cacheable = result.cacheable
this.buildInfo.fileDependencies = new Set(result.fileDependencies)
this.buildInfo.contextDependencies = new Set(result.contextDependencies)
}
if (err) {
if (!(err instanceof Error)) {
err = new NonErrorEmittedError(err)
}
const currentLoader = this.getCurrentLoader(loaderContext)
const error = new ModuleBuildError(this, err, {
from: currentLoader && compilation.runtimeTemplate.requestShortener.shorten(currentLoader.loader),
})
return callback(error)
}
const resourceBuffer = result.resourceBuffer
const source = result.result[0]
const sourceMap = result.result.length >= 1 ? result.result[1] : null
const extraInfo = result.result.length >= 2 ? result.result[2] : null
if (!Buffer.isBuffer(source) && typeof source !== "string") {
const currentLoader = this.getCurrentLoader(loaderContext, 0)
const err = new Error(
`Final loader (${
currentLoader ? compilation.runtimeTemplate.requestShortener.shorten(currentLoader.loader) : "unknown"
}) didn't return a Buffer or String`
)
const error = new ModuleBuildError(this, err)
return callback(error)
}
this._source = this.createSource(this.binary ? asBuffer(source) : asString(source), resourceBuffer, sourceMap)
this._ast =
typeof extraInfo === "object" && extraInfo !== null && extraInfo.webpackAST !== undefined
? extraInfo.webpackAST
: null
return callback()
}
)
}
}
发现其主要通过
build,
doBuild 去真正的执行编译流程
runLoaders() 获取 loader 处理后的 String 或者 Buffer
js
// 将前面loader处理后的String或者Buffer内容
// 1. 生成资源文件 包含路径和String类型的内容
// 2. 通过 SourceMapSource() 去生成对应的sourceMap文件
this._source = this.createSource(this.binary ? asBuffer(source) : asString(source), resourceBuffer, sourceMap)
继续回到 doBuild
这时候获取到 module 文件的内容了,那么这才开始真正的开始 parse
js
try {
const result = this.parser.parse(
this._ast || this._source.source(),
{
current: this,
module: this,
compilation: compilation,
options: options,
},
(err, result) => {
if (err) {
handleParseError(err);
} else {
handleParseResult(result);
}
}
);
if (result !== undefined) {
// parse is sync
handleParseResult(result);
}
}
Parser 的流程
js
class Parser extends Tapable {
parse(source, initialState) {
let ast
let comments
if (typeof source === "object" && source !== null) {
ast = source
comments = source.comments
} else {
comments = []
// 按照类型 将代码字符串转换成AST语法树
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments,
})
}
// 保存原来的作用域
const oldScope = this.scope
const oldState = this.state
const oldComments = this.comments
// 设置当前模块的作用域
this.scope = {
topLevelScope: true,
inTry: false,
inShorthand: false,
isStrict: false,
definitions: new StackedSetMap(),
renames: new StackedSetMap(),
}
const state = (this.state = initialState || {})
this.comments = comments
// 调用 parser 后的 program 钩子函数
// - UseStrictPlugin
// 用来检测文件是否有 use strict,如果有,则增加一个 ConstDependency 依赖。这里估计大家会有一个疑问:文件中已经有了,为什么还有增加一个这样的依赖呢?在 UseStrictPlugin.js 的源码中有一句注释
// Remove "use strict" expression. It will be added later by the renderer again. This is necessary in order to not break the strict mode when webpack prepends code.
// 意识是说,webpack 在处理我们的代码的时候,可能会在开头增加一些代码,这样会导致我们原本写在代码第一行的 "use strict" 不在第一行。所以 UseStrictPlugin 中通过增加 ConstDependency 依赖,来放置一个“占位符”,在最后生成打包文件的时候将其再转为 "use strict"。
// - HarmonyDetectionParserPlugin
// HarmonyDetectionParserPlugin 中,如果代码中有 import 或者 export 或者类型为 javascript/esm,那么会增加了两个依赖:HarmonyCompatibilityDependency, HarmonyInitDependency 依赖。
// - WebpackDeepScopeAnalysisPlugin
if (this.hooks.program.call(ast, comments) === undefined) {
// 处理 use struct 严格模式
// 检测当前执行块是否有 use strict,并设置 this.scope.isStrict = true
this.detectStrictMode(ast.body)
// prewalkStatements 阶段负责处理变量
// 处理 提前声明的属性
// 如 import
this.prewalkStatements(ast.body)
// 深入到函数的内部处理
// 1. 对于函数类型的 会通过 walkFunctionDeclaration 然后再次执行 detectStrictMode -> prewalkStatements -> walkStatements
this.walkStatements(ast.body)
}
this.scope = oldScope
this.state = oldState
this.comments = oldComments
return state
}
}
我们看 Parse 的流程发现其通过 acorn.js 将其按照类型转换成对应的 AST 树,然后
this.detectStrictMode(ast.body);
this.prewalkStatements(ast.body);
this.walkStatements(ast.body);
三步流程深度遍历处理 AST 中的语法,
其中在this.prewalkStatements(ast.body);
通过 case "ImportDeclaration": this.prewalkImportDeclaration(statement);
获取到当前 module 依赖的 Module 的数据,然后回到 doBuild
的过程,其通过
js
const handleParseResult = result => {
this._lastSuccessfulBuildMeta = this.buildMeta
this._initBuildHash(compilation)
return callback()
}
生成对应 module 的 Hash
然后回到Compilation.buildModule()
js
// 这个一般有 ProcessPlugin 上面不是在 this.hooks.buildModule.call(module);的时候ProcessPlugin生成当前module的编译进度命令行输出,这时候需要移除此输出
this.hooks.succeedModule.call(module)
回到_addModuleChain()
js
this.buildModule(module, false, null, null, err => {
if (err) {
this.semaphore.release()
return errorAndCallback(err)
}
if (currentProfile) {
const afterBuilding = Date.now()
currentProfile.building = afterBuilding - afterFactory
}
this.semaphore.release()
afterBuild()
})
其在当前 module 编译完成后 this.semaphore.release();
执行了一次信号机的释放方法,那么这时候就会将下一个 Module 的编译流程在从队列中取出加入到下一个 EventLoop 中,
在最后也调用了afterBuild();
js
const afterBuild = () => {
if (currentProfile) {
const afterBuilding = Date.now()
currentProfile.building = afterBuilding - afterFactory
}
if (addModuleResult.dependencies) {
this.processModuleDependencies(module, err => {
if (err) return callback(err)
callback(null, module)
})
} else {
return callback(null, module)
}
}
我们在 Parser 的时候将遇到的 import require 等依赖其他模块的描述符对象添加到 module.dependencies 中了,那么这时候就开始通过**this.processModuleDependencies()**去处理当前 module 的依赖了
js
/**
* @param {Module} module to be processed for deps
* @param {ModuleCallback} callback callback to be triggered
* @returns {void}
*/
processModuleDependencies(module, callback) {
// 保存当前依赖资源
// Map : {
// key : 依赖资源工厂构造函数 (NormalModuleFactory、ContextModuleFactory、) ,
// value : {
// 每一个工厂函数类型的资源文件
// module./module1.js : [ dependency ],
// module./module3.js : [ dependency ],
// }
// }
const dependencies = new Map();
const addDependency = dep => {
// 获取当前依赖资源的标识符
const resourceIdent = dep.getResourceIdentifier();
if (resourceIdent) {
// 获取依赖资源的工厂函数
const factory = this.dependencyFactories.get(dep.constructor);
if (factory === undefined) {
throw new Error(
`No module factory available for dependency type: ${
dep.constructor.name
}`
);
}
let innerMap = dependencies.get(factory);
if (innerMap === undefined) {
dependencies.set(factory, (innerMap = new Map()));
}
let list = innerMap.get(resourceIdent);
if (list === undefined) innerMap.set(resourceIdent, (list = []));
list.push(dep);
}
};
/**
* 处理不同形式的 子模块依赖
*
* @param {[type]} block [description]
*/
const addDependenciesBlock = block => {
// 处理
if (block.dependencies) {
iterationOfArrayCallback(block.dependencies, addDependency);
}
// 处理 import('loadsh').then( _ => {}) 这种
if (block.blocks) {
iterationOfArrayCallback(block.blocks, addDependenciesBlock);
}
if (block.variables) {
iterationBlockVariable(block.variables, addDependency);
}
};
try {
// 处理当前module中所有的依赖
addDependenciesBlock(module);
} catch (e) {
callback(e);
}
const sortedDependencies = [];
for (const pair1 of dependencies) {
for (const pair2 of pair1[1]) {
sortedDependencies.push({
factory: pair1[0],
dependencies: pair2[1]
});
}
}
this.addModuleDependencies(
module,
sortedDependencies,
this.bail,
null,
true,
callback
);
}
从上面我们发现其也是通过遍历 module.dependencies
,module.blocks
,module.variables
去处理不同的依赖方式声明的依赖,也是通过 this.dependencyFactories.get(dep.constructor)
每一种依赖的构造函数去获取其对应的 factory,然后最后在 this.addModuleDependencies(),
js
/**
* @param {Module} module module to add deps to
* @param {SortedDependency[]} dependencies set of sorted dependencies to iterate through
* @param {(boolean|null)=} bail whether to bail or not
* @param {TODO} cacheGroup optional cacheGroup
* @param {boolean} recursive whether it is recursive traversal
* @param {function} callback callback for when dependencies are finished being added
* @returns {void}
*/
addModuleDependencies(
module,
dependencies,
bail,
cacheGroup,
recursive,
callback
) {
const start = this.profile && Date.now();
const currentProfile = this.profile && {};
// 异步处理每一个依赖
asyncLib.forEach(
dependencies,
(item, callback) => {
const dependencies = item.dependencies;
const errorAndCallback = err => {
err.origin = module;
err.dependencies = dependencies;
this.errors.push(err);
if (bail) {
callback(err);
} else {
callback();
}
};
const warningAndCallback = err => {
err.origin = module;
this.warnings.push(err);
callback();
};
const semaphore = this.semaphore;
// 还是通过信号机的方式去处理每一个依赖
semaphore.acquire(() => {
const factory = item.factory;
factory.create(
{
contextInfo: {
issuer: module.nameForCondition && module.nameForCondition(),
compiler: this.compiler.name
},
resolveOptions: module.resolveOptions,
context: module.context,
dependencies: dependencies
},
(err, dependentModule) => {
let afterFactory;
const isOptional = () => {
return dependencies.every(d => d.optional);
};
const errorOrWarningAndCallback = err => {
if (isOptional()) {
return warningAndCallback(err);
} else {
return errorAndCallback(err);
}
};
if (err) {
semaphore.release();
return errorOrWarningAndCallback(
new ModuleNotFoundError(module, err)
);
}
if (!dependentModule) {
semaphore.release();
return process.nextTick(callback);
}
if (currentProfile) {
afterFactory = Date.now();
currentProfile.factory = afterFactory - start;
}
const iterationDependencies = depend => {
for (let index = 0; index < depend.length; index++) {
const dep = depend[index];
dep.module = dependentModule;
dependentModule.addReason(module, dep);
}
};
const addModuleResult = this.addModule(
dependentModule,
cacheGroup
);
dependentModule = addModuleResult.module;
iterationDependencies(dependencies);
const afterBuild = () => {
if (currentProfile) {
const afterBuilding = Date.now();
currentProfile.building = afterBuilding - afterFactory;
}
if (recursive && addModuleResult.dependencies) {
this.processModuleDependencies(dependentModule, callback);
} else {
return callback();
}
};
if (addModuleResult.issuer) {
if (currentProfile) {
dependentModule.profile = currentProfile;
}
dependentModule.issuer = module;
} else {
if (this.profile) {
if (module.profile) {
const time = Date.now() - start;
if (
!module.profile.dependencies ||
time > module.profile.dependencies
) {
module.profile.dependencies = time;
}
}
}
}
if (addModuleResult.build) {
this.buildModule(
dependentModule,
isOptional(),
module,
dependencies,
err => {
if (err) {
semaphore.release();
return errorOrWarningAndCallback(err);
}
if (currentProfile) {
const afterBuilding = Date.now();
currentProfile.building = afterBuilding - afterFactory;
}
semaphore.release();
afterBuild();
}
);
} else {
semaphore.release();
this.waitForBuildingFinished(dependentModule, afterBuild);
}
}
);
});
},
err => {
// In V8, the Error objects keep a reference to the functions on the stack. These warnings &
// errors are created inside closures that keep a reference to the Compilation, so errors are
// leaking the Compilation object.
if (err) {
// eslint-disable-next-line no-self-assign
err.stack = err.stack;
return callback(err);
}
return process.nextTick(callback);
}
);
}
然后我们看 addModuleDependencies() 发现其好长好长,但是其逻辑任然是通过 asyncLib 去串行异步处理所有的依赖(通过信号机添加一个 factory.create() -> XXXModuleFactory.create() -> resolve -> loader -> buildModule -> parse -> processModuleDependencies )
总结
从上面我们可以看出 make 的流程主要是以下:
SingleEntryPlugin
,MultiEntryPlugin
去处理单入口还是多入口实例化 Compilation 并添加 addEntry()
找到当前 module 对应的 XXXModuleFactory,并通过信号机的方式去调用 XXXModuleFactory.create()
在 XXXModuleFactory 中经历 resolve 的处理
loader 的处理
rules 的处理
生成一个当前 module 对应的 module 对象
buildModule 的过程
Parser 的过程
语法解析(找到 module 中的依赖)
processModuleDependencies 处理当前 module 的依赖,然后无限循环上面的 3 - 10 的过程