Skip to content

[Bug] Vite DevTools static preview fails with TypeError: undefined is not iterable and No dump match #26

@leonardas103

Description

@leonardas103

Environment

  • Vite Version: 8.0.14
  • Vite DevTools Version: 0.2.0 (@vitejs/devtools / @vitejs/devtools-rolldown)
  • Devframe Version: 0.4.1
  • Environment/Mode: Static preview mode (vite preview --outDir dist/devtools)

Describe the Bug

When attempting to load the static version of Vite DevTools served via vite preview --outDir dist/devtools, the dashboard fails to render and crashes with two distinct runtime console errors:

  1. TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))

    • Location: reviveIfStructuredClone / structuredCloneDeserialize.
    • Cause: The static RPC dump generator writes static RPC dumps enveloped in a wrapper object { serialization, fnName, data } rather than flat structured-clone array records ([[2, ...]]). When the devtools client encounters this enveloped object in structured-clone serialization mode, it attempts to deserialize the wrapper object directly, causing structuredCloneDeserialize to crash when attempting to iterate on it.
  2. Error: [devtools-rpc] No dump match for "devtoolskit:internal:messages:list" with args: [null]

    • Location: createStaticRpcCaller -> call().
    • Cause: Framework-level setup hooks (such as loading the messages list) call static RPC endpoints passing a single placeholder/dummy argument null (e.g. args = [null]). The static RPC handler strictly validates that no arguments are passed (if (args.length > 0) throw ...). Since the length is 1, it throws a No dump match error, breaking client initialization.

Suggested Fix

Both of these errors can be permanently resolved by upgrading/patching the underlying devframe client package (specifically createStaticRpcCaller in devframe/src/client/rpc-static.ts or dist/client/index.mjs) and building @vitejs/devtools using this updated version.

Below is the clean source-level diff required in devframe's client initialization:

 function createStaticRpcCaller(manifest, fetchJson) {
 	const staticCache = /* @__PURE__ */ new Map();
 	const queryRecordCache = /* @__PURE__ */ new Map();
+
+	// Unwraps enveloped static output files (containing serialization/fnName/data)
+	function unwrapRaw(raw) {
+		if (raw && typeof raw === "object" && "serialization" in raw && "data" in raw) {
+			return raw.data;
+		}
+		return raw;
+	}
+
 	function reviveIfStructuredClone(value, serialization) {
-		if (serialization === "structured-clone") return structuredCloneDeserialize(value);
+		// Safe validation ensures deserialization is only performed on flat array structures
+		if (serialization === "structured-clone" && Array.isArray(value)) return structuredCloneDeserialize(value);
 		return value;
 	}
 	async function loadStatic(entry) {
 		if (!staticCache.has(entry.path)) {
-			staticCache.set(entry.path, fetchJson(entry.path).then((value) => reviveIfStructuredClone(value, entry.serialization)));
+			staticCache.set(entry.path, fetchJson(entry.path).then((raw) => {
+				const unwrapped = unwrapRaw(raw);
+				return reviveIfStructuredClone(unwrapped, entry.serialization);
+			}));
 		}
 		const data = await staticCache.get(entry.path);
 		if (isRecord(data)) return resolveRecordOutput(data);
 		return data;
 	}
 	async function loadQueryRecord(path, serialization) {
 		if (!queryRecordCache.has(path)) {
-			queryRecordCache.set(path, fetchJson(path).then((value) => reviveIfStructuredClone(value, serialization)));
+			queryRecordCache.set(path, fetchJson(path).then((raw) => {
+				const unwrapped = unwrapRaw(raw);
+				return reviveIfStructuredClone(unwrapped, serialization);
+			}));
 		}
 		return await queryRecordCache.get(path);
 	}
 	async function call(functionName, args) {
 		if (!(functionName in manifest)) throw new Error(`[devtools-rpc] Function "${functionName}" not found in dump store`);
 		const entry = manifest[functionName];
 		if (isStaticEntry(entry)) {
-			const hasRealArgs = args.length > 0 && args.some(arg => arg !== null && arg !== undefined);
+			// Filter out dummy/placeholder arguments (null or undefined)
+			const hasRealArgs = args.length > 0 && args.some(arg => arg !== null && arg !== undefined);
 			if (hasRealArgs) throw new Error(`[devtools-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
 			return await loadStatic(entry);
 		}
 		if (isQueryEntry(entry)) {
 			const argsHash = hash(args);
 			const recordPath = entry.records[argsHash];
 			if (recordPath) return resolveRecordOutput(await loadQueryRecord(recordPath, entry.serialization));
 			if (entry.fallback) return resolveRecordOutput(await loadQueryRecord(entry.fallback, entry.serialization));
 			throw new Error(`[devtools-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
 		}
-		const hasRealArgs = args.length > 0 && args.some(arg => arg !== null && arg !== undefined);
+		const hasRealArgs = args.length > 0 && args.some(arg => arg !== null && arg !== undefined);
 		if (!hasRealArgs) return entry;
 		throw new Error(`[devtools-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
 	}

By applying this patch in devframe and recompiling/publishing a new version of @vitejs/devtools linked with it, static preview loads perfectly out of the box with zero runtime errors.

Relates to: vitejs/devtools#339

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions