webpack 核心编译器 十二 节

assignDepths(modules)

  • 作用:为一组模块计算并分配深度,深度表示模块在依赖图中的层级。
  • 实现:使用广度优先搜索(BFS)遍历所有模块,并按层级(深度)进行标记。
js 复制代码
/**
 * 为一组模块分配深度(深度表示模块在依赖图中的层级)。
 * 该方法使用广度优先搜索(BFS)计算模块的深度。
 * @param {Set<Module>} modules 需要分配深度的模块集合
 * @returns {void}
 */
assignDepths(modules) {
    const moduleGraph = this.moduleGraph;

    /** @type {Set<Module>} */
    const queue = new Set(modules); // 维护一个队列,存储待处理的模块
    let nextDepthAt = queue.size; // 记录当前深度的边界
    let depth = 0; // 当前深度计数

    let i = 0;
    for (const module of queue) {
        moduleGraph.setDepth(module, depth); // 设定模块的深度
        // 获取该模块的所有出边(依赖的模块)
        const connections = moduleGraph.getOutgoingConnectionsByModule(module);
        if (connections) {
            for (const refModule of connections.keys()) {
                if (refModule) queue.add(refModule);
            }
        }
        i++;
        // 由于是 BFS,所以当前深度的所有模块处理完后,深度 +1
        if (i >= nextDepthAt) {
            depth++;
            nextDepthAt = queue.size;
        }
    }
}

getDependencyReferencedExports(dependency, runtime)

  • 作用:获取某个依赖在特定运行时(runtime)中的引用导出信息。
  • 实现 :调用 dependency.getReferencedExports() 获取信息,并通过插件钩子 hooks.dependencyReferencedExports.call 允许外部修改结果。
js 复制代码
/**
 * 获取给定依赖项在特定运行时(runtime)中的引用导出(referenced exports)。
 * 这通常用于确定模块的哪些导出被使用,以便进行优化。
 * @param {Dependency} dependency 需要查询的依赖项
 * @param {RuntimeSpec} runtime 运行时环境
 * @returns {(string[] | ReferencedExport)[]} 依赖项引用的导出信息
 */
getDependencyReferencedExports(dependency, runtime) {
    // 获取依赖项的引用导出
    const referencedExports = dependency.getReferencedExports(
        this.moduleGraph,
        runtime
    );
    // 通过 hooks 允许外部插件修改引用的导出信息
    return this.hooks.dependencyReferencedExports.call(
        referencedExports,
        dependency,
        runtime
    );
}

removeReasonsOfDependencyBlock(module, block)

  • 作用:移除某个依赖块对模块的引用,从模块图中删除无效依赖。
  • 实现 :递归遍历 block.blocksblock.dependencies,移除依赖并更新 moduleGraphchunkGraph
js 复制代码
/**
 * 移除某个依赖块(Dependency Block)对模块的引用。
 * 这通常用于优化和清理依赖关系,以移除不必要的模块连接。
 * @param {Module} module 目标模块
 * @param {DependenciesBlockLike} block 依赖块(包含多个依赖项)
 * @returns {void}
 */
removeReasonsOfDependencyBlock(module, block) {
    if (block.blocks) {
        for (const b of block.blocks) {
            this.removeReasonsOfDependencyBlock(module, b);
        }
    }

    if (block.dependencies) {
        for (const dep of block.dependencies) {
            const originalModule = this.moduleGraph.getModule(dep);
            if (originalModule) {
                // 移除依赖项的连接
                this.moduleGraph.removeConnection(dep);

                // 如果 ChunkGraph 存在,则更新 Chunk 依赖关系
                if (this.chunkGraph) {
                    for (const chunk of this.chunkGraph.getModuleChunks(
                        originalModule
                    )) {
                        this.patchChunksAfterReasonRemoval(originalModule, chunk);
                    }
                }
            }
        }
    }
}

patchChunksAfterReasonRemoval(module, chunk)

  • 作用:当删除某个模块的依赖后,更新其所在的 Chunk(代码块),如果不再有理由存在于 Chunk,则移除。
  • 实现:检查模块是否仍然需要该 Chunk,如果不需要,则移除 Chunk 依赖。
js 复制代码
/**
 * 在删除某个模块的依赖关系后,更新模块与代码块(Chunk)之间的关系。
 * 如果模块不再有任何理由属于某个 Chunk,则将其移除。
 * @param {Module} module 需要检查的模块
 * @param {Chunk} chunk 需要检查的代码块
 * @returns {void}
 */
patchChunksAfterReasonRemoval(module, chunk) {
    // 如果模块在该 Chunk 的 runtime 里不再有任何依赖,则移除其依赖
    if (!module.hasReasons(this.moduleGraph, chunk.runtime)) {
        this.removeReasonsOfDependencyBlock(module, module);
    }
    // 如果模块不再属于该 Chunk,则断开连接并移除 Chunk 依赖
    if (
        !module.hasReasonForChunk(chunk, this.moduleGraph, this.chunkGraph) &&
        this.chunkGraph.isModuleInChunk(module, chunk)
    ) {
        this.chunkGraph.disconnectChunkAndModule(chunk, module);
        this.removeChunkFromDependencies(module, chunk);
    }
}

removeChunkFromDependencies(block, chunk)

  • 作用:从依赖块中递归移除代码块(Chunk),确保不必要的 Chunk 被删除。
  • 实现 :遍历 block.blocks,获取 chunkGroup,然后递归移除 chunk,并更新依赖关系。
js 复制代码
/**
 * 从给定的依赖块(Dependencies Block)中移除指定的代码块(Chunk)。
 * 该方法会递归处理异步依赖块,并移除相关代码块。
 * @param {DependenciesBlock} block 依赖块
 * @param {Chunk} chunk 需要移除的代码块
 * @returns {void}
 */
removeChunkFromDependencies(block, chunk) {
    /**
     * 处理单个依赖项,如果依赖的模块存在,则调用 `patchChunksAfterReasonRemoval`
     * @param {Dependency} d 依赖项
     */
    const iteratorDependency = d => {
        const depModule = this.moduleGraph.getModule(d);
        if (!depModule) {
            return;
        }
        this.patchChunksAfterReasonRemoval(depModule, chunk);
    };

    const blocks = block.blocks;
    for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) {
        const asyncBlock = blocks[indexBlock];
        const chunkGroup =
            /** @type {ChunkGroup} */
            (this.chunkGraph.getBlockChunkGroup(asyncBlock));
        // 获取 ChunkGroup 内所有的 chunks
        const chunks = chunkGroup.chunks;
        // 移除所有 chunks,并递归调用
        for (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) {
            const iteratedChunk = chunks[indexChunk];
            chunkGroup.removeChunk(iteratedChunk);
            this.removeChunkFromDependencies(block, iteratedChunk);
        }
    }

    if (block.dependencies) {
        for (const dep of block.dependencies) iteratorDependency(dep);
    }
}

assignRuntimeIds()

  • 作用:为所有入口点(Entrypoints)分配运行时 ID(Runtime ID),用于唯一标识入口点的运行时环境。
  • 实现 :遍历所有同步和异步入口点,将 runtime 关联到 chunkGraph 并赋予唯一 ID。
js 复制代码
/**
 * 为所有入口点分配运行时 ID(Runtime ID)。
 * 该方法遍历所有入口点(同步和异步),并为每个入口点的运行时分配唯一的标识符。
 * @returns {void}
 */
assignRuntimeIds() {
    const { chunkGraph } = this;

    /**
     * 为单个入口点分配运行时 ID
     * @param {Entrypoint} ep 入口点
     */
    const processEntrypoint = ep => {
        const runtime = /** @type {string} */ (ep.options.runtime || ep.name);
        const chunk = /** @type {Chunk} */ (ep.getRuntimeChunk());
        chunkGraph.setRuntimeId(runtime, /** @type {ChunkId} */(chunk.id));
    };

    // 处理所有同步入口点
    for (const ep of this.entrypoints.values()) {
        processEntrypoint(ep);
    }
    // 处理所有异步入口点
    for (const ep of this.asyncEntrypoints) {
        processEntrypoint(ep);
    }
}
相关推荐
憧憬成为web高手5 小时前
ACTF 12307复现
前端·bootstrap·html
wordbaby6 小时前
Axios 上传大文件崩溃:鸿蒙 RNOH 下 XHR 返回空响应头引发的"假失败"
前端·react native
wordbaby6 小时前
React Native 列表分页实战:下拉刷新与上拉加载的工程化方案
前端·react native
wordbaby7 小时前
脱离 Tab 栏的艺术:React Native 全屏子页面的导航架构实践
前端·react native·harmonyos
陈随易7 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
wordbaby7 小时前
React Native 新架构落地鸿蒙:跨三端政务级应用的工程实践与深度复盘
前端·react native·harmonyos
excel8 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
ZC跨境爬虫9 小时前
模块化烹饪小程序开发日记 Day7:(菜谱详情接口开发与JSON数据读取全流程)
前端·javascript·css·ui·微信小程序·json
এ慕ོ冬℘゜9 小时前
JS 前端基础面试题
开发语言·前端·javascript
LaughingZhu9 小时前
Product Hunt 每日热榜 | 2026-05-25
前端·人工智能·经验分享·chatgpt·html