UnoCSS 在 Node 23 的 Windows 上报 ERR_UNSUPPORTED_ESM_URL_SCHEME?排查 UnoCSS Node 23 兼容坑
【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss
如果你的 Astro 项目刚升级完 UnoCSS 和 Node,pnpm dev一敲下去终端直接抛出一大段报错、服务死活起不来,这篇文章写给你的就是这类 UnoCSS Node 23 兼容问题。ERR_UNSUPPORTED_ESM_URL_SCHEME 的修复其实不碰任何业务代码,真正的坑在于"谁来加载 uno.config.ts、路径是怎么传进去的"。
复现条件:命中这 4 条就说明你踩了 Node 23 Windows 路径坑
- UnoCSS 升到较新的大版本(65.x 之后开始暴露,仓库当前已迭代到 66.x 线)
- Node.js 版本是 23 及以上
- 系统是 Windows,工作目录在 D:\ 这类盘符路径下
- 项目存在
uno.config.ts或unocss.config.ts,且由 Astro / Vite 插件在 dev 或 build 时加载
四条全中基本必现。把 Node 23 换成 22,或者把 Windows 换成 macOS,通常一切正常——这种不对称性也正是这个坑容易误判的原因。
ERR_UNSUPPORTED_ESM_URL_SCHEME 到底在说什么
原始报错长这样:
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'd:'说白了就是:Node 的 ESM 加载器用动态import()加载模块时,只认file://、data:、node:这类 URL 协议,而它收到的却是一个 Windows 盘符绝对路径D:\...\uno.config.ts,把d:当成了"协议"来解析。
直觉误区在这里:报错看起来像在骂"ESM loader"或者"Node 23 有 bug",你很想赖 Node。真正的原因不是 Node,而是依赖链里某段配置加载逻辑,把裸绝对路径直接塞给了import(),没有先转成合法的file://URL。加载器只是喊错的那个,不是闯祸的那个。
底层发生了什么:unconfig 的动态 import 遇上裸路径
因果链只有三步:
- 因为 UnoCSS 把配置文件加载外包给了 unconfig 包(loadConfig 里包装 unconfig 的
createConfigLoader完成这件事)。老版本处理 TS 配置走 jiti:先转译成 JS 再执行,路径问题被 jiti 吞掉了。 - 因为 Node 23 默认开启了原生 TypeScript type stripping(Node 自己剥掉类型标注直接跑
.ts文件),unconfig 对.ts配置改用了原生动态import(),不再经过 jiti。 - 因为这次
import()传入的是 Windows 盘符裸路径,ESM loader 把d:当成 URL 协议,直接抛出。
Linux 和 macOS 的绝对路径以/开头,Node 会自动补成合法的file://URL,所以这两个平台从来不炸。坑只埋在 Windows,报错措辞又绕,排查起来更费劲。
怎么修:先钉住修好的 unconfig,再考虑关掉 type stripping
一劳永逸:升级 UnoCSS,或用 overrides 钉住修好的 unconfig 版本
根治点在 unconfig 内部的路径规范化:import之前先用pathToFileURL把 Windows 绝对路径转成file://URL。如果你已升级 UnoCSS 到包含该修复的版本,直接验证即可;如果项目还停在旧版 UnoCSS,或 lockfile 里锁的还是旧 unconfig,用 overrides 强制钉住:
{ "pnpm": { "overrides": { "unconfig": "^7.5.0" } } }改完删掉node_modules重装,用pnpm why unconfig确认实际锁定的版本。
适用场景:希望项目在 Node 23 下长期可用,而不是让每个新同事都记得加一次性参数。
临时绕过:关掉 type stripping,让加载回落到 jiti
如果暂时没法升级依赖(项目停留在旧版、短期内动不了大版本),可以关掉 Node 23 的原生 type stripping:
{ "scripts": { "dev": "NODE_OPTIONS=--no-experimental-strip-types astro dev" } }在 Windows cmd 里前缀环境变量语法不生效,需要在.npmrc里加一行,让 pnpm 用模拟 shell 的方式执行脚本:
shell-emulator=true副作用提醒:这个 flag 对整个 Node 进程生效,.ts文件全部退回"先转译再执行"的路径。如果项目里还有别的东西依赖原生 TS 运行,留意一下行为差异。
举一反三:Node 23 兼容问题的 3 个可迁移排查习惯
- 遇到"加载器挂了"这类报错,先查
import()的路径参数。自己代码里做动态 import 时,养成先过一遍pathToFileURL的习惯;看到Received protocol 'x:'这种措辞,第一时间对照协议表定位。 - 升级 Node 大版本时,扫一遍实验性特性的默认开关变化。type stripping 只是其中一例,任何从"手动开启"变成"默认开启"的特性,都可能悄悄改变依赖树里所有配置加载器的行为。
- 依赖升级后用
pnpm why核对实际锁定的版本。你以为的"已升级",很可能在node_modules里跑的还是旧版。
下次再看到Received protocol 'd:',先别急着骂 Node——把堆栈展开,找到是谁在 import 一个裸路径,修复就已经完成了一半。
【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考