Vite配置解析是怎么做的?
-
本文为笔者学习
Vite
源码时的一些笔记,如有错误,请指出✊ -
也就是 怎么解析 我们写的
vite.config.ts
等的vite配置文件 -
这一步是由 vite配置解析的
resolveConfig
函数来做的 -
export async function resolveConfig(inlineConfig: InlineConfig,command: 'build' | 'serve',defaultMode = 'development',defaultNodeEnv = 'development',): Promise<ResolvedConfig>
export async function resolveConfig( inlineConfig: InlineConfig, command: 'build' | 'serve', defaultMode = 'development', defaultNodeEnv = 'development', ): Promise<ResolvedConfig>
export async function resolveConfig( inlineConfig: InlineConfig, command: 'build' | 'serve', defaultMode = 'development', defaultNodeEnv = 'development', ): Promise<ResolvedConfig>
1. 加载配置文件
-
大概思路是首先加载,解析配置文件,然后 合并命令行的配置
-
let { configFile } = config // config 是 resolveConfig 的参数 inlineConfigif (configFile !== false) {// 默认会走到这里 除非显示指定conFile为falseconst loadResult = await loadConfigFromFile(configEnv,configFile,config.root,config.logLevel,)if (loadResult) {// 解析配置后 应该与命令行的配置合并config = mergeConfig(loadResult.config, config)configFile = loadResult.path/** 因为配置文件代码可能会有第三方库的依赖,所以当第三方库依赖的代码更改时,Vite * 可以通过 HMR 处理逻辑中记录的configFileDependencies检测到更改,再重启 * DevServer ,来保证当前生效的配置永远是最新的*/configFileDependencies = loadResult.dependencies}}
let { configFile } = config // config 是 resolveConfig 的参数 inlineConfig if (configFile !== false) { // 默认会走到这里 除非显示指定conFile为false const loadResult = await loadConfigFromFile( configEnv, configFile, config.root, config.logLevel, ) if (loadResult) { // 解析配置后 应该与命令行的配置合并 config = mergeConfig(loadResult.config, config) configFile = loadResult.path /* * 因为配置文件代码可能会有第三方库的依赖,所以当第三方库依赖的代码更改时,Vite * 可以通过 HMR 处理逻辑中记录的configFileDependencies检测到更改,再重启 * DevServer ,来保证当前生效的配置永远是最新的 */ configFileDependencies = loadResult.dependencies } }
let { configFile } = config // config 是 resolveConfig 的参数 inlineConfig if (configFile !== false) { // 默认会走到这里 除非显示指定conFile为false const loadResult = await loadConfigFromFile( configEnv, configFile, config.root, config.logLevel, ) if (loadResult) { // 解析配置后 应该与命令行的配置合并 config = mergeConfig(loadResult.config, config) configFile = loadResult.path /* * 因为配置文件代码可能会有第三方库的依赖,所以当第三方库依赖的代码更改时,Vite * 可以通过 HMR 处理逻辑中记录的configFileDependencies检测到更改,再重启 * DevServer ,来保证当前生效的配置永远是最新的 */ configFileDependencies = loadResult.dependencies } }
-
loadConfigFromFile
函数这里先不做详细介绍,他的主要作用是加载,解析配置文件
2. 解析用户插件
- 这一步主要干了2件事:
根据apply参数,剔除不生效的插件, 给插件排好顺序
- 有些插件只在开发阶段生效,或者说只在生产环境生效,我们可以通过
apply: 'serve' 或 'build'
来指定它们,同时也可以将apply
配置为一个函数,来自定义插件生效的条件 - 因为插件执行时机不一样,所以需要排序,顺便合并插件的配置
// user config may provide an alternative mode. But --mode has a higher priority// 优先级为 命令行 > 配置文件声明 > 默认mode = inlineConfig.mode || config.mode || modeconfigEnv.mode = modeconst filterPlugin = (p: Plugin) => {if (!p) {return false} else if (!p.apply) {// 没有显示声明apply,默认都执行return true} else if (typeof p.apply === 'function') {// 如果为函数的话 则执行这个函数 用函数来定义apply的话可以自定义插件生效时机return p.apply({ ...config, mode }, configEnv)} else {return p.apply === command}}......// resolve pluginsconst rawUserPlugins = ((await asyncFlatten(config.plugins || [])) as Plugin[]).filter(filterPlugin)// 这里干了两件事 排序 + 过滤const [prePlugins, normalPlugins, postPlugins] =sortUserPlugins(rawUserPlugins)// user config may provide an alternative mode. But --mode has a higher priority // 优先级为 命令行 > 配置文件声明 > 默认 mode = inlineConfig.mode || config.mode || mode configEnv.mode = mode const filterPlugin = (p: Plugin) => { if (!p) { return false } else if (!p.apply) { // 没有显示声明apply,默认都执行 return true } else if (typeof p.apply === 'function') { // 如果为函数的话 则执行这个函数 用函数来定义apply的话可以自定义插件生效时机 return p.apply({ ...config, mode }, configEnv) } else { return p.apply === command } } ...... // resolve plugins const rawUserPlugins = ( (await asyncFlatten(config.plugins || [])) as Plugin[] ).filter(filterPlugin) // 这里干了两件事 排序 + 过滤 const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawUserPlugins)// user config may provide an alternative mode. But --mode has a higher priority // 优先级为 命令行 > 配置文件声明 > 默认 mode = inlineConfig.mode || config.mode || mode configEnv.mode = mode const filterPlugin = (p: Plugin) => { if (!p) { return false } else if (!p.apply) { // 没有显示声明apply,默认都执行 return true } else if (typeof p.apply === 'function') { // 如果为函数的话 则执行这个函数 用函数来定义apply的话可以自定义插件生效时机 return p.apply({ ...config, mode }, configEnv) } else { return p.apply === command } } ...... // resolve plugins const rawUserPlugins = ( (await asyncFlatten(config.plugins || [])) as Plugin[] ).filter(filterPlugin) // 这里干了两件事 排序 + 过滤 const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawUserPlugins)
调用 插件的 config 钩子,进行配置合并
// run config hooks// 这一步操作由runConfigHook这个函数内部实现const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins]config = await runConfigHook(config, userPlugins, configEnv)// run config hooks // 这一步操作由runConfigHook这个函数内部实现 const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins] config = await runConfigHook(config, userPlugins, configEnv)// run config hooks // 这一步操作由runConfigHook这个函数内部实现 const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins] config = await runConfigHook(config, userPlugins, configEnv)
解析root参数,alias参数
-
如果在配置文件内没有指定的话,默认root解析的是
process.cwd()
-
解析alias时,需要加上一些内置的 alias 规则,如
@vite/env
、@vite/client
这种直接重定向到 Vite 内部的模块 -
// resolve rootconst resolvedRoot = normalizePath(config.root ? path.resolve(config.root) : process.cwd(),)// 内置alias规则const clientAlias = [{find: /^\/?@vite\/env/,replacement: path.posix.join(FS_PREFIX, normalizePath(ENV_ENTRY)),},{find: /^\/?@vite\/client/,replacement: path.posix.join(FS_PREFIX, normalizePath(CLIENT_ENTRY)),},]// resolve alias with internal client aliasconst resolvedAlias = normalizeAlias(mergeAlias(clientAlias, config.resolve?.alias || []),)
// resolve root const resolvedRoot = normalizePath( config.root ? path.resolve(config.root) : process.cwd(), ) // 内置alias规则 const clientAlias = [ { find: /^\/?@vite\/env/, replacement: path.posix.join(FS_PREFIX, normalizePath(ENV_ENTRY)), }, { find: /^\/?@vite\/client/, replacement: path.posix.join(FS_PREFIX, normalizePath(CLIENT_ENTRY)), }, ] // resolve alias with internal client alias const resolvedAlias = normalizeAlias( mergeAlias(clientAlias, config.resolve?.alias || []), )
// resolve root const resolvedRoot = normalizePath( config.root ? path.resolve(config.root) : process.cwd(), ) // 内置alias规则 const clientAlias = [ { find: /^\/?@vite\/env/, replacement: path.posix.join(FS_PREFIX, normalizePath(ENV_ENTRY)), }, { find: /^\/?@vite\/client/, replacement: path.posix.join(FS_PREFIX, normalizePath(CLIENT_ENTRY)), }, ] // resolve alias with internal client alias const resolvedAlias = normalizeAlias( mergeAlias(clientAlias, config.resolve?.alias || []), )
3. 加载环境变量
-
没有指定
envDir
的话,默认扫描process.cwd()
目录下的.env文件 -
loadEnv
函数会去扫描process.env
与.env
文件,解析出 env 对象,这个对象的属性最终会被挂载到import.meta.env
这个全局对象上 -
// load .env filesconst envDir = config.envDir? normalizePath(path.resolve(resolvedRoot, config.envDir)): resolvedRoot/** loadEnv的具体步骤(详细代码在src/node/env.ts文件):* 1. 遍历 process.env 的属性,拿到指定前缀开头的属性(默认指定为VITE_),并挂载* 在 env 对象上* 2. 遍历 .env 文件,解析文件,然后往 env 对象挂载那些以指定前缀开头的属性。遍历的* 文件先后顺序如下:* .env.${mode}.local* .env.${mode}* .env.local* .env*/const userEnv =inlineConfig.envFile !== false &&loadEnv(mode, envDir, resolveEnvPrefix(config))
// load .env files const envDir = config.envDir ? normalizePath(path.resolve(resolvedRoot, config.envDir)) : resolvedRoot /* * loadEnv的具体步骤(详细代码在src/node/env.ts文件): * 1. 遍历 process.env 的属性,拿到指定前缀开头的属性(默认指定为VITE_),并挂载 * 在 env 对象上 * 2. 遍历 .env 文件,解析文件,然后往 env 对象挂载那些以指定前缀开头的属性。遍历的 * 文件先后顺序如下: * .env.${mode}.local * .env.${mode} * .env.local * .env */ const userEnv = inlineConfig.envFile !== false && loadEnv(mode, envDir, resolveEnvPrefix(config))
// load .env files const envDir = config.envDir ? normalizePath(path.resolve(resolvedRoot, config.envDir)) : resolvedRoot /* * loadEnv的具体步骤(详细代码在src/node/env.ts文件): * 1. 遍历 process.env 的属性,拿到指定前缀开头的属性(默认指定为VITE_),并挂载 * 在 env 对象上 * 2. 遍历 .env 文件,解析文件,然后往 env 对象挂载那些以指定前缀开头的属性。遍历的 * 文件先后顺序如下: * .env.${mode}.local * .env.${mode} * .env.local * .env */ const userEnv = inlineConfig.envFile !== false && loadEnv(mode, envDir, resolveEnvPrefix(config))
-
特殊情况: 如果在加载过程中遇到 NODE_ENV 属性,则挂到
process.env.VITE_USER_NODE_ENV
,Vite 会优先通过这个属性来决定是否走生产环境
的构建 -
其他一些附带操作
-
/** 解析资源公共路径 base* 关键在于 resolvebaseUrl 函数,里面的细节主要有:* 空字符或者 ./ 在开发阶段特殊处理,全部重写为/* .开头的路径,自动重写为 /* 以http(s)://开头的路径,在开发环境下重写为对应的 pathname* 确保路径开头和结尾都是/*/// During dev, we ignore relative base and fallback to '/'// For the SSR build, relative base isn't possible by means// of import.meta.url.const resolvedBase = relativeBaseShortcut? !isBuild || config.build?.ssr? '/': './': resolveBaseUrl(config.base, isBuild, logger) ?? '/'// 解析生产环境的构建配置const resolvedBuildOptions = resolveBuildOptions(config.build,logger,resolvedRoot,)// 对cacheDir的解析,这个路径相对于在 Vite 预编译时写入依赖产物的路径// resolve cache directoryconst pkgDir = findNearestPackageData(resolvedRoot, packageCache)?.dir/** 当显示指定cacheDir时,cache directory为配置文件中指定的位置* 否则 判断 pkgDir 是否存在* 存在的话 指定为 pkgDir下的 node_modules/.vite* 不存在 则为 root 位置下的 .vite*/const cacheDir = normalizePath(config.cacheDir? path.resolve(resolvedRoot, config.cacheDir): pkgDir? path.join(pkgDir, `node_modules/.vite`): path.join(resolvedRoot, `.vite`),)// 处理用户配置的assetsInclude,将其转换为一个过滤器函数:// Vite 在最终整理所有配置阶段,会将用户传入的 assetsInclude 和内置的规则合并// 这个配置决定是否让 Vite 将对应的后缀名视为静态资源文件(asset)来处理const assetsFilter =config.assetsInclude &&(!Array.isArray(config.assetsInclude) || config.assetsInclude.length)? createFilter(config.assetsInclude): () => false// 最终所有配置会被合并为这个对象const resolvedConfig: ResolvedConfig = {......assetsInclude(file: string) {return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)},......}
/* * 解析资源公共路径 base * 关键在于 resolvebaseUrl 函数,里面的细节主要有: * 空字符或者 ./ 在开发阶段特殊处理,全部重写为/ * .开头的路径,自动重写为 / * 以http(s)://开头的路径,在开发环境下重写为对应的 pathname * 确保路径开头和结尾都是/ */ // During dev, we ignore relative base and fallback to '/' // For the SSR build, relative base isn't possible by means // of import.meta.url. const resolvedBase = relativeBaseShortcut ? !isBuild || config.build?.ssr ? '/' : './' : resolveBaseUrl(config.base, isBuild, logger) ?? '/' // 解析生产环境的构建配置 const resolvedBuildOptions = resolveBuildOptions( config.build, logger, resolvedRoot, ) // 对cacheDir的解析,这个路径相对于在 Vite 预编译时写入依赖产物的路径 // resolve cache directory const pkgDir = findNearestPackageData(resolvedRoot, packageCache)?.dir /* * 当显示指定cacheDir时,cache directory为配置文件中指定的位置 * 否则 判断 pkgDir 是否存在 * 存在的话 指定为 pkgDir下的 node_modules/.vite * 不存在 则为 root 位置下的 .vite */ const cacheDir = normalizePath( config.cacheDir ? path.resolve(resolvedRoot, config.cacheDir) : pkgDir ? path.join(pkgDir, `node_modules/.vite`) : path.join(resolvedRoot, `.vite`), ) // 处理用户配置的assetsInclude,将其转换为一个过滤器函数: // Vite 在最终整理所有配置阶段,会将用户传入的 assetsInclude 和内置的规则合并 // 这个配置决定是否让 Vite 将对应的后缀名视为静态资源文件(asset)来处理 const assetsFilter = config.assetsInclude && (!Array.isArray(config.assetsInclude) || config.assetsInclude.length) ? createFilter(config.assetsInclude) : () => false // 最终所有配置会被合并为这个对象 const resolvedConfig: ResolvedConfig = { ...... assetsInclude(file: string) { return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file) }, ...... }
/* * 解析资源公共路径 base * 关键在于 resolvebaseUrl 函数,里面的细节主要有: * 空字符或者 ./ 在开发阶段特殊处理,全部重写为/ * .开头的路径,自动重写为 / * 以http(s)://开头的路径,在开发环境下重写为对应的 pathname * 确保路径开头和结尾都是/ */ // During dev, we ignore relative base and fallback to '/' // For the SSR build, relative base isn't possible by means // of import.meta.url. const resolvedBase = relativeBaseShortcut ? !isBuild || config.build?.ssr ? '/' : './' : resolveBaseUrl(config.base, isBuild, logger) ?? '/' // 解析生产环境的构建配置 const resolvedBuildOptions = resolveBuildOptions( config.build, logger, resolvedRoot, ) // 对cacheDir的解析,这个路径相对于在 Vite 预编译时写入依赖产物的路径 // resolve cache directory const pkgDir = findNearestPackageData(resolvedRoot, packageCache)?.dir /* * 当显示指定cacheDir时,cache directory为配置文件中指定的位置 * 否则 判断 pkgDir 是否存在 * 存在的话 指定为 pkgDir下的 node_modules/.vite * 不存在 则为 root 位置下的 .vite */ const cacheDir = normalizePath( config.cacheDir ? path.resolve(resolvedRoot, config.cacheDir) : pkgDir ? path.join(pkgDir, `node_modules/.vite`) : path.join(resolvedRoot, `.vite`), ) // 处理用户配置的assetsInclude,将其转换为一个过滤器函数: // Vite 在最终整理所有配置阶段,会将用户传入的 assetsInclude 和内置的规则合并 // 这个配置决定是否让 Vite 将对应的后缀名视为静态资源文件(asset)来处理 const assetsFilter = config.assetsInclude && (!Array.isArray(config.assetsInclude) || config.assetsInclude.length) ? createFilter(config.assetsInclude) : () => false // 最终所有配置会被合并为这个对象 const resolvedConfig: ResolvedConfig = { ...... assetsInclude(file: string) { return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file) }, ...... }
-
4. 定义路径解析器工厂
主流程
-
这里所说的
路径解析器
,是指调用插件容器进行路径解析
的函数 -
// create an internal resolver to be used in special scenarios, e.g.// optimizer & handling css @importsconst createResolver: ResolvedConfig['createResolver'] = (options) => {let aliasContainer: PluginContainer | undefinedlet resolverContainer: PluginContainer | undefined// 返回了一个函数 这个函数就是路径解析器return async (id, importer, aliasOnly, ssr) => {let container: PluginContainerif (aliasOnly) {// 新建 aliasPlugincontainer =aliasContainer ||(aliasContainer = await createPluginContainer({...resolved,plugins: [aliasPlugin({ entries: resolved.resolve.alias })],}))} else {// 新建 resolvePlugincontainer =resolverContainer ||(resolverContainer = await createPluginContainer({...resolved,plugins: [aliasPlugin({ entries: resolved.resolve.alias }),resolvePlugin({...resolved.resolve,root: resolvedRoot,isProduction,isBuild: command === 'build',ssrConfig: resolved.ssr,asSrc: true,preferRelative: false,tryIndex: true,...options,idOnly: true,}),],}))}return (await container.resolveId(id, importer, {ssr,scan: options?.scan,}))?.id}}// 这里有 aliasContainer 和 resolverContainer 两个工具对象,它们都含有 resolveId 这个专门解析路径的方法,可以被 Vite 调用来获取解析结果// container 的类型是 PluginContainer 这个我们后续在插件机制那块会讲到
// create an internal resolver to be used in special scenarios, e.g. // optimizer & handling css @imports const createResolver: ResolvedConfig['createResolver'] = (options) => { let aliasContainer: PluginContainer | undefined let resolverContainer: PluginContainer | undefined // 返回了一个函数 这个函数就是路径解析器 return async (id, importer, aliasOnly, ssr) => { let container: PluginContainer if (aliasOnly) { // 新建 aliasPlugin container = aliasContainer || (aliasContainer = await createPluginContainer({ ...resolved, plugins: [aliasPlugin({ entries: resolved.resolve.alias })], })) } else { // 新建 resolvePlugin container = resolverContainer || (resolverContainer = await createPluginContainer({ ...resolved, plugins: [ aliasPlugin({ entries: resolved.resolve.alias }), resolvePlugin({ ...resolved.resolve, root: resolvedRoot, isProduction, isBuild: command === 'build', ssrConfig: resolved.ssr, asSrc: true, preferRelative: false, tryIndex: true, ...options, idOnly: true, }), ], })) } return ( await container.resolveId(id, importer, { ssr, scan: options?.scan, }) )?.id } } // 这里有 aliasContainer 和 resolverContainer 两个工具对象,它们都含有 resolveId 这个专门解析路径的方法,可以被 Vite 调用来获取解析结果 // container 的类型是 PluginContainer 这个我们后续在插件机制那块会讲到
// create an internal resolver to be used in special scenarios, e.g. // optimizer & handling css @imports const createResolver: ResolvedConfig['createResolver'] = (options) => { let aliasContainer: PluginContainer | undefined let resolverContainer: PluginContainer | undefined // 返回了一个函数 这个函数就是路径解析器 return async (id, importer, aliasOnly, ssr) => { let container: PluginContainer if (aliasOnly) { // 新建 aliasPlugin container = aliasContainer || (aliasContainer = await createPluginContainer({ ...resolved, plugins: [aliasPlugin({ entries: resolved.resolve.alias })], })) } else { // 新建 resolvePlugin container = resolverContainer || (resolverContainer = await createPluginContainer({ ...resolved, plugins: [ aliasPlugin({ entries: resolved.resolve.alias }), resolvePlugin({ ...resolved.resolve, root: resolvedRoot, isProduction, isBuild: command === 'build', ssrConfig: resolved.ssr, asSrc: true, preferRelative: false, tryIndex: true, ...options, idOnly: true, }), ], })) } return ( await container.resolveId(id, importer, { ssr, scan: options?.scan, }) )?.id } } // 这里有 aliasContainer 和 resolverContainer 两个工具对象,它们都含有 resolveId 这个专门解析路径的方法,可以被 Vite 调用来获取解析结果 // container 的类型是 PluginContainer 这个我们后续在插件机制那块会讲到
-
这个解析器 未来会用于依赖预构建过程
const resolve = config.createResolver()// 调用以拿到 react 路径rseolve('react', undefined, undefined, false)const resolve = config.createResolver() // 调用以拿到 react 路径 rseolve('react', undefined, undefined, false)
const resolve = config.createResolver() // 调用以拿到 react 路径 rseolve('react', undefined, undefined, false)
解析 public 参数
// 顺带解析了 public 参数 -> 静态资源目录const { publicDir } = configconst resolvedPublicDir =publicDir !== false && publicDir !== ''? path.resolve(resolvedRoot,typeof publicDir === 'string' ? publicDir : 'public',): ''// 顺带解析了 public 参数 -> 静态资源目录 const { publicDir } = config const resolvedPublicDir = publicDir !== false && publicDir !== '' ? path.resolve( resolvedRoot, typeof publicDir === 'string' ? publicDir : 'public', ) : ''// 顺带解析了 public 参数 -> 静态资源目录 const { publicDir } = config const resolvedPublicDir = publicDir !== false && publicDir !== '' ? path.resolve( resolvedRoot, typeof publicDir === 'string' ? publicDir : 'public', ) : ''
最终阶段
-
对上面所有解析结果进行合并
-
// 上述的解析 只列举了几个 详细的所有配置解析 可以自行查看源码const resolvedConfig: ResolvedConfig = {configFile: configFile ? normalizePath(configFile) : undefined,configFileDependencies: configFileDependencies.map((name) =>normalizePath(path.resolve(name)),),inlineConfig,root: resolvedRoot,base: resolvedBase.endsWith('/') ? resolvedBase : resolvedBase + '/',rawBase: resolvedBase,resolve: resolveOptions,publicDir: resolvedPublicDir,cacheDir,command,mode,ssr,isWorker: false,mainConfig: null,isProduction,plugins: userPlugins,esbuild:config.esbuild === false? false: {jsxDev: !isProduction,...config.esbuild,},server,build: resolvedBuildOptions,preview: resolvePreviewOptions(config.preview, server),envDir,env: {...userEnv,BASE_URL,MODE: mode,DEV: !isProduction,PROD: isProduction,},assetsInclude(file: string) {return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)},logger,packageCache,createResolver,optimizeDeps: {disabled: 'build',...optimizeDeps,esbuildOptions: {preserveSymlinks: resolveOptions.preserveSymlinks,...optimizeDeps.esbuildOptions,},},worker: resolvedWorkerOptions,appType: config.appType ?? (middlewareMode === 'ssr' ? 'custom' : 'spa'),experimental: {importGlobRestoreExtension: false,hmrPartialAccept: false,...config.experimental,},getSortedPlugins: undefined!,getSortedPluginHooks: undefined!,}const resolved: ResolvedConfig = {...config,...resolvedConfig,}
// 上述的解析 只列举了几个 详细的所有配置解析 可以自行查看源码 const resolvedConfig: ResolvedConfig = { configFile: configFile ? normalizePath(configFile) : undefined, configFileDependencies: configFileDependencies.map((name) => normalizePath(path.resolve(name)), ), inlineConfig, root: resolvedRoot, base: resolvedBase.endsWith('/') ? resolvedBase : resolvedBase + '/', rawBase: resolvedBase, resolve: resolveOptions, publicDir: resolvedPublicDir, cacheDir, command, mode, ssr, isWorker: false, mainConfig: null, isProduction, plugins: userPlugins, esbuild: config.esbuild === false ? false : { jsxDev: !isProduction, ...config.esbuild, }, server, build: resolvedBuildOptions, preview: resolvePreviewOptions(config.preview, server), envDir, env: { ...userEnv, BASE_URL, MODE: mode, DEV: !isProduction, PROD: isProduction, }, assetsInclude(file: string) { return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file) }, logger, packageCache, createResolver, optimizeDeps: { disabled: 'build', ...optimizeDeps, esbuildOptions: { preserveSymlinks: resolveOptions.preserveSymlinks, ...optimizeDeps.esbuildOptions, }, }, worker: resolvedWorkerOptions, appType: config.appType ?? (middlewareMode === 'ssr' ? 'custom' : 'spa'), experimental: { importGlobRestoreExtension: false, hmrPartialAccept: false, ...config.experimental, }, getSortedPlugins: undefined!, getSortedPluginHooks: undefined!, } const resolved: ResolvedConfig = { ...config, ...resolvedConfig, }
// 上述的解析 只列举了几个 详细的所有配置解析 可以自行查看源码 const resolvedConfig: ResolvedConfig = { configFile: configFile ? normalizePath(configFile) : undefined, configFileDependencies: configFileDependencies.map((name) => normalizePath(path.resolve(name)), ), inlineConfig, root: resolvedRoot, base: resolvedBase.endsWith('/') ? resolvedBase : resolvedBase + '/', rawBase: resolvedBase, resolve: resolveOptions, publicDir: resolvedPublicDir, cacheDir, command, mode, ssr, isWorker: false, mainConfig: null, isProduction, plugins: userPlugins, esbuild: config.esbuild === false ? false : { jsxDev: !isProduction, ...config.esbuild, }, server, build: resolvedBuildOptions, preview: resolvePreviewOptions(config.preview, server), envDir, env: { ...userEnv, BASE_URL, MODE: mode, DEV: !isProduction, PROD: isProduction, }, assetsInclude(file: string) { return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file) }, logger, packageCache, createResolver, optimizeDeps: { disabled: 'build', ...optimizeDeps, esbuildOptions: { preserveSymlinks: resolveOptions.preserveSymlinks, ...optimizeDeps.esbuildOptions, }, }, worker: resolvedWorkerOptions, appType: config.appType ?? (middlewareMode === 'ssr' ? 'custom' : 'spa'), experimental: { importGlobRestoreExtension: false, hmrPartialAccept: false, ...config.experimental, }, getSortedPlugins: undefined!, getSortedPluginHooks: undefined!, } const resolved: ResolvedConfig = { ...config, ...resolvedConfig, }
5. 生成插件流水线
-
// 先生成完整插件列表传给resolve.plugins// 细节都在 resolvePlugins 函数内部 后续会详细研究这个函数;(resolved.plugins as Plugin[]) = await resolvePlugins(resolved,prePlugins,normalPlugins,postPlugins,)......// call configResolved hooks// 调用每个插件的 configResolved 钩子函数await Promise.all([...resolved.getSortedPluginHooks('configResolved').map((hook) => hook(resolved)),...resolvedConfig.worker.getSortedPluginHooks('configResolved').map((hook) => hook(workerResolved)),])......
// 先生成完整插件列表传给resolve.plugins // 细节都在 resolvePlugins 函数内部 后续会详细研究这个函数 ;(resolved.plugins as Plugin[]) = await resolvePlugins( resolved, prePlugins, normalPlugins, postPlugins, ) ...... // call configResolved hooks // 调用每个插件的 configResolved 钩子函数 await Promise.all([ ...resolved .getSortedPluginHooks('configResolved') .map((hook) => hook(resolved)), ...resolvedConfig.worker .getSortedPluginHooks('configResolved') .map((hook) => hook(workerResolved)), ]) ......
// 先生成完整插件列表传给resolve.plugins // 细节都在 resolvePlugins 函数内部 后续会详细研究这个函数 ;(resolved.plugins as Plugin[]) = await resolvePlugins( resolved, prePlugins, normalPlugins, postPlugins, ) ...... // call configResolved hooks // 调用每个插件的 configResolved 钩子函数 await Promise.all([ ...resolved .getSortedPluginHooks('configResolved') .map((hook) => hook(resolved)), ...resolvedConfig.worker .getSortedPluginHooks('configResolved') .map((hook) => hook(workerResolved)), ]) ......
-
最后 这个
resolvedConfig
函数会 返回 最终的 配置结果 ->resolved
加载配置文件中 的关键函数 loadConfigFromFile
-
// 定义部分 接受四个参数export async function loadConfigFromFile(configEnv: ConfigEnv,configFile?: string,configRoot: string = process.cwd(),logLevel?: LogLevel,): Promise<{path: stringconfig: UserConfigdependencies: string[]} | null>
// 定义部分 接受四个参数 export async function loadConfigFromFile( configEnv: ConfigEnv, configFile?: string, configRoot: string = process.cwd(), logLevel?: LogLevel, ): Promise<{ path: string config: UserConfig dependencies: string[] } | null>
// 定义部分 接受四个参数 export async function loadConfigFromFile( configEnv: ConfigEnv, configFile?: string, configRoot: string = process.cwd(), logLevel?: LogLevel, ): Promise<{ path: string config: UserConfig dependencies: string[] } | null>
主要思路
-
既然是 加载配置文件,那么就需要处理 不同的配置文件类型,主要有以下四种
TS + ESM
TS + CJS
JS + ESM
JS + CJS
-
所以,要做的就首先识别 配置文件的类型,然后根据不同的类型,进行解析
1. 寻找配置文件路径
-
// node/contants.tsexport const DEFAULT_CONFIG_FILES = ['vite.config.js','vite.config.mjs','vite.config.ts','vite.config.cjs','vite.config.mts','vite.config.cts',]// node/config.tslet resolvedPath: string | undefined// configfile 就是 传入的参数 也就是 在命令行启动 vite 的时候指定的参数if (configFile) {// explicit config path is always resolved from cwd// configFile 存在的话 则用这个路径来 resolveresolvedPath = path.resolve(configFile)} else {// implicit config file loaded from inline root (if present)// otherwise from cwd// 否则的话 从默认的 跟路径 process.cwd() 来resolvefor (const filename of DEFAULT_CONFIG_FILES) {const filePath = path.resolve(configRoot, filename)if (!fs.existsSync(filePath)) continueresolvedPath = filePathbreak}}// 这不到 则返回 null ,同时,给出提示if (!resolvedPath) {debug?.('no config file found.')return null}
// node/contants.ts export const DEFAULT_CONFIG_FILES = [ 'vite.config.js', 'vite.config.mjs', 'vite.config.ts', 'vite.config.cjs', 'vite.config.mts', 'vite.config.cts', ] // node/config.ts let resolvedPath: string | undefined // configfile 就是 传入的参数 也就是 在命令行启动 vite 的时候指定的参数 if (configFile) { // explicit config path is always resolved from cwd // configFile 存在的话 则用这个路径来 resolve resolvedPath = path.resolve(configFile) } else { // implicit config file loaded from inline root (if present) // otherwise from cwd // 否则的话 从默认的 跟路径 process.cwd() 来resolve for (const filename of DEFAULT_CONFIG_FILES) { const filePath = path.resolve(configRoot, filename) if (!fs.existsSync(filePath)) continue resolvedPath = filePath break } } // 这不到 则返回 null ,同时,给出提示 if (!resolvedPath) { debug?.('no config file found.') return null }
// node/contants.ts export const DEFAULT_CONFIG_FILES = [ 'vite.config.js', 'vite.config.mjs', 'vite.config.ts', 'vite.config.cjs', 'vite.config.mts', 'vite.config.cts', ] // node/config.ts let resolvedPath: string | undefined // configfile 就是 传入的参数 也就是 在命令行启动 vite 的时候指定的参数 if (configFile) { // explicit config path is always resolved from cwd // configFile 存在的话 则用这个路径来 resolve resolvedPath = path.resolve(configFile) } else { // implicit config file loaded from inline root (if present) // otherwise from cwd // 否则的话 从默认的 跟路径 process.cwd() 来resolve for (const filename of DEFAULT_CONFIG_FILES) { const filePath = path.resolve(configRoot, filename) if (!fs.existsSync(filePath)) continue resolvedPath = filePath break } } // 这不到 则返回 null ,同时,给出提示 if (!resolvedPath) { debug?.('no config file found.') return null }
2. 识别配置文件的类别
-
let isESM = false// vite 首先会 检查 这个跟路径的命名,是否包含 mjs , cjs 的后缀,// 如果有的话,会修改isESM 的标识if (/\.m[jt]s$/.test(resolvedPath)) {isESM = true} else if (/\.c[jt]s$/.test(resolvedPath)) {isESM = false} else {// check package.json for type: "module" and set `isESM` to true// 没有的话 会查看 package.json 文件,// 如果有 type: "module"则打上 isESM 的标识try {const pkg = lookupFile(configRoot, ['package.json'])isESM =!!pkg && JSON.parse(fs.readFileSync(pkg, 'utf-8')).type === 'module'} catch (e) {}}
let isESM = false // vite 首先会 检查 这个跟路径的命名,是否包含 mjs , cjs 的后缀, // 如果有的话,会修改isESM 的标识 if (/\.m[jt]s$/.test(resolvedPath)) { isESM = true } else if (/\.c[jt]s$/.test(resolvedPath)) { isESM = false } else { // check package.json for type: "module" and set `isESM` to true // 没有的话 会查看 package.json 文件, // 如果有 type: "module"则打上 isESM 的标识 try { const pkg = lookupFile(configRoot, ['package.json']) isESM = !!pkg && JSON.parse(fs.readFileSync(pkg, 'utf-8')).type === 'module' } catch (e) {} }
let isESM = false // vite 首先会 检查 这个跟路径的命名,是否包含 mjs , cjs 的后缀, // 如果有的话,会修改isESM 的标识 if (/\.m[jt]s$/.test(resolvedPath)) { isESM = true } else if (/\.c[jt]s$/.test(resolvedPath)) { isESM = false } else { // check package.json for type: "module" and set `isESM` to true // 没有的话 会查看 package.json 文件, // 如果有 type: "module"则打上 isESM 的标识 try { const pkg = lookupFile(configRoot, ['package.json']) isESM = !!pkg && JSON.parse(fs.readFileSync(pkg, 'utf-8')).type === 'module' } catch (e) {} }
3. 利用 esbuild 打包,解析 配置文件
-
try {// 首先 用 esbuild 将配置文件 编译,打包为为 js 文件 (因为 可能为 ts 格式 所以需要先转一下)const bundled = await bundleConfigFile(resolvedPath, isESM)// 解析 打包后的配置文件 这个函数 详细信息在下面,// 主要就是 分为 esm cjs 格式去做不同的解析const userConfig = await loadConfigFromBundledFile(resolvedPath,bundled.code,isESM,)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,// esbuild 打包过程中收集的依赖信息dependencies: bundled.dependencies,}} catch (e) {createLogger(logLevel).error(colors.red(`failed to load config from ${resolvedPath}`),{ error: e },)throw e}......// loadConfigFromBundledFile 函数// 创建 require 函数 用于 下面的 cjs 格式配置文件处理// 这个 createRequire 方法 来自于 node:moduleconst _require = createRequire(import.meta.url)async function loadConfigFromBundledFile(fileName: string,bundledCode: string,isESM: boolean,): Promise<UserConfigExport> {// for esm, before we can register loaders without requiring users to run node// with --experimental-loader themselves, we have to do a hack here:// write it to disk, load it with native Node ESM, then delete the file.// 如果是 ESM格式,Vite 会将编译后的 js 代码写入临时文件,通过 Node 原生 ESM Import 来读取这个临时的内容,以获取到配置内容,再直接删掉临时文件if (isESM) {// import 路径结果要加上时间戳 query,是因为// 为了让 dev server 重启后仍然读取最新的配置,避免缓存const fileBase = `${fileName}.timestamp-${Date.now()}-${Math.random().toString(16).slice(2)}`const fileNameTmp = `${fileBase}.mjs`const fileUrl = `${pathToFileURL(fileBase)}.mjs`await fsp.writeFile(fileNameTmp, bundledCode)try {// 通过 Node 原生 ESM Import 来读取这个临时的内容,以获取到配置内容return (await dynamicImport(fileUrl)).default} finally {// 最后直接 删掉临时文件fs.unlink(fileNameTmp, () => {}) // Ignore errors}}// for cjs, we can register a custom loader via `_require.extensions`// 如果是 cjs 格式,那么主要的思路是// 通过拦截原生 require.extensions 的加载函数来实现对 bundle 后配置代码的加载else {// 默认加载器const extension = path.extname(fileName)// We don't use fsp.realpath() here because it has the same behaviour as// fs.realpath.native. On some Windows systems, it returns uppercase volume// letters (e.g. "C:\") while the Node.js loader uses lowercase volume letters.// See https://github.com/vitejs/vite/issues/12923// 拿到 promisifyed 过的真实的文件名字const realFileName = await promisifiedRealpath(fileName)// 默认 拦截原生 require 对于 js 文件的加载const loaderExt = extension in _require.extensions ? extension : '.js'// 先保存 一份 原来的 加载器 -> loaderconst defaultLoader = _require.extensions[loaderExt]!// 这里 进行 拦截,重写_require.extensions[loaderExt] = (module: NodeModule, filename: string) => {// 如果加载的文件 是 该配置文件 则 调用 module._compile 方法进行编译if (filename === realFileName) {;(module as NodeModuleWithCompile)._compile(bundledCode, filename)} else {defaultLoader(module, filename)}}// clear cache in case of server restartdelete _require.cache[_require.resolve(fileName)]// 编译后 再 进行一次手动的 require 即可拿到配置对象const raw = _require(fileName)// 恢复原生的加载方法_require.extensions[loaderExt] = defaultLoaderreturn raw.__esModule ? raw.default : raw}}// node/utils.ts// 这里 注释已经给的很明显了 在非 jest 下 dynamicImport 返回的是// new Function('file', 'return import(file)')// @ts-expect-error jest only exists when running Jestexport const usingDynamicImport = typeof jest === 'undefined'/*** Dynamically import files. It will make sure it's not being compiled away by TS/Rollup.** As a temporary workaround for Jest's lack of stable ESM support, we fallback to require* if we're in a Jest environment.* See https://github.com/vitejs/vite/pull/5197#issuecomment-938054077** @param file File path to import.*/// 为什么不直接 import, 而是要用 new Function 包裹?// 这是为了避免打包工具处理这段代码,比如 Rollup 和 TSC,类似的手段还有 evalexport const dynamicImport = usingDynamicImport? new Function('file', 'return import(file)'): _require
try { // 首先 用 esbuild 将配置文件 编译,打包为为 js 文件 (因为 可能为 ts 格式 所以需要先转一下) const bundled = await bundleConfigFile(resolvedPath, isESM) // 解析 打包后的配置文件 这个函数 详细信息在下面, // 主要就是 分为 esm cjs 格式去做不同的解析 const userConfig = await loadConfigFromBundledFile( resolvedPath, bundled.code, isESM, ) 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, // esbuild 打包过程中收集的依赖信息 dependencies: bundled.dependencies, } } catch (e) { createLogger(logLevel).error( colors.red(`failed to load config from ${resolvedPath}`), { error: e }, ) throw e } ...... // loadConfigFromBundledFile 函数 // 创建 require 函数 用于 下面的 cjs 格式配置文件处理 // 这个 createRequire 方法 来自于 node:module const _require = createRequire(import.meta.url) async function loadConfigFromBundledFile( fileName: string, bundledCode: string, isESM: boolean, ): Promise<UserConfigExport> { // for esm, before we can register loaders without requiring users to run node // with --experimental-loader themselves, we have to do a hack here: // write it to disk, load it with native Node ESM, then delete the file. // 如果是 ESM格式,Vite 会将编译后的 js 代码写入临时文件,通过 Node 原生 ESM Import 来读取这个临时的内容,以获取到配置内容,再直接删掉临时文件 if (isESM) { // import 路径结果要加上时间戳 query,是因为 // 为了让 dev server 重启后仍然读取最新的配置,避免缓存 const fileBase = `${fileName}.timestamp-${Date.now()}-${Math.random() .toString(16) .slice(2)}` const fileNameTmp = `${fileBase}.mjs` const fileUrl = `${pathToFileURL(fileBase)}.mjs` await fsp.writeFile(fileNameTmp, bundledCode) try { // 通过 Node 原生 ESM Import 来读取这个临时的内容,以获取到配置内容 return (await dynamicImport(fileUrl)).default } finally { // 最后直接 删掉临时文件 fs.unlink(fileNameTmp, () => {}) // Ignore errors } } // for cjs, we can register a custom loader via `_require.extensions` // 如果是 cjs 格式,那么主要的思路是 // 通过拦截原生 require.extensions 的加载函数来实现对 bundle 后配置代码的加载 else { // 默认加载器 const extension = path.extname(fileName) // We don't use fsp.realpath() here because it has the same behaviour as // fs.realpath.native. On some Windows systems, it returns uppercase volume // letters (e.g. "C:\") while the Node.js loader uses lowercase volume letters. // See https://github.com/vitejs/vite/issues/12923 // 拿到 promisifyed 过的真实的文件名字 const realFileName = await promisifiedRealpath(fileName) // 默认 拦截原生 require 对于 js 文件的加载 const loaderExt = extension in _require.extensions ? extension : '.js' // 先保存 一份 原来的 加载器 -> loader const defaultLoader = _require.extensions[loaderExt]! // 这里 进行 拦截,重写 _require.extensions[loaderExt] = (module: NodeModule, filename: string) => { // 如果加载的文件 是 该配置文件 则 调用 module._compile 方法进行编译 if (filename === realFileName) { ;(module as NodeModuleWithCompile)._compile(bundledCode, filename) } else { defaultLoader(module, filename) } } // clear cache in case of server restart delete _require.cache[_require.resolve(fileName)] // 编译后 再 进行一次手动的 require 即可拿到配置对象 const raw = _require(fileName) // 恢复原生的加载方法 _require.extensions[loaderExt] = defaultLoader return raw.__esModule ? raw.default : raw } } // node/utils.ts // 这里 注释已经给的很明显了 在非 jest 下 dynamicImport 返回的是 // new Function('file', 'return import(file)') // @ts-expect-error jest only exists when running Jest export const usingDynamicImport = typeof jest === 'undefined' /** * Dynamically import files. It will make sure it's not being compiled away by TS/Rollup. * * As a temporary workaround for Jest's lack of stable ESM support, we fallback to require * if we're in a Jest environment. * See https://github.com/vitejs/vite/pull/5197#issuecomment-938054077 * * @param file File path to import. */ // 为什么不直接 import, 而是要用 new Function 包裹? // 这是为了避免打包工具处理这段代码,比如 Rollup 和 TSC,类似的手段还有 eval export const dynamicImport = usingDynamicImport ? new Function('file', 'return import(file)') : _require
try { // 首先 用 esbuild 将配置文件 编译,打包为为 js 文件 (因为 可能为 ts 格式 所以需要先转一下) const bundled = await bundleConfigFile(resolvedPath, isESM) // 解析 打包后的配置文件 这个函数 详细信息在下面, // 主要就是 分为 esm cjs 格式去做不同的解析 const userConfig = await loadConfigFromBundledFile( resolvedPath, bundled.code, isESM, ) 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, // esbuild 打包过程中收集的依赖信息 dependencies: bundled.dependencies, } } catch (e) { createLogger(logLevel).error( colors.red(`failed to load config from ${resolvedPath}`), { error: e }, ) throw e } ...... // loadConfigFromBundledFile 函数 // 创建 require 函数 用于 下面的 cjs 格式配置文件处理 // 这个 createRequire 方法 来自于 node:module const _require = createRequire(import.meta.url) async function loadConfigFromBundledFile( fileName: string, bundledCode: string, isESM: boolean, ): Promise<UserConfigExport> { // for esm, before we can register loaders without requiring users to run node // with --experimental-loader themselves, we have to do a hack here: // write it to disk, load it with native Node ESM, then delete the file. // 如果是 ESM格式,Vite 会将编译后的 js 代码写入临时文件,通过 Node 原生 ESM Import 来读取这个临时的内容,以获取到配置内容,再直接删掉临时文件 if (isESM) { // import 路径结果要加上时间戳 query,是因为 // 为了让 dev server 重启后仍然读取最新的配置,避免缓存 const fileBase = `${fileName}.timestamp-${Date.now()}-${Math.random() .toString(16) .slice(2)}` const fileNameTmp = `${fileBase}.mjs` const fileUrl = `${pathToFileURL(fileBase)}.mjs` await fsp.writeFile(fileNameTmp, bundledCode) try { // 通过 Node 原生 ESM Import 来读取这个临时的内容,以获取到配置内容 return (await dynamicImport(fileUrl)).default } finally { // 最后直接 删掉临时文件 fs.unlink(fileNameTmp, () => {}) // Ignore errors } } // for cjs, we can register a custom loader via `_require.extensions` // 如果是 cjs 格式,那么主要的思路是 // 通过拦截原生 require.extensions 的加载函数来实现对 bundle 后配置代码的加载 else { // 默认加载器 const extension = path.extname(fileName) // We don't use fsp.realpath() here because it has the same behaviour as // fs.realpath.native. On some Windows systems, it returns uppercase volume // letters (e.g. "C:\") while the Node.js loader uses lowercase volume letters. // See https://github.com/vitejs/vite/issues/12923 // 拿到 promisifyed 过的真实的文件名字 const realFileName = await promisifiedRealpath(fileName) // 默认 拦截原生 require 对于 js 文件的加载 const loaderExt = extension in _require.extensions ? extension : '.js' // 先保存 一份 原来的 加载器 -> loader const defaultLoader = _require.extensions[loaderExt]! // 这里 进行 拦截,重写 _require.extensions[loaderExt] = (module: NodeModule, filename: string) => { // 如果加载的文件 是 该配置文件 则 调用 module._compile 方法进行编译 if (filename === realFileName) { ;(module as NodeModuleWithCompile)._compile(bundledCode, filename) } else { defaultLoader(module, filename) } } // clear cache in case of server restart delete _require.cache[_require.resolve(fileName)] // 编译后 再 进行一次手动的 require 即可拿到配置对象 const raw = _require(fileName) // 恢复原生的加载方法 _require.extensions[loaderExt] = defaultLoader return raw.__esModule ? raw.default : raw } } // node/utils.ts // 这里 注释已经给的很明显了 在非 jest 下 dynamicImport 返回的是 // new Function('file', 'return import(file)') // @ts-expect-error jest only exists when running Jest export const usingDynamicImport = typeof jest === 'undefined' /** * Dynamically import files. It will make sure it's not being compiled away by TS/Rollup. * * As a temporary workaround for Jest's lack of stable ESM support, we fallback to require * if we're in a Jest environment. * See https://github.com/vitejs/vite/pull/5197#issuecomment-938054077 * * @param file File path to import. */ // 为什么不直接 import, 而是要用 new Function 包裹? // 这是为了避免打包工具处理这段代码,比如 Rollup 和 TSC,类似的手段还有 eval export const dynamicImport = usingDynamicImport ? new Function('file', 'return import(file)') : _require
-
在处理
ESM
类型的配置文件时,采用的是将bundle(打包编译)
后的js
代码写入临时文件
,通过 Node 原生ESM Import
来读取这个临时的内容,以获取到配置内容,再直接删掉临时文件- 这种先编译配置文件,再将产物写入临时目录,最后加载临时目录产物的做法,也是 AOT (Ahead Of Time)编译技术的一种具体实现
-
在处理
CJS
类型的配置文件时, 采用的是拦截原生require.extensions
的加载函数来实现对bundle(打包编译)
后的js
代码的加载- 这种运行时加载
JS
配置的方式,也叫做JIT
(即时编译),这种方式和AOT
最大的区别在于不会将内存中计算出来的js
代码写入磁盘再加载,而是通过拦截 Node.js 原生require.extension
方法实现即时加载
- 这种运行时加载
总结
-
主要梳理了 Vite 配置解析的整体流程
和
加载配置文件的方法 -
Vite 配置文件解析的逻辑由
resolveConfig
函数统一实现- 经历了加载配置文件、解析用户插件、加载环境变量、创建路径解析器工厂和生成插件流水线这几个主要的流程
-
在
加载配置文件
的过程中,Vite 需要处理四种类型的配置文件((TS, JS)-(ESM, CJS)
)- 首先先 用
esbuild
将TS
代码 打包编译为JS
代码 - 其中对于
ESM
和CJS
两种格式文件,分别采用了AOT
和JIT
两种编译技术实现了配置加载
- 首先先 用
-
学习链接
- 掘金小测-《深入浅出Vite》
- Vite
node/config.ts
等文件 - 互联网上其他关于Vite一些文章,由于笔者学习的时候,没有记录,特此感谢?