Node 能解析 ## 别名,Vite 却报 Missing "#" specifier
包内 imports 的名称含第二个井号时,Vite 可能把后半段当成 URL fragment。统一改为单井号命名时要同时调整映射与导入。
Node 直接执行可以解析包内 ##/ 别名,Vite 的 ssrLoadModule 或 SSR 构建却报 Missing "#" specifier。若 package.json 使用了第二个 #,先检查它是不是被 Vite 拆成 URL fragment,而不是立即补装一个叫 # 的包。
已核对的 Vite 8.3.4 正式子路径解析会先去掉第一个 #,再把剩下字符串按文件名和后缀拆开。名称若以 ##/ 开头,第二个 # 在这个阶段被当作 fragment 起点,实际拿去查 imports 的键只剩第一个 #。
包内名称与页面锚点经过了不同规则
Node 的包内 imports 必须以 # 开头;合法的第二个井号不自动表示浏览器页面锚点。下面是用于说明映射的示例:
{
"type": "module",
"imports": {
"##/*": "./src/*"
}
}
源码里的 import '##/foo.js' 原本要定位 src/foo.js。确认最近的 package.json、映射目标及文件真实存在,且 Node 与 Vite 比较的是同一份检出、相同条件和相同模块入口。不同条件导出、错误工作目录也可能让两边得到不同结果。
把包内别名统一成单井号名称
需要兼容当前 Vite 路径时,可以采用没有第二个井号的包内命名,例如把上述映射改为:
{
"type": "module",
"imports": {
"#app/*": "./src/*"
}
}
并同步修改对应导入:
import '#app/foo.js';
这是同一个目标文件的新名称,#app 仅作示例,应先确认项目没有占用这个别名。保存原清单和代码,在可审查的改动中更新全部包内引用、测试入口以及生成代码的模板。不要只改 package.json 而保留旧 import,也不要批量替换页面 URL 的 hash、CSS 选择器或字符串数据。
如果 alias 是一个包对外约定的部分,先检查所有包内消费者和构建入口;不便统一改名时,可以仅对受影响的本地调用改用准确相对导入,但仍需覆盖其实际调用范围。给 Vite 新增一个同名 resolve.alias 会引入第二套映射规则,不宜在没有核对 Node 与测试运行器的情况下叠加。
重新启动开发服务,以原 SSR 入口验证,再检查生产 SSR 构建。除了“不报错”,还要确认被导入模块的导出与副作用来自预期文件,正常单井号别名仍可解析。
已核对的 8.3.4 仍有上述拆分顺序,不能把关闭的问题或未发布代码当作现有修复。后续正式版本修复后,是否恢复旧命名应按项目维护成本决定;回退要同时恢复清单与全部引用,避免两种名称只有一半被支持。


