先记住这个答案
import.meta.url 是当前模块文件自己的完整 URL。在浏览器里,它是这个模块脚本被请求时的地址,比如 https://cdn.example.com/lib/a.js;在 Node.js 里,它是 file:///Users/me/app/a.mjs 这样的文件 URL。它和页面地址、入口文件都没关系,每个模块读到的都是自己的地址。所以它主要用来定位「和我放在一起的其它文件」。
- 它是当前模块的地址,不是页面地址,也不是入口文件地址
- 浏览器通常是网络 URL,Node 是文件 URL
- ESM 用
import.meta.url定位当前文件 new URL()可定位相邻资源
Node.js 里替代 __dirname
CommonJS 里有 __filename 和 __dirname,换成 ESM 后这两个变量不存在了,直接用会报 ReferenceError。替代方案就是从 import.meta.url 推出来。
// /Users/me/app/src/config.mjs
import { fileURLToPath } from 'node:url'
import path from 'node:path'
console.log(import.meta.url)
// file:///Users/me/app/src/config.mjs
const __filename = fileURLToPath(import.meta.url) // /Users/me/app/src/config.mjs
const __dirname = path.dirname(__filename) // /Users/me/app/src
// 读同目录的文件,也可以直接传 URL 对象给 fs
const tplUrl = new URL('./template.html', import.meta.url)一定要用 fileURLToPath 转换,不要用字符串把 file:// 截掉。路径里有空格或中文时,URL 里是 %20、%E4%B8%AD 这样的编码,手动截取得到的路径根本找不到文件,Windows 上还会多出一个开头的斜杠。
较新版本的 Node.js 直接提供了 import.meta.dirname 和 import.meta.filename,效果和上面两行一样。项目要兼容旧版本时还是用 fileURLToPath 最稳。
浏览器里的值和资源定位
浏览器里 import.meta.url 就是模块脚本的最终请求地址,包括查询参数。如果是写在 HTML 里的内联 <script type="module">,它没有自己的文件,拿到的是页面的地址。
- 用
new URL定位静态资源 - 组件库里经常要加载和自己放在一起的图片、字体或 WASM。写
new URL("./icon.svg", import.meta.url).href,拿到的地址会跟着模块走:模块部署到 CDN,图片地址也自动是 CDN 上的地址,不受页面路径影响。 这个写法还有一个好处,Vite 和 Webpack 5 都能识别new URL("./x", import.meta.url)这种固定形式,构建时会把对应文件一起打包并改写成带哈希的地址。所以写法要保持字面量形式,路径拼成变量就识别不出来了。
容易答错的地方
- 以为它是页面的 URL
- 只有内联模块脚本才是页面地址。外部模块拿到的是模块自己的地址,页面在
/user/list,模块在/assets/a.js,读到的就是后者。要页面地址用location.href。 - 以为 Node.js 里它就是文件路径
- 它是 URL 对象的字符串形式,带
file://前缀,字符经过编码。传给需要路径的 API 前要先fileURLToPath。好在fs的大部分方法本身也接受URL对象,传new URL(...)也行。 - 在 CommonJS 或普通脚本里用它
import.meta只能出现在 ES 模块里。CommonJS 文件或普通<script>里写它,解析阶段就是语法错误,不是运行时返回undefined。
面试官还会怎么问?
为什么 ESM 不提供 __dirname?
ESM 是为浏览器和服务端共同设计的,浏览器里的模块根本没有「目录」的概念,只有 URL。所以规范统一给的是 URL,文件路径由 Node.js 这类宿主自己换算。__dirname 是 CommonJS 包装函数注入的局部变量,本来就不是语言的一部分。
加了查询参数的导入,import.meta.url 会带上吗?
会。import("./a.mjs?v=2") 加载的模块里,import.meta.url 末尾就是 ?v=2。同时它也会被当成一个新模块再执行一次,因为模块缓存是按完整 URL 区分的。
Worker 里怎么用它?
创建模块 Worker 时写 new Worker(new URL("./worker.js", import.meta.url), { type: "module" }),保证 Worker 文件相对当前模块定位。Vite 和 Webpack 5 也专门识别这个写法,会把 Worker 文件单独打包。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。