为什么 Vite、Webpack、Rspack 能实现 @ 别名
Resolver 是什么?
对于:
import Button from "@/components/Button";
JavaScript 引擎实际上只看到了 这些:
specifier = "@/components/Button"
接下来需要解决的问题是:
specifier
↓
真实模块
这个过程称为 Resolution。
Node.js ESM 规范中的入口算法:
ESM_RESOLVE(specifier, parentURL)
本质就是完成这个映射过程。
Node 默认支持哪些 specifier
Node 原生 Resolver 只支持四类:
import "./foo.js"
import "../foo.js"
import "/app/foo.js"
路径模块。
import react from "react"
包模块。
import db from "#db"
Package Imports。
import x from "file:///app/a.js"
URL 模块。
除此之外:
import x from "@/utils"
Node 无法解析。
直接进入:
Module Not Found
异常路径。
为什么 Alias 可以工作
假设存在:
import Button from "@/components/Button"
Webpack 配置:
resolve: {
alias: {
"@": "/project/src"
}
}
Webpack 在执行真正解析前:
@/components/Button
先经过 Alias Resolver:
/project/src/components/Button
随后再进入正常模块解析。
因此 Alias 并不是一种新的模块类型。
而是:
旧 specifier
↓
新 specifier
的一次转换。
Vite Alias
Vite:
resolve: {
alias: {
"@": path.resolve(__dirname, "src")
}
}
执行流程与 Webpack 完全一致。
对于:
import x from "@/utils/request"
Vite 首先执行:
@ -> /project/src
得到:
/project/src/utils/request
然后继续执行后续解析。
因此:
Webpack Alias
Vite Alias
Rspack Alias
本质是同一个东西。
区别只是 Resolver 实现不同。
TypeScript Paths 为什么经常失效
配置:
{
"compilerOptions": {
"paths": {
"@/*": ["src/*"]
}
}
}
很多人会发现:
VSCode 正常
TypeScript 正常
Node 运行失败
原因是:
paths
不是运行时 Resolver。
TypeScript 只会在:
类型检查
IDE 跳转
编译阶段
使用 paths。
Node 运行时根本不会读取:
tsconfig.json
因此:
{
"paths": {}
}
无法让 Node 识别:
import "@/utils"
必须额外配置:
Webpack Alias
Vite Alias
Rspack Alias
Node Loader
其中之一。
Node Loader 如何实现 Alias
Node 提供:
export async function resolve(
specifier,
context,
nextResolve
)
允许接管解析过程。
例如:
export async function resolve(
specifier,
context,
nextResolve
) {
if (specifier.startsWith("@/")) {
return {
url: pathToFileURL(
path.resolve(
process.cwd(),
"src",
specifier.slice(2)
)
).href,
shortCircuit: true
}
}
return nextResolve(specifier, context)
}
此时:
import x from "@/utils"
会变成:
file:///project/src/utils.js
然后进入正常加载流程。
Browser Import Map
浏览器没有:
node_modules
因此:
import React from "react"
无法工作。
Import Map 的作用:
<script type="importmap">
{
"imports": {
"react": "/vendor/react.js"
}
}
</script>
执行:
import React from "react"
时。
浏览器先映射:
react
↓
/vendor/react.js
再继续加载。
Resolver 的本质
观察:
Webpack Alias
Vite Alias
Rspack Alias
TS Paths
Node Loader
Import Map
会发现它们都在做同一件事:
specifier
↓
mapping
↓
new specifier
↓
real URL
区别仅在于:
映射规则存放的位置不同
模块图的起点
对于:
import App from "./App"
编译器首先执行:
specifier
↓
resolver
↓
real module
得到:
App.tsx
然后继续递归:
App.tsx
↓
Button.tsx
↓
request.ts
↓
...
最终构建:
Module Graph
Tree Shaking、Code Splitting、HMR、Chunk 拆分全部建立在这个图之上。
因此 Resolver 是整个现代前端工具链真正的入口。