{"id":"1473b1ba0c630b81","repo":"vitejs/vite","slug":"cannot-deep-clone-non-plain-object","errorCode":null,"errorMessage":"Cannot deep clone non-plain object","messagePattern":"Cannot deep clone non-plain object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vite/src/node/utils.ts","lineNumber":1231,"sourceCode":"export function deepClone<T>(value: T): DeepWritable<T> {\n  if (Array.isArray(value)) {\n    return value.map((v) => deepClone(v)) as DeepWritable<T>\n  }\n  if (isObject(value)) {\n    const cloned: Record<string, any> = {}\n    for (const key in value) {\n      cloned[key] = deepClone(value[key])\n    }\n    return cloned as DeepWritable<T>\n  }\n  if (typeof value === 'function') {\n    return value as DeepWritable<T>\n  }\n  if (value instanceof RegExp) {\n    return new RegExp(value) as DeepWritable<T>\n  }\n  if (typeof value === 'object' && value != null) {\n    throw new Error('Cannot deep clone non-plain object')\n  }\n  return value as DeepWritable<T>\n}\n\ntype MaybeFallback<D, V> = undefined extends V ? Exclude<V, undefined> | D : V\n\ntype MergeWithDefaultsResult<D, V> =\n  Equal<D, undefined> extends true\n    ? V\n    : D extends Function | Array<any>\n      ? MaybeFallback<D, V>\n      : V extends Function | Array<any>\n        ? MaybeFallback<D, V>\n        : D extends Record<string, any>\n          ? V extends Record<string, any>\n            ? {\n                [K in keyof D | keyof V]: K extends keyof D\n                  ? K extends keyof V","sourceCodeStart":1213,"sourceCodeEnd":1249,"githubUrl":"https://github.com/vitejs/vite/blob/89620f09afcfef6b35e7bb8660132ab5b4d0cd3b/packages/vite/src/node/utils.ts#L1213-L1249","documentation":"Vite's deepClone utility recursively clones arrays, plain objects (via isObject), functions (passed by reference), and RegExps. Any other non-null object that is not a plain object or array — e.g. a Map, Set, Date, class instance, Error, URL, or Buffer — falls through to the final branch and throws, because Vite cannot safely deep-clone arbitrary structured objects without risking data loss or prototype issues.","triggerScenarios":"Passing a config value (or any object processed by deepClone) that is a class instance, Map, Set, Date, Error, URL, or other built-in non-plain object. deepClone is used internally when normalizing/merging certain config structures, so a custom object placed where Vite expects a plain record triggers it.","commonSituations":"Putting a class instance, Date, Map, or Set into Vite config (e.g. a plugin option, define value, or resolved config field) that gets deep-cloned. Passing a third-party library object (like a compiled schema or AST node instance) where a plain config object is expected. Custom resolvers that attach non-plain metadata.","solutions":["Replace the non-plain object with a plain object literal (convert Map/Set to arrays/objects, Date to ISO string, class instance to a plain serializable shape).","If the value is meant to be shared by reference, pass a function instead — deepClone passes functions through without cloning.","Restructure so the non-plain object is constructed lazily inside a plugin hook rather than stored in cloned config."],"exampleFix":"// before — Date instance fails deepClone\nconst config = {\n  myPlugin: { startedAt: new Date() },\n}\n\n// after — store as ISO string, reconstruct when needed\nconst config = {\n  myPlugin: { startedAt: new Date().toISOString() },\n}","handlingStrategy":"type-guard","validationCode":"// Validate config values are plain-serializable before passing to Vite\nfunction isPlainlyCloneable(v: unknown): boolean {\n  if (v === null || typeof v !== 'object') return true\n  if (v instanceof RegExp || typeof v === 'function') return true\n  if (Array.isArray(v)) return v.every(isPlainlyCloneable)\n  const proto = Object.getPrototypeOf(v)\n  if (proto !== Object.prototype && proto !== null) return false\n  return Object.values(v).every(isPlainlyCloneable)\n}\n\nif (!isPlainlyCloneable(myConfigValue)) {\n  throw new Error('Config contains a non-plain object that Vite cannot deep-clone')\n}","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  if (v === null || typeof v !== 'object') return false\n  const proto = Object.getPrototypeOf(v)\n  return proto === Object.prototype || proto === null\n}","tryCatchPattern":null,"preventionTips":["Keep Vite config values to plain objects, arrays, primitives, functions, and RegExps only.","Convert Date/Map/Set/class instances to plain serializable forms before placing in config.","Store complex objects lazily inside plugin hooks rather than in cloned config."],"tags":["config","serialization","deep-clone"],"analyzedSha":"89620f09afcfef6b35e7bb8660132ab5b4d0cd3b","analyzedAt":"2026-08-03T19:28:02.920Z","schemaVersion":2}