对象 | 钩子 | 解释 |
---|---|---|
Compiler | environment | 在设置编译器环境之前触发的钩子 |
Compiler | afterEnvironment | 设置编译器环境之后触发的钩子 |
Compiler | entryOption | 在处理入口选项时触发的钩子 |
Compiler | afterPlugins | 加载插件之后触发的钩子 |
Compiler | afterResolvers | 初始化解析器后触发的钩子 |
Compiler | initialize | 初始化编译器时触发的钩子 |
Compiler | beforeRun | 在运行编译器之前触发的钩子 |
Compiler | run | 在运行编译器时触发的钩子 |
Compiler | infrastructureLog | 记录编译器基础设施日志时触发的钩子 |
Compiler | readRecords | 在读取构建记录时触发的钩子 |
Compiler | normalModuleFactory | 创建普通模块工厂时触发的钩子 |
Compiler | contextModuleFactory | 创建上下文模块工厂时触发的钩子 |
Compiler | beforeCompile | 在编译之前触发的钩子 |
Compiler | compile | 在编译开始时触发的钩子 |
Compiler | thisCompilation | 在当前编译之前触发的钩子 |
Compiler | compilation | 在新编译实例创建后触发的钩子 |
Compiler | make | 在创建模块及代码块之前触发的钩子 |
Compilation | addEntry | 在添加入口点时触发的钩子 |
NormalModuleFactory | beforeResolve | 解析普通模块之前触发的钩子 |
NormalModuleFactory | factorize | 在普通模块工厂创建模块之前触发的钩子 |
NormalModuleFactory | resolve | 在普通模块工厂解析模块时触发的钩子 |
NormalModuleFactory | afterResolve | 解析普通模块之后触发的钩子 |
NormalModuleFactory | createModule | 在创建普通模块之前触发的钩子 |
NormalModuleFactory | module | 在普通模块创建后触发的钩子 |
Compilation | buildModule | 在构建模块时触发的钩子 |
JavascriptParser | program | 在解析JavaScript程序时触发的钩子 |
JavascriptParser | preStatement | 在处理JavaScript语句之前触发的钩子 |
JavascriptParser | blockPreStatement | 在处理JavaScript块级语句之前触发的钩子 |
JavascriptParser | import | 在解析import语句时触发的钩子 |
JavascriptParser | importSpecifier | 在解析import声明时触发的钩子 |
JavascriptParser | statement | 在处理JavaScript语句时触发的钩子 |
JavascriptParser | finish | 在完成JavaScript解析时触发的钩子 |
Compilation | succeedModule | 在模块构建成功后触发的钩子 |
NormalModuleFactory | beforeResolve | 解析普通模块之前触发的钩子 |
NormalModuleFactory | factorize | 在普通模块工厂创建模块之前触发的钩子 |
NormalModuleFactory | resolve | 在普通模块工厂解析模块时触发的钩子 |
NormalModuleFactory | afterResolve | 解析普通模块之后触发的钩子 |
NormalModuleFactory | createModule | 在创建普通模块之前触发的钩子 |
NormalModuleFactory | module | 在普通模块创建后触发的钩子 |
Compilation | buildModule | 在构建模块时触发的钩子 |
JavascriptParser | program | 在解析JavaScript程序时触发的钩子 |
JavascriptParser | preStatement | 在处理JavaScript语句之前触发的钩子 |
JavascriptParser | blockPreStatement | 在处理JavaScript块级语句之前触发的钩子 |
JavascriptParser | export | 在解析export语句时触发的钩子 |
JavascriptParser | exportExpression | 在解析export表达式时触发的钩子 |
JavascriptParser | finish | 在完成JavaScript解析时触发的钩子 |
Compilation | succeedModule | 在模块构建成功后触发的钩子 |
Compilation | succeedEntry | 在入口模块构建成功后触发的钩子 |
Compiler | finishMake | 在完成模块和代码块创建后触发的钩子 |
Compilation | finishModules | 在完成模块构建后触发的钩子 |
Compilation | seal | 在封闭编译之前触发的钩子 |
Compilation | optimizeDependencies | 在优化依赖关系之前触发的钩子 |
Compilation | afterOptimizeDependencies | 在优化依赖关系之后触发的钩子 |
Compilation | beforeChunks | 在创建代码块之前触发的钩子 |
Compilation | afterChunks | 在创建代码块之后触发的钩子 |
Compilation | optimize | 在优化编译过程中触发的钩子 |
Compilation | optimizeModules | 在优化模块时触发的钩子 |
Compilation | afterOptimizeModules | 在优化模块之后触发的钩子 |
Compilation | optimizeChunks | 在优化代码块时触发的钩子 |
Compilation | afterOptimizeChunks | 在优化代码块之后触发的钩子 |
Compilation | optimizeTree | 在优化模块和代码块关系树时触发的钩子 |
Compilation | afterOptimizeTree | 在优化模块和代码块关系树之后触发的钩子 |
Compilation | optimizeChunkModules | 在优化代码块模块时触发的钩子 |
Compilation | afterOptimizeChunkModules | 在优化代码块模块之后触发的钩子 |
Compilation | shouldRecord | 在决定是否记录编译过程中触发的钩子 |
Compilation | reviveModules | 在恢复模块时触发的钩子 |
Compilation | beforeModuleIds | 在分配模块ID之前触发的钩子 |
Compilation | moduleIds | 在分配模块ID时触发的钩子 |
Compilation | optimizeModuleIds | 在优化模块ID时触发的钩子 |
Compilation | afterOptimizeModuleIds | 在优化模块ID之后触发的钩子 |
Compilation | reviveChunks | 在恢复代码块时触发的钩子 |
Compilation | beforeChunkIds | 在分配代码块ID之前触发的钩子 |
Compilation | chunkIds | 在分配代码块ID时触发的钩子 |
Compilation | optimizeChunkIds | 在优化代码块ID时触发的钩子 |
Compilation | afterOptimizeChunkIds | 在优化代码块ID之后触发的钩子 |
Compilation | recordModules | 在记录模块时触发的钩子 |
Compilation | recordChunks | 在记录代码块时触发的钩子 |
Compilation | optimizeCodeGeneration | 在优化代码生成时触发的钩子 |
Compilation | beforeModuleHash | 在模块哈希之前触发的钩子 |
Compilation | afterModuleHash | 在模块哈希之后触发的钩子 |
Compilation | beforeCodeGeneration | 在代码生成之前触发的钩子 |
Compilation | afterCodeGeneration | 在代码生成之后触发的钩子 |
Compilation | beforeRuntimeRequirements | 在处理运行时需求之前触发的钩子 |
Compilation | additionalModuleRuntimeRequirements | 在处理额外模块运行时需求时触发的钩子 |
Compilation | additionalChunkRuntimeRequirements | 在处理额外代码块运行时需求时触发的钩子 |
Compilation | additionalTreeRuntimeRequirements | 在处理额外依赖树运行时需求时触发的钩子 |
Compilation | runtimeModule | 在运行时模块创建时触发的钩子 |
Compilation | afterRuntimeRequirements | 在处理运行时需求之后触发的钩子 |
Compilation | beforeHash | 在哈希计算之前触发的钩子 |
Compilation | chunkHash | 在代码块哈希计算时触发的钩子 |
Compilation | contentHash | 在内容哈希计算时触发的钩子 |
Compilation | fullHash | 在完整哈希计算时触发的钩子 |
Compilation | afterHash | 在哈希计算之后触发的钩子 |
Compilation | recordHash | 在记录哈希时触发的钩子 |
Compilation | beforeModuleAssets | 在处理模块资源之前触发的钩子 |
Compilation | shouldGenerateChunkAssets | 在决定是否生成代码块资源时触发的钩子 |
Compilation | beforeChunkAssets | 在处理代码块资源之前触发的钩子 |
Compilation | renderManifest | 在渲染清单时触发的钩子 |
Compilation | assetPath | 在处理资源路径时触发的钩子 |
Compilation | chunkAsset | 在处理代码块资源时触发的钩子 |
Compilation | optimizeAssets | 在优化资源时触发的钩子 |
Compilation | processAssets | 在处理资源时触发的钩子 |
Compilation | afterOptimizeAssets | 在优化资源之后触发的钩子 |
Compilation | afterProcessAssets | 在处理资源之后触发的钩子 |
Compilation | record | 在记录编译结果时触发的钩子 |
Compilation | needAdditionalSeal | 在判断是否需要额外封装时触发的钩子 |
Compilation | afterSeal | 在封装之后触发的钩子 |
Compiler | afterCompile | 在编译完成后触发的钩子 |
Compiler | shouldEmit | 在决定是否输出文件前触发的钩子 |
Compiler | emit | 在输出文件时触发的钩子 |
Compilation | assetPath | 在处理资源路径时触发的钩子 |
Compiler | afterEmit | 在输出文件之后触发的钩子 |
Compilation | needAdditionalPass | 在判断是否需要额外的编译过程时触发的钩子 |
Compiler | emitRecords | 在输出记录时触发的钩子 |
Compiler | done | 在编译完成时触发的钩子 |
Compiler | shutdown | 在编译器关闭时触发的钩子 |
Compilation | statsNormalize | 在统计标准化时触发的钩子 |
Compilation | statsFactory | 在统计工厂创建时触发的钩子 |
Compilation | statsPrinter | 在统计打印机创建时触发的钩子 |
Compilation | processErrors | 在处理错误时触发的钩子 |
Compilation | processWarnings | 在处理警告时触发的钩子 |
debug.js
const webpack = require('./webpack/lib/webpack');
/**
* 1.读取和解析配置:Webpack首先读取配置文件(如:webpack.config.js),
* 将配置文件中的参数解析成一个配置对象。如果没有指定配置文件,Webpack会使用默认配置
*/
const config = require('./webpack.config');
const compiler = webpack(config);
compiler.run((err, stats) => {
console.log(stats)
});
webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
devtool: false,
entry: {
main: { import: './src/index.js' }
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
webpack\lib\webpack.js
const Compiler = require("./Compiler");
const { getNormalizedWebpackOptions } = require("./config/normalization");
const { applyWebpackOptionsDefaults, applyWebpackOptionsBaseDefaults } = require("./config/defaults");
const WebpackOptionsApply = require("./WebpackOptionsApply");
const NodeEnvironmentPlugin = require("./node/NodeEnvironmentPlugin");
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
const compiler = new Compiler(options.context, options);
new NodeEnvironmentPlugin().apply(compiler);
//4.注册插件:将配置中的插件实例化并挂载到Compiler上。插件会在构建过程的各个阶段通过监听钩子来影响构建结果
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
plugin.apply(compiler);
}
}
//5.初始化内置钩子:在初始化过程中,Webpack会初始化一些内置的钩子,用于在构建过程中触发一些事件
applyWebpackOptionsDefaults(options);
//6.触发environment钩子:在环境准备好之前,Compiler触发environment钩子事件
compiler.hooks.environment.call();
//7.触发afterEnvironment钩子:在环境准备好之后,Compiler触发afterEnvironment钩子事件
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
const webpack = options => {
//2.配置校验:Webpack使用schema-utils对解析得到的配置对象进行校验,确保配置参数正确无误
validateSchema(options);
//3.实例化Compiler:根据解析后的配置对象创建一个Compiler对象。Compiler对象是Webpack的核心,它负责管理整个构建过程
const compiler = createCompiler(options);
return compiler;
};
function validateSchema(options) {
return options;
}
module.exports = webpack;
webpack\lib\Compiler.js
const { SyncHook, SyncBailHook, AsyncParallelHook } = require("tapable");
class Compiler {
constructor(context, options) {
this.context = context;
this.options = options;
this.hooks = {
environment: new SyncHook(),
afterEnvironment: new SyncHook(),
initialize: new SyncHook(),
entryOption: new SyncBailHook(["context", "entry"]),
compilation: new SyncHook(["compilation", "params"]),
make: new AsyncParallelHook(["compilation"]),
afterPlugins: new SyncHook(["compiler"]),
afterResolvers: new SyncHook(["compiler"]),
};
}
//12.开始编译:调用Compiler的run方法开始执行编译过程。此时,Compiler会进入到构建流程的各个阶段,包括构建模块、分析依赖、优化等
run() {
this.compile();
}
compile() {
console.log('compile');
}
}
module.exports = Compiler;
webpack\lib\config\normalization.js
const getNormalizedWebpackOptions = config => {
return config;
};
exports.getNormalizedWebpackOptions = getNormalizedWebpackOptions;
webpack\lib\config\defaults.js
const applyWebpackOptionsDefaults = options => {
}
const applyWebpackOptionsBaseDefaults = options => {
};
exports.applyWebpackOptionsDefaults = applyWebpackOptionsDefaults;
exports.applyWebpackOptionsBaseDefaults = applyWebpackOptionsBaseDefaults;
webpack\lib\WebpackOptionsApply.js
const EntryOptionPlugin = require("./EntryOptionPlugin");
//const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
//const RuntimePlugin = require("./RuntimePlugin");
class WebpackOptionsApply {
process(options, compiler) {
//new JavascriptModulesPlugin().apply(compiler);
//8.触发entryOption钩子:在解析入口选项前,Compiler触发entryOption钩子事件
new EntryOptionPlugin().apply(compiler);
//触发entryOption事件执行
compiler.hooks.entryOption.call(options.context, options.entry);
//new RuntimePlugin().apply(compiler);
//10.触发afterPlugins钩子:在插件注册完毕后,Compiler触发afterPlugins钩子事件
compiler.hooks.afterPlugins.call(compiler);
//11.触发afterResolvers钩子:在解析器准备完毕后,Compiler触发afterResolvers钩子事件
compiler.hooks.afterResolvers.call(compiler);
}
}
module.exports = WebpackOptionsApply;
webpack\lib\node\NodeEnvironmentPlugin.js
const fs = require("fs");
class NodeEnvironmentPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.outputFileSystem = fs;
compiler.intermediateFileSystem = fs;
}
}
module.exports = NodeEnvironmentPlugin;
webpack\lib\EntryOptionPlugin.js
const EntryPlugin = require("./EntryPlugin");
class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
EntryOptionPlugin.applyEntryOption(compiler, context, entry);
return true;
});
}
static applyEntryOption(compiler, context, entry) {
for (const name of Object.keys(entry)) {
const desc = entry[name];
const options = { name }
for (const entry of desc.import) {
new EntryPlugin(context, entry, options).apply(compiler);
}
}
}
}
module.exports = EntryOptionPlugin;
webpack\lib\EntryPlugin.js
const EntryDependency = require("./dependencies/EntryDependency");
class EntryPlugin {
constructor(context, entry, options) {
this.context = context;
this.entry = entry;
this.options = options;
}
apply(compiler) {
compiler.hooks.compilation.tap("EntryPlugin", (compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(EntryDependency, normalModuleFactory);
});
//9.解析入口文件:根据配置对象的entry属性解析入口文件。Webpack会为每个入口文件创建一个Chunk,并确定各个模块之间的依赖关系
const { entry, options, context } = this;
const dep = new EntryDependency(entry);
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
compilation.addEntry(context, dep, options, callback);
});
}
}
module.exports = EntryPlugin;
webpack\lib\dependencies\EntryDependency.js
const ModuleDependency = require("./ModuleDependency");
class EntryDependency extends ModuleDependency {
constructor(request) {
super(request);
}
get type() {
return "entry";
}
get category() {
return "esm";
}
}
module.exports = EntryDependency;
webpack\lib\dependencies\ModuleDependency.js
const Dependency = require("../Dependency");
const DependencyTemplate = require("../DependencyTemplate");
class ModuleDependency extends Dependency {
constructor(request) {
super();
this.request = request;
}
}
ModuleDependency.Template = DependencyTemplate;
module.exports = ModuleDependency;
webpack\lib\Dependency.js
class Dependency {
}
module.exports = Dependency;
webpack\lib\DependencyTemplate.js
class DependencyTemplate {
apply(dependency, source, templateContext) {
throw new Error("Abstract method");
}
}
module.exports = DependencyTemplate;
addEntry
方法用于向编译过程中添加入口点addModuleTree
方法用于向Compilation中添加整个模块树,并构建相应的依赖关系handleModuleCreation
方法根据文件类型构建模块factorizeModule
方法的作用是使用模块工厂生产对应的模块create
方法用于创建一个新的 NormalModule 对象factorize
用于生成模块createModule
方法创建一个新的NormalModule实例,并设置模块的上下文、请求、解析器、工厂等属性addModule
向编译器中添加一个新的模块moduleGraph
记录模块依赖图buildModule
方法编译构建指定的模块NormalModule
的build方法是编译模块的过程,将模块的源代码转化为可在浏览器中执行的代码,并且会递归处理模块依赖runLoaders
方法用于执行与模块相关的所有 loader,并将每个 loader 处理后的结果传递给下一个 loader 或者传递给后续流程处理JavaScriptParser
的parse方法解析JavaScript代码,生成AST抽象语法树,并通过遍历AST树触发各种处理方法。acorn
的parse
方法是将源代码解析为抽象语法树(AST)的函数blockPreWalkStatements
方法用于遍历语句块中的每个语句,并在遍历每个语句前调用一个回调函数,可以用于代码转换等操作addDependency
方法将依赖资源添加为 Dependency 对象,并通过调用 module 对象的 addDependency 将 Dependency 对象转换为 Module 对象并添加到依赖数组中HarmonyImportSideEffectDependency
用于处理 ES6 import 语句,当 import 的模块不会导出任何值时,会产生该依赖。它表示该依赖仅仅是一种副作用,不产生任何导出和导入,可以在构建过程中被忽略掉HarmonyImportSpecifierDependency
是一个依赖项类型,它表示一个 JavaScript 模块导入另一个 JavaScript 模块中的特定部分(例如,变量、函数等)的依赖关系HarmonyCompatibilityDependency
用于处理 Webpack 和 ES6 模块之间的兼容性问题。当使用 ES6 模块时,由于 ES6 模块默认是严格模式,而 CommonJS 模块是非严格模式,所以在使用 ES6 模块时,需要特殊处理,将 ES6 模块转换为 CommonJS 模块ConstDependency
表示一个常量依赖关系,主要用于将源代码中的特定字符串替换为一个固定值(常量)。HarmonyExportExpressionDependency
:
HarmonyExportExpressionDependency 是一个表示 ES6 模块导出表达式的依赖类,用于处理模块中的 export 语句,以便在构建过程中正确解析和生成导出的代码。HarmonyExportHeaderDependency
用于处理 ES6 模块导出依赖。当使用 export default foo 这样的语法导出模块时,会产生该依赖handleParseResult
方法用于处理模块解析后的 AST 和依赖数组,进一步解析依赖并添加到模块对象的依赖数组中processModuleDependencies
方法用于处理模块依赖,包括分析依赖关系、创建依赖对象和处理循环依赖等Compilation
类中,维护一个全局唯一的 ModuleGraph
实例对象Module
、Dependency
以及模块间关系——ModuleConnection
记录到 compilation.moduleGraph
对象中_dependencyMap
属性:记录 Dependency
对象与 ModuleGraphConnection
连接对象之间的映射关系。在后续处理中,可以基于这个映射快速找到 Dependency
实例对应的引用者和被引用者_moduleMap
属性:记录 Module
与 ModuleGraphModule
之间的映射关系_dependencyMap
Map(3){
{
'EntryDependency'{request:'./src/index.js'}: ModuleGraphConnection{
"originModule": null,
"module": NormalModule["index.js"],
"dependency": EntryDependency['index.js'],
}
},
{
'HarmonyImportSideEffectDependency'{request:'./src/title.js'}:ModuleGraphConnection{
"originModule": NormalModule["index.js"],
"module": NormalModule["title.js"],
"dependency": HarmonyImportSideEffectDependency['title.js'],
}
}
}
_moduleMap
Map(3){
{
"NormalModule[index.js]": ModuleGraphModule{
"incomingConnections": [
ModuleGraphConnection{
"originModule": null,
"module": NormalModule["index.js"],
"dependency": EntryDependency['index.js'],
}
],
"outgoingConnections": [
ModuleGraphConnection{
"originModule": NormalModule["index.js"],
"module": NormalModule[name.js],
"dependency": HarmonyImportSideEffectDependency['name.js'],
},
ModuleGraphConnection{
"originModule": NormalModule["index.js"],
"module": NormalModule["age.js"],
"dependency": HarmonyImportSideEffectDependency['age.js'],
}
]
},
"NormalModule[title.js]": ModuleGraphModule{
"incomingConnections": [
ModuleGraphConnection{
"originModule": NormalModule["index.js"],
"module": NormalModule["title.js"],
"dependency": HarmonyImportSideEffectDependency['title.js'],
}
],
}
}
}
webpack.config.js
const path = require('path');
module.exports = {
context: process.cwd(),
mode: 'development',
devtool: false,
entry: {
main: { import: './src/index.js' },
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
src\index.js
import title from './title';
console.log('hello', title);
src\title.js
export default 'title';
index.js
const NormalModule = require("./NormalModule");
const ModuleGraph = require("./ModuleGraph");
const EntryDependency = require("./EntryDependency");
const ModuleGraphConnection = require("./ModuleGraphConnection");
const ModuleGraphModule = require("./ModuleGraphModule");
const modules = new Set()
const moduleGraph = new ModuleGraph();
const indexEntry = new EntryDependency("./src/index.js");
let indexModule;
let titleModule;
//1.`addEntry`方法用于向编译过程中添加入口点
addEntry(indexEntry);
function addEntry() {
//2.`addModuleTree`方法用于向Compilation中添加整个模块树,并构建相应的依赖关系
addModuleTree();
}
function addModuleTree() {
//3.`handleModuleCreation`方法根据文件类型构建模块
handleModuleCreation();
}
function handleModuleCreation() {
//4.`factorizeModule` 方法的作用是使用模块工厂生产对应的模块
factorizeModule();
}
function factorizeModule() {
//5.`create` 方法用于创建一个新的 NormalModule 对象
create();
}
function create() {
//6.`factorize` 用于生成模块
factorize();
}
function factorize() {
//7.`createParser` 用于创建一个新的 Parser 对象
createModule();
}
function createModule() {
indexModule = new NormalModule('./src/index.js');
//8.`addModule`向编译器中添加一个新的模块
addModule();
}
function addModule() {
modules.add(indexModule);
//9.moduleGraph记录模块依赖图
const moduleGraphConnection = new ModuleGraphConnection(null, indexModule, indexEntry);
moduleGraph._dependencyMap.set(indexEntry, moduleGraphConnection);
const moduleGraphModule = new ModuleGraphModule(indexModule);
moduleGraph._moduleMap.set(indexModule, moduleGraphModule);
//10.buildModule方法编译构建指定的模块
buildModule();
}
function buildModule() {
//11.`build`方法用于编译构建模块
build();
}
function build() {
//12.runLoaders 方法用于执行与模块相关的所有 loader,并将每个 loader 处理后的结果传递给下一个 loader 或者传递给后续流程处理
runLoaders();
}
function runLoaders() {
//13.JavaScriptParser的parse方法解析JavaScript代码,生成AST抽象语法树,并通过遍历AST树触发各种处理方法。
parse();
}
function parse() {
//15.blockPreWalkStatements方法用于遍历语句块中的每个语句,并在遍历每个语句前调用一个回调函数,可以用于代码转换等操作
blockPreWalkStatements();
}
function blockPreWalkStatements() {
//16.addDependency 方法将依赖资源添加为 Dependency 对象,并通过调用 module 对象的 addDependency 将 Dependency 对象转换为 Module 对象并添加到依赖数组中
addDependency();
}
const harmonyImportSideEffectDependency = new HarmonyImportSideEffectDependency()
function addDependency() {
indexModule.addDependency(new HarmonyCompatibilityDependency());
indexModule.addDependency(new HarmonyImportSideEffectDependency());
indexModule.addDependency(new HarmonyCompatibilityDependency());
indexModule.addDependency(new ConstDependency());
//17.handleParseResult 方法用于处理模块解析后的 AST 和依赖数组,进一步解析依赖并添加到模块对象的依赖数组中
handleParseResult();
}
function handleParseResult() {
//18.processModuleDependencies方法用于处理模块依赖,包括分析依赖关系、创建依赖对象和处理循环依赖等
processModuleDependencies();
}
function processModuleDependencies() {
titleModule = new NormalModule('./src/title.js');
titleModule.addDependency(new HarmonyCompatibilityDependency());
titleModule.addDependency(new HarmonyExportHeaderDependency());
titleModule.addDependency(new HarmonyExportExpressionDependency());
const moduleGraphConnection = new ModuleGraphConnection(indexModule, titleModule, harmonyImportSideEffectDependency);
moduleGraph._dependencyMap.set(harmonyImportSideEffectDependency, moduleGraphConnection);
const moduleGraphModule = new ModuleGraphModule(titleModule);
moduleGraph._moduleMap.set(titleModule, moduleGraphModule);
}
NormalModule.js
class NormalModule {
constructor(request) {
this.request = request;
this.dependencies = [];
}
}
module.exports = NormalModule;
ModuleGraph.js
class ModuleGraph {
constructor() {
this._dependencyMap = new Map();
this._moduleMap = new Map();
}
}
module.exports = ModuleGraph;
EntryDependency.js
class EntryDependency {
constructor(request) {
this.request = request;
}
}
module.exports = EntryDependency;
ModuleGraphConnection.js
class ModuleGraphConnection {
constructor(originModule, module, dependency) {
this.originModule = originModule;
this.module = module;
this.dependency = dependency;
}
}
module.exports = ModuleGraphConnection;
ModuleGraphModule.js
class ModuleGraphModule {
constructor(module) {
this.module = module;
this.incomingConnections = new Set();
this.outgoingConnections = new Set();
}
}
module.exports = ModuleGraphModule
let util = require('util');
class Chunk {
constructor(name) {
this.name = name;
this._groups = new Set();
}
}
class ChunkGraphChunk {
constructor() {
this.modules = new Set();
}
}
class ChunkGroup {
constructor(name) {
this.name = name;
this.chunks = new Set();
this._children = new Set();
this._parents = new Set();
}
}
class EntryPoint extends ChunkGroup {
constructor(name) {
super();
}
}
class NormalModule {
constructor(id) {
this.id = id;
}
}
class ChunkGraph {
constructor() {
this.chunks = new Map();
}
}
let chunkGraph = new ChunkGraph();
let entry1Chunk = new Chunk('entry1');
let entry1ChunkGraphChunk = new ChunkGraphChunk();
let entry1EntryPoint = new EntryPoint('entry1');
let entry1Module = new NormalModule('./src/entry1.js');
let entry1SyncModule = new NormalModule('./src/entry1_sync.js');
let entry1SyncSyncModule = new NormalModule('./src/entry1_sync_sync.js');
let syncCommonModule = new NormalModule('./src/sync_common.js');
entry1ChunkGraphChunk.modules.add(entry1Module);
entry1ChunkGraphChunk.modules.add(entry1SyncModule);
entry1ChunkGraphChunk.modules.add(entry1SyncSyncModule);
entry1ChunkGraphChunk.modules.add(syncCommonModule);
entry1EntryPoint.chunks.add(entry1Chunk);
entry1Chunk._groups.add(entry1EntryPoint);
chunkGraph.chunks.set(entry1Chunk, entry1ChunkGraphChunk);
let entry2Chunk = new Chunk('entry2');
let entry2ChunkGraphChunk = new ChunkGraphChunk();
let entry2EntryPoint = new EntryPoint('entry2');
let entry2Module = new NormalModule('./src/entry2.js');
let entry2SyncModule = new NormalModule('./src/entry2_sync.js');
let entry2SyncSyncModule = new NormalModule('./src/entry2_sync_sync.js');
entry2ChunkGraphChunk.modules.add(entry2Module);
entry2ChunkGraphChunk.modules.add(entry2SyncModule);
entry2ChunkGraphChunk.modules.add(entry2SyncSyncModule);
entry2ChunkGraphChunk.modules.add(syncCommonModule);
entry2EntryPoint.chunks.add(entry2Chunk);
entry2Chunk._groups.add(entry2EntryPoint);
chunkGraph.chunks.set(entry2Chunk, entry2ChunkGraphChunk);
let entry1AsyncChunk = new Chunk('entry1_async');
let entry1AsyncChunkGraphChunk = new ChunkGraphChunk();
let entry1AsyncModule = new NormalModule('./src/entry1_async.js');
let entry1AsyncSyncModule = new NormalModule('./src/entry1_async_sync.js');
entry1AsyncChunkGraphChunk.modules.add(entry1AsyncModule);
entry1AsyncChunkGraphChunk.modules.add(entry1AsyncSyncModule);
let entry1AsyncChunkGroup = new ChunkGroup('entry1_async');
entry1AsyncChunkGroup.chunks.add(entry1AsyncChunk);
entry1AsyncChunkGroup._parents.add(entry1EntryPoint);
entry1EntryPoint._children.add(entry1AsyncChunkGroup);
entry1AsyncChunk._groups.add(entry1AsyncChunkGroup);
chunkGraph.chunks.set(entry1AsyncChunk, entry1AsyncChunkGraphChunk);
let entry2AsyncChunk = new Chunk('entry2_async');
let entry2AsyncChunkGraphChunk = new ChunkGraphChunk();
let entry2AsyncModule = new NormalModule('./src/entry2_async.js');
let entry2AsyncSyncModule = new NormalModule('./src/entry2_async_sync.js');
entry2AsyncChunkGraphChunk.modules.add(entry2AsyncModule);
entry2AsyncChunkGraphChunk.modules.add(entry2AsyncSyncModule);
let entry2AsyncChunkGroup = new ChunkGroup('entry2_async');
entry2AsyncChunkGroup.chunks.add(entry2AsyncChunk);
entry2AsyncChunkGroup._parents.add(entry2EntryPoint);
entry2EntryPoint._children.add(entry2AsyncChunkGroup);
entry2AsyncChunk._groups.add(entry2AsyncChunkGroup);
chunkGraph.chunks.set(entry2AsyncChunk, entry2AsyncChunkGraphChunk);
console.log(util.inspect(chunkGraph, true, Infinity));
ChunkGraph {
chunks: Map(4) {
Chunk {
name: 'entry1',
_groups: Set(1) {
EntryPoint {
name: undefined,
chunks: Set(1) { [Circular *1] },
_children: Set(1) {
ChunkGroup {
name: 'entry1_async',
chunks: Set(1) {
Chunk {
name: 'entry1_async',
_groups: Set(1) { [Circular *2] }
}
},
_children: Set(0) {},
_parents: Set(1) { [Circular *3] }
}
},
_parents: Set(0) {}
}
}
} => ChunkGraphChunk {
modules: Set(4) {
NormalModule { id: './src/entry1.js' },
NormalModule { id: './src/entry1_sync.js' },
NormalModule { id: './src/entry1_sync_sync.js' },
NormalModule { id: './src/sync_common.js' }
}
},
Chunk {
name: 'entry2',
_groups: Set(1) {
EntryPoint {
name: undefined,
chunks: Set(1) { [Circular *4] },
_children: Set(1) {
ChunkGroup {
name: 'entry2_async',
chunks: Set(1) {
Chunk {
name: 'entry2_async',
_groups: Set(1) { [Circular *5] }
}
},
_children: Set(0) {},
_parents: Set(1) { [Circular *6] }
}
},
_parents: Set(0) {}
}
}
} => ChunkGraphChunk {
modules: Set(4) {
NormalModule { id: './src/entry2.js' },
NormalModule { id: './src/entry2_sync.js' },
NormalModule { id: './src/entry2_sync_sync.js' },
NormalModule { id: './src/sync_common.js' }
}
},
Chunk {
name: 'entry1_async',
_groups: Set(1) {
ChunkGroup {
name: 'entry1_async',
chunks: Set(1) { [Circular *7] },
_children: Set(0) {},
_parents: Set(1) {
EntryPoint {
name: undefined,
chunks: Set(1) {
Chunk {
name: 'entry1',
_groups: Set(1) { [Circular *3] }
}
},
_children: Set(1) { [Circular *2] },
_parents: Set(0) {}
}
}
}
}
} => ChunkGraphChunk {
modules: Set(2) {
NormalModule { id: './src/entry1_async.js' },
NormalModule { id: './src/entry1_async_sync.js' }
}
},
Chunk {
name: 'entry2_async',
_groups: Set(1) {
ChunkGroup {
name: 'entry2_async',
chunks: Set(1) { [Circular *8] },
_children: Set(0) {},
_parents: Set(1) {
EntryPoint {
name: undefined,
chunks: Set(1) {
Chunk {
name: 'entry2',
_groups: Set(1) { [Circular *6] }
}
},
_children: Set(1) { [Circular *5] },
_parents: Set(0) {}
}
}
}
}
} => ChunkGraphChunk {
modules: Set(2) {
NormalModule { id: './src/entry2_async.js' },
NormalModule { id: './src/entry2_async_sync.js' }
}
}
}
}
let commonChunk = new Chunk('common');
let commonChunkGraphChunk = new ChunkGraphChunk();
commonChunkGraphChunk.modules.add(syncCommonModule);
commonChunk._groups.add(entry1AsyncChunkGroup);
commonChunk._groups.add(entry2AsyncChunkGroup);
entry1AsyncChunkGroup.chunks.add(commonChunk);
entry2AsyncChunkGroup.chunks.add(commonChunk);
chunkGraph.chunks.set(commonChunk, commonChunkGraphChunk);
entry1ChunkGraphChunk.modules.delete(syncCommonModule)
entry2ChunkGraphChunk.modules.delete(syncCommonModule)
console.log(util.inspect(chunkGraph, true, Infinity));
src\entry1.js
import entry1_sync from './entry1_sync';
import sync_common from './sync_common';
console.log('entry1_sync', entry1_sync);
console.log('sync_common', sync_common);
import('./entry1_async').then(entry1_async => {
console.log('entry1_async', entry1_async);
});
src\entry1_sync.js
import entry1_sync_sync from './entry1_sync_sync';
console.log('entry1_sync_sync', entry1_sync_sync);
export default 'entry1_sync';
src\entry1_sync_sync.js
export default 'entry1_sync_sync';
src\sync_common.js
export default 'sync_common';
src\entry1_async.js
import entry1_async_sync from './entry1_async_sync';
console.log('entry1_async_sync', entry1_async_sync);
export default 'entry1_async';
src\entry1_async_sync.js
export default 'entry1_async_sync';
src\entry1.js
import entry2_sync from './entry2_sync';
import sync_common from './sync_common';
console.log('entry2_sync', entry2_sync);
console.log('sync_common', sync_common);
import('./entry2_async').then(entry2_async => {
console.log('entry2_async', entry2_async);
});
src\entry2_sync.js
import entry2_sync_sync from './entry2_sync_sync';
console.log('entry2_sync_sync', entry2_sync_sync);
export default 'entry2_sync';
src\entry2_sync_sync.js
export default 'entry2_sync_sync';
src\entry2_async.js
import entry1_async_sync from './entry2_async_sync';
console.log('entry2_async_sync', entry1_async_sync);
export default 'entry2_async';
src\entry2_async_sync.js
export default 'entry2_async_sync';
codeGeneration
方法的主要任务是从一个模块实例生成代码。流程如下:generator
生成模块的原始代码(source code),这个步骤会将源代码从不同的资源或加载器转换为 JavaScript 代码dependencyTemplates
处理模块的依赖关系。遍历模块的所有依赖,使用相应的 dependencyTemplate
生成依赖相关的代码,并将其插入到原始代码中CodeGenerationResult
对象并返回processRuntimeRequirements
处理与运行时有关的需求和生成相关的代码runtimeTemplate.generate
方法,生成与运行时相关的代码片段Compilation.assets
中,以便在最后的输出阶段将其写入文件系统{
runtime: string, // 当前的运行时名称
module: Module, // 当前正在处理的模块实例
chunk: Chunk, // 当前的 chunk 实例
runtimeRequirements: Set, // 运行时需求的集合
runtimeTemplate: RuntimeTemplate, // 运行时模板,用于生成运行时相关的代码
dependencyTemplates: DependencyTemplates, // 依赖模板映射
moduleGraph: ModuleGraph, // 模块图,存储模块间的依赖关系
chunkGraph: ChunkGraph, // chunk 图,存储 chunk 间的依赖关系
}
index.js
let NormalModule = require('./NormalModule');
let Chunk = require('./Chunk');
let ChunkGraphChunk = require('./ChunkGraphChunk');
let EntryPoint = require('./Entrypoint');
let HarmonyImportSideEffectDependency = require('./HarmonyImportSideEffectDependency');
let HarmonyImportSpecifierDependency = require('./HarmonyImportSpecifierDependency');
let ConstDependency = require('./ConstDependency');
let HarmonyCompatibilityDependency = require('./HarmonyCompatibilityDependency');
let HarmonyExportExpressionDependency = require('./HarmonyExportExpressionDependency');
let HarmonyExportHeaderDependency = require('./HarmonyExportHeaderDependency');
let JavascriptModulesPlugin = require('./JavascriptModulesPlugin');
let modules = new Set();
let mainChunk = new Chunk('main');
let mainChunkGraphChunk = new ChunkGraphChunk();
let mainEntryPoint = new EntryPoint('main');
mainChunk._groups.add(mainEntryPoint);
mainEntryPoint.chunks.add(mainChunk);
let indexModule = new NormalModule('./src/index.js',
[
`import title from './title';`,
`console.log('hello', title);`
]
, [
new HarmonyImportSideEffectDependency(),
new HarmonyImportSpecifierDependency(),
new ConstDependency(),
new HarmonyCompatibilityDependency()
]);
let titleModule = new NormalModule(
'./src/title.js',
[`export default 'title';`]
, [
new HarmonyExportExpressionDependency(),
new HarmonyCompatibilityDependency(),
new HarmonyExportHeaderDependency()
]);
modules.add(indexModule);
modules.add(titleModule);
let chunks = new Map();
chunks.set(mainEntryPoint, mainChunkGraphChunk);
for (const module of modules) {
let source = module.codeGeneration();
module._source = source;
}
let javascriptModulesPlugin = new JavascriptModulesPlugin();
for (const chunk of chunks.values()) {
let source = javascriptModulesPlugin.renderMain(chunk);
console.log(source);
}
NormalModule.js
const JavascriptGenerator = require('./JavascriptGenerator');
class NormalModule {
constructor(id, _source, dependencies) {
this.id = id;
this._source = _source;
this.dependencies = dependencies;
this.generator = new JavascriptGenerator();
}
codeGeneration() {
const source = this.generator.generate(this);
return source;
}
}
module.exports = NormalModule;
Chunk.js
class Chunk {
constructor(name) {
this.name = name;
this._groups = new Set();
}
}
module.exports = Chunk;
seal\ChunkGraphChunk.js
class ChunkGraphChunk {
constructor() {
this.modules = new Set();
}
}
module.exports = ChunkGraphChunk;
EntryPoint
const ChunkGroup = require('./ChunkGroup.js');
class EntryPoint extends ChunkGroup {}
module.exports = EntryPoint;
HarmonyImportSideEffectDependency
const InitFragment = require('./InitFragment')
class HarmonyImportSideEffectDependency {
}
HarmonyImportSideEffectDependency.Template = class HarmonyImportSideEffectDependencyTemplate {
apply(dependency, source, initFragments) {
const importStatement = 'var _title__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/title.js");\n'
initFragments.push(
new InitFragment(importStatement)
)
}
}
module.exports = HarmonyImportSideEffectDependency;
HarmonyImportSpecifierDependency
class HarmonyImportSpecifierDependency {
}
HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependencyTemplate {
apply(dependency, source) {
const exportExpr = `, _title__WEBPACK_IMPORTED_MODULE_0__["default"]`;
source[0] = source[0].replace(`, title`, exportExpr);
}
}
module.exports = HarmonyImportSpecifierDependency;
ConstDependency
class ConstDependency {
}
ConstDependency.Template = class ConstDependencyTemplate {
apply(dependency, source, initFragments) {
source[0] = source[0].replace(`import title from './title';\r\n`, '');
}
}
module.exports = ConstDependency;
HarmonyCompatibilityDependency
const InitFragment = require("./InitFragment");
class HarmonyCompatibilityDependency {
}
HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate {
apply(dependency, source, initFragments) {
const content = `__webpack_require__.r(__webpack_exports__);\n`;
initFragments.push(
new InitFragment(content)
)
}
}
module.exports = HarmonyCompatibilityDependency;
HarmonyExportExpressionDependency
class HarmonyExportExpressionDependency {
}
HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTemplate {
apply(dependency, source, initFragments) {
//runtimeRequirements.add(RuntimeGlobals.exports);
/**
*__webpack_require__.d(__webpack_exports__, {
"default": () => (__WEBPACK_DEFAULT_EXPORT__)
});
*/
//initFragments.push(new HarmonyExportInitFragment(exportsName, map));
let content = `
const __WEBPACK_DEFAULT_EXPORT__ = ();
`
}
};
module.exports = HarmonyExportExpressionDependency;
HarmonyExportHeaderDependency
class HarmonyExportHeaderDependency {
}
HarmonyExportHeaderDependency.Template = class HarmonyExportHeaderDependencyTemplate {
apply(dependency, source, initFragments) {
source[0].replace('export default', '');
}
};
module.exports = HarmonyExportHeaderDependency;
JavascriptModulesPlugin
class JavascriptModulesPlugin {
renderMain(chunk) {
let code = `
(() => {
var webpackModules = {
"./src/title.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
webpackRequire.r(webpackExports);
webpackRequire.d(webpackExports, {
"default": () => webpackDefaultExport
});
const webpackDefaultExport = 'title';
}
};
var webpackModuleCache = {};
function webpackRequire(moduleId) {
var cachedModule = webpackModuleCache[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = webpackModuleCache[moduleId] = {
exports: {}
};
webpackModules[moduleId](module, module.exports, webpackRequire);
return module.exports;
}
(() => {
webpackRequire.d = (exports, definition) => {
for (var key in definition) {
if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key]
});
}
}
};
})();
(() => {
webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
webpackRequire.r = exports => {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
});
}
Object.defineProperty(exports, 'esmodule', {
value: true
});
};
})();
var webpackExports = {};
(() => {
webpackRequire.r(webpackExports);
var _titlewebpackImportedModule0 = webpackRequire("./src/title.js");
console.log('hello', _titlewebpackImportedModule0["default"]);
})();
})();
`;
return code;
}
}
module.exports = JavascriptModulesPlugin;
InitFragment.js
class InitFragment {
constructor(content, endContent) {
this.content = content;
this.endContent = endContent;
}
static addToSource(source, initFragments) {
let concatSource = [];
const endContents = [];
for (const initFragment of initFragments) {
concatSource.push(initFragment.content);
if (initFragment.endContent)
endContents.push(initFragment.endContent);
}
concatSource.push(source);
for (const content of endContents.reverse()) {
concatSource.push(content);
}
return concatSource;
}
}
module.exports = InitFragment;
JavascriptGenerator.js
const InitFragment = require("./InitFragment");
class JavascriptGenerator {
generate(module) {
const source = module._source;
const initFragments = [];
for (const dependency of module.dependencies) {
const template = new dependency.constructor.Template();
template.apply(dependency, source, initFragments);
}
return InitFragment.addToSource(source, initFragments);
}
}
module.exports = JavascriptGenerator;
seal\JavascriptModulesPlugin.js.js
class JavascriptModulesPlugin {
renderMain(chunk) {
let code = `
(() => {
var webpackModules = {
"./src/title.js": (unusedWebpackModule, webpackExports, webpackRequire) => {
webpackRequire.r(webpackExports);
webpackRequire.d(webpackExports, {
"default": () => webpackDefaultExport
});
const webpackDefaultExport = 'title';
}
};
var webpackModuleCache = {};
function webpackRequire(moduleId) {
var cachedModule = webpackModuleCache[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = webpackModuleCache[moduleId] = {
exports: {}
};
webpackModules[moduleId](module, module.exports, webpackRequire);
return module.exports;
}
(() => {
webpackRequire.d = (exports, definition) => {
for (var key in definition) {
if (webpackRequire.o(definition, key) && !webpackRequire.o(exports, key)) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key]
});
}
}
};
})();
(() => {
webpackRequire.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
webpackRequire.r = exports => {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: 'Module'
});
}
Object.defineProperty(exports, 'esmodule', {
value: true
});
};
})();
var webpackExports = {};
(() => {
webpackRequire.r(webpackExports);
var _titlewebpackImportedModule0 = webpackRequire("./src/title.js");
console.log('hello', _titlewebpackImportedModule0["default"]);
})();
})();
`;
return code;
}
}
module.exports = JavascriptModulesPlugin;
html-webpack-plugin.js
const path = require('path');
const fs = require('fs');
class HtmlWebpackPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('SimplifiedHtmlWebpackPlugin', (compilation, callback) => {
// 读取模板文件
fs.readFile(this.options.template, 'utf8', (err, data) => {
if (err) throw err;
// 替换模板中的占位符
const scriptTags = Object.keys(compilation.assets)
.filter(file => file.endsWith('.js'))
.map(file => `<script defer src="${file}"></script>`)
.join('\n');
const html = data.replace('</head>', `${scriptTags}\n</head>`);
// 将生成的 HTML 添加到 Webpack 的输出中
compilation.assets['index.html'] = {
source: () => html,
size: () => html.length
};
callback();
});
});
}
}
module.exports = HtmlWebpackPlugin;