Vite source code analysis (1): resolveConfig parameter analysis

Vite source code analysis (1): resolveConfig parameter analysis

Vite resolveConfig analysis


This article aims to analyze the function resolveConfig that parses config in vite source code.

config source

  1. inlineConfig , from the command line or npm script.
  2. vite.config.* , the user's configuration file.
  3. Plugin.config , the configuration options returned by the plug-in's config method.

Involved function

  1. Set --configFile false to disable the configuration file

  2. Load plugins on demand

  3. Mandatory order of plugins

  4. Load the .env file

  5. plugin.config hook function

  6. plugin.configResolved hook function

Process

Entrance

const config = the await resolveConfig (inlineConfig, 'serve' , 'Development' ) copying the code

resolveConfig

Function parameters

function resolveConfig ( inlineConfig: InlineConfig, command: 'build' | 'serve' , = DefaultMode 'Development' ): Promise < ResolvedConfig > copy the code

Set config, mode, configFileDependencies variables

let config = inlineConfig //store configuration let configFileDependencies: string[] = [] //store configuration file dependencies let mode = inlineConfig.mode || defaultMode //set mode if (mode === 'production' ) { process.env.NODE_ENV = 'production' } const configEnv = { mode, command } Copy code

Load the configuration file, reset the configuration mode, and know that you can use --configFile false to disable the configuration file *

let {configFile} = config if (configFile !== false ) { const loadResult = await loadConfigFromFile(...args) //pseudo parameter if (loadResult) { config = mergeConfig(loadResult.config, config) //merge user configuration configFile = loadResult.path configFileDependencies = loadResult.dependencies //Get the dependencies of the configuration file } } //According to user configuration, reset mode mode = config.mode || mode; Copy code

loadConfigFromFile is to obtain the relevant configuration file according to the project directory. It should be noted that when the configuration file type is ts and the es module is used, it will be read by esbuild escaped, and then the escaped file will be deleted

function loadConfigFromFile ( ...args ) { //... pseudo code if (isMjs && isTS) { const bundled = await bundleConfigFile(resolvedPath, true ) //escape dependencies = bundled.dependencies fs.writeFileSync(resolvedPath + ' .js ' , bundled.code) //Temporary read userConfig = ( await eval ( `import(fileUrl +'.js?t = ${ Date .now()) ')` ) ) .default fs.unlinkSync(resolvedPath + '. js ' ) //delete temporary files } //...Fake code } Copy code

Parse plug-ins, configure plug-ins on demand: plugin, apply , force plug-in sorting: plugin.enforce , execute plugin.config hook function , and add user configuration again

//Flat array, at the same time filter the plug-ins applied under the current command const rawUserPlugins = config.plugins.flat().filter( ( p ) => { return p && (!p.apply || p.apply === command ); }); //The sortUserPlugins method sorts according to the plugin's enforce parameters const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawUserPlugins); //Execute the plugin.config hook function, and configure const userPlugins = [...prePlugins, ...normalPlugins again , ...postPlugins]; for ( const p of userPlugins) { if (p.config) { const res = await p.config(config, configEnv); if (res) { config = mergeConfig(config, res); } } } Copy code

Resolve resolve parameters: alias, dedupe, you can know that alias, dedupe parameters can be used in the same level of resolve. The analysis of/^/@vite//here is to parse the client file path of 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 } Copy code

Load the .env file to configure user environment variables , and the distinction between pro/dev environment and mode mentioned on the official website is also reflected here . So far, the user has changed the pro/dev environment and mode three times: 1. Command line designation, 2. Configuration file , 3.env file. At the same time, you can know that configuring --enFile false can disable .env, but it seems that there is no such command.

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' } Copy code

The loadEnv method is to use dotenv to load the .env file in the environment according to the mode, and determine the'VITE_' prefix , and configure the VITE_USER_NODE_ENV variable according to the NODE_ENV configured by the user .

function loadEnv ( mode: string , root: string ,prefix = 'VITE_' ) { //... pseudo code 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 } } } //...Fake code } return env } Copy code

Parse BASE_URL , buildOptions , cacheDir , assetsFilter , publicDir

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' ) : '' Copy code

Add built-in plug-ins, such as css parsing, ts parsing, etc., and sort all plug-ins

(resolved.plugins as Plugin[]) = await resolvePlugins( resolved, prePlugins, normalPlugins, postPlugins ) function resolvePlugins ( ...args ): Promise < Plugin []> { //... pseudo code 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[] } Copy code

createResolver, create a plug-in resolver for internal use and execute all plug-ins

const createResolver: ResolvedConfig[ 'createResolver' ] = ( options ) => { return async (id, importer, aliasOnly) => { let container: PluginContainer //... pseudocode to create a container } return ( await container.resolveId(id, importer))?.id } } Copy code

Execute the hook function plugin.configResolved

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

Summary resolved, here, there are additional data in the user 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, //add additional env parameters 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 } } } Copy code