diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index c0a7a3d50..c2b344ebe 100644 --- a/docs/guide/api-javascript.md +++ b/docs/guide/api-javascript.md @@ -76,7 +76,11 @@ parentServer.use(vite.middlewares) The `InlineConfig` interface extends `UserConfig` with additional properties: -- `configFile`: specify config file to use. If not set, Vite will try to automatically resolve one from project root. Set to `false` to disable auto resolving. +- `configFile`: Specify config file to use. If not set, Vite will try to automatically resolve one from project root. Set to `false` to disable auto resolving. +- `configFileNativeImport`: Whether to use native `import()` to import the config file. This is useful if the default behaviour of writing a temporary file to the filesystem is undesirable. However, you'll lose other features such as: + - TypeScript support if the runtime does not support it. + - Automatic server restarts if the files imported by the config (recursively) are updated. + - Cache busting of files imported by the config (recursively). A full manual restart is required. - `envFile`: Set to `false` to disable `.env` files. ## `ResolvedConfig` diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index b520b9b51..2c775dad0 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -19,6 +19,7 @@ interface GlobalCLIOptions { '--'?: string[] c?: boolean | string config?: string + configNativeImport?: boolean base?: string l?: LogLevel logLevel?: LogLevel @@ -83,6 +84,7 @@ function cleanGlobalCLIOptions( delete ret['--'] delete ret.c delete ret.config + delete ret.configNativeImport delete ret.base delete ret.l delete ret.logLevel @@ -180,6 +182,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, + configFileNativeImport: options.configNativeImport, logLevel: options.logLevel, clearScreen: options.clearScreen, optimizeDeps: { force: options.force }, @@ -304,6 +307,7 @@ cli base: options.base, mode: options.mode, configFile: options.config, + configFileNativeImport: options.configNativeImport, logLevel: options.logLevel, clearScreen: options.clearScreen, build: buildOptions, @@ -340,6 +344,7 @@ cli root, base: options.base, configFile: options.config, + configFileNativeImport: options.configNativeImport, logLevel: options.logLevel, mode: options.mode, }, @@ -382,6 +387,7 @@ cli root, base: options.base, configFile: options.config, + configFileNativeImport: options.configNativeImport, logLevel: options.logLevel, mode: options.mode, build: { diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 95872da64..ae218cfe7 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -519,7 +519,22 @@ export interface ResolvedWorkerOptions { } export interface InlineConfig extends UserConfig { + /** + * Specify config file to use. If not set, Vite will try to automatically resolve one from project root. + * Set to false to disable auto resolving. + */ configFile?: string | false + /** + * Whether to use native `import()` to import the config file. This is useful if the default behaviour of + * writing a temporary file to the filesystem is undesirable. However, you'll lose other features such as: + * - TypeScript support if the runtime does not support it. + * - Automatic server restarts if the files imported by the config (recursively) are updated. + * - Cache busting of files imported by the config (recursively). A full manual restart is required. + */ + configFileNativeImport?: boolean + /** + * Set to false to disable .env files + */ envFile?: false } @@ -834,7 +849,10 @@ export async function resolveConfig( let { configFile } = config if (configFile !== false) { - const loadResult = await loadConfigFromFile( + const loadConfigFn = config.configFileNativeImport + ? loadConfigNative + : loadConfigFromFile + const loadResult = await loadConfigFn( configEnv, configFile, config.root, @@ -1465,6 +1483,76 @@ export function sortUserPlugins( return [prePlugins, normalPlugins, postPlugins] } +function searchConfigFile(root: string, configFile?: string) { + let resolvedPath: string | undefined + + if (configFile) { + // explicit config path is always resolved from cwd + resolvedPath = path.resolve(configFile) + } else { + // implicit config file loaded from inline root (if present) + // otherwise from cwd + for (const filename of DEFAULT_CONFIG_FILES) { + const filePath = path.resolve(root, filename) + if (!fs.existsSync(filePath)) continue + + resolvedPath = filePath + break + } + } + + return resolvedPath +} + +async function loadConfigNative( + configEnv: ConfigEnv, + configFile?: string, + configRoot: string = process.cwd(), + logLevel?: LogLevel, + customLogger?: Logger, +): Promise<{ + path: string + config: UserConfig + dependencies: string[] +} | null> { + const start = debug ? performance.now() : 0 + const getTime = () => `${(performance.now() - start).toFixed(2)}ms` + + const resolvedPath = searchConfigFile(configRoot, configFile) + if (!resolvedPath) { + debug?.('no config file found.') + return null + } + + try { + const result = await import( + pathToFileURL(resolvedPath).href + '?t=' + Date.now() + ) + debug?.(`config file imported in ${getTime()}`) + + const userConfig = result.default as UserConfigExport + 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) { + createLogger(logLevel, { customLogger }).error( + colors.red(`failed to load config from ${resolvedPath}`), + { + error: e, + }, + ) + throw e + } +} + export async function loadConfigFromFile( configEnv: ConfigEnv, configFile?: string, @@ -1476,26 +1564,10 @@ export async function loadConfigFromFile( config: UserConfig dependencies: string[] } | null> { - const start = performance.now() + const start = debug ? performance.now() : 0 const getTime = () => `${(performance.now() - start).toFixed(2)}ms` - let resolvedPath: string | undefined - - if (configFile) { - // explicit config path is always resolved from cwd - resolvedPath = path.resolve(configFile) - } else { - // implicit config file loaded from inline root (if present) - // otherwise from cwd - for (const filename of DEFAULT_CONFIG_FILES) { - const filePath = path.resolve(configRoot, filename) - if (!fs.existsSync(filePath)) continue - - resolvedPath = filePath - break - } - } - + const resolvedPath = searchConfigFile(configRoot, configFile) if (!resolvedPath) { debug?.('no config file found.') return null