Node.js 文件 URL 转路径:pathname 里的空格编码,为什么不能直接当文件名
把模块地址交给 new URL 后,取出 pathname 再传给文件接口,在简单英文目录里可能一直正常。目录一旦含空格或中文,程序就像在寻找另一份文件;迁移到 Windows 时,盘符前又多出斜杠。pathname 是 URL 的路径部分,不是已经适配当前操作系统的文件路径。
让 URL 转换函数处理完整地址
Node.js 提供 fileURLToPath,把 file 协议地址转换成平台路径。它不仅解码百分号字符,还处理盘符、分隔符和网络共享地址。手动对 pathname 做 decodeURIComponent,只解决其中的字符解码,无法替代完整转换;特别是共享主机名原本就不在 pathname 里面。
下面保存为 demo.mjs,用 node demo.mjs 执行。代码只转换合成地址,不创建文件,也不连接共享服务器。显式传入 windows 选项,使 Linux 上也能检查 Windows 路径文字;这验证的是转换结果,没有宣称已在另一套操作系统完成文件读写测试。
import assert from 'node:assert/strict';
import { fileURLToPath, pathToFileURL } from 'node:url';
const posix = new URL('file:///tmp/demo%20%E6%96%87.txt');
assert.equal(posix.pathname, '/tmp/demo%20%E6%96%87.txt');
assert.equal(fileURLToPath(posix, { windows: false }), '/tmp/demo 文.txt');
const drive = new URL('file:///C:/demo/report%20one.txt');
const windowsPath = fileURLToPath(drive, { windows: true });
assert.equal(drive.pathname, '/C:/demo/report%20one.txt');
assert.equal(windowsPath, 'C:\\demo\\report one.txt');
const share = new URL('file://server/share/report.txt');
const networkPath = fileURLToPath(share, { windows: true });
assert.equal(share.pathname, '/share/report.txt');
assert.equal(networkPath, '\\\\server\\share\\report.txt');
const original = '/tmp/report #1%.txt';
const encoded = pathToFileURL(original, { windows: false });
assert.equal(encoded.href, 'file:///tmp/report%20%231%25.txt');
assert.equal(fileURLToPath(encoded, { windows: false }), original);
assert.throws(() => fileURLToPath('https://example.com/file'), TypeError);
assert.throws(() => fileURLToPath('file:///tmp/a%2Fb',
{ windows: false }), TypeError);
assert.throws(() => fileURLToPath(share, { windows: false }), TypeError);
console.log('pathname:', posix.pathname);
console.log('posix:', fileURLToPath(posix, { windows: false }));
console.log('windows:', JSON.stringify(windowsPath));
console.log('share:', JSON.stringify(networkPath));
console.log('roundtrip:', encoded.href);
console.log('rejected: wrong scheme, encoded slash, posix host');字符转换与平台结构同时检查
前两行把同一个地址分别显示为编码路径和含空格、中文的实际路径。随后 Windows 盘符变成以盘符开头的反斜杠路径,共享地址则保留服务器部分。输出用 JSON.stringify 显示反斜杠,所以终端里看到的成对反斜杠是字符串表示法,不是文件名真的多了一倍分隔符。
AI生成概念示意图:文件 URL 经过完整转换后,才成为符合目标平台约定的文件路径。
共享地址尤其能说明为何不能只拿 pathname:服务器保存在 URL 的主机部分,取完路径之后再解码,已经没有这条信息可用。示例把相同共享地址按 POSIX 规则转换时要求报错,提醒调用方平台规则不是装饰选项,不能遇到异常就随便换一个布尔值让它通过。
反向转换也不要手工拼协议头
从文件路径生成地址时使用 pathToFileURL。示例文件名含井号和百分号,转换后它们成为编码字符,再转回来与原始路径严格相等。如果直接在路径前拼 file 协议头,井号可能被理解为片段开始,路径里的普通字符就被分到 URL 的另一个部分,之后难以恢复原意。
fileURLToPath 只接受文件地址,不能把任意网页地址变成本地文件名。代码还验证编码斜杠被拒绝,避免把一个路径段里的编码字符悄悄解释成新的目录分隔。收到这些异常时,应检查地址来源与协议约定,而不是先全串解码再重试来绕开检查。
在模块中取得自身位置时,可以把 import.meta.url 交给转换函数;定位旁边的资源则先以它为基址建立新的 URL,再按目标接口需要转换。某些 Node.js 文件接口本身接受 file URL,可以直接传入对象,但应以具体接口文档为准,不能推断所有第三方库也支持。
本文的 windows 选项需要支持该选项的 Node.js 版本,官方记录它从二十点十三及二十二点一开始提供。实际程序若只访问当前系统,通常可省略选项使用平台默认值。用于生成另一平台配置时才显式选择,并把配置使用端的验证安排清楚。
转换成功只说明地址能够成为路径,不证明文件存在、可读或位于允许目录。真正访问前仍要根据用途检查目标与权限;验收样本则保留空格、中文、井号、百分号、盘符和共享地址。这样跨平台问题可以在转换层暴露,不必等到部署后只看到找不到文件。
资料核对日期:2026年10月2日(北京时间)。示例在 Node.js v24.19.0 中独立执行并通过断言。


