上一章讲解了通过入口文件 index.html 进行脚本提取,确定整个扫描的关系链, 接下来以上章的案例继续进行链路扫描, 以下为提取的脚本内容:
import "/src/main.js"
// 或者
import "/src/main"
export default {}
# 相对路径或者绝对路径的 js、 tsx、 jsx、 mjs 后缀解析
无论是相对路径和绝对路径的 js、 tsx、 jsx、 mjs 文件都将被 catch All 最终模块 onResolved方法进行拦截
提示
对于后缀而言是支持省略,在 resolve 插件方法内部调用 @rollup/plugin-alias 解析路径的时候,会借助后缀进行自动匹配。
// catch all -------------------------------------------------------------
build.onResolve({filter: /.*/},async ({ path: id, importer }) => {})
id 表示导入的模块路径 /src/main.js importer 表示导入id模块的文件, /src/main.js 是在html文件内进行导入的, 所以返回的则是 index.html 文件的绝对路径地址。
# 解析导入模块路径, 获取绝对路径
const resolved = await resolve(id, importer)
resolved 将导入的模块地址进行解析, 返回此导入文件的绝对路径。
提示
在解析当中, 就算解析成功返回, 往往有时候不符合预期, 后续会进行解析路径的检测优化
if (resolved) {
.....
} else {
// resolve failed... probably unsupported type
return externalUnlessEntry({ path: id })
}
如果导入模块路径解析失败, 直接进行过滤, 不再对此路径进行深入扫描, 视为一个无效的导入模块。
# 导入模块路径解析成功, 检测优化路径
if (resolved) {
if (shouldExternalizeDep(resolved, id)) {
return externalUnlessEntry({ path: id })
}
}
export function pshouldExternalizeDep(
resolvedId: string,
rawId: string
): boolean {
// not a valid file path
if (!path.isAbsolute(resolvedId)) {
return true
}
// virtual id
if (resolvedId === rawId || resolvedId.includes('\0')) {
return true
}
// resolved is not a scannable type
if (!JS_TYPES_RE.test(resolvedId) && !htmlTypesRE.test(resolvedId)) {
return true
}
return false
}
路径解析成功返回同时需要对文件路径进行检测优化,以下解析后的路径需要进行过滤:
- 不是绝对路径。
- 是一个虚拟的模块。
- 路径不是以
.js、.ts、.jsx、.tsx、.mjs后缀, 并且不是以.vue、.html、.svelte后缀文件结尾。
# 遇上 .vue .html .svelte 后缀的导入路径模块
const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined
return {
path: path.resolve(cleanUrl(resolved)),
namespace
}
此时解析的路径是一个 .vue,.html, .svelte文件, 直接返回不进行 onLoad 脚本提取, 直接返回交给 esbuild 继续链路扫描时/depedency/esbuild插件基础介绍.html#esbuild插件基础介绍, 需要设置 namespace: html 分配置虚拟命名空间, 继续交给onload /depedency/HTML入口扫描.html。 其余的正常返回, 让 esbuild 继续深入寻找导入关系链。
# 什么情况会解析到 .vue、 .html、 .svelte 后缀文件
[/depedency/收集依赖插件esbuildScanPlugin.html#解析路径种类](8 大onResolved方法中), 一开始就会解析 .vue, .html, .svelte文件,但如果引入的路径省略后缀,resolve 路径时会进行补全,但是此时已 filter .*拦截, 所以此时需要再进行 onLoad 提取脚本处理。
相关问题可以查看此issues (opens new window)
# 深入路径解析
在 /src/main.js 继续引入其它模块。
// /src/main.js
import util from './util/index.js'
// /src/util/index.js
import cache from './cache.js'
// /src/util/cache.js
export default {}
整个深入路径解析的过程是:
/user/project/src/main.js -> /user/project/src/util/index.js -> /user/project//src/util/cache.js
esbuild 的解析模块就是遇到一个导入模块, 则对导入模块进行处理。会出现以下几个处理情况:
- 遇到过过滤模块进行过滤。
- 遇到非法模块进行过滤。
- 遇到提取脚本模块进行提取脚本, 进行深入解析。
- 遇到
js类型文件深入解析。
# 中断解析关系链
// /src/main.js
import util from '../util/index.js'
这是一个错误的路径,通过 resolve 解析出来是 undfined, 此时就会对 ../util/index.js 进行 external, 同时 util/index.js 内部的 import cache from './cache.js' 都不会被 esbuild 进行导入解析, 直接中断。
如果不设置 external, esbuild 将会报错:
> src/main.js:4:7: error: Could not resolve "../util/index.js"
4 │ import '../util/index.js'
╵ ~~~~~~~~~~~~~~~~~~
error when starting dev server:
Error: Build failed with 1 error:
src/main.js:4:7: error: Could not resolve "../util/index.js"
# 过滤导入模块, 不再进行深入解析
除了 js 类型导入模块, 可提取脚本模块, 其余的将一并进行模块过滤, 因为其它模块内部不会存在构建收集依赖的情况, 同样也可能对这些文件类型无法进行构建解析。
# url 导入过滤
export const externalRE = /^(https?:)?\/\//
build.onResolve({ filter: externalRE }, ({ path }) => ({
path,
external: true
}))
# data Url 导入过滤
export const dataUrlRE = /^\s*data:/i
// data urls
build.onResolve({ filter: dataUrlRE }, ({ path }) => ({
path,
external: true
}))
# 样式和 json 导入过滤
// css & json
build.onResolve(
{
filter: /\.(css|less|sass|scss|styl|stylus|pcss|postcss|json)$/
},
externalUnlessEntry
)
# 视为 webworker、 url、 文本 的 query(worker、url, raw) 后缀 导入过滤
// known vite query types: ?worker, ?raw
export const SPECIAL_QUERY_RE = /[\?&](?:worker|sharedworker|raw|url)\b/
build.onResolve({ filter: SPECIAL_QUERY_RE }, ({ path }) => ({
path,
external: true
}))