Skip to content

[fix]Solutions for require.cache Invalidation Caused by module-alias and Webpack Bundling(修复方案:module-alias 与 webpack 打包导致 require.cache 失效) #150

@MorningZheng

Description

@MorningZheng

module-alias 与 webpack 打包导致 require.cache 失效

简短描述

  • 在使用 webpack 打包时,动态路径的 require 可能被打包为类似 requireEmpty 的形式,导致运行时的 require.cache 与原生 Node 的行为不一致,从而让 module-alias 的 reset() 中清理 require.cache 失效,进而导致别名不生效或缓存残留。

复现步骤

  1. 在项目中使用 module-alias,并在代码里依赖动态路径(例如通过拼接路径再 require)。
  2. 使用 webpack 打包(或类似工具),运行打包后的输出。
  3. 调用 module-alias.reset()(或在测试场景中重置模块路径),发现模块缓存并没有按预期被清理。

实际行为

  • reset() 里遍历 require.cache 时无法找到打包后的缓存条目(或调用的 require 被替换),导致无法删除相关缓存,别名映射失效或行为异常。

预期行为

  • reset() 能正确遍历并清理对应的模块缓存,使得别名能被正确重置。

根因

  • 打包工具会修改或替换全局 require,或将 require 的实现替换为空或打包时的占位(如 requireEmpty)。因此直接使用全局 require/cache 会失效。

最小修复方案(思路)

  • 使用 Node 内置模块的 createRequire,在当前文件上下文创建一个可靠的 require(这里命名为 $require),并在需要访问缓存或加载 package.json、访问 require.main 的位置使用 $require 而不是全局 require。这样避免被 webpack 等工具替换的全局 require 影响。

补丁(要做的三处最小替换)

  1. 在文件里引入 nodePath 后,创建 $require:
// 在 var nodePath = require('path'); 之后增加:
const $require = BuiltinModule.createRequire(__filename || nodePath.dirname(process.argv[1]));
  1. reset() 中遍历缓存时,用 $require.cache 替换 require.cache:
Object.getOwnPropertyNames($require.cache).forEach(function (name) {
  if (name.indexOf(path) !== -1) {
    delete $require.cache[name]
  }
});
  1. 加载 package.json 时使用 $require:
npmPackage = $require(nodePath.join(base, 'package.json'))
  1. 获取主模块时使用 $require.main:
function getMainModule () {
  return $require.main._simulateRepl ? undefined : $require.main
}

其他备注

  • 这些改动是尽量小的本地修复,不会影响模块别名的其他逻辑。
  • 已验证:使用 createRequire 后,reset() 能正常删除对应缓存条目,别名重置恢复正常。

module-alias and webpack bundling cause require.cache to fail

Short description

  • When bundling with webpack, dynamic require calls can be replaced (e.g. requireEmpty), making runtime require.cache differ from Node's native behavior. This breaks module-alias.reset() cache cleanup, causing aliases to not reset or leaving stale caches.

Reproduction steps

  1. Use module-alias and require modules via dynamic paths (e.g. path concatenation).
  2. Bundle the project with webpack (or similar) and run the bundled output.
  3. Call module-alias.reset() (or reset module paths in tests) and observe that module cache is not cleared as expected.

Actual behavior

  • reset() iterates require.cache but cannot find bundled cache entries (or the global require was replaced), so it cannot delete them and alias mapping fails or behaves incorrectly.

Expected behavior

  • reset() should properly traverse and clear the corresponding module cache so aliases can be correctly reset.

Root cause

  • Bundlers modify or replace the global require (or insert placeholders like requireEmpty). Direct use of global require/cache becomes unreliable.

Minimal fix idea

  • Use Node's builtin createRequire to create a reliable require in the current file context (named $require). Use $require for cache access, package.json loading, and main module access instead of the global require to avoid interference from bundlers.

Patch (three/four minimal replacements)

  1. After importing nodePath, create $require:
// After: var nodePath = require('path');
const $require = BuiltinModule.createRequire(__filename || nodePath.dirname(process.argv[1]));
  1. In reset(), iterate $require.cache instead of require.cache:
Object.getOwnPropertyNames($require.cache).forEach(function (name) {
  if (name.indexOf(path) !== -1) {
    delete $require.cache[name]
  }
});
  1. Load package.json using $require:
npmPackage = $require(nodePath.join(base, 'package.json'))
  1. Access main module via $require.main:
function getMainModule () {
  return $require.main._simulateRepl ? undefined : $require.main
}

Notes

  • These are minimal, local changes that do not affect other module-alias logic.
  • Verified: using createRequire allows reset() to remove corresponding cache entries and restores alias reset behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions