webpack 格式化模块 第 二 节

一、getCompilationHooks(compilation) 静态方法

用于为某个 Compilation 实例附加或获取一组 模块构建过程的钩子NormalModuleCompilationHooks):

  • 参数校验 :如果不是 Compilation 实例,抛出类型错误。

  • 缓存机制 :利用 WeakMap 存储和获取绑定在 compilation 上的钩子。

  • 注册钩子

    • loader:同步钩子,用于设置 loaderContext
    • beforeLoaders:同步钩子,在加载器设置前调用。
    • beforeParsebeforeSnapshot:解析/快照前调用。
    • readResourceForScheme:适配旧的资源读取方式,为兼容性设计(即将弃用)。
    • readResource:根据 URL scheme 注册的资源读取钩子。
    • needBuild:异步钩子,用于判断模块是否需要重新构建。

二、构造函数 constructor({...})

负责初始化一个 NormalModule 实例。接收构建时从 NormalModuleFactory 提供的参数:

模块来源信息(从工厂获得):

  • requestuserRequestrawRequest:模块请求路径的不同形式。
  • type:模块类型(如 javascript/auto)。
  • binary:是否为二进制模块(如 asset/webassembly)。
  • parsergenerator:用于解析与生成模块代码的实例。
  • resource:资源路径。
  • matchResource:匹配用资源路径。
  • loaders:对应的 loader 列表。
  • resolveOptions:模块的解析选项。

构建相关的缓存和状态信息:

  • this._source:模块源代码。
  • this._sourceSizes:源代码大小缓存。
  • this._sourceTypes:源类型缓存。
  • _forceBuild:是否强制构建。
  • _codeGeneratorData:代码生成相关的数据缓存。
  • _lastSuccessfulBuildMeta:记录上一次成功构建的元数据。
  • _isEvaluatingSideEffects:是否正在评估副作用。

三、核心方法

identifier()

返回模块的唯一标识符(作为缓存键使用):

  • 格式:type|requesttype|request|layer

readableIdentifier(requestShortener)

返回模块的可读标识符,通常用于日志和调试信息。

libIdent(options)

生成用于 library export 的标识符,常用于 DllPlugin 或模块缓存。

nameForCondition()

返回模块用于条件匹配的路径(主要是用于配置中 test/resource/include 等字段的匹配判断)。


四、缓存相关方法

updateCacheModule(module)

当模块来自缓存时更新其引用属性(如 loader、parser、request 等),以同步当前实例的状态。

cleanupForCache()

清理当前模块中的引用,释放内存以优化缓存大小。特别保留 sourceTypessize() 缓存数据,确保构建统计正常。

js 复制代码
class NormalModule extends Module {
    /**
     * 获取当前 compilation 实例对应的 NormalModule 的 hooks。
     * 如果之前未创建过,则初始化一组 Hook 并缓存。
     *
     * @param {Compilation} compilation 当前编译过程中的 Compilation 实例
     * @returns {NormalModuleCompilationHooks} 当前 compilation 的 hook 集合
     */
    static getCompilationHooks(compilation) {
            if (!(compilation instanceof Compilation)) {
                    throw new TypeError("The 'compilation' argument must be an instance of Compilation");
            }

            // 从缓存中尝试获取 hooks
            let hooks = compilationHooksMap.get(compilation);
            if (hooks === undefined) {
                    // 首次获取,为该 compilation 创建一套新的 hooks
                    hooks = {
                            // 在执行 loader 函数时调用
                            loader: new SyncHook(["loaderContext", "module"]),
                            // 在设置 loaders 之前调用,可修改 loader 列表
                            beforeLoaders: new SyncHook(["loaders", "module", "loaderContext"]),
                            // 在资源被解析前调用
                            beforeParse: new SyncHook(["module"]),
                            // 在创建文件系统快照之前调用(主要用于缓存判断)
                            beforeSnapshot: new SyncHook(["module"]),
                            // 为向后兼容保留(将在 webpack 6 中废弃)
                            readResourceForScheme: new HookMap(scheme => {
                                    const hook = hooks.readResource.for(scheme);
                                    return createFakeHook({
                                            // 将回调包装为使用 resource 和 module 的方式
                                            tap: (options, fn) =>
                                                    hook.tap(options, loaderContext =>
                                                            fn(loaderContext.resource, loaderContext._module)
                                                    ),
                                            tapAsync: (options, fn) =>
                                                    hook.tapAsync(options, (loaderContext, callback) =>
                                                            fn(loaderContext.resource, loaderContext._module, callback)
                                                    ),
                                            tapPromise: (options, fn) =>
                                                    hook.tapPromise(options, loaderContext =>
                                                            fn(loaderContext.resource, loaderContext._module)
                                                    )
                                    });
                            }),
                            // 真正用于读取资源(根据 scheme 分类)
                            readResource: new HookMap(() => new AsyncSeriesBailHook(["loaderContext"])),
                            // 判断是否需要重新构建模块
                            needBuild: new AsyncSeriesBailHook(["module", "context"])
                    };

                    // 缓存 hook 集合
                    compilationHooksMap.set(compilation, hooks);
            }

            return hooks;
    }

    /**
     * NormalModule 构造函数,表示 Webpack 中最常见的模块类型。
     * 用于处理像 JS、CSS 等非内建模块。
     *
     * @param {NormalModuleCreateData} options 创建模块时传入的数据
     */
    constructor({
            layer,
            type,
            request,
            userRequest,
            rawRequest,
            loaders,
            resource,
            resourceResolveData,
            context,
            matchResource,
            parser,
            parserOptions,
            generator,
            generatorOptions,
            resolveOptions
    }) {
            // 初始化父类 Module
            super(type, context || getContext(resource), layer);

            // 模块请求信息(来自工厂)
            this.request = request; // 带 loader 前缀的请求字符串
            this.userRequest = userRequest; // 用户可读的请求路径
            this.rawRequest = rawRequest; // 原始请求路径(不含 loader)
            this.binary = /^(asset|webassembly)\b/.test(type); // 是否为二进制模块(如 wasm 或 asset)
            this.parser = parser; // 模块使用的解析器
            this.parserOptions = parserOptions; // 解析器选项
            this.generator = generator; // 生成器(生成最终的输出资源)
            this.generatorOptions = generatorOptions; // 生成器选项
            this.resource = resource; // 资源的绝对路径
            this.resourceResolveData = resourceResolveData; // 资源解析过程中的数据
            this.matchResource = matchResource; // 指定替换资源的路径(通常用于 loader 特殊场景)
            this.loaders = loaders; // 模块使用的 loader 列表

            if (resolveOptions !== undefined) {
                    this.resolveOptions = resolveOptions; // 自定义的解析选项
            }

            // 构建过程状态
            this.error = null; // 构建中出现的错误
            this._source = null; // 构建生成的源码
            this._sourceSizes = undefined; // 缓存各类型源码大小
            this._sourceTypes = undefined; // 模块输出的资源类型(如 javascript, asset)

            // 缓存控制与构建优化
            this._lastSuccessfulBuildMeta = {}; // 上一次成功构建的元信息
            this._forceBuild = true; // 是否强制重新构建
            this._isEvaluatingSideEffects = false; // 是否正在评估副作用
            this._addedSideEffectsBailout = undefined; // 判断副作用跳过的缓存
            this._codeGeneratorData = new Map(); // 存储代码生成相关的缓存数据
    }

    /**
     * 获取模块的唯一标识符(用于模块映射与缓存键)
     * @returns {string}
     */
    identifier() {
            if (this.layer === null) {
                    // 默认情况下,如果是 JS 模块,直接返回 request
                    if (this.type === JAVASCRIPT_MODULE_TYPE_AUTO) {
                            return this.request;
                    }
                    return `${this.type}|${this.request}`;
            }
            return `${this.type}|${this.request}|${this.layer}`;
    }

    /**
     * 获取用户可读的模块标识符(用于日志、错误等输出)
     *
     * @param {RequestShortener} requestShortener
     * @returns {string}
     */
    readableIdentifier(requestShortener) {
            return requestShortener.shorten(this.userRequest);
    }

    /**
     * 获取模块在 library 构建中的唯一标识符(用于构建 library 结构时的依赖引用)
     *
     * @param {LibIdentOptions} options
     * @returns {string | null}
     */
    libIdent(options) {
            let ident = contextify(
                    options.context,
                    this.userRequest,
                    options.associatedObjectForCache
            );
            if (this.layer) ident = `(${this.layer})/${ident}`;
            return ident;
    }

    /**
     * 获取可用于 chunk 条件匹配的名称(通常是资源路径,不含 query)
     *
     * @returns {string | null}
     */
    nameForCondition() {
            const resource = this.matchResource || this.resource;
            const idx = resource.indexOf("?");
            if (idx >= 0) return resource.slice(0, idx);
            return resource;
    }

    /**
     * 使用缓存模块替换当前模块的内部状态(例如编译过程中重新利用缓存)
     *
     * @param {Module} module 缓存中已有的模块
     */
    updateCacheModule(module) {
            super.updateCacheModule(module);
            const m = /** @type {NormalModule} */ (module);

            this.binary = m.binary;
            this.request = m.request;
            this.userRequest = m.userRequest;
            this.rawRequest = m.rawRequest;
            this.parser = m.parser;
            this.parserOptions = m.parserOptions;
            this.generator = m.generator;
            this.generatorOptions = m.generatorOptions;
            this.resource = m.resource;
            this.resourceResolveData = m.resourceResolveData;
            this.context = m.context;
            this.matchResource = m.matchResource;
            this.loaders = m.loaders;
    }

    /**
     * 清理模块内部无关缓存的数据(以释放内存)
     * 通常用于构建后阶段,例如缓存写入前的内存优化
     */
    cleanupForCache() {
            // 构建过的模块,在清理前需要缓存类型和大小信息供 stats 使用
            if (this.buildInfo) {
                    if (this._sourceTypes === undefined) this.getSourceTypes();
                    for (const type of this._sourceTypes) {
                            this.size(type); // 缓存 size
                    }
            }

            // 调用父类的清理逻辑
            super.cleanupForCache();

            // 清除内部无关状态
            this.parser = undefined;
            this.parserOptions = undefined;
            this.generator = undefined;
            this.generatorOptions = undefined;
    }

}

这段代码展示了 NormalModule 的核心职责:

  1. 构建相关的状态和属性管理(如 parser、generator、resource 等)。
  2. 模块唯一性和可读性处理(如 identifier、libIdent)。
  3. 模块缓存机制支持 (如 updateCacheModulecleanupForCache)。
  4. 钩子机制支持模块构建生命周期扩展getCompilationHooks)。
相关推荐
恋猫de小郭3 小时前
Flutter 3.35 发布,快来看看有什么更新吧
android·前端·flutter
chinahcp20084 小时前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
gnip5 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip6 小时前
运行时模块批量导入
前端·javascript
hyy27952276846 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅6 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
若梦plus6 小时前
http基于websocket协议通信分析
前端·网络协议
不羁。。7 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html
这是个栗子7 小时前
【问题解决】Vue调试工具Vue Devtools插件安装后不显示
前端·javascript·vue.js
姑苏洛言7 小时前
待办事项小程序开发
前端·javascript