Skip to content

[🐞] v2: Optimizer's transform_props_destructuring mis-rewrites plain helper arrows, breaking dev SSR and production #8638

@46ki75

Description

@46ki75

Which component is affected?

Qwik Optimizer (rust)

Describe the bug

The optimizer's transform_props_destructuring pass treats any arrow
function with a single destructured parameter and a return statement as a
Qwik inline component, regardless of whether the function actually is one.

The pass renames the parameter to _rawProps and rewrites identifier
reads of the destructured names to _rawProps.<name> — but it does not
rewrite the LHS of assignments.

When the function body reassigns the destructured binding (a common pattern
in plain helpers that default an optional argument, e.g.
if (!ogImage) ogImage = …), the rewritten code ends up writing to a
now-undeclared identifier.

User-visible failure modes (all empirically observed, see Additional
Information):

  • Dev SSR (vite --mode ssr): throws ReferenceError: <name> is not defined inside the SSR render path. The error is swallowed by Qwik's
    component SSR pipeline and the request hangs forever with no log output.
  • Production (pnpm build + any SSR adapter or vite preview): ships
    the same broken code. Depending on the RHS shape and downstream
    simplification, either the broken assignment is preserved (template
    literal RHS → 500 with the same ReferenceError at runtime) or the
    entire conditional is eliminated as dead code (string literal RHS →
    silent miscompilation: the default value is never applied).

I intend to submit a PR for this issue. Candidate fix (refusing the
transform when the body reassigns a destructured binding) is on the branch
fix/props-destructuring-reassign-bug against build/v2 — see Additional
Information for the change summary.

Reproduction

https://github.com/46ki75/qwik-v2-optimizer-destructure-reassign-bug

Steps to reproduce

pnpm install
pnpm dev      # vite --mode ssr ; serves http://localhost:5173/
curl --max-time 15 http://localhost:5173/

Expected (broken) result: curl exits with timeout (exit code 28). Vite
logs no error.

To see the actual error rather than a hang, switch the invocation in
src/routes/index.tsx to eager (module top-level) form:

import { buildHead } from "~/utils/head";
const _eager = buildHead({});
void _eager;

curl http://localhost:5173/ then returns a 500 with:

"message":"ogImage is not defined"
"stack":"    at buildHead (src/utils/head.ts:N:M)
         at eval (src/routes/index.tsx:N:M)
         at async ESModulesEvaluator.runInlinedModule (vite/.../module-runner.js)
"

To inspect the Vite-transformed output directly (post-optimizer SSR
module the runtime actually executes):

curl http://localhost:5173/src/utils/head.ts?import

To verify production is also affected:

pnpm build
# Then grep dist/build/q-*.js for the chunk containing "hang repro";
# the body is `const r = e => (e.ogImage || (ogImage = "fallback-image-url"), …)`
# — a write to an undeclared `ogImage` that throws in strict mode at runtime.

System Info

System:
    OS: Linux 6.6 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
    CPU: (16) x64 Intel(R) Core(TM) Ultra 7 255H
    Memory: 6.53 GB / 15.31 GB
    Container: Yes
    Shell: 5.2.21 - /bin/bash
  Binaries:
    Node: 24.15.0 - /home/ikuma/.vite-plus/js_runtime/node/24.15.0/bin/node
    npm: 11.12.1 - /home/ikuma/.vite-plus/js_runtime/node/24.15.0/bin/npm
    pnpm: 10.33.0 - /home/ikuma/.volta/bin/pnpm
  Browsers:
    Chrome: 148.0.7778.167
  npmPackages:
    @qwik.dev/core: 2.0.0-beta.35 => 2.0.0-beta.35
    @qwik.dev/router: 2.0.0-beta.35 => 2.0.0-beta.35
    typescript: 5.8 => 5.8.3
    vite: 7.3.2 => 7.3.2

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    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