You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js中TypeScript如何清除模块缓存实现热重载动态导入

动态导入ES模块并清除缓存的解决方案

问题背景

在ES6模块普及前,常用CommonJS的缓存清除方法实现模块重载:

function requireUncached(module) {
    delete require.cache[require.resolve(module)];
    return require(module);
}

但在Node.js v18.16.0中,项目设置"type": "module"启用ES模块特性后,运行包含混用require和动态import的代码时,出现ERR_REQUIRE_ESM错误:

报错代码

import fs from 'fs'

boot()

async function boot() {
  const path = JSON.parse(JSON.stringify('./foo.ts'))
  save(path, "const x = 20");
  let file = await load(path)
  console.log(String(file))
  save(path, 'const x = 100');
  file = await load(path);
  console.log(String(file));
}

async function load(path: string) {
  delete require.cache[require.resolve(path)] // CommonJS API,ES模块环境下不支持
  return (await import(path)) as unknown
}

function save(path: string, code: string) {
  fs.writeFileSync(path, code)
}

错误信息

./node_modules/ts-node/dist-raw/node-internal-errors.js:46
  const err = new Error(getErrRequireEsmMessage(filename, parentPath, packageJsonPath))
              ^
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: ./test.ts
require() of ES modules is not supported.
require() of ./test.ts is an ES module file as it is a .ts file whose nearest parent package.json contains "type": "module" which defines all .ts files in that package scope as ES modules.
Instead change the requiring code to use import(), or remove "type": "module" from ./package.json.

    at createErrRequireEsm (./node_modules/ts-node/dist-raw/node-internal-errors.js:46:15)

核心原因

项目启用"type": "module"后,所有.ts文件被识别为ES模块,此时禁止使用任何CommonJS的require相关API(包括require.resolve和require.cache),这是报错的直接原因。此外,Node.js并未暴露ES模块的缓存对象,无法像CommonJS那样直接删除缓存。

解决方案

1. 通过URL查询参数绕过缓存

Node.js会将带有不同查询参数的模块URL视为独立模块,不会复用缓存。我们可以在动态导入时添加唯一标识(如文件修改时间、时间戳)作为查询参数,强制加载最新模块:

基于文件修改时间的实现(推荐)

async function load(path: string) {
  // 获取文件最后修改时间
  const stat = await fs.promises.stat(path);
  const mtime = stat.mtimeMs.toString();
  
  // 构造带缓存击穿参数的模块URL
  const moduleUrl = new URL(path, import.meta.url);
  moduleUrl.searchParams.set('cache-bust', mtime);
  
  return (await import(moduleUrl.href)) as unknown;
}

基于时间戳的简化实现

async function load(path: string) {
  const moduleUrl = new URL(path, import.meta.url);
  // 用当前时间戳作为唯一参数,每次导入都触发重载
  moduleUrl.searchParams.set('t', Date.now().toString());
  return (await import(moduleUrl.href)) as unknown;
}

2. 自定义缓存管理层

如果需要更精细的缓存控制(比如手动触发失效),可以自己实现缓存逻辑,结合文件修改时间判断是否需要重新加载:

// 自定义缓存:存储模块路径、加载结果、最后修改时间
const moduleCache = new Map<string, { data: unknown; mtime: number }>();

async function load(path: string) {
  const stat = await fs.promises.stat(path);
  const currentMtime = stat.mtimeMs;
  
  // 检查缓存是否有效
  const cached = moduleCache.get(path);
  if (cached && cached.mtime === currentMtime) {
    return cached.data;
  }
  
  // 重新加载最新模块
  const moduleUrl = new URL(path, import.meta.url);
  moduleUrl.searchParams.set('mtime', currentMtime.toString());
  const moduleData = (await import(moduleUrl.href)) as unknown;
  
  // 更新缓存
  moduleCache.set(path, { data: moduleData, mtime: currentMtime });
  return moduleData;
}

3. 临时修改文件扩展名(不推荐)

如果上述方法不适用,可临时将目标文件改为非ES模块扩展名(如.cjs),但这种方式会破坏项目的ES模块一致性,仅作为临时 workaround。

注意事项

  • 动态导入返回的是Promise,必须用await处理
  • import.meta.url是ES模块特有的变量,用于获取当前模块的URL,构造相对路径时必须依赖它
  • 频繁重载模块可能影响性能,建议仅在开发环境或特定需求场景下使用

内容的提问来源于stack exchange,提问作者Lance Pollard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 20:10:25