Cloudflare 重写了开源组件 workerd 中的模块注册表。workerd 是 Workers 运行时环境的核心组件,此举旨在提升速度、标准合规性,并使模块的加载与解析方式更接近 Node.js 的行为。与此同时,Workers 默认启用了稳定的 Node.js API,并允许在所有计划中部署大小最高为 64 MiB 的应用程序,此前的压缩包大小限制已经移除。
不过,最重要的变化并不只涉及可用 API 的数量。Node.js 应用还依赖运行时确定、加载和缓存模块的方式。这包括 ESM、CommonJS 和 WebAssembly 模块,而这些职责由 workerd 内部的模块注册表负责。
新注册表有哪些变化?
开发者可以通过在 Worker 配置中添加 new_module_registry 标志来试用新实现。启用后,Workers 支持 import.meta.url、import.meta.main 和 import.meta.resolve(),并将模块说明符视为真正的 URL,包括查询字符串和片段。
实际上,这意味着相对导入遵循与 new URL() 相同的规则,并且完整 URL 可以用作模块说明符。查询字符串或 URL 片段不同的模块会被视为独立模块,即使它们指向同一源。因此,同一个文件的两个版本可以拥有彼此独立的顶层状态,而重复使用相同的说明符则会返回同一个模块实例。
该注册表还支持正确验证导入属性。目前可用的类型是 json;text 和 bytes 等类型虽然已经定义,但由于尚未启用,会通过明确的错误消息被拒绝。未知属性也不再被静默忽略,而是会导致显式错误。
更强的 Node.js 兼容性
使用 require() 加载 ESM 模块时,会遵循 Node.js 关于 require(esm) 的规则。如果模块导出了名为 module.exports 的值,就会返回该值;否则,调用会返回模块的命名空间对象。例外是 workerd 内置的 node: 模块,它们会返回预期的 CommonJS 接口,而不是迫使开发者访问 default 属性。
这里有一个重要限制:require() 无法加载包含 top-level await 的模块,也无法加载依赖于包含该语法模块的模块,因为 require 必须同步返回结果。在这种情况下,Workers 会抛出错误,而异步的 import() 才是合适的方式。即使该模块此前已经通过 import() 预先加载,这一规则仍然适用。
新版本还统一了错误类别及其消息格式,无论采用哪种加载方式,包括静态 import、动态 import() 或 require()。找不到模块时会返回普通错误,而无法解析为 URL 的说明符则会导致 TypeError。对于构建自定义加载器或重试逻辑的开发者来说,这种一致性很有帮助。
开发者实际会遇到哪些变化?
Workers 的工具(例如 Wrangler)过去会使用 esbuild 将大多数应用文件和依赖项合并到一个模块中,从而减少运行时需要处理的依赖图规模。而使用 Cloudflare Vite plugin 时,Vite 8 依靠 Rolldown 生成一个入口模块,并在代码拆分时生成额外模块,例如动态加载的模块。
新注册表为打包工具进行更少转换、更多依靠运行时解析模块创造了条件。这一点在以多个模块的形式部署应用、使用 --no-bundle 选项,或将 Wasm、文本和二进制文件作为独立文件处理而不是嵌入单个包中时尤其重要。
新实现还会将编译推迟到模块第一次被导入时,无论是静态导入还是动态导入,并允许多个运行同一个 Worker 的 V8 isolate 共享代码缓存。Cloudflare 表示,这解决了旧实现中的部分重复编译问题,以及内存中存在多个源代码副本的问题。
限制与注意事项
尽管进行了这些更改,新模块注册表不会自动为任何 Worker 启用,无论是旧 Worker 还是新 Worker,也无论使用何种兼容性日期。必须明确添加该标志;Cloudflare 也保留了旧实现,并表示已部署的 Workers 将继续按原方式运行。
新实现还支持导入 WebAssembly 模块的源阶段,并直接返回 WebAssembly.Module 对象,但该功能目前仅适用于 WebAssembly,任何其他类型都会导致语法错误。因此,这次更新并不是在没有限制的情况下全面转变所有模块加载路径,而是提供了一个兼容性更强的基础,供开发者逐步测试。
编辑解读:这项变化的实际价值在于,将 Workers 从对模块行为的部分模拟,转向更接近 JavaScript 和 Node.js 标准的模型。这可能减少构建工具强制进行的转换,并使多模块应用更易于迁移。不过,最终影响将取决于开发者对新标志的测试,尤其是涉及使用 require()、top-level await 或导入属性的依赖项时。由于该功能尚未默认启用,增强后的兼容性还不是所有应用的通用行为。