fix: clone config without plugins #902

This commit is contained in:
alex8088 2026-09-24 19:30:27 +08:00
parent 811a9f2a75
commit ac2cdd1a21
2 changed files with 55 additions and 2 deletions

View File

@ -28,7 +28,7 @@ import modulePathPlugin from './plugins/modulePath'
import isolateEntriesPlugin from './plugins/isolateEntries' import isolateEntriesPlugin from './plugins/isolateEntries'
import { type ExternalOptions, externalizeDepsPlugin } from './plugins/externalizeDeps' import { type ExternalOptions, externalizeDepsPlugin } from './plugins/externalizeDeps'
import { type BytecodeOptions, bytecodePlugin } from './plugins/bytecode' import { type BytecodeOptions, bytecodePlugin } from './plugins/bytecode'
import { isObject, isFilePathESM, deepClone, asyncFlatten } from './utils' import { isObject, isFilePathESM, cloneConfig, asyncFlatten } from './utils'
import { findConfigFile, bundleConfigFile, loadConfigFormBundledFile } from './load' import { findConfigFile, bundleConfigFile, loadConfigFormBundledFile } from './load'
export { defineConfig as defineViteConfig } from 'vite' export { defineConfig as defineViteConfig } from 'vite'
@ -229,7 +229,7 @@ export abstract class ConfigFactory<T extends MainViteConfig | PreloadViteConfig
async build(cleanMode?: boolean): Promise<T> { async build(cleanMode?: boolean): Promise<T> {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const config = mergeConfig(deepClone(this.baseConfig) as any, deepClone(this.inlineConfig)) as T const config = mergeConfig(cloneConfig(this.baseConfig) as any, cloneConfig(this.inlineConfig)) as T
config.mode = this.inlineConfig.mode || config.mode || process.env.NODE_ENV config.mode = this.inlineConfig.mode || config.mode || process.env.NODE_ENV

View File

@ -112,6 +112,59 @@ export function deepClone<T>(value: T): DeepWritable<T> {
return value as DeepWritable<T> return value as DeepWritable<T>
} }
/**
* Isolate configuration data without cloning plugins or runtime instances.
* Plugins may capture their own object in hooks, so their identity must survive.
*/
export function cloneConfig<T>(config: T): T {
const seen = new WeakMap<object, unknown>()
const pluginArrays = new WeakMap<object, unknown>()
function clone(value: unknown, plugins = false): unknown {
if (value === null || typeof value !== 'object') return value
const cache = plugins ? pluginArrays : seen
if (cache.has(value)) return cache.get(value)
if (Array.isArray(value)) {
const result: unknown[] = new Array(value.length)
cache.set(value, result)
value.forEach((item, index) => {
result[index] = clone(item, plugins)
})
return result
}
// Preserve plugin objects, promises and other opaque plugin options.
if (plugins) return value
if (value instanceof RegExp) {
const result = new RegExp(value)
result.lastIndex = value.lastIndex
cache.set(value, result)
return result
}
const prototype = Object.getPrototypeOf(value)
if (prototype !== Object.prototype && prototype !== null) return value
const result = Object.create(prototype) as Record<PropertyKey, unknown>
cache.set(value, result)
for (const key of Reflect.ownKeys(value)) {
if (!Object.prototype.propertyIsEnumerable.call(value, key)) continue
Object.defineProperty(result, key, {
value: clone((value as Record<PropertyKey, unknown>)[key], key === 'plugins'),
enumerable: true,
configurable: true,
writable: true
})
}
return result
}
return clone(config) as T
}
type AsyncFlatten<T extends unknown[]> = T extends (infer U)[] ? Exclude<Awaited<U>, U[]>[] : never type AsyncFlatten<T extends unknown[]> = T extends (infer U)[] ? Exclude<Awaited<U>, U[]>[] : never
export async function asyncFlatten<T extends unknown[]>(arr: T): Promise<AsyncFlatten<T>> { export async function asyncFlatten<T extends unknown[]>(arr: T): Promise<AsyncFlatten<T>> {