Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Qwik 2: rest destructuring of a plain object inside a route loader silently yields {}

The optimizer rewrites const { a, ...rest } = obj inside a routeLoader$ into core's _restProps(obj, ["a"]). _restProps only understands component-props proxies — it reads the _VAR_PROPS / _CONST_PROPS symbols off its argument. For an ordinary object those symbols are undefined, so it returns an empty props proxy. The rest object loses every key, server-side, with no error.

Versions

  • @qwik.dev/core 2.0.0-beta.38 (pristine, unpatched)
  • @qwik.dev/router 2.0.0-beta.38
  • vite 6.4.3, bun 1.2.17, node/darwin arm64

Reproduce

bun install
bun run dev   # vite --mode ssr, port 5803

Buggy route — src/routes/index.tsx

The loader awaits resolveValue(useBase) and then rest-destructures a plain object.

curl -sL http://localhost:5803/ | grep -oE 'RESULT[^<]*'

Actual:

RESULT {}

Expected:

RESULT {"slug":"ada","avatarId":"abc"}

Because the rest object is empty, profile.avatarId and profile.slug are undefined and the derived img is { id: undefined, slug: undefined }.

Control route — src/routes/control/index.tsx

Identical code with the useBase loader and the resolveValue call removed.

curl -sL http://localhost:5803/control/ | grep -oE 'RESULT[^<]*'

Actual (correct, HTML-escaped in the raw response):

RESULT {"slug":"ada","avatarId":"abc"}

So the rest destructure alone is fine. Empirically, in this repro the two routes are compiled differently: the SSR transform of the buggy route imports and calls _restProps, and the control route keeps the native rest pattern. Inspecting Vite's SSR transform:

=== /src/routes/index.tsx        restProps:true
const __vite_ssr_import_0__ = await __vite_ssr_import__(".../core.mjs", {"importedNames":["_restProps"]});
  const profile = (0,__vite_ssr_import_0__._restProps)(user, [

=== /src/routes/control/index.tsx restProps:false
  const { name, ...profile } = user;

I have not traced why the optimizer picks _restProps in one case and not the other; the resolveValue chain is simply the smallest shape I found that reliably triggers it. The misbehavior of _restProps on a plain object is the part that is unambiguous.

Cause

_restProps in node_modules/@qwik.dev/core/dist/core.mjs (around line 3865):

const _restProps = (props, omit = [], target = {}) => {
    let constPropsTarget = null;
    const constProps = props[_CONST_PROPS];
    ...
    const varProps = props[_VAR_PROPS];
    for (const key in varProps) { ... }
    ...
};

For a plain object both props[_CONST_PROPS] and props[_VAR_PROPS] are undefined, every loop body is skipped, and the function returns an empty props proxy.

A plain-object fallback fixes it: when neither symbol is present, copy the object's own enumerable keys that are not in the omit list into target and return that. (Alternatively, the optimizer should not rewrite rest destructuring of values that are not component props.)

Consequence

Any loader that derives its return value through rest destructuring silently loses fields — no exception, no warning, just missing data downstream.

About

Minimal reproduction: Qwik 2.0.0-beta.38 bug

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages