feat(config): support native import

This commit is contained in:
bluwy 2024-10-29 21:51:07 +08:00
parent a0336bd519
commit 8e936cd3d9
3 changed files with 102 additions and 20 deletions

View File

@ -76,7 +76,11 @@ parentServer.use(vite.middlewares)
The `InlineConfig` interface extends `UserConfig` with additional properties: 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. - `envFile`: Set to `false` to disable `.env` files.
## `ResolvedConfig` ## `ResolvedConfig`

View File

@ -19,6 +19,7 @@ interface GlobalCLIOptions {
'--'?: string[] '--'?: string[]
c?: boolean | string c?: boolean | string
config?: string config?: string
configNativeImport?: boolean
base?: string base?: string
l?: LogLevel l?: LogLevel
logLevel?: LogLevel logLevel?: LogLevel
@ -83,6 +84,7 @@ function cleanGlobalCLIOptions<Options extends GlobalCLIOptions>(
delete ret['--'] delete ret['--']
delete ret.c delete ret.c
delete ret.config delete ret.config
delete ret.configNativeImport
delete ret.base delete ret.base
delete ret.l delete ret.l
delete ret.logLevel delete ret.logLevel
@ -180,6 +182,7 @@ cli
base: options.base, base: options.base,
mode: options.mode, mode: options.mode,
configFile: options.config, configFile: options.config,
configFileNativeImport: options.configNativeImport,
logLevel: options.logLevel, logLevel: options.logLevel,
clearScreen: options.clearScreen, clearScreen: options.clearScreen,
optimizeDeps: { force: options.force }, optimizeDeps: { force: options.force },
@ -304,6 +307,7 @@ cli
base: options.base, base: options.base,
mode: options.mode, mode: options.mode,
configFile: options.config, configFile: options.config,
configFileNativeImport: options.configNativeImport,
logLevel: options.logLevel, logLevel: options.logLevel,
clearScreen: options.clearScreen, clearScreen: options.clearScreen,
build: buildOptions, build: buildOptions,
@ -340,6 +344,7 @@ cli
root, root,
base: options.base, base: options.base,
configFile: options.config, configFile: options.config,
configFileNativeImport: options.configNativeImport,
logLevel: options.logLevel, logLevel: options.logLevel,
mode: options.mode, mode: options.mode,
}, },
@ -382,6 +387,7 @@ cli
root, root,
base: options.base, base: options.base,
configFile: options.config, configFile: options.config,
configFileNativeImport: options.configNativeImport,
logLevel: options.logLevel, logLevel: options.logLevel,
mode: options.mode, mode: options.mode,
build: { build: {

View File

@ -519,7 +519,22 @@ export interface ResolvedWorkerOptions {
} }
export interface InlineConfig extends UserConfig { 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 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 envFile?: false
} }
@ -834,7 +849,10 @@ export async function resolveConfig(
let { configFile } = config let { configFile } = config
if (configFile !== false) { if (configFile !== false) {
const loadResult = await loadConfigFromFile( const loadConfigFn = config.configFileNativeImport
? loadConfigNative
: loadConfigFromFile
const loadResult = await loadConfigFn(
configEnv, configEnv,
configFile, configFile,
config.root, config.root,
@ -1465,6 +1483,76 @@ export function sortUserPlugins(
return [prePlugins, normalPlugins, postPlugins] 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( export async function loadConfigFromFile(
configEnv: ConfigEnv, configEnv: ConfigEnv,
configFile?: string, configFile?: string,
@ -1476,26 +1564,10 @@ export async function loadConfigFromFile(
config: UserConfig config: UserConfig
dependencies: string[] dependencies: string[]
} | null> { } | null> {
const start = performance.now() const start = debug ? performance.now() : 0
const getTime = () => `${(performance.now() - start).toFixed(2)}ms` const getTime = () => `${(performance.now() - start).toFixed(2)}ms`
let resolvedPath: string | undefined const resolvedPath = searchConfigFile(configRoot, configFile)
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
}
}
if (!resolvedPath) { if (!resolvedPath) {
debug?.('no config file found.') debug?.('no config file found.')
return null return null