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:
-
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.
-
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
Environment
8.0.140.2.0(@vitejs/devtools/@vitejs/devtools-rolldown)0.4.1vite 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:TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))reviveIfStructuredClone/structuredCloneDeserialize.{ serialization, fnName, data }rather than flat structured-clone array records ([[2, ...]]). When the devtools client encounters this enveloped object instructured-cloneserialization mode, it attempts to deserialize the wrapper object directly, causingstructuredCloneDeserializeto crash when attempting to iterate on it.Error: [devtools-rpc] No dump match for "devtoolskit:internal:messages:list" with args: [null]createStaticRpcCaller->call().null(e.g.args = [null]). The static RPC handler strictly validates that no arguments are passed (if (args.length > 0) throw ...). Since the length is1, it throws aNo dump matcherror, breaking client initialization.Suggested Fix
Both of these errors can be permanently resolved by upgrading/patching the underlying
devframeclient package (specificallycreateStaticRpcCallerindevframe/src/client/rpc-static.tsordist/client/index.mjs) and building@vitejs/devtoolsusing 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
devframeand recompiling/publishing a new version of@vitejs/devtoolslinked with it, static preview loads perfectly out of the box with zero runtime errors.Relates to: vitejs/devtools#339