webpac4 阅读
webapck核心概念
- Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
- Output:输出结果,在Webpack经过一系列处理并得出最终想要的代码后输出结果。
- Module:模块,在 Webpack里一切皆模块,一个模块对应着一个文件。Webpack会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在Webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。
Webpack
记住webpack上面几个概念,再去了解几个核心模块.
- Resolver 路径解析里面的选项相当webpackConfig里面resolver选项说明,
- Parser 源码抽象解析,webpack采用的是 acorn 库对js源码语法解析,抽象成标准estree
- NormalModuleFactory 创建模块,不同的依赖对象由不同的模块工厂创建
compiler.hooks.compilation.tap(
"SingleEntryPlugin",
(compilation, { normalModuleFactory }) => {
// 添加一个模块工厂,当添加依赖文件与入口文件时会根据实例对象的构造函数读取对应工厂进行create创建模块
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
}
);
- Module 模块添加依赖、添加代码码、运行loaders、源码语法解析(各种插件对表达式处理,例如解析代码中的import对模块添加依赖) dependencies、loaders,parser,generator
几个插件顺序
- NodeEnvironmentPlugin 在webpack plugin执行前就执行,定义了文件系统环境
- JavascriptModulesPlugin 定义js文件解析器和源码生成器
- EntryOptionPlugin 解析入口文件
- HarmonyModulesPlugin es6模块解析
- CommonJsStuffPlugin commonjs模块解析
常用的一些钩子:
compiler.js
thisCompilation:创建编译实例对象
compilation:和上面是一样,只是在执行子编译时上面的不会触发,这个会触发
make:编译模块,这个动作是异步也是最耗时的,例如:入口文件就是定义了此钩子到模块编译完成,从模块loaded转换到parser ast语法解析,解析webpack提供一些内置变量和方法.例如代码import通过一系列parser钩子去解析,然后添加依赖到模块中。当模块编译完成后,会收集模块依赖,收集依赖过程相当重复之前的动作
emit:准备把所有assets输出到目录,
compilation.js
seal:开始创建chunk和优化chunks,创建输出资源assets,assets
的结构
compilation.assets['文件名']={source:()=>string,size:()=>number}
/**
* 目录
* src
* b.js
* index.js
* util
* index.js
*
* ##index.js
* export default 'index';
*/
var compiler=webpack({
entry:'./src/index',
plugins[
// 定义自定义插件
{
apply(compiler){
// 添加入口文件
compiler.hooks.make.tapAsync('addEntry', (compilation, callback) => {
let entry=SingleEntryPlugin.createDependency('./src/b.js','index')
compilation.addEntry(compiler.context,entry,'index',callback)
callback();
});
compiler.hooks.thisCompilation.tap('compilation', (compilation,{
}) => {
// 给一个文件添加一个依赖模块和一段代码
compilation.hooks.buildModule.tap('addDependency',(module)=>{
if(module.request.indexOf('src/index.js')!=-1){
var m=new HarmonyImportSideEffectDependency('./util',module,0,{});
module.addDependency(m);
const c = new ConstDependency("var $=window.jQuery;",0);
module.addDependency(c);
}
});
});
// 变量重命名
function rename(name,newName){
return function (parser){
parser.hooks.varDeclaration.for(name).tap('rename',(expression)=>{
const dep = new ConstDependency(newName, expression.range);
dep.loc = expression.loc;
parser.state.current.addDependency(dep);
return true;
});
parser.hooks.varDeclarationLet.for(name).tap('rename',(expression)=>{
})
}
}
compiler.hooks.normalModuleFactory.tap('MyPlugin', factory => {
factory.hooks.parser.for('javascript/auto').tap('MyPlugin', (parser, options) => {
rename('cust_name','private_cust_name');
});
});
}
}
]
});
compiler.run(callback);//编译
// 设置配置项
options = new WebpackOptionsDefaulter().process(options);
compiler = new Compiler(options.context);// 创建编译器
compiler.options = options;
/*
node环境文件系统配置
compiler.inputFileSystem 文件读取
compiler.outputFileSystem 文件输出
compiler.watchFileSystem 监听文件
*/
new NodeEnvironmentPlugin().apply(compiler);
if (options.plugins && Array.isArray(options.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);
Compiler 编译器
常用钩子:
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),//此时返回 true/false。 输出资源前
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),//编译(compilation)完成。
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),//compiler.run() 执行之前,添加一个钩子。
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),//开始读取 records 之前,钩入(hook into) compiler
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),// 生成资源到 output 目录之前。
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
//触发 compilation 事件之前执行(查看下面的 compilation)。 不会复制到子编译
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),//编译(compilation)创建之后,执行插件。
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),//NormalModuleFactory 创建之后,执行插件。
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),//编译(compilation)参数创建之后,执行插件
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),//一个新的编译(compilation)创建之后,钩入(hook into) compiler。
/** @type {AsyncParallelHook<Compilation>} */
/*
从entry到buildModule、processModuleDependencies(addModuleDependencies)整个过程
**/
make: new AsyncParallelHook(["compilation"]),// 整合资源,模块生成
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
// 监听模式下,一个新的编译(compilation)触发之后,执行一个插件,但是是在实际编译开始之前。
watchRun: new AsyncSeriesHook(["compiler"]),//
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),//environment 准备好之后,执行插件
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),//environment 安装完成之后,执行插件
/** @type {SyncHook<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),//设置完初始插件之后,执行插件。
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),//resolver 安装完成之后,执行插件。
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])//在 webpack 选项中的 entry 配置项 处理过之后,执行插件。
};
子编译不会触发的钩子:"make","compile","emit","afterEmit","invalid","done","thisCompilation"
run(callback){
// 开始运行
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);
});
});
});
}
createNormalModuleFactory() {
const normalModuleFactory = new NormalModuleFactory(
this.options.context,
this.resolverFactory,
this.options.module || {}
);
this.hooks.normalModuleFactory.call(normalModuleFactory);
return normalModuleFactory;
}
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory(),
compilationDependencies: new Set()
};
return params;
}
newCompilation(params) {
const compilation = this.createCompilation();
compilation.fileTimestamps = this.fileTimestamps;
compilation.contextTimestamps = this.contextTimestamps;
compilation.name = this.name;
compilation.records = this.records;
compilation.compilationDependencies = params.compilationDependencies;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
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);
// 编译,如果有异步编译需求,可以在此钩子下处理
this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
// 编译完成
compilation.finish(err => {
if (err) return callback(err);
// 开始优化
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
}
Compilation
钩子
this.hooks = {
/** @type {SyncHook<Module>} */
buildModule: new SyncHook(["module"]),//在模块构建开始之前触发。
/** @type {SyncHook<Module>} */
rebuildModule: new SyncHook(["module"]),//在重新构建一个模块之前触发。
/** @type {SyncHook<Module, Error>} */
failedModule: new SyncHook(["module", "error"]),//模块构建失败时执行。
/** @type {SyncHook<Module>} */
succeedModule: new SyncHook(["module"]),//模块构建成功时执行。
/** @type {SyncHook<Dependency, string>} */
addEntry: new SyncHook(["entry", "name"]),
/** @type {SyncHook<Dependency, string, Error>} */
failedEntry: new SyncHook(["entry", "name", "error"]),
/** @type {SyncHook<Dependency, string, Module>} */
succeedEntry: new SyncHook(["entry", "name", "module"]),
/** @type {SyncWaterfallHook<DependencyReference, Dependency, Module>} */
dependencyReference: new SyncWaterfallHook([
"dependencyReference",
"dependency",
"module"
]),
/** @type {AsyncSeriesHook<Module[]>} */
finishModules: new AsyncSeriesHook(["modules"]),//所有模块都完成构建。
/** @type {SyncHook<Module>} */
finishRebuildingModule: new SyncHook(["module"]),//一个模块完成重新构建。
/** @type {SyncHook} */
unseal: new SyncHook([]),//编译(compilation)开始接收新模块时触发。
/** @type {SyncHook} */
seal: new SyncHook([]),//编译(compilation)停止接收新模块时触发
/** @type {SyncHook} */
beforeChunks: new SyncHook([]),
/** @type {SyncHook<Chunk[]>} */
afterChunks: new SyncHook(["chunks"]),
/** @type {SyncBailHook<Module[]>} */
optimizeDependenciesBasic: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
optimizeDependencies: new SyncBailHook(["modules"]),//依赖优化开始时触发。
/** @type {SyncBailHook<Module[]>} */
optimizeDependenciesAdvanced: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
afterOptimizeDependencies: new SyncHook(["modules"]),
/** @type {SyncHook} */
optimize: new SyncHook([]),//优化阶段开始时触发。
/** @type {SyncBailHook<Module[]>} */
optimizeModulesBasic: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
optimizeModules: new SyncBailHook(["modules"]),
/** @type {SyncBailHook<Module[]>} */
optimizeModulesAdvanced: new SyncBailHook(["modules"]),
/** @type {SyncHook<Module[]>} */
afterOptimizeModules: new SyncHook(["modules"]),
/** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */
optimizeChunksBasic: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */
optimizeChunks: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncBailHook<Chunk[], ChunkGroup[]>} */
optimizeChunksAdvanced: new SyncBailHook(["chunks", "chunkGroups"]),
/** @type {SyncHook<Chunk[], ChunkGroup[]>} */
afterOptimizeChunks: new SyncHook(["chunks", "chunkGroups"]),
/** @type {AsyncSeriesHook<Chunk[], Module[]>} */
optimizeTree: new AsyncSeriesHook(["chunks", "modules"]),
/** @type {SyncHook<Chunk[], Module[]>} */
afterOptimizeTree: new SyncHook(["chunks", "modules"]),
/** @type {SyncBailHook<Chunk[], Module[]>} */
optimizeChunkModulesBasic: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncBailHook<Chunk[], Module[]>} */
optimizeChunkModules: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncBailHook<Chunk[], Module[]>} */
optimizeChunkModulesAdvanced: new SyncBailHook(["chunks", "modules"]),
/** @type {SyncHook<Chunk[], Module[]>} */
afterOptimizeChunkModules: new SyncHook(["chunks", "modules"]),
/** @type {SyncBailHook} */
shouldRecord: new SyncBailHook([]),
/** @type {SyncHook<Module[], any>} */
reviveModules: new SyncHook(["modules", "records"]),
/** @type {SyncHook<Module[]>} */
optimizeModuleOrder: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
advancedOptimizeModuleOrder: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
beforeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
moduleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
optimizeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Module[]>} */
afterOptimizeModuleIds: new SyncHook(["modules"]),
/** @type {SyncHook<Chunk[], any>} */
reviveChunks: new SyncHook(["chunks", "records"]),
/** @type {SyncHook<Chunk[]>} */
optimizeChunkOrder: new SyncHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
beforeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
optimizeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
afterOptimizeChunkIds: new SyncHook(["chunks"]),
/** @type {SyncHook<Module[], any>} */
recordModules: new SyncHook(["modules", "records"]),
/** @type {SyncHook<Chunk[], any>} */
recordChunks: new SyncHook(["chunks", "records"]),
/** @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} */
shouldGenerateChunkAssets: new SyncBailHook([]),
/** @type {SyncHook} */
beforeChunkAssets: new SyncHook([]),
/** @type {SyncHook<Chunk[]>} */
additionalChunkAssets: new SyncHook(["chunks"]),
/** @type {AsyncSeriesHook} */
additionalAssets: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Chunk[]>} */
optimizeChunkAssets: new AsyncSeriesHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
afterOptimizeChunkAssets: new SyncHook(["chunks"]),
/** @type {AsyncSeriesHook<CompilationAssets>} */
optimizeAssets: new AsyncSeriesHook(["assets"]),
/** @type {SyncHook<CompilationAssets>} */
afterOptimizeAssets: new SyncHook(["assets"]),
/** @type {SyncBailHook} */
needAdditionalSeal: new SyncBailHook([]),
/** @type {AsyncSeriesHook} */
afterSeal: new AsyncSeriesHook([]),
/** @type {SyncHook<Chunk, Hash>} */
chunkHash: new SyncHook(["chunk", "chunkHash"]),
/** @type {SyncHook<Module, string>} */
moduleAsset: new SyncHook(["module", "filename"]),
/** @type {SyncHook<Chunk, string>} */
chunkAsset: new SyncHook(["chunk", "filename"]),
/** @type {SyncWaterfallHook<string, TODO>} */
assetPath: new SyncWaterfallHook(["filename", "data"]), // TODO MainTemplate
/** @type {SyncBailHook} */
needAdditionalPass: new SyncBailHook([]),
/** @type {SyncHook<Compiler, string, number>} */
childCompiler: new SyncHook([
"childCompiler",
"compilerName",
"compilerIndex"
]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook<object, Module>} */
normalModuleLoader: new SyncHook(["loaderContext", "module"]),
/** @type {SyncBailHook<Chunk[]>} */
optimizeExtractedChunksBasic: new SyncBailHook(["chunks"]),
/** @type {SyncBailHook<Chunk[]>} */
optimizeExtractedChunks: new SyncBailHook(["chunks"]),
/** @type {SyncBailHook<Chunk[]>} */
optimizeExtractedChunksAdvanced: new SyncBailHook(["chunks"]),
/** @type {SyncHook<Chunk[]>} */
afterOptimizeExtractedChunks: new SyncHook(["chunks"])
};
finish(callback) {
const modules = this.modules;
this.hooks.finishModules.callAsync(modules, err => {
if (err) return callback(err);
for (let index = 0; index < modules.length; index++) {
const module = modules[index];
this.reportDependencyErrorsAndWarnings(module, [module]);
}
callback();
});
}
/**
* @param {Callback} callback signals when the seal method is finishes
* @returns {void}
*/
seal(callback) {
this.hooks.seal.call();
// 优化输出
while (
this.hooks.optimizeDependenciesBasic.call(this.modules) ||
this.hooks.optimizeDependencies.call(this.modules) ||
this.hooks.optimizeDependenciesAdvanced.call(this.modules)
) {
/* empty */
}
this.hooks.afterOptimizeDependencies.call(this.modules);
this.hooks.beforeChunks.call();
for (const preparedEntrypoint of this._preparedEntrypoints) {
const module = preparedEntrypoint.module;
const name = preparedEntrypoint.name;
const chunk = this.addChunk(name);
const entrypoint = new Entrypoint(name);
entrypoint.setRuntimeChunk(chunk);
entrypoint.addOrigin(null, name, preparedEntrypoint.request);
this.namedChunkGroups.set(name, entrypoint);
this.entrypoints.set(name, entrypoint);
this.chunkGroups.push(entrypoint);
GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
GraphHelpers.connectChunkAndModule(chunk, module);
chunk.entryModule = module;
chunk.name = name;
this.assignDepth(module);
}
this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
this.sortModules(this.modules);
this.hooks.afterChunks.call(this.chunks);
this.hooks.optimize.call();
while (
this.hooks.optimizeModulesBasic.call(this.modules) ||
this.hooks.optimizeModules.call(this.modules) ||
this.hooks.optimizeModulesAdvanced.call(this.modules)
) {
/* empty */
}
this.hooks.afterOptimizeModules.call(this.modules);
while (
this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups) ||
this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups) ||
this.hooks.optimizeChunksAdvanced.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(err);
}
this.hooks.afterOptimizeTree.call(this.chunks, this.modules);
while (
this.hooks.optimizeChunkModulesBasic.call(this.chunks, this.modules) ||
this.hooks.optimizeChunkModules.call(this.chunks, this.modules) ||
this.hooks.optimizeChunkModulesAdvanced.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.optimizeModuleOrder.call(this.modules);
this.hooks.advancedOptimizeModuleOrder.call(this.modules);
this.hooks.beforeModuleIds.call(this.modules);
this.hooks.moduleIds.call(this.modules);
this.applyModuleIds();
this.hooks.optimizeModuleIds.call(this.modules);
this.hooks.afterOptimizeModuleIds.call(this.modules);
this.sortItemsWithModuleIds();
this.hooks.reviveChunks.call(this.chunks, this.records);
this.hooks.optimizeChunkOrder.call(this.chunks);
this.hooks.beforeChunkIds.call(this.chunks);
this.applyChunkIds();
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.beforeHash.call();
this.createHash();
this.hooks.afterHash.call();
if (shouldRecord) {
this.hooks.recordHash.call(this.records);
}
this.hooks.beforeModuleAssets.call();
this.createModuleAssets();
if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
this.hooks.beforeChunkAssets.call();
this.createChunkAssets();
}
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(err);
}
this.hooks.optimizeChunkAssets.callAsync(this.chunks, err => {
if (err) {
return callback(err);
}
this.hooks.afterOptimizeChunkAssets.call(this.chunks);
this.hooks.optimizeAssets.callAsync(this.assets, err => {
if (err) {
return callback(err);
}
this.hooks.afterOptimizeAssets.call(this.assets);
if (this.hooks.needAdditionalSeal.call()) {
this.unseal();
return this.seal(callback);
}
return this.hooks.afterSeal.callAsync(callback);
});
});
});
});
}
/**
* Builds the module object
*
* @param {Module} module module to be built
* @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);
}
};
this.hooks.buildModule.call(module);
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);
}
const originalMap = module.dependencies.reduce((map, v, i) => {
map.set(v, i);
return map;
}, new Map());
module.dependencies.sort((a, b) => {
const cmp = compareLocations(a.loc, b.loc);
if (cmp) return cmp;
return originalMap.get(a) - originalMap.get(b);
});
if (error) {
this.hooks.failedModule.call(module, error);
return callback(error);
}
this.hooks.succeedModule.call(module);
return callback();
}
);
}
_addModuleChain(context, dependency, onModule, callback) {
//代码....
const Dep = /** @type {DepConstructor} */ (dependency.constructor);
const moduleFactory = this.dependencyFactories.get(Dep);
this.semaphore.acquire(() => {
moduleFactory.create(
{
contextInfo: {
issuer: "",
compiler: this.compiler.name
},
context: context,
dependencies: [dependency]
},
(err, module) => {
// 添加module 到this.modules中
const addModuleResult = this.addModule(module);
module = addModuleResult.module;
onModule(module);
dependency.module = module;
module.addReason(null, dependency);
const afterBuild = () => {
if (currentProfile) {
const afterBuilding = Date.now();
currentProfile.building = afterBuilding - afterFactory;
}
if (addModuleResult.dependencies) {
//收集module依赖,添加到依赖项
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) {
// 生成模块
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);
}
}
);
});
}
addEntry(context, entry, name, callback) {
this.hooks.addEntry.call(entry, name);
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
const idx = this._preparedEntrypoints.findIndex(slot => slot.name === name);
if (idx >= 0) {
// Overwrite existing entrypoint
this._preparedEntrypoints[idx] = slot;
} else {
this._preparedEntrypoints.push(slot);
}
this._addModuleChain(
context,
entry,
module => {
this.entries.push(module);
},
(err, module) => {
// 入口文件编译完成
if (err) {
this.hooks.failedEntry.call(entry, name, err);
return callback(err);
}
if (module) {
slot.module = module;
} else {
const idx = this._preparedEntrypoints.indexOf(slot);
if (idx >= 0) {
this._preparedEntrypoints.splice(idx, 1);
}
}
this.hooks.succeedEntry.call(entry, name, module);
return callback(null, module);
}
);
}
Parser源码AST抽象转换
Resolver
const fs = require("fs");
const path=require('path')
const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
// create a resolver
const myResolver = ResolverFactory.createResolver({
// Typical usage will consume the `fs` + `CachedInputFileSystem`, which wraps Node.js `fs` to add caching.
fileSystem: new CachedInputFileSystem(fs, 4000),
alias: {
Utilities: require.resolve('webpack')
},
extensions:['.js','.json'],// 文件扩展名
aliasFields:['web'],
descriptionFiles:['package.json'],// 查找模块时文件说明
enforceExtension:false,// 是否强制扩展名
modules:['node_modules'],// 当查找模块时优先目录
mainFields: ['web', 'module','main'],// 读取模块是根据描述文件加载
mainFiles:['index'], // 默认读取目录时加载的文件
resolveToContext:false,// 如果为正只查找目录
/* any other resoelver options here. Options/defaults can be seen below */
});
// resolve a file with the new resolver
const context = {
issuer:""
};
const resolveContext = {
};
const lookupStartPath = path.resolve(__dirname);
const request = "./src/app";
myResolver.resolve(context, lookupStartPath, request, resolveContext, (
err /*Error*/,
filepath /*string*/
) => {
// Do something with the path
console.log(filepath)
});
SingleEntryDependency
/**
* An entry plugin which will handle
* creation of the SingleEntryDependency
*
* @param {string} context context path
* @param {string} entry entry path
* @param {string} name entry key name
*/
constructor(context, entry, name) {
this.context = context;
this.entry = entry;
this.name = name;
}
/**
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
"SingleEntryPlugin",
(compilation, { normalModuleFactory }) => {
// 添加依赖项处理工厂映射
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
}
);
compiler.hooks.make.tapAsync(
"SingleEntryPlugin",
(compilation, callback) => {
const { entry, name, context } = this;
// 创建一个实现ModuleDependency类的实例对象
const dep = SingleEntryPlugin.createDependency(entry, name);
// 把依赖对象添加到入口编译数组中
compilation.addEntry(context, dep, name, callback);
}
);
}
/**
* @param {string} entry entry request
* @param {string} name entry name
* @returns {SingleEntryDependency} the dependency
*/
static createDependency(entry, name) {
const dep = new SingleEntryDependency(entry);
dep.loc = { name };
return dep;
}
NormalModuleFactory
模块工厂通常要实现一个create方法
constructor(context, resolverFactory, options) {
super();
this.hooks = {
resolver: new SyncWaterfallHook(["resolver"]),
factory: new SyncWaterfallHook(["factory"]),
beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
afterResolve: new AsyncSeriesWaterfallHook(["data"]),
createModule: new SyncBailHook(["data"]),
module: new SyncWaterfallHook(["module", "data"]),
createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])),
createGenerator: new HookMap(
() => new SyncBailHook(["generatorOptions"])
),
generator: new HookMap(
() => new SyncHook(["generator", "generatorOptions"])
)
};
this.resolverFactory = resolverFactory;
// module/rules/loader 规则
this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
this.cachePredicate =
typeof options.unsafeCache === "function"
? options.unsafeCache
: Boolean.bind(null, options.unsafeCache);
this.context = context || "";
this.parserCache = Object.create(null);
this.generatorCache = Object.create(null);
this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
// 创建一个解析器
let resolver = this.hooks.resolver.call(null);
// Ignored
if (!resolver) return 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);
}
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;
const request = data.request;
const loaderResolver = this.getResolver("loader");
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);
}
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] = cachedCleverMerge(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);
if (matchResource === undefined) {
loaders = results[0].concat(loaders, results[1], results[2]);
} else {
loaders = results[0].concat(results[1], loaders, 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
});
});
}
);
}
);
});
}
create(data, callback) {
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();
// 创建一个工厂方法
const factory = this.hooks.factory.call(null);
// Ignored
if (!factory) return callback();
// 生成模块
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);
});
}
);
}