1.webpack打包过程 #

1.1 初始化阶段 #

2.流程钩子 #

对象 钩子 解释
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 在处理警告时触发的钩子

3.初始化阶段 #

3.1 工作流程 #

3.2 初始化阶段 #

3.2.1 debug.js #

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)
});

3.2.2 webpack.config.js #

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'
    }
}

3.2.3 webpack.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;

3.2.4 Compiler.js #

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;

3.2.5 normalization.js #

webpack\lib\config\normalization.js

const getNormalizedWebpackOptions = config => {
    return config;
};
exports.getNormalizedWebpackOptions = getNormalizedWebpackOptions;

3.2.6 defaults.js #

webpack\lib\config\defaults.js

const applyWebpackOptionsDefaults = options => {

}
const applyWebpackOptionsBaseDefaults = options => {

};
exports.applyWebpackOptionsDefaults = applyWebpackOptionsDefaults;
exports.applyWebpackOptionsBaseDefaults = applyWebpackOptionsBaseDefaults;

3.2.7 WebpackOptionsApply.js #

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;

3.2.8 NodeEnvironmentPlugin.js #

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;

3.2.9 EntryOptionPlugin.js #

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;

3.2.10 EntryPlugin.js #

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;

3.2.11 EntryDependency.js #

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;

3.2.12 ModuleDependency.js #

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;

3.2.13 Dependency.js #

webpack\lib\Dependency.js

class Dependency {
}
module.exports = Dependency;

3.2.14 DependencyTemplate.js #

webpack\lib\DependencyTemplate.js

class DependencyTemplate {
    apply(dependency, source, templateContext) {
        throw new Error("Abstract method");
    }
}
module.exports = DependencyTemplate;

4.构建阶段 #

4.1 构建流程 #

4.2 ModuleGraph #

_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'],
                }
                        ],
        }
    }
}

4.3 代码 #

4.3.1 webpack.config.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'
    }
}

4.3.2 src\index.js #

src\index.js

import title from './title';
console.log('hello', title);

4.3.3 src\title.js #

src\title.js

export default 'title';

4.3.4 index.js #

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);
}

4.3.5 NormalModule.js #

NormalModule.js

class NormalModule {
    constructor(request) {
        this.request = request;
        this.dependencies = [];
    }
}
module.exports = NormalModule;

4.3.6 ModuleGraph.js #

ModuleGraph.js

class ModuleGraph {
    constructor() {
        this._dependencyMap = new Map();
        this._moduleMap = new Map();
    }

}
module.exports = ModuleGraph;

4.3.7 EntryDependency.js #

EntryDependency.js

class EntryDependency {
    constructor(request) {
        this.request = request;
    }
}
module.exports = EntryDependency;

4.3.8 ModuleGraphConnection.js #

ModuleGraphConnection.js

class ModuleGraphConnection {
    constructor(originModule, module, dependency) {
        this.originModule = originModule;
        this.module = module;
        this.dependency = dependency;
    }
}
module.exports = ModuleGraphConnection;

4.3.9 ModuleGraphModule.js #

ModuleGraphModule.js

class ModuleGraphModule {
    constructor(module) {
        this.module = module;
        this.incomingConnections = new Set();
        this.outgoingConnections = new Set();
    }
}
module.exports = ModuleGraphModule

5.封装阶段 #

5.1 工作流程 #

5.2 数据结构 #

5.2.1 伪代码 #

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' }
      }
    }
  }
}

5.2.2 提取公共代码 #

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));

5.2.3 src\entry1.js #

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);
});

5.2.4 src\entry1_sync.js #

src\entry1_sync.js

import entry1_sync_sync from './entry1_sync_sync';
console.log('entry1_sync_sync', entry1_sync_sync);
export default 'entry1_sync';

5.2.5 src\entry1_sync_sync.js #

src\entry1_sync_sync.js

export default 'entry1_sync_sync';

5.2.6 src\sync_common.js #

src\sync_common.js

export default 'sync_common';

5.2.7 src\entry1_async.js #

src\entry1_async.js

import entry1_async_sync from './entry1_async_sync';
console.log('entry1_async_sync', entry1_async_sync);
export default 'entry1_async';

5.2.8 src\entry1_async_sync.js #

src\entry1_async_sync.js

export default 'entry1_async_sync';

5.2.9 src\entry2.js #

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);
});

5.2.10 src\entry2_sync.js #

src\entry2_sync.js

import entry2_sync_sync from './entry2_sync_sync';
console.log('entry2_sync_sync', entry2_sync_sync);
export default 'entry2_sync';

5.2.11 src\entry2_sync_sync.js #

src\entry2_sync_sync.js

export default 'entry2_sync_sync';

5.2.12 src\entry2_async.js #

src\entry2_async.js

import entry1_async_sync from './entry2_async_sync';
console.log('entry2_async_sync', entry1_async_sync);
export default 'entry2_async';

5.2.13 src\entry2_async_sync.js #

src\entry2_async_sync.js

export default 'entry2_async_sync';

6.代码生成 #

6.1 生成流程 #

{
  runtime: string, // 当前的运行时名称
  module: Module, // 当前正在处理的模块实例
  chunk: Chunk, // 当前的 chunk 实例
  runtimeRequirements: Set, // 运行时需求的集合
  runtimeTemplate: RuntimeTemplate, // 运行时模板,用于生成运行时相关的代码
  dependencyTemplates: DependencyTemplates, // 依赖模板映射
  moduleGraph: ModuleGraph, // 模块图,存储模块间的依赖关系
  chunkGraph: ChunkGraph, // chunk 图,存储 chunk 间的依赖关系
}

6.2 数据结构 #

6.2.1 index.js #

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);
}

6.2.2 NormalModule.js #

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;

6.2.3 Chunk.js #

Chunk.js

class Chunk {
    constructor(name) {
        this.name = name;
        this._groups = new Set();
    }
}
module.exports = Chunk;

6.2.4 ChunkGraphChunk.js #

seal\ChunkGraphChunk.js

class ChunkGraphChunk {
    constructor() {
        this.modules = new Set();
    }
}
module.exports = ChunkGraphChunk;

6.2.5 EntryPoint #

EntryPoint

const ChunkGroup = require('./ChunkGroup.js');
class EntryPoint extends ChunkGroup {}
module.exports = EntryPoint;

6.2.6 HarmonyImportSideEffectDependency #

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;

6.2.7 HarmonyImportSpecifierDependency #

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;

6.2.8 ConstDependency #

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;

6.2.9 HarmonyCompatibilityDependency #

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;

6.2.10 HarmonyExportExpressionDependency #

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;

6.2.11 HarmonyExportHeaderDependency #

HarmonyExportHeaderDependency


class HarmonyExportHeaderDependency {

}

HarmonyExportHeaderDependency.Template = class HarmonyExportHeaderDependencyTemplate {
    apply(dependency, source, initFragments) {
        source[0].replace('export default', '');
    }
};

module.exports = HarmonyExportHeaderDependency;

6.2.12 JavascriptModulesPlugin #

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;


6.2.13 InitFragment.js #

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;

6.2.14 JavascriptGenerator.js #

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;

6.2.15 seal\JavascriptModulesPlugin.js.js #

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;

7. 实现html-webpack-plugin #

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;