Webpack 示例精讲:用 require.resolve 与 require.cache 实现模块缓存清除与强制重新执行

Webpack 示例精讲:用 require.resolve 与 require.cache 实现模块缓存清除与强制重新执行 Webpack 示例精讲用 require.resolve 与 require.cache 实现模块缓存清除与强制重新执行【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack导读本文基于 GitHub 开源仓库 webpack当前工作目录GitHub_Trending/web/webpack中 require.resolve 示例展开讲解在 webpack 打包产物中如何利用require.resolve()拿到模块 ID、并通过delete require.cache[id]让模块在下次require时被重新执行的完整套路。读完本文你将理解 webpack 运行时模块缓存的底层机制掌握一套可用于配置热重载测试隔离运行时降级刷新等场景的缓存清除方案并学会从该仓库的示例、源码与测试中验证这套机制的实现细节。示例概览一个演示模块缓存清除的迷你项目该示例位于 examples/require.resolve 目录结构非常精简只包含四个文件example.js入口模块演示完整的取 ID → 清缓存 → 重新加载 → 校验流程a.js被加载的依赖模块其导出结果每次执行都不相同template.mdMarkdown 生成模板用于把源码与构建产物渲染进 READMEREADME.md模板渲染后的成品文档包含源码、dist/output.js编译结果与构建统计信息。在 examples/README.md 的示例索引中官方对该示例的定位是一句话点题require.resolveexample demonstrating how to cache clearing of modules withrequire.resolveandrequire.cache.也就是说本示例的核心价值是回答一个问题模块一旦被require过就被缓存在内存中如何在 webpack 环境里把它作废让下一次加载真正重新执行一次核心代码拆解四步完成模块强制重新加载先看两个源文件。依赖模块 a.js 只有一行module.exports Math.random();Math.random()意味着该模块每次执行都会产出一个新对象天然适合用来验证到底有没有重新执行。入口模块 example.js 则用四步完成了整个演示var a require(./a); // get module id var aId require.resolve(./a.js); // clear module in require.cache delete require.cache[aId]; // require module again, it should be reexecuted var a2 require(./a); // verify it if(a a2) throw new Error(Cache clear failed :();逐行解读其逻辑首次加载require(./a)把a.js执行一次得到随机数a此时模块被写入运行时模块缓存获取模块 IDrequire.resolve(./a.js)并不执行模块只返回该模块在打包表module registry中的数值 ID。注意这里请求路径是带扩展名的./a.js说明resolve遵循与require相同的解析规则扩展名可写可不写示例中两种写法./a与./a.js都能解析到同一模块清除缓存delete require.cache[aId]把缓存的模块实例从缓存表中摘除再次加载并校验第二次require(./a)时缓存已空模块函数会被重新执行产出新的随机数a2。末尾的if (a a2) throw ...是自检断言——如果两个值相等说明缓存没有被清掉程序会主动抛错保证示例的语义可被自动化验证。这套取 ID → 删缓存 → 重新 require的模式正是 Node.js 生态中清除require缓存的标准写法在 webpack 中同样成立。走进编译产物require.resolve 在打包后发生了什么模板渲染出的 README.md 保留了dist/output.js的构建结果能最直观地看到上面的源码在 webpack 编译后的形态。入口模块对应的产物片段如下var a __webpack_require__(/*! ./a */ 1); // get module id var aId /*require.resolve*/(/*! ./a.js */ 1); // clear module in require.cache delete __webpack_require__.c[aId]; // require module again, it should be reexecuted var a2 __webpack_require__(/*! ./a */ 1); // verify it if(a a2) throw new Error(Cache clear failed :();这里有三个关键编译事实其一模块调用被改写成__webpack_require__(id)。require(./a)和require(./a)都变成对内部运行时函数__webpack_require__的调用实参是模块 ID1源码注释/*! ./a */只是便于阅读的标注。模块本身被放进以 ID 为下标的模块函数表__webpack_modules__中本示例该表下标0是入口下标1是./a.js。其二require.resolve(./a.js)被编译为一个带注释的常量。产物写作var aId /*require.resolve*/(/*! ./a.js */ 1);——webpack 在编译期就能确定被解析模块的 ID因此把这次解析直接折叠成了常量1运行期不会再做任何文件解析动作。其三require.cache被映射到__webpack_require__.c。删除缓存语句被改写为delete __webpack_require__.c[aId];。在运行时代码片段中可以看到缓存表的定义// The module cache const __webpack_module_cache__ {}; // The require function function __webpack_require__(moduleId) { // Check if module is in cache const cachedModule __webpack_module_cache__[moduleId]; if (cachedModule ! undefined) { return cachedModule.exports; } // Create a new module (and put it into the cache) const module __webpack_module_cache__[moduleId] { id: moduleId, loaded: false, exports: {} }; // Execute the module function __webpack_modules__moduleId; // Flag the module as loaded module.loaded true; // Return the exports of the module return module.exports; } // expose the module cache __webpack_require__.c __webpack_module_cache__;这个运行时的执行契约非常清晰__webpack_require__(moduleId)首先查缓存__webpack_module_cache__[moduleId]命中则直接返回cachedModule.exports未命中时新建{ id, loaded: false, exports: {} }并写入缓存然后调用__webpack_modules__[moduleId]执行模块函数置loaded true后返回导出__webpack_require__.c是对整个缓存表的公开引用。因此示例中删掉__webpack_require__.c[1]后再调__webpack_require__(1)会让查找缓存失败、走重新建模块并执行的路径——这就是模块被重新执行的本质。另外产物开头有一句注释module cache are used so entry inlining is disabled说明一旦模块依赖缓存语义webpack 会自动关闭入口内联entry inlining等可破坏该语义的优化从而保证行为的确定性。构建统计信息也印证了打包结构见 README.md 的 Info 一节未优化模式下output.js约 2.41 KiB入口 chunk 内包含 1 个dependent module即a.jsproduction 模式下产物被压缩到 317 bytes同时[used exports unknown]变为[no exports used]——这是压缩、摇树tree-shaking优化在入口无显式导出消费时的典型表现./example.js的 282 bytes 源码在两份构建中保持一致。源码溯源解析器如何识别 require.resolve示例中的行为并非魔法而是由 webpack 的依赖解析插件显式实现的。核心入口在 lib/dependencies/CommonJsImportsParserPlugin.js该插件对require表达式建立了一系列子处理器tap其中 L529-L530 明确注册了require.resolve与require.resolveWeak两类表达式对require.resolve调用插件会创建 RequireResolveDependency 依赖对象见 CommonJsImportsParserPlugin 中new RequireResolveDependency(...)的位置把解析模块 ID这一操作下沉到依赖图中去完成在编译阶段依赖模板会把它渲染成require.resolve形式的调用最终在运行时与模块 ID 常量挂钩产物中的/*require.resolve*/(/*! ./a.js */ 1)即这一产物形态的可读呈现。关于缓存表的运行时暴露仓库还专门定义了内部运行时代码常量在 lib/RuntimeGlobals.js 中可以看到module.exports.moduleCache __webpack_require__.c;——这解释了为什么所有编译产物中缓存表都统一命名为__webpack_require__.c这是 webpack 内部运行时代码的契约式全局命名由RuntimeGlobals.moduleCache统一引用各类 chunk 加载方案web/jsonp、node 的 CommonJsChunkLoading 等共用同一套语义。换言之从源码结构看webpack 把require.resolve当做一个只解析、不执行的编译期常量化操作把require.cache当做一个运行期对象引用二者组合正好覆盖了按 ID 定位 按 ID 作废缓存的完整能力。模板机制说明README 是从 template.md 生成的需要说明的是template.md 本身并不是给人直接阅读的手写文档而是示例体系的渲染模板。它用_{{example.js}}_、_{{a.js}}_、_{{dist/output.js}}_、_{{stdout}}_、_{{production:stdout}}_等占位符分别指代源码文件、构建产物和控制台输出配合 examples/template-common.js 中的replaceBase/replaceResults逻辑在构建示例时把占位符替换成真实内容最终生成上面我们分析的 README.md。因此阅读该示例请以渲染完成的 README.md 为准template.md 的价值在于向你展示这套示例的自动化生成管道。如果你希望亲自构建该示例并观察产物examples/README.md 给出了官方步骤在仓库根目录运行yarn安装依赖运行yarn setup完成初始化运行yarn add --dev webpack-cli进入示例目录执行node build.js例如cd examples/commonjs node build.js而 buildAll.js 用于一键构建全部示例对应根目录npm run build:examples。构建完成后dist/output.js与构建统计信息会自动回填进 README你即可对照源码与产物逐行核对本文分析。应用场景与注意事项从仓库的示例语义出发这套缓存清除 强制重载技术可以在以下场景中落地开发期配置/规则热重载当配置模块内容变化、又不想重启整个进程时先清掉它的缓存再重新require即可拿到最新配置测试隔离每个用例执行前清空被测模块缓存避免模块级状态如单例、计数器跨用例污染这在 Node 测试框架中非常常见动态降级与运行时补丁在运行时根据条件把有问题的模块替换为兜底实现利用清除缓存后的重新执行完成无缝切换。但使用时务必注意几个边界这些在本示例中已有暗示只对 CommonJS 语义的模块生效。本示例依赖module.exports直接赋值产物注释里标有CommonJS bailout: module.exports is used directlywebpack 产物完整保留了模块缓存语义对原生 ESMimport绑定规范层面并不提供等价的缓存清除接口处理思路完全不同。清除是按实例而非按依赖图的。删除缓存只影响下一次直接require该模块的代码已持有旧导出引用的其他模块不会自动更新可能出现新旧实例并存的不一致状态工程上需要自行保证引用一致性。浏览器端本质是内存对象操作。__webpack_require__.c只是 JS 内存对象刷新页面即重置若目标是从远端获取新代码则属于 Code Splitting / HMR / 动态 chunk 加载的范畴而非本机制所能覆盖。不要在产物里依赖未开启缓存语义的优化路径。示例产物中entry inlining disabled的注释表明webpack 在检测到缓存操作时会主动让路若你的构建开启过强的拼接/内联优化并出现与预期不符的结果应回到缓存语义本身排查。小结本示例用最小化的两个文件讲清了一个关键运行时事实webpack 的模块加载是缓存优先的require.resolve负责在编译期给出模块 IDrequire.cache/__webpack_require__.c负责在运行期按 ID 管理缓存实例二者组合便构成了对单个模块作废并重新执行的完整通道。示例本身的if (a a2) throw ...自检以及仓库中 test/Examples.test.js 等测试框架对示例产出物的校验都保证了这套语义在 webpack 持续迭代中始终如一。若要继续深入推荐顺藤摸瓜阅读 CommonJsImportsParserPlugin.js、RequireResolveDependency.js 以及 RuntimeGlobals.js以了解从语法解析、依赖生成到运行时命名的完整链路。【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考