先记住这个答案
浏览器原生 ESM 只认完整 URL 和 ./、../ 或 /src/a.js 这类路径,import { ref } from "vue" 这种裸说明符会直接报错。import map 就是一张查找表,写在 <script type="importmap"> 里,浏览器解析每个说明符前先查表,把 vue 替换成表里对应的 URL 再加载。它只改变「说明符到 URL」这一步,模块的加载和执行方式不变。
- import map 把模块名映射到 URL
scopes可覆盖局部解析规则- 键以斜杠结尾是前缀映射,能覆盖子路径
- 必须在第一个用到它的模块脚本之前出现
一个不用打包也能写裸模块名的页面
最常见的用法是配合 CDN,让业务代码里的导入写法和在 Node.js 项目里一样。
imports 和前缀映射HTML<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
"lodash-es/": "https://cdn.jsdelivr.net/npm/lodash-es/",
"@/": "/src/"
}
}
</script>
<script type="module">
import { createApp } from 'vue' // 解析成上面的 unpkg 地址
import debounce from 'lodash-es/debounce.js' // 前缀映射,拼成完整地址
import { api } from '@/api.js' // 解析成 /src/api.js
</script>imports 里有两种键。不带斜杠结尾的是精确匹配,vue 只匹配 vue 本身。以斜杠结尾的是前缀映射,lodash-es/ 会匹配所有以它开头的说明符,把前缀替换掉,剩下的路径原样拼上去。前缀映射的值也必须以斜杠结尾,否则这条映射无效。
@/ 那条说明 import map 也能当路径别名用,效果类似打包器里的 alias。
用 scopes 解决版本冲突
有时两个库依赖同一个包的不同版本。imports 是全局的,只能给出一个答案,这时就需要 scopes。
- 按目录覆盖映射
scopes的键是一个 URL 前缀,值是一张新的查找表。发起导入的模块 URL 落在这个前缀下时,先查这张表,查不到再回到全局的imports。比如全局把lodash-es/映射到新版本,同时在scopes里写"/legacy/": { "lodash-es/": "https://cdn.example.com/lodash-es@4.17.0/" },那么/legacy/目录下的模块导入 lodash 时拿到的是旧版本,其它模块不受影响。 这个机制和 Node.js 里每个包有自己的node_modules很像,只不过浏览器没有文件系统可以逐层查找,只能由你把规则写死。
容易答错的地方
- 以为 import map 放在哪里都行
- import map 必须在第一个需要它的模块开始解析之前出现。模块脚本已经加载后再插入 import map,对已经发起的解析不生效,浏览器会在控制台给出警告。稳妥的做法是把它放在
<head>里所有模块脚本之前。 - 以为 import map 能替代打包
- 它只解决「写裸模块名」这一件事。每个模块依然是单独一个请求,依赖树深的库会产生大量请求,也没有 tree shaking 和压缩。小工具、演示页、内部系统可以直接用,大型生产项目通常还是走打包器。
- 以为它会改写页面上所有请求
- import map 作用于静态
import、动态import()以及import.meta.resolve。普通的<script src>、fetch、<link>这些请求不会查这张表。
面试官还会怎么问?
Node.js 为什么不需要 import map?
Node.js 有自己的解析算法:遇到裸说明符就从当前目录开始逐层往上找 node_modules,再读包的 package.json 里的 exports 或 main。浏览器只能通过网络请求,一层层去试探不存在的 URL 代价太高,所以只能靠显式映射。
import map 能用 src 引用外部文件吗?
目前规范不支持 <script type="importmap" src="...">,只能内联写 JSON。需要动态生成时,可以在服务端渲染页面时输出,或者在任何模块脚本执行前用 JS 创建一个内联的 importmap 标签插进去。
在 Vite 或 Webpack 项目里会用到 import map 吗?
平时开发基本不用手写,它们在构建时已经把裸说明符改写成了具体路径。import map 常出现在微前端里:多个子应用共享同一份 Vue 或 React,由基座页面用 import map 统一指定版本,子应用构建时把这些依赖标成外部依赖。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。