Vite2源码分析

一、vite是什么?

Vite 是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

  • 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
  • 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。

Vite 意在提供开箱即用的配置,同时它的 插件 API 和 JavaScript API 带来了高度的可扩展性,并有完整的类型支持。本篇主要分析npm run dev时发生了什么

二、环境准备

1.下载vite源码

从github上下载vite2.7.2的源码,本文下载到了D:\workspace\vue3\vite-2.7.2目录下。下载完成之后cd到vite-2.7.2目录,cmd执行pnpm install(pnpm如何安装),安装完成之后cd到packages/vite,cmd中执行pnpm run dev,构建完成之后就可以将vite命令软链到全局,进入到packages/vite下,执行pnpm link --global,link之后如图:

pnpm 报如下错误的解决方案

pnpm link --global 这个命令报错 Run "pnpm setup" to create it automatically, or set the global-bin-dir setting, or the PNPM_HOME env variable. 
The global bin directory should be in the PATH

执行pnpm config delete global-bin-dir 然后在执行pnpm link --global

2.创建vue3项目

使用vite命令创建vue3项目,cmd D:\workspace\vue3中(实际操作时可以创建到任意盘符,此处只是为了下文路径描述的准确,将项目创建在D:\workspace\vue3中),打开cmd执行

npm create vite@latest vitelearn -- --template vue 

 项目结构如图:

从package.json中删除掉vite,然后执行npm install安装依赖。然后执行pnpm link --global vite,此时会将本地的vite2.7.2的源码依赖进来。

如图node_modules目录下的vite右侧出现了一个箭头。 

安装完毕之后执行npm run dev时,node会执行node_modules\.bin\vite.cmd中的脚本,vite.cmd内容:

 脚本意思就是判断有没有NODE_PATH这个环境变量,如果有那么执行 C:\Users\EDZ\AppData\Roaming\npm\pnpm-global\5\node_modules\vite\bin\vite.js的vite.js文件,该文件其实就是D:\workspace\vue3\vite-2.7.2\packages\vite\bin\vite.js文件,

vite.js关键代码如下:

代码1
D:\workspace\vue3\vite-2.7.2\packages\vite\bin\vite.js

function start() {
  require('../dist/node/cli')
}

if (profileIndex > 0) {
  process.argv.splice(profileIndex, 1)
  const next = process.argv[profileIndex]
  if (next && !next.startsWith('-')) {
    process.argv.splice(profileIndex, 1)
  }
  const inspector = require('inspector')
  const session = (global.__vite_profile_session = new inspector.Session())
  session.connect()
  session.post('Profiler.enable', () => {
    session.post('Profiler.start', start)
  })
} else {
  start()
}

因为执行npm run dev时并没有传递--profile参数,所以profileIndex=-1,也就是直接执行start方法,start方法加载了D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\cli.ts文件,关键代码如下:

代码2
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\cli.ts


cli
  .command('[root]') // default command
  .alias('serve') // the command is called 'serve' in Vite's API
  .alias('dev') // alias to align with the script name
  .option('--host [host]', `[string] specify hostname`)
  .option('--port <port>', `[number] specify port`)
  .option('--https', `[boolean] use TLS + HTTP/2`)
  .option('--open [path]', `[boolean | string] open browser on startup`)
  .option('--cors', `[boolean] enable CORS`)
  .option('--strictPort', `[boolean] exit if specified port is already in use`)
  .option(
    '--force',
    `[boolean] force the optimizer to ignore the cache and re-bundle`
  )
  .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
    // output structure is preserved even after bundling so require()
    // is ok here
    const { createServer } = await import('./server')
    try {
      const server = await createServer({
        root,
        base: options.base,
        mode: options.mode,
        configFile: options.config,
        logLevel: options.logLevel,
        clearScreen: options.clearScreen,
        server: cleanOptions(options)
      })

      if (!server.httpServer) {
        throw new Error('HTTP server not available')
      }

      await server.listen()

      const info = server.config.logger.info

      info(
        colors.cyan(`\n  vite v${require('vite/package.json').version}`) +
          colors.green(` dev server running at:\n`),
        {
          clear: !server.config.logger.hasWarned
        }
      )

      server.printUrls()

      // @ts-ignore
      if (global.__vite_start_time) {
        // @ts-ignore
        const startupDuration = performance.now() - global.__vite_start_time
        info(
          `\n  ${colors.cyan(`ready in ${Math.ceil(startupDuration)}ms.`)}\n`
        )
      }
    } catch (e) {
      createLogger(options.logLevel).error(
        colors.red(`error when starting dev server:\n${e.stack}`),
        { error: e }
      )
      process.exit(1)
    }
  })

调用createServer方法创建本地服务器

代码3
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\index.ts


export async function createServer(
  inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
  const config = await resolveConfig(inlineConfig, 'serve', 'development')
  const root = config.root
  const serverConfig = config.server
  const httpsOptions = await resolveHttpsConfig(
    config.server.https,
    config.cacheDir
  )
  ...省略代码
}

接着看下resolveConfig方法

代码4
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts


export async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development'
): Promise<ResolvedConfig> {
  ...省略代码
  //指明要使用的配置文件。如果没有设置,Vite 将尝试从项目根目录自动解析。设置为 false 可以禁用自动解析功能
  let { configFile } = config
  if (configFile !== false) {
    const loadResult = await loadConfigFromFile(
      configEnv,
      configFile,
      config.root,
      config.logLevel
    )
    if (loadResult) {
      config = mergeConfig(loadResult.config, config)
      configFile = loadResult.path
      configFileDependencies = loadResult.dependencies
    }
  }
  ...省略代码
}

执行npm run dev时没有禁用configFile的自动解析功能,看下loadConfigFromFile方法

代码5
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts

export async function loadConfigFromFile(
  configEnv: ConfigEnv,
  configFile?: string,
  configRoot: string = process.cwd(),
  logLevel?: LogLevel
): Promise<{
  path: string
  config: UserConfig
  dependencies: string[]
} | null> {
  ...省略代码
  try {
    let userConfig: UserConfigExport | undefined
    ...省略代码
    if (!userConfig) {
      // Bundle config file and transpile it to cjs using esbuild.
      //resolvedPath='D:\\workspace\\vue3\\vitelearn\\vite.config.ts'
      const bundled = await bundleConfigFile(resolvedPath)
      dependencies = bundled.dependencies
      userConfig = await loadConfigFromBundledFile(resolvedPath, bundled.code)
      debug(`bundled config file loaded in ${getTime()}`)
    }

    const config = await (typeof userConfig === 'function'
      ? userConfig(configEnv)
      : userConfig)
    if (!isObject(config)) {
      throw new Error(`config must export or return an object.`)
    }
    return {
      path: normalizePath(resolvedPath),
      config,
      dependencies
    }
  } catch (e) {
    ...省略代码
  }
}

来看下bundleConfigFile方法

代码6
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts

async function bundleConfigFile(
  fileName: string,
  isESM = false
): Promise<{ code: string; dependencies: string[] }> {
  const result = await build({
    absWorkingDir: process.cwd(),
    entryPoints: [fileName],
    outfile: 'out.js',
    write: false,
    platform: 'node',
    bundle: true,
    format: isESM ? 'esm' : 'cjs',
    sourcemap: 'inline',
    metafile: true,
    plugins: [
      {
        name: 'externalize-deps',
        setup(build) {
          build.onResolve({ filter: /.*/ }, (args) => {
            const id = args.path
            if (id[0] !== '.' && !path.isAbsolute(id)) {
              return {
                external: true
              }
            }
          })
        }
      },
      {
        name: 'replace-import-meta',
        setup(build) {
          build.onLoad({ filter: /\.[jt]s$/ }, async (args) => {
            const contents = await fs.promises.readFile(args.path, 'utf8')
            return {
              loader: args.path.endsWith('.ts') ? 'ts' : 'js',
              contents: contents
                .replace(
                  /\bimport\.meta\.url\b/g,
                  JSON.stringify(`file://${args.path}`)
                )
                .replace(
                  /\b__dirname\b/g,
                  JSON.stringify(path.dirname(args.path))
                )
                .replace(/\b__filename\b/g, JSON.stringify(args.path))
            }
          })
        }
      }
    ]
  })
  const { text } = result.outputFiles[0]
  return {
    code: text,
    dependencies: result.metafile ? Object.keys(result.metafile.inputs) : []
  }
}

调用esbuild对vite.config.ts进行编译,text即为编译后的内容,此时result如图:

 所以其dependencies为[vite.config.ts],代码6返回了vite.config.ts编译的内容以及vite.config.ts的依赖项,回到代码5,代码继续往下执行

代码8
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts



export async function loadConfigFromFile(
  configEnv: ConfigEnv,
  configFile?: string,
  configRoot: string = process.cwd(),
  logLevel?: LogLevel
): Promise<{
  path: string
  config: UserConfig
  dependencies: string[]
} | null> {
  ...省略代码
  try {
    let userConfig: UserConfigExport | undefined
    ...省略代码
    if (!userConfig) {
      // Bundle config file and transpile it to cjs using esbuild.
      //resolvedPath='D:\\workspace\\vue3\\vitelearn\\vite.config.ts'
      const bundled = await bundleConfigFile(resolvedPath)
      //dependencies=['vite.config.ts']
      dependencies = bundled.dependencies
      userConfig = await loadConfigFromBundledFile(resolvedPath, bundled.code)
      debug(`bundled config file loaded in ${getTime()}`)
    }
    ...省略代码
  } catch (e) {
    ...省略代码
  }
}

接着看loadConfigFromBundledFile方法

代码9
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts

//fileName='D:\\workspace\\vue3\\vitelearn\\vite.config.ts'
//bundledCode='vite.config.ts文件编译后的内容'
async function loadConfigFromBundledFile(
  fileName: string,
  bundledCode: string
): Promise<UserConfig> {
  const extension = path.extname(fileName)
  const defaultLoader = require.extensions[extension]!
  require.extensions[extension] = (module: NodeModule, filename: string) => {
    if (filename === fileName) {
      ;(module as NodeModuleWithCompile)._compile(bundledCode, filename)
    } else {
      defaultLoader(module, filename)
    }
  }
  // clear cache in case of server restart
  delete require.cache[require.resolve(fileName)]
  const raw = require(fileName)
  const config = raw.__esModule ? raw.default : raw
  require.extensions[extension] = defaultLoader
  return config
}

此时config如图:

回到代码4处,代码4继续往下执行

代码10
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts



export async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development'
): Promise<ResolvedConfig> {
    ...省略代码
    if (configFile !== false) {
        const loadResult = await loadConfigFromFile(
          configEnv,
          configFile,
          config.root,
          config.logLevel
        )
        if (loadResult) {
          config = mergeConfig(loadResult.config, config)
          configFile = loadResult.path
          configFileDependencies = loadResult.dependencies
        }
    }
    ...省略代码
}

此时loadResult如图:

 调用mergeConfig将两个配置项做合并,代码10继续往下执行

代码11
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts




export async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development'
): Promise<ResolvedConfig> {
    ...省略代码
    //将插件分为前置插件、正常插件、后置插件
    const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawUserPlugins)
    //调用每个插件的config方法,vite独有的钩子,此时userPlugins只有vuePlugin一个插件
    for (const p of userPlugins) {
        if (p.config) {
          const res = await p.config(config, configEnv)
          if (res) {
            config = mergeConfig(config, res)
          }
        }
    }
    ...省略代码
    //注册别名插件
    const clientAlias = [
        { find: /^[\/]?@vite\/env/, replacement: () => ENV_ENTRY },
        { find: /^[\/]?@vite\/client/, replacement: () => CLIENT_ENTRY }
    ]

    // resolve alias with internal client alias
    const resolvedAlias = mergeAlias(
        // @ts-ignore because @rollup/plugin-alias' type doesn't allow function
        // replacement, but its implementation does work with function values.
        clientAlias,
        config.resolve?.alias || config.alias || []
    )

    const resolveOptions: ResolvedConfig['resolve'] = {
        dedupe: config.dedupe,
        ...config.resolve,
        alias: resolvedAlias
    }
    ...省略代码
    //注册一系列插件,例如:resolvePlugin、htmlInlineScriptProxyPlugin、cssPlugin、jsonPlugin、definePlugin、cssPostPlugin、clientInjectionsPlugin、importAnalysisPlugin等
    ;(resolved.plugins as Plugin[]) = await resolvePlugins(
        resolved,
        prePlugins,
        normalPlugins,
        postPlugins
    )

    // call configResolved hooks
    //循环调用用户插件中的configResolved方法
    await Promise.all(userPlugins.map((p) => p.configResolved?.(resolved)))
    ...省略代码
    return resolved
}

回到代码3,代码继续往下执行,主要是对server注册一些中间件以及执行依赖预构建

代码12
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\index.ts


export async function createServer(
  inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
    ...省略代码
    //循环调用每个插件的configureServer方法
    for (const plugin of config.plugins) {
        if (plugin.configureServer) {
          postHooks.push(await plugin.configureServer(server))
        }
    }
    ...省略代码
    if (!middlewareMode && httpServer) {
        let isOptimized = false
        // overwrite listen to run optimizer before server start
        const listen = httpServer.listen.bind(httpServer)
        httpServer.listen = (async (port: number, ...args: any[]) => {
          if (!isOptimized) {
            try {
              await container.buildStart({})
              await runOptimize()
              isOptimized = true
            } catch (e) {
              httpServer.emit('error', e)
              return
            }
          }
          return listen(port, ...args)
        }) as any
    } else {
        ...省略代码
    }
    ...省略代码
    return server
}

看下container.buildStart({})方法

代码13
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts

async buildStart() {
      await Promise.all(
        plugins.map((plugin) => {
          if (plugin.buildStart) {
            return plugin.buildStart.call(
              new Context(plugin) as any,
              container.options as NormalizedInputOptions
            )
          }
        })
      )
    },

循环调用每个插件的buildStart方法,做一些初始化的工作,代码12继续往下执行,执行runOptimize,也即依赖预构建

代码14
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\index.ts


const runOptimize = async () => {
    server._isRunningOptimizer = true
    try {
      server._optimizeDepsMetadata = await optimizeDeps(
        config,
        config.server.force || server._forceOptimizeOnRestart
      )
    } finally {
      server._isRunningOptimizer = false
    }
    server._registerMissingImport = createMissingImporterRegisterFn(server)
  }

来看下optimizeDeps方法

代码15
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\index.ts


export async function optimizeDeps(
  config: ResolvedConfig,
  force = config.server.force,//设置为 true 强制使依赖预构建
  asCommand = false,
  newDeps?: Record<string, string>, // missing imports encountered after server has started
  ssr?: boolean
): Promise<DepOptimizationMetadata | null> {
  ...省略代码
  //先判断D:\\workspace\\vue3\\vitelearn\\node_modules\\.vite目录中是否存在_metadata.json,如果存在说明之前已经编译过了,直接读取缓存,如果不存在那么创建缓存目录
  const dataPath = path.join(cacheDir, '_metadata.json')
  const mainHash = getDepHash(root, config)
  const data: DepOptimizationMetadata = {
    hash: mainHash,
    browserHash: mainHash,
    optimized: {}
  }

  if (!force) {
    let prevData: DepOptimizationMetadata | undefined
    try {
      prevData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'))
    } catch (e) {}
    // hash is consistent, no need to re-bundle
    if (prevData && prevData.hash === data.hash) {
      log('Hash is consistent. Skipping. Use --force to override.')
      return prevData
    }
  }

  if (fs.existsSync(cacheDir)) {
    emptyDir(cacheDir)
  } else {
    fs.mkdirSync(cacheDir, { recursive: true })
  }
  // a hint for Node.js
  // all files in the cache directory should be recognized as ES modules
  writeFile(
    path.resolve(cacheDir, 'package.json'),
    JSON.stringify({ type: 'module' })
  )

  let deps: Record<string, string>, missing: Record<string, string>
  if (!newDeps) {
    ;({ deps, missing } = await scanImports(config))
  } else {
    deps = newDeps
    missing = {}
  }
  ...省略文件
  return data
}

因为是初次运行,所以肯定不存在缓存目录。来看下scanImports方法

代码16
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


export async function scanImports(config: ResolvedConfig): Promise<{
  deps: Record<string, string>
  missing: Record<string, string>
}> {
  ...省略代码
  //默认情况下,Vite 会抓取你的 index.html 来检测需要预构建的依赖项。如果指定了 
  build.rollupOptions.input,Vite 将转而去抓取这些入口点

  const explicitEntryPatterns = config.optimizeDeps.entries
  const buildInput = config.build.rollupOptions?.input

  if (explicitEntryPatterns) {
    entries = await globEntries(explicitEntryPatterns, config)
  } else if (buildInput) {
    const resolvePath = (p: string) => path.resolve(config.root, p)
    if (typeof buildInput === 'string') {
      entries = [resolvePath(buildInput)]
    } else if (Array.isArray(buildInput)) {
      entries = buildInput.map(resolvePath)
    } else if (isObject(buildInput)) {
      entries = Object.values(buildInput).map(resolvePath)
    } else {
      throw new Error('invalid rollupOptions.input value.')
    }
  } else {
    entries = await globEntries('**/*.html', config)//抓取index.html来检测需要预构建的依赖项
  }
  ...省略代码
  const deps: Record<string, string> = {}
  const missing: Record<string, string> = {}
  const container = await createPluginContainer(config)
  const plugin = esbuildScanPlugin(config, container, deps, missing, entries)

  const { plugins = [], ...esbuildOptions } = config.optimizeDeps?.esbuildOptions ?? {}

  await Promise.all(
    entries.map((entry) =>
      build({
        absWorkingDir: process.cwd(),
        write: false,
        entryPoints: [entry],
        bundle: true,
        format: 'esm',
        logLevel: 'error',
        plugins: [...plugins, plugin],
        ...esbuildOptions
      })
    )
  )

  debug(`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`, deps)

  return {
    deps,
    missing
  }
}

对入口文件循环进行构建时会回调plugin相关的方法,来看下esbuildScanPlugin方法

代码17
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
  ...省略代码
  return {
    name: 'vite:dep-scan',
    setup(build) {
      ...省略代码
    }
  }
}

构建index.html时先匹配上了

代码18
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
                return {
                  path: await resolve(path, importer),
                  namespace: 'html'
                }
            })
            ...省略代码
        }
    }
    ...省略代码
}

看下resolve方法

代码19
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    const seen = new Map<string, string | undefined>()

    //id='D:/workspace/vue3/vitelearn/index.html'
    //importer=''
    const resolve = async (id: string, importer?: string) => {
        const key = id + (importer && path.dirname(importer))
        //如果已经解析过了那么读缓存中的结果
        if (seen.has(key)) {
          return seen.get(key)
        }
        const resolved = await container.resolveId(
          id,
          importer && normalizePath(importer)
        )
        const res = resolved?.id
        seen.set(key, res)
        return res
    }
    ...省略代码
}

然后调用container.resolveId方法

代码20
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts

//rawId='D:/workspace/vue3/vitelearn/index.html'
//importer=''
//options=undefined
async resolveId(rawId, importer = join(root, 'index.html'), options) {
      ...省略代码
      const ctx = new Context()
      ...省略代码
      for (const plugin of plugins) {
        ...省略代码
        const result = await plugin.resolveId.call(
          ctx as any,
          rawId,
          importer,
          { ssr }
        )
        if (!result) continue
        ...省略代码
        break
      }
      ...省略代码
      if (id) {
        partial.id = isExternalUrl(id) ? id : normalizePath(id)
        return partial as PartialResolvedId
      } else {
        return null
      }
    }

 循环调用每个插件的resolveId方法,被resolvePlugin插件匹配到,看下resolvePlugin的resolveId方法

代码21
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
    ...省略代码
    return {
        name: 'vite:resolve',
        ...省略代码
        //id='D:/workspace/vue3/vitelearn/index.html'
        //importer=''
        resolveId(id, importer, resolveOpts) {
            ...省略代码
            // absolute fs paths
            // 绝对路径匹配成功
            if (path.isAbsolute(id) && (res = tryFsResolve(id, options))) {
                isDebug && debug(`[fs] ${colors.cyan(id)} -> ${colors.dim(res)}`)
                //res='D:/workspace/vue3/vitelearn/index.html'
                return res
            }
            ...省略代码
        }
        //省略代码
    }
}

回到代码18,代码18返回如下内容

function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
                return {
                  path: await resolve(path, importer),//path='D:/workspace/vue3/vitelearn/index.html'
                  namespace: 'html'
                }
            })
            ...省略代码
        }
    }
    ...省略代码
}

代码18继续往下执行,又匹配到如下的规则

代码22
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='D:/workspace/vue3/vitelearn/index.html'
            build.onLoad({ filter: htmlTypesRE, namespace: 'html' },async ({ path }) => {
               let raw = fs.readFileSync(path, 'utf-8')//读取index.html文件的内容
               // Avoid matching the content of the comment
               raw = raw.replace(commentRE, '<!---->')
               const isHtml = path.endsWith('.html')
               const regex = isHtml ? scriptModuleRE : scriptRE
               regex.lastIndex = 0
               let js = ''
               let loader: Loader = 'js'
               let match: RegExpExecArray | null
               while ((match = regex.exec(raw))) {
                    const [, openTag, content] = match
                    const typeMatch = openTag.match(typeRE)
                    const type = typeMatch && (typeMatch[1] || typeMatch[2] || typeMatch[3])
                    const langMatch = openTag.match(langRE)
                    const lang = langMatch && (langMatch[1] || langMatch[2] || langMatch[3])
                    // skip type="application/ld+json" and other non-JS types
                    if (
                      type &&
                      !(
                        type.includes('javascript') ||
                        type.includes('ecmascript') ||
                        type === 'module'
                      )
                    ) {
                      continue
                    }
                    if (lang === 'ts' || lang === 'tsx' || lang === 'jsx') {
                      loader = lang
                    }
                    //匹配index.html中的<script type="module" src="/src/main.ts"></script>
                    const srcMatch = openTag.match(srcRE)
                    if (srcMatch) {
                          const src = srcMatch[1] || srcMatch[2] || srcMatch[3]
                          js += `import ${JSON.stringify(src)}\n`
                    } else if (content.trim()) {
                      // There can be module scripts (`<script context="module">` in Svelte and `<script>` in Vue)
                      // or local scripts (`<script>` in Svelte and `<script setup>` in Vue)
                      // We need to handle these separately in case variable names are reused between them
                      const contextMatch = openTag.match(contextRE)
                      const context =
                        contextMatch &&
                        (contextMatch[1] || contextMatch[2] || contextMatch[3])
                      if (
                        (path.endsWith('.vue') && setupRE.test(openTag)) ||
                        (path.endsWith('.svelte') && context !== 'module')
                      ) {
                        // append imports in TS to prevent esbuild from removing them
                        // since they may be used in the template
                        const localContent =
                          content +
                          (loader.startsWith('ts') ? extractImportPaths(content) : '')
                        localScripts[path] = {
                          loader,
                          contents: localContent
                        }
                        js += `import '${virtualModulePrefix}${path}';\n`
                  } else {
                        js += content + '\n'
                  }
             }
          }

          // `<script>` in Svelte has imports that can be used in the template
          // so we handle them here too
          if (loader.startsWith('ts') && path.endsWith('.svelte')) {
            js += extractImportPaths(js)
          }

          // This will trigger incorrectly if `export default` is contained
          // anywhere in a string. Svelte and Astro files can't have
          // `export default` as code so we know if it's encountered it's a
          // false positive (e.g. contained in a string)
          if (!path.endsWith('.vue') || !js.includes('export default')) {
            js += '\nexport default {}'
          }

          if (js.includes('import.meta.glob')) {
            return {
              // transformGlob already transforms to js
              loader: 'js',
              contents: await transformGlob(js, path, config.root, loader)
            }
          }
          //此时js='import /src/main.ts\n\nexport default {}'
          //loader='js'
          return {
            loader,
            contents: js
          }
          ...省略代码
        }
    }
    ...省略代码
}

返回的contents中有import /src/main.ts内容,然后import /src/main.ts匹配到

代码23
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //id='/src/main.ts' 
            //importer='D:/workspace/vue3/vitelearn/index.html'
            build.onResolve({filter: /.*/}, async ({ path: id, importer }) => {
                  // use vite resolver to support urls and omitted extensions
                  const resolved = await resolve(id, importer)
                  if (resolved) {
                    if (shouldExternalizeDep(resolved, id)) {
                      return externalUnlessEntry({ path: id })
                    }

                    const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined

                    return {
                      path: path.resolve(cleanUrl(resolved)),
                      namespace: undefined
                    }
                  } else {
                    // resolve failed... probably unsupported type
                    return externalUnlessEntry({ path: id })
                  }
                }
            )
            ...省略代码
}

再看下resolve方法

代码24
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    const seen = new Map<string, string | undefined>()
    //id='/src/main.ts'
    //importer='D:/workspace/vue3/vitelearn/index.html'
    const resolve = async (id: string, importer?: string) => {
        const key = id + (importer && path.dirname(importer))
        if (seen.has(key)) {
          return seen.get(key)//如果之前已经解析过路径了,那么直接读取缓存
        }
        const resolved = await container.resolveId(
          id,
          importer && normalizePath(importer)
        )
        const res = resolved?.id
        seen.set(key, res)
        return res
    }
    ...省略代码
}

然后调用container.resolveId方法

代码25
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts

//rawId='/src/main.ts' importer='D:/workspace/vue3/vitelearn/index.html'
async resolveId(rawId, importer = join(root, 'index.html'), options) {
      ...省略代码
      const ctx = new Context()
      ...省略代码
      for (const plugin of plugins) {
        ...省略代码
        const result = await plugin.resolveId.call(
          ctx as any,
          rawId,
          importer,
          { ssr }
        )
        if (!result) continue

        if (typeof result === 'string') {
          id = result
        } else {
          id = result.id
          Object.assign(partial, result)
        }
        ...省略代码
        break
      }
      ...省略代码
      if (id) {
        partial.id = isExternalUrl(id) ? id : normalizePath(id)
        return partial as PartialResolvedId
      } else {
        return null
      }
    }

循环调用每个插件的resolveId方法,rawId='/src/main.ts',被D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts的resolvePlugin插件匹配上,看下该插件的resolveId方法

代码26
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
    ...省略代码
    return {
        name: 'vite:resolve',
        //id='/src/main.ts' importer='D:/workspace/vue3/vitelearn/index.html'
        resolveId(id, importer, resolveOpts) {
            ...省略代码
            // id='/src/main.ts'满足该条件
            if (asSrc && id.startsWith('/')) {
                const fsPath = path.resolve(root, id.slice(1))
                if ((res = tryFsResolve(fsPath, options))) {
                  isDebug && debug(`[url] ${colors.cyan(id)} -> ${colors.dim(res)}`)
                  //res='D:/workspace/vue3/vitelearn/src/main.ts'
                  return res
                }
            }
            ...省略代码
        }
        ...省略代码
    }
}

所以最终代码23返回了

build.onResolve(
        {
          filter: /.*/
        },
        //id='/src/main.ts'
        async ({ path: id, importer }) => {
            ...省略代码
            return {
                path: 'D:/workspace/vue3/vitelearn/src/main.ts',
                namespace: undefined
            }
        }

path: 'D:/workspace/vue3/vitelearn/src/main.ts'匹配到如下规则:

代码27
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //id='D:/workspace/vue3/vitelearn/src/main.ts'
            build.onLoad({ filter: JS_TYPES_RE }, ({ path: id }) => {
                let ext = path.extname(id).slice(1)
                if (ext === 'mjs') ext = 'js'
        
                //读取main.ts文件的内容
                let contents = fs.readFileSync(id, 'utf-8')
                if (ext.endsWith('x') && config.esbuild && config.esbuild.jsxInject) {
                  contents = config.esbuild.jsxInject + `\n` + contents
                }

                if (contents.includes('import.meta.glob')) {
                  return transformGlob(contents, id, config.root, ext as Loader).then(
                    (contents) => ({
                      loader: ext as Loader,
                      contents
                    })
                  )
                }
                return {
                  loader: ext as Loader,
                  contents
                }
            })
        }
    }
}

代码27返回了如下内容

return {
    loader: 'ts',
    contents: " 
                import { createApp } from 'vue'
                import App from './App.vue'
                createApp(App).mount('#app')
    "
}

其中import { createApp } from 'vue'会匹配到

代码28
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            //id='vue' importer='D:/workspace/vue3/vitelearn/src/main.ts'
            build.onResolve(
                    {
                      // avoid matching windows volume
                      filter: /^[\w@][^:]/
                    },
                async ({ path: id, importer }) => {
                  if (moduleListContains(exclude, id)) {
                    return externalUnlessEntry({ path: id })
                  }
                  if (depImports[id]) {
                    return externalUnlessEntry({ path: id })
                  }
                  const resolved = await resolve(id, importer)
                  if (resolved) {
                    if (shouldExternalizeDep(resolved, id)) {
                      return externalUnlessEntry({ path: id })
                    }
                    if (resolved.includes('node_modules') || include?.includes(id)) {
                      // dependency or forced included, externalize and stop crawling
                      if (OPTIMIZABLE_ENTRY_RE.test(resolved)) {
                        depImports[id] = resolved
                      }
                      return externalUnlessEntry({ path: id })
                    } else {
                      const namespace = htmlTypesRE.test(resolved) ? 'html' : undefined
                      // linked package, keep crawling
                      return {
                        path: path.resolve(resolved),
                        namespace
                      }
                    }
                  } else {
                    missing[id] = normalizePath(importer)
                  }
                }
              )
              ...省略代码
}

调用resolve方法

代码29
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    const seen = new Map<string, string | undefined>()

    //id='vue' importer='D:/workspace/vue3/vitelearn/src/main.ts'
    const resolve = async (id: string, importer?: string) => {
    const key = id + (importer && path.dirname(importer))
        if (seen.has(key)) {
          return seen.get(key)
        }
        const resolved = await container.resolveId(
          id,
          importer && normalizePath(importer)
        )
        const res = resolved?.id
        seen.set(key, res)
        return res
    }
    ...省略代码
}

调用container.resolveId方法

代码30
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts的TransformContext类


//rawId='vue' importer='D:/workspace/vue3/vitelearn/src/main.ts'
async resolveId(rawId, importer = join(root, 'index.html'), options) {
      ...省略代码
      const ctx = new Context()
      ...省略代码
      for (const plugin of plugins) {
        ...省略代码
        const result = await plugin.resolveId.call(
          ctx as any,
          rawId,
          importer,
          { ssr }
        )
        if (!result) continue

        if (typeof result === 'string') {
          id = result
        } else {
          id = result.id
          Object.assign(partial, result)
        }
        ...省略代码
        // resolveId() is hookFirst - first non-null result is returned.
        break
      }

      ...省略代码

      if (id) {
        partial.id = isExternalUrl(id) ? id : normalizePath(id)
        return partial as PartialResolvedId
      } else {
        return null
      }
    }

循环调用每个插件的resolveId方法,rawId='vue'被D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts的resolvePlugin匹配上,看下resolvePlugin的resolveId方法

代码31
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
    ...省略代码
    return {
        name: 'vite:resolve',
        resolveId(id, importer, resolveOpts) {
            ...省略代码
            if (bareImportRE.test(id)) {
                ...省略代码
                if (
                  (res = tryNodeResolve(id, importer, options, targetWeb, server, ssr))
                ) {
                  return res
                }
                ...省略代码
            }
        },
        ...省略代码
    }
}

看下tryNodeResolve方法

代码32
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


//id:'vue' importer='D:/workspace/vue3/vitelearn/src/main.ts'
export function tryNodeResolve(
  id: string,
  importer: string | null | undefined,
  options: InternalResolveOptions,
  targetWeb: boolean,
  server?: ViteDevServer,
  ssr?: boolean
): PartialResolvedId | undefined {
  ...省略代码

  let pkg: PackageData | undefined
  //possiblePkgId=['vue']
  const pkgId = possiblePkgIds.reverse().find((pkgId) => {
    pkg = resolvePackageData(pkgId, basedir, preserveSymlinks, packageCache)!
    return pkg
  })!

  if (!pkg) {
    return
  }
  ...省略代码
  
  return { id: resolved }
}

看下resolvePackageData方法

代码33
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\packages.ts

//id='vue'
//basedir='D:/workspace/vue3/vitelearn/src'
export function resolvePackageData(
  id: string,
  basedir: string,
  preserveSymlinks = false,
  packageCache?: PackageCache
): PackageData | null {
  ...省略代码
  let pkgPath: string | undefined
  try {
    //获取vue的package.json文件的路径
    //pkgPath='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\package.json'
    pkgPath = resolveFrom(`${id}/package.json`, basedir, preserveSymlinks)
    //读取D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\package.json文件的内容,并返回PackageData对象
    pkg = loadPackageData(pkgPath, true, packageCache)
    if (packageCache) {
      packageCache.set(cacheKey!, pkg)
    }
    return pkg
  } catch (e) {
    ...省略代码
  }
  return null
}

回到代码32,代码32继续往下执行

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


//id:'vue' importer='D:/workspace/vue3/vitelearn/src/main.ts'
export function tryNodeResolve(
  id: string,
  importer: string | null | undefined,
  options: InternalResolveOptions,
  targetWeb: boolean,
  server?: ViteDevServer,
  ssr?: boolean
): PartialResolvedId | undefined {
  ...省略代码

  let pkg: PackageData | undefined
  //possiblePkgId=['vue']
  const pkgId = possiblePkgIds.reverse().find((pkgId) => {
    pkg = resolvePackageData(pkgId, basedir, preserveSymlinks, packageCache)!
    return pkg
  })!

  if (!pkg) {
    return
  }
  let resolveId = resolvePackageEntry
  let unresolvedId = pkgId
  if (unresolvedId !== nestedPath) {
    resolveId = resolveDeepImport
    unresolvedId = '.' + nestedPath.slice(pkgId.length)
  }

  let resolved: string | undefined
  try {
    resolved = resolveId(unresolvedId, pkg, targetWeb, options)
  } catch (err) {
    if (!options.tryEsmOnly) {
      throw err
    }
  }
  ...省略代码
  
  return { id: resolved }
}

查看下resolveId方法,resolvePackageEntry赋值给了resolveId,resolvePackageEntry是D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts里面的方法

代码34
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


//id='vue'
export function resolvePackageEntry(
  id: string,
  { dir, data, setResolvedCache, getResolvedCache }: PackageData,
  targetWeb: boolean,
  options: InternalResolveOptions
): string | undefined {
  ...省略代码
  //entryPoint =D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\dist\\vue.runtime.esm-bundler.js
    //dir='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue'
    entryPoint = path.join(dir, entryPoint)//entryPoint='./dist/vue.runtime.esm-bundler.js'
    //resolvedEntryPoint=D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js
    const resolvedEntryPoint = tryFsResolve(entryPoint, options)

    if (resolvedEntryPoint) {
      isDebug &&
        debug(
          `[package entry] ${colors.cyan(id)} -> ${colors.dim(
            resolvedEntryPoint
          )}`
        )
      setResolvedCache('.', resolvedEntryPoint, targetWeb)
      //resolveEntryPoint='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\dist\\vue.runtime.esm-bundler.js'
      return resolvedEntryPoint
    } else {
      packageEntryFailure(id)
    }
  } catch (e) {
    packageEntryFailure(id, e.message)
  }
}

回到代码32,代码32继续往下执行

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


//id:'vue' importer='D:/workspace/vue3/vitelearn/src/main.ts'
export function tryNodeResolve(
  id: string,
  importer: string | null | undefined,
  options: InternalResolveOptions,
  targetWeb: boolean,
  server?: ViteDevServer,
  ssr?: boolean
): PartialResolvedId | undefined {
  ...省略代码

  let pkg: PackageData | undefined
  //possiblePkgId=['vue']
  const pkgId = possiblePkgIds.reverse().find((pkgId) => {
    pkg = resolvePackageData(pkgId, basedir, preserveSymlinks, packageCache)!
    return pkg
  })!

  if (!pkg) {
    return
  }
  let resolveId = resolvePackageEntry
  let unresolvedId = pkgId
  if (unresolvedId !== nestedPath) {
    resolveId = resolveDeepImport
    unresolvedId = '.' + nestedPath.slice(pkgId.length)
  }

  let resolved: string | undefined
  try {
    //resolved ='D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'
    resolved = resolveId(unresolvedId, pkg, targetWeb, options)
  } catch (err) {
    if (!options.tryEsmOnly) {
      throw err
    }
  }
  if (!resolved) {
    return
  }

  // link id to pkg for browser field mapping check
  idToPkgMap.set(resolved, pkg)//加入缓存
  if (isBuild) {
    ...省略代码
  } else {
    //server._isRunningOptimizer此时为真
    if (
      !resolved.includes('node_modules') || // linked
      !server || // build
      server._isRunningOptimizer || // optimizing
      !server._optimizeDepsMetadata
    ) {
      return { id: resolved }
    }
    ...省略代码
  }
  ...省略代码
  
  return { id: resolved }
}

回到代码28处,代码28继续往下执行


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            build.onResolve(
                {
                  // avoid matching windows volume
                  filter: /^[\w@][^:]/
                },
                async ({ path: id, importer }) => {
                    ...省略代码
                     //resolved='D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'
                    const resolved = await resolve(id, importer)
                    if (resolved) {
                        ...省略代码
                        if (resolved.includes('node_modules') || include?.includes(id)) {
                            if (OPTIMIZABLE_ENTRY_RE.test(resolved)) {
                                //depImports={vue:'D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'}
                                depImports[id] = resolved
                            }
                            
                            return externalUnlessEntry({ path: id })
                        }
                    } else {
                        ...省略代码
                    }
                }
            )
            ...省略代码
        }
    }
}

 因为resolveId='D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'含有node_modules,所以代码28返回如下内容

return {
    path:'D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js',
    external: true
}

回到代码27,代码27返回内容如下:

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='D:\workspace\vue3\vitelearn\src\main.ts'
            build.onLoad({ filter: htmlTypesRE, namespace: 'html' },async ({ path }) => {
                ...省略代码
                return {
                    loader: 'ts',
                    contents: " 
                            import { createApp } from 'vue'
                            import App from './App.vue'

                            createApp(App).mount('#app')
                    "
                }
            })
            ...省略代码
        }
    }
}


其中import { createApp } from 'vue'这条线已经分析完了,然后执行./App.vue的导入分析

 import App from './App.vue',匹配到了

代码35
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='./App.vue' importer='D:\\workspace\\vue3\\vitelearn\\src\\main.ts'
            build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
                return {
                  path: await resolve(path, importer),
                  namespace: 'html'
                }
            })
            ...省略代码
        }
    }
}


看下resolve方法

代码36
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
  const seen = new Map<string, string | undefined>()

  //id='./App.vue' importer='D:\\workspace\\vue3\\vitelearn\\src\\main.ts'
  const resolve = async (id: string, importer?: string) => {
    const key = id + (importer && path.dirname(importer))
    if (seen.has(key)) {
      return seen.get(key)
    }
    const resolved = await container.resolveId(
      id,
      importer && normalizePath(importer)
    )
    const res = resolved?.id
    seen.set(key, res)
    return res
  }
  ...省略代码
}

调用container.resolveId方法

代码37
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts


//rawId='./App.vue'
//importer='D:/workspace/vue3/vitelearn/src/main.ts'
//options=undefined
async resolveId(rawId, importer = join(root, 'index.html'), options) {
      ...省略代码
      const ctx = new Context()
      ...省略代码
      for (const plugin of plugins) {
        ...省略代码
        const result = await plugin.resolveId.call(
          ctx as any,
          rawId,
          importer,
          { ssr }
        )
        if (!result) continue

        if (typeof result === 'string') {
          id = result
        } else {
          id = result.id
          Object.assign(partial, result)
        }

        ...省略代码
        break
      }

      ...省略代码

      if (id) {
        partial.id = isExternalUrl(id) ? id : normalizePath(id)
        return partial as PartialResolvedId
      } else {
        return null
      }
    }

循环调用每个插件的resolveId方法 ,被D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts的resolvePlugin插件匹配到,看下这个插件的resolveId方法

代码38
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
    ...省略代码
    return {
        name: 'vite:resolve',
        ...省略代码
        //id='./App.vue' importer='D:/workspace/vue3/vitelearn/src/main.ts'
        resolveId(id, importer, resolveOpts) {
            ...省略代码
            //id='./App.vue'以.开头
            if (id.startsWith('.') || (preferRelative && /^\w/.test(id))) {
                //basedir='D:/workspace/vue3/vitelearn/src'
                const basedir = importer ? path.dirname(importer) : process.cwd()
                //fsPath='D:\\workspace\\vue3\\vitelearn\\src\\App.vue'
                const fsPath = path.resolve(basedir, id)
                // handle browser field mapping for relative imports

                //normalizedFsPath='D:/workspace/vue3/vitelearn/src/App.vue'
                const normalizedFsPath = normalizePath(fsPath)
                //pathFromBasedir='/App.vue'
                const pathFromBasedir = normalizedFsPath.slice(basedir.length)
                ...省略代码
                if ((res = tryFsResolve(fsPath, options))) {
                      isDebug && debug(`[relative] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
                      //pkg=undefined
                      const pkg = importer != null && idToPkgMap.get(importer)
                      if (pkg) {
                        idToPkgMap.set(res, pkg)
                        return {
                          id: res,
                          moduleSideEffects: pkg.hasSideEffects(res)
                        }
                      }
                      //res='D:/workspace/vue3/vitelearn/src/App.vue'
                      return res
                }
            }
            ...省略代码
        },
        ...省略代码
    }
}

回到代码35,代码35返回

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='./App.vue' importer='D:\\workspace\\vue3\\vitelearn\\src\\main.ts'
            build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
                return {
                  //path='D:/workspace/vue3/vitelearn/src/App.vue'
                  path: await resolve(path, importer),
                  namespace: 'html'
                }
            })
            ...省略代码
        }
    }
}

 然后匹配到

代码39
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='D:/workspace/vue3/vitelearn/src/App.vue'
            build.onLoad({ filter: htmlTypesRE, namespace:'html' }, async ({ path }) => {
                  //读取App.vue文件的内容
                  let raw = fs.readFileSync(path, 'utf-8')
                  // Avoid matching the content of the comment
                  raw = raw.replace(commentRE, '<!---->')
                  const isHtml = path.endsWith('.html')
                  const regex = isHtml ? scriptModuleRE : scriptRE
                  regex.lastIndex = 0
                  let js = ''
                  let loader: Loader = 'js'
                  let match: RegExpExecArray | null
                  while ((match = regex.exec(raw))) {
                        //openTag='<script setup lang="ts">'
                        const [, openTag, content] = match
                        ...省略代码
                        //srcRE表达式匹配的是src,而App.vue的并没有src,所以匹配结果为null
                        const srcMatch = openTag.match(srcRE)
                        if (srcMatch) {
                            ...省略代码
                        } else if(content.trim()){
                            ...省略代码
                            if (
                                (path.endsWith('.vue') && setupRE.test(openTag)) ||
                                (path.endsWith('.svelte') && context !== 'module')
                            ) {
                                const localContent =
                                  content +
                                  (loader.startsWith('ts') ? extractImportPaths(content) : '')
                                localScripts[path] = {
                                      loader,
                                      contents: localContent
                                }
                                js += `import '${virtualModulePrefix}${path}';\n`
                            }
                        }
                  }
                  ...省略代码
                  return {
                    loader,
                    contents: js
                  }
            })
            ...省略代码
        }
    }
}

 此时

localScripts={
    'D:/workspace/vue3/vitelearn/src/App.vue': {
        contents:'
            // This starter template is using Vue 3 <script setup> SFCs
            // Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
            import HelloWorld from './components/HelloWorld.vue'

            import './components/HelloWorld.vue'
        ',
        loader:'ts'
    }
}

 代码39返回如下:

return {
    loader:'ts',
    contents: '
        import 'virtual-module:D:/workspace/vue3/vitelearn/src/App.vue';

        export default {}
    '
}

 import 'virtual-module:D:/workspace/vue3/vitelearn/src/App.vue';又匹配到了

代码40
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            // local scripts (`<script>` in Svelte and `<script setup>` in Vue)
            build.onResolve({ filter: virtualModuleRE }, ({ path }) => {
               return {
                    // strip prefix to get valid filesystem path so esbuild can resolve imports in the file
                    path: path.replace(virtualModulePrefix, ''),
                    namespace: 'local-script'
               }
            })
            ...省略代码
        }
    }
}

 将路径中的virtual-module:字样去掉,代码40返回

return {
    path:'D:/workspace/vue3/vitelearn/src/App.vue',
    namespace:'local-script'
}

 然后去加载D:/workspace/vue3/vitelearn/src/App.vue的内容

代码41
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='D:/workspace/vue3/vitelearn/src/App.vue'
            build.onLoad({ filter: /.*/, namespace: 'local-script' }, ({ path }) => {
                  return localScripts[path]
            })
            ...省略代码
        }
    }
}

 localScripts在代码39处已经赋值过了,所以代码41返回

return {
        contents:'
            // This starter template is using Vue 3 <script setup> SFCs
            // Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
            import HelloWorld from './components/HelloWorld.vue'

            import './components/HelloWorld.vue'
        ',
        loader:'ts'
    }

 其内容import './components/HelloWorld.vue'匹配到了

代码42
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='./components/HelloWorld.vue'
            //importer='D:/workspace/vue3/vitelearn/src/App.vue'
            build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
                return {
                  path: await resolve(path, importer),
                  namespace: 'html'
                }
            })
            ...省略代码
        }
    }
}

看下resolve方法

代码43
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
  const seen = new Map<string, string | undefined>()
  
  //id='./components/HelloWorld.vue'
  //importer='D:/workspace/vue3/vitelearn/src/App.vue'
  const resolve = async (id: string, importer?: string) => {
    const key = id + (importer && path.dirname(importer))
    if (seen.has(key)) {
      return seen.get(key)
    }
    const resolved = await container.resolveId(
      id,
      importer && normalizePath(importer)
    )
    const res = resolved?.id
    seen.set(key, res)
    return res
  }
  ...省略代码
}

调用container.resolveId方法

代码44
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts


//rawId='./components/HelloWorld.vue'
//importer='D:/workspace/vue3/vitelearn/src/App.vue'
//options=undefined
async resolveId(rawId, importer = join(root, 'index.html'), options) {
      ...省略代码
      const ctx = new Context()
      ...省略代码
      for (const plugin of plugins) {
        ...省略代码
        const result = await plugin.resolveId.call(
          ctx as any,
          rawId,
          importer,
          { ssr }
        )
        if (!result) continue

        if (typeof result === 'string') {
          id = result
        } else {
          id = result.id
          Object.assign(partial, result)
        }

        ...省略代码
        break
      }

      ...省略代码

      if (id) {
        partial.id = isExternalUrl(id) ? id : normalizePath(id)
        return partial as PartialResolvedId
      } else {
        return null
      }
    }

循环调用每个插件的resolveId方法,被D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts的resolvePlugin插件匹配,看下resolvePlugin的resolveId方法

代码45
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\plugins\resolve.ts


export function resolvePlugin(baseOptions: InternalResolveOptions): Plugin {
    ...省略代码
    return {
        name: 'vite:resolve',
        ...省略代码
        //id='./components/HelloWorld.vue'
        resolveId(id, importer, resolveOpts) {
            if (id.startsWith('.') || (preferRelative && /^\w/.test(id))) {
                //basedir='D:/workspace/vue3/vitelearn/src'
                const basedir = importer ? path.dirname(importer) : process.cwd()
                //fsPath='D:\\workspace\\vue3\\vitelearn\\src\\components\\HelloWorld.vue'
                const fsPath = path.resolve(basedir, id)
                // handle browser field mapping for relative imports
                
                //normalizedFsPath='D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue'
                const normalizedFsPath = normalizePath(fsPath)
                //pathFromBasedir='/components/HelloWorld.vue'
                const pathFromBasedir = normalizedFsPath.slice(basedir.length)
                ...省略代码
                if ((res = tryFsResolve(fsPath, options))) {
                      isDebug && debug(`[relative] ${chalk.cyan(id)} -> ${chalk.dim(res)}`)
                      //pkg=undefined
                      const pkg = importer != null && idToPkgMap.get(importer)
                      if (pkg) {
                        idToPkgMap.set(res, pkg)
                        return {
                          id: res,
                          moduleSideEffects: pkg.hasSideEffects(res)
                        }
                      }
                      //res='D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue'
                      return res
                }
            }
            ...省略代码
        },
        ...省略代码
    }
}

所以代码42返回如下:

代码42
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='./components/HelloWorld.vue'
            //importer='D:/workspace/vue3/vitelearn/src/App.vue'
            build.onResolve({ filter: htmlTypesRE }, async ({ path, importer }) => {
                return {
                  //path='D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue'
                  path: await resolve(path, importer),
                  namespace: 'html'
                }
            })
            ...省略代码
        }
    }
}

然后去加载D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue文件的内容

代码46
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path="D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue"
            build.onLoad({ filter: htmlTypesRE, namespace: 'html' },async ({ path }) ={
                  //读取HelloWorld.vue文件的内容
                  let raw = fs.readFileSync(path, 'utf-8')
                  // Avoid matching the content of the comment
                  raw = raw.replace(commentRE, '<!---->')
                  const isHtml = path.endsWith('.html')
                  const regex = isHtml ? scriptModuleRE : scriptRE
                  regex.lastIndex = 0
                  let js = ''
                  let loader: Loader = 'js'
                  let match: RegExpExecArray | null
                  while ((match = regex.exec(raw))) {
                        //openTag='<script setup lang="ts">'
                        const [, openTag, content] = match
                        ...省略代码
                        const srcMatch = openTag.match(srcRE)
                        if (srcMatch) {
                            ...省略代码
                        } else if (content.trim()) {
                            ...省略代码
                            if (
                                (path.endsWith('.vue') && setupRE.test(openTag)) ||
                                (path.endsWith('.svelte') && context !== 'module')
                              ) {
                                const localContent =
                                  content +
                                    (loader.startsWith('ts') ? extractImportPaths(content) : '')
                                    localScripts[path] = {
                                      loader,
                                      contents: localContent
                                    }
                                    js += `import '${virtualModulePrefix}${path}';\n`
                            } else {
                                ...省略代码
                            }
                        }
                  }
                  ...省略代码
            })
            ...省略代码
            return {
                loader,
                contents: js
            }
        }
    }
}

此时 

localScripts={
    'D:/workspace/vue3/vitelearn/src/App.vue':{ 
          contents:'
            // This starter template is using Vue 3 <script setup> SFCs
           // Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
           import HelloWorld from './components/HelloWorld.vue'

           import './components/HelloWorld.vue'
     ',
     loader:'ts'
    },
    'D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue':{   
          contents:'
           import { ref } from 'vue'

           defineProps<{ msg: string }>()

           const count = ref(0)

            import 'vue'
     ',
     loader:'ts'
    }
    
}

代码46返回

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path="D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue"
            build.onLoad({ filter: htmlTypesRE, namespace: 'html' },async ({ path }) ={
                ...省略代码
                return {
                    //loader='ts'
                    loader,
                    /**
                    * contents:'
                          import 'virtual-module:D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue';
                          export default {}
                        ',
                    */
                    contents: js
                }
            })
            ...省略代码
        }
    }
}

然后匹配到

代码47
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path='virtual-module:D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue'
            build.onResolve({ filter: virtualModuleRE }, ({ path }) => {
                return {
                      // strip prefix to get valid filesystem path so esbuild can resolve imports in the file
                      path: path.replace(virtualModulePrefix, ''),
                      namespace: 'local-script'
                }
            })
            ...省略代码
        }
    }
}

 代码47返回

return {
   namespace:'local-script',
   path:'D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue'
}

 然后匹配到

代码48
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts



function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            //path="D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue"
            build.onLoad({ filter: /.*/, namespace: 'local-script' }, ({ path }) => {
                return localScripts[path]
            })
            ...省略代码
    }
}

 根据代码46处localScripts对象中的数据,代码48返回的内容如下:

return {
    contents:'
         import { ref } from 'vue'

         defineProps<{ msg: string }>()

         const count = ref(0)

         import 'vue'
    ',
    loader:'ts'
}

 import { ref } from 'vue'匹配到

代码49
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


function esbuildScanPlugin(
  config: ResolvedConfig,
  container: PluginContainer,
  depImports: Record<string, string>,
  missing: Record<string, string>,
  entries: string[]
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-scan',
        setup(build) {
            ...省略代码
            build.onResolve(
                {
                  // avoid matching windows volume
                  filter: /^[\w@][^:]/
                },
                //id='vue' importer='D:/workspace/vue3/vitelearn/src/components/HelloWorld.vue'
                async ({ path: id, importer }) => {
                    if (moduleListContains(exclude, id)) {
                        return externalUnlessEntry({ path: id })
                    }
                    //在编译main.ts时vue已经编译过了,所以直接返回
                    if (depImports[id]) {
                        return externalUnlessEntry({ path: id })
                    }
                    ...省略代码
                }
            )
            ...省略代码
        }
    }
}

代码49返回

return {
    external:true,
    path:'vue'
}

至此index.html作为entrypoint的依赖都已经收集完毕, 也就是D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts的scanImports方法执行完毕,让我们回到代码16,代码16返回

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\scan.ts


export async function scanImports(config: ResolvedConfig): Promise<{
  deps: Record<string, string>
  missing: Record<string, string>
}> {
    ...省略代码
    //entries=['D:\\workspace\\vue3\\vitelearn\\index.html']
    await Promise.all(
        entries.map((entry) =>
          build({
            absWorkingDir: process.cwd(),
            write: false,
            entryPoints: [entry],
            bundle: true,
            format: 'esm',
            logLevel: 'error',
            plugins: [...plugins, plugin],
            ...esbuildOptions
          })
        )
    )
    debug(`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`, deps)
    return {
        //deps={vue:D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js}
        deps,
        //missing={}
        missing
    }
}

回到代码15,

代码50
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\index.ts

export async function optimizeDeps(
  config: ResolvedConfig,
  force = config.server.force,
  asCommand = false,
  newDeps?: Record<string, string>, // missing imports encountered after server has started
  ssr?: boolean
): Promise<DepOptimizationMetadata | null> {
    ...省略代码
    let deps: Record<string, string>, missing: Record<string, string>
    if (!newDeps) {
      //deps={'vue':'D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js',missing:{}}
      ;({ deps, missing } = await scanImports(config))
    } else {
      deps = newDeps
      missing = {}
    }
    ...省略代码
    //flatIdDeps={vue:'D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'}
    const result = await build({
        absWorkingDir: process.cwd(),
        entryPoints: Object.keys(flatIdDeps),
        bundle: true,
        format: 'esm',
        target: config.build.target || undefined,
        external: config.optimizeDeps?.exclude,
        logLevel: 'error',
        splitting: true,
        sourcemap: true,
        outdir: cacheDir,
        ignoreAnnotations: true,
        metafile: true,
        define,
        plugins: [
          ...plugins,
          esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr)
        ],
        ...esbuildOptions
    })
    ...省略代码
    return data
}

plugins=[],esbuildDepPlugin会返回一个plugin,esBuild对入口文件['vue']进行打包,首先匹配上了

代码51
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\esbuildDepPlugin.ts


//qualified={vue:'D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'}
export function esbuildDepPlugin(
  qualified: Record<string, string>,
  exportsData: Record<string, ExportsData>,
  config: ResolvedConfig,
  ssr?: boolean
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-pre-bundle',
        setup(build) {
            ...省略代码
            //id:'vue' importer='' kind='entry-point'
            build.onResolve({ filter: /^[\w@][^:]/ }, async ({ path: id, importer, kind }) => {
              ...省略代码
              // ensure esbuild uses our resolved entries
              let entry: { path: string; namespace: string } | undefined
              // if this is an entry, return entry namespace resolve result
              //importer=''
              if (!importer) {
                if ((entry = resolveEntry(id))) return entry
                ...省略代码
              }
              ...省略代码
            })
            ...省略代码
        }
    }
}

看下resolveEntry方法

代码52
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\esbuildDepPlugin.ts

//id='vue'
function resolveEntry(id: string) {
    //flatId='vue'
    const flatId = flattenId(id)
    //qualified={'vue':'D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'}
    if (flatId in qualified) {
      return {
          path: flatId,
          namespace: 'dep'
      }
    }
}

也就是代码51返回

return {
    path:'vue',
    namespace: 'dep'
}

然后匹配到

代码53
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\esbuildDepPlugin.ts


export function esbuildDepPlugin(
  qualified: Record<string, string>,
  exportsData: Record<string, ExportsData>,
  config: ResolvedConfig,
  ssr?: boolean
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-pre-bundle',
        setup(build) {
            ...省略代码
            //id='vue'
            build.onLoad({ filter: /.*/, namespace: 'dep' }, ({ path: id }) => {
                //entryFile='D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js'
                const entryFile = qualified[id]
                //relativePath='node_modules/vue/dist/vue.runtime.esm-bundler.js'
                let relativePath = normalizePath(path.relative(root, entryFile))
                if (
                    !relativePath.startsWith('./') &&
                    !relativePath.startsWith('../') &&
                    relativePath !== '.'
                 ) {
                    //relativePath='./node_modules/vue/dist/vue.runtime.esm-bundler.js'
                    relativePath = `./${relativePath}`
                }
                let contents = ''
                //exportsData在代码15处已经赋值过,data=[[],['compile'],false,hasReExports:true],第一项为imports的依赖,第二项为exports
                const data = exportsData[id]
                const [imports, exports] = data
                if (!imports.length && !exports.length) {
                    ...省略代码
                } else {
                    ...省略代码
                    if (
                        data.hasReExports ||
                        exports.length > 1 ||
                        exports[0] !== 'default'
                      ) {
                        contents += `\nexport * from "${relativePath}"`
                      }
                }
                //ext='js'
                let ext = path.extname(entryFile).slice(1)
                return {
                      loader: ext as Loader,
                      //contents='\nexport * from "./node_modules/vue/dist/vue.runtime.esm-bundler.js"'
                      contents,
                      //root='D:\\workspace\\vue3\\vitelearn'
                      resolveDir: root
                }
            })
            ...省略代码
        }
    }
}

D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js依赖@vue/runtime-dom,看下runtime.esm-bundler.js的源码:

代码54
D:/workspace/vue3/vitelearn/node_modules/vue/dist/vue.runtime.esm-bundler.js


import { initCustomFormatter, warn } from '@vue/runtime-dom';
export * from '@vue/runtime-dom';

...省略代码

export { compile };

import { initCustomFormatter, warn } from '@vue/runtime-dom';匹配到了

代码55
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\esbuildDepPlugin.ts


export function esbuildDepPlugin(
  qualified: Record<string, string>,
  exportsData: Record<string, ExportsData>,
  config: ResolvedConfig,
  ssr?: boolean
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-pre-bundle',
        setup(build) {
            ...省略代码
            //id='@vue/runtime-dom' 
     //importer='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\dist\\vue.runtime.esm-bundler.js'
            //kind='import-statement'
            build.onResolve({filter:/^[\w@][^:]/},async({path: id, importer, kind }) => {
                ...省略代码
                const resolved = await resolve(id, importer, kind)
                if (resolved) {
                    if (resolved.startsWith(browserExternalId)) {
                      return {
                            path: id,
                            namespace: 'browser-external'
                      }
                    }
                    if (isExternalUrl(resolved)) {
                      return {
                        path: resolved,
                        external: true
                      }
                    }
                    return {
                      path: path.resolve(resolved)
                    }
               }
            })
            ...省略代码
        }
    } 
}

来看resolve方法

代码56
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\esbuildDepPlugin.ts



export function esbuildDepPlugin(
  qualified: Record<string, string>,
  exportsData: Record<string, ExportsData>,
  config: ResolvedConfig,
  ssr?: boolean
): Plugin {
     // default resolver which prefers ESM
     const _resolve = config.createResolver({ asSrc: false })

     // cjs resolver that prefers Node
     const _resolveRequire = config.createResolver({
            asSrc: false,
            isRequire: true
     })
    //id='@vue/runtime-dom'
    //importer='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\dist\\vue.runtime.esm-bundler.js'
     //kind='import-statement'
     const resolve = (
            id: string,
            importer: string,
            kind: ImportKind,
            resolveDir?: string
     ): Promise<string | undefined> => {
            let _importer: string
            // explicit resolveDir - this is passed only during yarn pnp resolve for
            // entries
            if (resolveDir) {
              _importer = normalizePath(path.join(resolveDir, '*'))
            } else {
              // map importer ids to file paths for correct resolution
              _importer = importer in qualified ? qualified[importer] : importer
            }
            //resolver=_resolve
            const resolver = kind.startsWith('require') ? _resolveRequire : _resolve
            return resolver(id, _importer, undefined, ssr)
    }
    ...省略代码
}

看下createResolver的定义

代码58
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts



export async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development'
): Promise<ResolvedConfig> {
    ...省略代码
    const createResolver: ResolvedConfig['createResolver'] = (options) => {
        let aliasContainer: PluginContainer | undefined
        let resolverContainer: PluginContainer | undefined
        return async (id, importer, aliasOnly, ssr) => {
              ...省略代码
        }
    }
}

 createResolver其实返回了一个函数

return async (id, importer, aliasOnly, ssr) => {...省略代码}

回到代码56处就是调用了代码58处返回的方法,看下createResolver的具体实现

代码59
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\config.ts


export async function resolveConfig(
  inlineConfig: InlineConfig,
  command: 'build' | 'serve',
  defaultMode = 'development'
): Promise<ResolvedConfig> {
    ...省略代码
    const createResolver: ResolvedConfig['createResolver'] = (options) => {
        let aliasContainer: PluginContainer | undefined
        let resolverContainer: PluginContainer | undefined
        //id='@vue/runtime-dom'
    //importer='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\dist\\vue.runtime.esm-bundler.js'
        //aliasOnly=undefined
        return async (id, importer, aliasOnly, ssr) => {
              let container: PluginContainer
              if (aliasOnly) {
                   ...省略代码
              } else {
                   container =
                      resolverContainer ||
                      (resolverContainer = await createPluginContainer({
                            ...resolved,
                            plugins: [
                              //entries:[{ find: /^[\/]?@vite\/env/, replacement: () => ENV_ENTRY },
                              //{ find: /^[\/]?@vite\/client/, replacement: () => CLIENT_ENTRY }] 
                              //是在resolveConfig的resolvedAlias进行的赋值
                              aliasPlugin({ entries: resolved.resolve.alias }),
                              resolvePlugin({
                                ...resolved.resolve,
                                root: resolvedRoot,
                                isProduction,
                                isBuild: command === 'build',
                                ssrConfig: resolved.ssr,
                                asSrc: true,
                                preferRelative: false,
                                tryIndex: true,
                                ...options
                              })
                            ]
                   }))
              }
              return (await container.resolveId(id, importer, { ssr }))?.id
        }
    }
}

此时container就只有两个plugin,一个aliasPlugin一个resolvePlugin,然后调用了container.resolveId方法

代码60
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\server\pluginContainer.ts



class TransformContext extends Context {
    ...省略代码
     //id='@vue/runtime-dom'
    //importer='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\dist\\vue.runtime.esm-bundler.js'
    //options={ssr:undefined}
    async resolveId(rawId, importer = join(root, 'index.html'), options) {
            const skip = options === null || options === void 0 ? void 0 : options.skip;
            ...省略代码
            const ctx = new Context();
            ...省略代码
            for (const plugin of plugins) {
                ...省略代码
                const result = await plugin.resolveId.call(ctx, rawId, importer, { ssr });
                if (!result)
                    continue;
                if (typeof result === 'string') {
                    id = result;
                }
                else {
                    id = result.id;
                    Object.assign(partial, result);
                }
                ...省略代码
                break;
            }
            ...省略代码
            if (id) {
                partial.id = isExternalUrl(id) ? id : normalizePath$4(id);
                return partial;
            }
            else {
                return null;
            }
    }
    ...省略代码
}

循环调用aliasPlugin和resolvePlugin的resovleId方法,被resolvePlugin匹配上,剩余解析过程参考vue的解析过程,最终代码60返回如下

return {
    id: 'D:\workspace\vue3\vitelearn\node_modules\@vue\runtime-dom\dist\runtime-dom.esm-bundler.js'
}

回到代码55,代码55返回了

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\esbuildDepPlugin.ts


export function esbuildDepPlugin(
  qualified: Record<string, string>,
  exportsData: Record<string, ExportsData>,
  config: ResolvedConfig,
  ssr?: boolean
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-pre-bundle',
        setup(build) {
            ...省略代码
            //id='@vue/runtime-dom' 
     //importer='D:\\workspace\\vue3\\vitelearn\\node_modules\\vue\\dist\\vue.runtime.esm-bundler.js'
            //kind='import-statement'
            build.onResolve({filter:/^[\w@][^:]/},async({path: id, importer, kind }) => {
                ...省略代码
                const resolved = await resolve(id, importer, kind)
                if (resolved) {
                    if (resolved.startsWith(browserExternalId)) {
                      return {
                            path: id,
                            namespace: 'browser-external'
                      }
                    }
                    if (isExternalUrl(resolved)) {
                      return {
                        path: resolved,
                        external: true
                      }
                    }
                    return {
                      //path='D:/workspace/vue3/vitelearn/node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js'
                      path: path.resolve(resolved)
                    }
               }
            })
            ...省略代码
        }
    } 
}

 esbuildDepPlugin插件继续执行,然后匹配到了

代码61
D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\esbuildDepPlugin.ts



export function esbuildDepPlugin(
  qualified: Record<string, string>,
  exportsData: Record<string, ExportsData>,
  config: ResolvedConfig,
  ssr?: boolean
): Plugin {
    ...省略代码
    return {
        name: 'vite:dep-pre-bundle',
        setup(build) {
            ...省略代码
            //id='@vue/runtime-core'
            //importer='D:\\workspace\\vue3\\vitelearn\\node_modules\\@vue\\runtime-dom\\dist\\runtime-dom.esm-bundler.js'
            //kind='import-statement'
            build.onResolve({filter:/^[\w@][^:]/},async({path:id,importer, kind }) => {
                  const resolved = await resolve(id, importer, kind)
                  if (resolved) {
                    if (resolved.startsWith(browserExternalId)) {
                      return {
                        path: id,
                        namespace: 'browser-external'
                      }
                    }
                    if (isExternalUrl(resolved)) {
                      return {
                        path: resolved,
                        external: true
                      }
                    }
                    return {
                      path: path.resolve(resolved)
                    }
                  }
            })
            ...省略代码
        }
    }
}

打开D:\\workspace\\vue3\\vitelearn\\node_modules\\@vue\\runtime-dom\\dist\\runtime-dom.esm-bundler.js可以看到runtime-dom依赖了@vue/compiler-core和@vue/shared

 @vue/runtime-core的解析过程与vue的解析过程一样,不在重复,代码61返回

return {
   path:'D:/workspace/vue3/vitelearn/node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js' 
}

 剩余依赖也同理,至此将D:\workspace\vue3\vitelearn\node_modules\vue\dist\vue.runtime.esm-bundler.js作为入口文件,与之相关联的依赖都已经收集完毕,回到代码50处

D:\workspace\vue3\vite-2.7.2\packages\vite\src\node\optimizer\index.ts

export async function optimizeDeps(
  config: ResolvedConfig,
  force = config.server.force,
  asCommand = false,
  newDeps?: Record<string, string>, // missing imports encountered after server has started
  ssr?: boolean
): Promise<DepOptimizationMetadata | null> {
    ...省略代码
    const result = await build({
        absWorkingDir: process.cwd(),
        entryPoints: Object.keys(flatIdDeps),
        bundle: true,
        format: 'esm',
        target: config.build.target || undefined,
        external: config.optimizeDeps?.exclude,
        logLevel: 'error',
        splitting: true,
        sourcemap: true,
        outdir: cacheDir,
        ignoreAnnotations: true,
        metafile: true,
        define,
        plugins: [
          ...plugins,
          esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr)
        ],
        ...esbuildOptions
    })
    const meta = result.metafile!
    const cacheDirOutputPath = path.relative(process.cwd(), cacheDir)

    for (const id in deps) {
        const entry = deps[id]
        data.optimized[id] = {
          file: normalizePath(path.resolve(cacheDir, flattenId(id) + '.js')),
          src: entry,
          needsInterop: needsInterop(
            id,
            idToExports[id],
            meta.outputs,
            cacheDirOutputPath
          )
    }
    //写入缓存文件中
    writeFile(dataPath, JSON.stringify(data, null, 2))
    return data
}

result结构如图 

然后将编译结果缓存到D:\workspace\vue3\vitelearn\node_modules\.vite目录下,至此那些在node_modules中的依赖都已经预编译好了。也即server创建好了,之后就是开启server.listen监听客户端的连接。


总结

npm run dev 其实只是创建了一个本地服务器,然后注册一些中间件,做依赖预构建的工作,整个过程都是一些不怎么耗时的步骤,这也是vite启动快的原因

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值