上一章讲解了通过入口文件 index.html 进行脚本提取,确定整个扫描的关系链, 接下来以上章的案例继续进行链路扫描, 以下为提取的脚本内容:

import "/src/main.js"
// 或者  
import "/src/main"
export default {}

# 相对路径或者绝对路径的 jstsxjsxmjs 后缀解析

无论是相对路径和绝对路径的 jstsxjsxmjs 文件都将被 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
}

路径解析成功返回同时需要对文件路径进行检测优化,以下解析后的路径需要进行过滤:

  1. 不是绝对路径。
  2. 是一个虚拟的模块。
  3. 路径不是以 .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#解析路径种类](8onResolved方法中), 一开始就会解析 .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 的解析模块就是遇到一个导入模块, 则对导入模块进行处理。会出现以下几个处理情况:

  1. 遇到过过滤模块进行过滤。
  2. 遇到非法模块进行过滤。
  3. 遇到提取脚本模块进行提取脚本, 进行深入解析。
  4. 遇到 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"
    4import '../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
)

# 视为 webworkerurl文本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
}))