vite源码分析(1) :resolveConfig参数解析

vite源码分析(1) :resolveConfig参数解析

技术杂谈小彩虹2021-07-12 21:38:0860A+A-

vite resolveConfig分析


本文旨在分析vite源码中解析config的函数resolveConfig。

config来源

  1. inlineConfig,来自于命令行或npm script。
  2. vite.config.*,用户的配置文件。
  3. Plugin.config,插件的config方法返回的配置选项。

涉及功能

  1. 设置--configFile false 来禁用配置文件

  2. 按需加载插件

  3. 插件强制顺序

  4. 加载.env文件

  5. plugin.config 钩子函数

  6. plugin.configResolved钩子函数

流程

入口

const config = await resolveConfig(inlineConfig, 'serve', 'development')  

resolveConfig

函数参数

function resolveConfig( inlineConfig: InlineConfig, command: 'build' | 'serve', defaultMode = 'development' ): Promise<ResolvedConfig> 

设置config,mode,configFileDependencies变量

let config = inlineConfig// 存放配置
let configFileDependencies: string[] = []// 存放配置文件的依赖
let mode = inlineConfig.mode || defaultMode// 设置mode

if (mode === 'production') {
    process.env.NODE_ENV = 'production'
  }
  const configEnv = {
    mode,
    command
  }

加载配置文件,重置配置mode,同时知道可以使用--configFile false 来禁用配置文件*

  let { configFile } = config
  if (configFile !== false) {
    const loadResult = await loadConfigFromFile(...args)// 伪参数
    if (loadResult) {
      config = mergeConfig(loadResult.config, config)// 合并用户配置
      configFile = loadResult.path
      configFileDependencies = loadResult.dependencies// 获取配置文件的依赖
    }
  }
	// 根据用户配置,重置mode
	mode = config.mode || mode;

loadConfigFromFile就是根据项目目录,获取相关的配置文件,需要的注意是,当使用配置文件类型是ts且使用es module时,会被esbuild转义读取,然后删除转义后的文件

function loadConfigFromFile(...args){
    // ...伪代码
    
    if (isMjs && isTS) {  
        const bundled = await bundleConfigFile(resolvedPath, true) // 转义
        dependencies = bundled.dependencies
        fs.writeFileSync(resolvedPath + '.js', bundled.code)     // 暂存读取
        userConfig = (await eval(`import(fileUrl + '.js?t=${Date.now()}')`))
            .default
        fs.unlinkSync(resolvedPath + '.js')  // 删除临时文件
    } 
    
      // ...伪代码
}

解析插件,按需配置插件:plugin,apply强制插件排序:plugin.enforce,执行plugin.config钩子函数,再次添加用户配置

	// 扁平数组,同时筛选应用在当前command下的插件
	const rawUserPlugins = config.plugins.flat().filter((p) => {
		return p && (!p.apply || p.apply === command);
	});
	// sortUserPlugins方法根据插件的enforce参数进行排序
	const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawUserPlugins);
    // 执行plugin.config钩子函数,同时再次配置 
	const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins];
	for (const p of userPlugins) {
		if (p.config) {
			const res = await p.config(config, configEnv);
			if (res) {
				config = mergeConfig(config, res);
			}
		}
	}

解析resolve参数:alias,dedupe,可以知道,alias,dedupe参数可以用于resolve同级。此处解析 /^/@vite//,是为了解析hmr的客户端文件路径。

 const resolvedAlias = mergeAlias(
    [{ find: /^\/@vite\//, replacement: () => CLIENT_DIR + '/' }],
    config.resolve?.alias || config.alias || []
  )

  const resolveOptions: ResolvedConfig['resolve'] = {
    dedupe: config.dedupe,
    ...config.resolve,
    alias: resolvedAlias
  }

加载.env文件配置用户环境变量同时官网所说的区分pro/dev环境和模式也在此体现,至此,用户总共有三次改变pro/dev环境和模式:1.命令行指定,2.配置文件,3.env文件 。同时可以知道,配置--enFile false 可以禁用.env,不过貌似没有这个命令。

  const userEnv = inlineConfig.envFile !== false && loadEnv(mode, resolvedRoot)
  const isProduction = (process.env.VITE_USER_NODE_ENV || mode) === 'production'
  if (isProduction) {
    process.env.NODE_ENV = 'production'
  }

loadEnv方法就是根据mode 使用dotenv加载环境下的.env文件,并判断' VITE_'前缀,同时根据用户配置的NODE_ENV配置VITE_USER_NODE_ENV变量。

 function loadEnv( mode: string, root: string,prefix = 'VITE_') {
     // ...伪代码
     
  for (const file of envFiles) {
      for (const [key, value] of Object.entries(parsed)) {
        if (key.startsWith(prefix) && env[key] === undefined) {
          env[key] = value
        } else if (key === 'NODE_ENV') {
          process.env.VITE_USER_NODE_ENV = value
        }
      }
    }
     // ...伪代码
     
  }
  return env
}

解析BASE_URLbuildOptionscacheDirassetsFilterpublicDir

  const BASE_URL = resolveBaseUrl(config.base, command === 'build', logger)
  const resolvedBuildOptions = resolveBuildOptions(config.build)
  const pkgPath = lookupFile(
    resolvedRoot,
    [`package.json`],
    true /* pathOnly */
  )
  const cacheDir = config.cacheDir
    ? path.resolve(resolvedRoot, config.cacheDir)
    : pkgPath && path.join(path.dirname(pkgPath), `node_modules/.vite`)

  const assetsFilter = config.assetsInclude
    ? createFilter(config.assetsInclude)
    : () => false

  const { publicDir } = config
  const resolvedPublicDir =
    publicDir !== false && publicDir !== ''
      ? path.resolve(
          resolvedRoot,
          typeof publicDir === 'string' ? publicDir : 'public'
        )
      : ''

添加内置插件,如css解析,ts解析等,并对所有插件排序

  (resolved.plugins as Plugin[]) = await resolvePlugins(
    resolved,
    prePlugins,
    normalPlugins,
    postPlugins
  )

function resolvePlugins(...args): Promise<Plugin[]> {
 // ...伪代码
    
  return [
    isBuild ? null : preAliasPlugin(),
    aliasPlugin({ entries: config.resolve.alias }),
    ...prePlugins,
    dynamicImportPolyfillPlugin(config),
    resolvePlugin({
      ...config.resolve,
      root: config.root,
      isProduction: config.isProduction,
      isBuild,
      ssrTarget: config.ssr?.target,
      asSrc: true
    }),
    htmlInlineScriptProxyPlugin(),
    cssPlugin(config),
    config.esbuild !== false ? esbuildPlugin(config.esbuild) : null,
    jsonPlugin(
      {
        namedExports: true,
        ...config.json
      },
      isBuild
    ),
    wasmPlugin(config),
    webWorkerPlugin(config),
    assetPlugin(config),
    ...normalPlugins,
    definePlugin(config),
    cssPostPlugin(config),
    ...buildPlugins.pre,
    ...postPlugins,
    ...buildPlugins.post,
    // internal server-only plugins are always applied after everything else
    ...(isBuild
      ? []
      : [clientInjectionsPlugin(config), importAnalysisPlugin(config)])
  ].filter(Boolean) as Plugin[]
}

 

createResolver,创建一个内部使用的插件解析器,执行所有的插件

const createResolver: ResolvedConfig['createResolver'] = (options) => {
    return async (id, importer, aliasOnly) => {
      let container: PluginContainer
      	// ...伪代码 创建container
      }
      return (await container.resolveId(id, importer))?.id
    }
  }

执行钩子函数plugin.configResolved

  await Promise.all(userPlugins.map((p) => p.configResolved?.(resolved)))

汇总resolved,在这里,有用户env中额外的数据

  const resolved: ResolvedConfig = {
    ...config,
    configFile: configFile ? normalizePath(configFile) : undefined,
    configFileDependencies,
    inlineConfig,
    root: resolvedRoot,
    base: BASE_URL,
    resolve: resolveOptions,
    publicDir: resolvedPublicDir,
    cacheDir,
    command,
    mode,
    isProduction,
    plugins: userPlugins,
    server: resolveServerOptions(resolvedRoot, config.server),
    build: resolvedBuildOptions,
     // 添加额外的env参数
    env: {
      ...userEnv,
      BASE_URL,
      MODE: mode,
      DEV: !isProduction,
      PROD: isProduction
    },
    assetsInclude(file: string) {
      return DEFAULT_ASSETS_RE.test(file) || assetsFilter(file)
    },
    logger,
    createResolver,
    optimizeDeps: {
      ...config.optimizeDeps,
      esbuildOptions: {
        keepNames: config.optimizeDeps?.keepNames,
        ...config.optimizeDeps?.esbuildOptions
      }
    }
  }

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们