Skip to content

src/jsx-loader.js: several correctness/DX defects in JSX + inferred-observability compilation #282

Description

@jstockdi

What happened?

While exercising wc-compiler's JSX + inferred-observability compilation (via Greenwood's @greenwood/plugin-import-jsx), I hit a cluster of correctness/DX defects that all live in src/jsx-loader.js. Each is individually reproducible with a minimal .jsx component; several fail silently (green build, broken or missing output), and a few crash the build with an error that points at wc-compiler internals rather than the user's source.

Because they share one file and some are adjacent, I'm reporting them together; a PR follows with one commit (fix + regression test) per defect.

The defects:

  1. Unsupported JSX child expressions are silently dropped (~parseJsxElement, lines ~131–306). Calls, ternaries, binary ops, template literals, .map() lists, and computed members produce no output and no warning — e.g. {items.map(i => <li>{i}</li>)} renders an empty <ul>. Fix: emit a console.warn from the fall-through so the drop is visible (also handles the {' '} / {'text'} child-Literal idiom, which was itself dropped).
  2. class X extends HTMLElement {}; customElements.define(...); export default X; ships undefined.$$tmpl0(...) (lines ~417–427 / 770 / 819). componentName is only captured for the inline export default class Name form, so the effect-append pass emits undefined.$$tmpl0(...) → client TypeError on connect (build green, SSR correct).
  3. {this.count.get()} compiles to ${undefined.get()} (lines ~280–298), crashing SSR (Cannot read properties of undefined (reading 'get')) on a valid source line. Only the destructured {count.get()} form works today.
  4. A plain class Foo {} (no extends) in a .jsx file crashes compilation with Cannot read properties of null (reading 'name') (lines ~431 / 698; node.superClass is null), with no file named.
  5. export const inferredObservability = false still enables observability (line ~413): Boolean(node.declaration.declarations[0].init.raw) runs on the source text "false", and Boolean("false") === true.
  6. A component with its own observedAttributes / attributeChangedCallback gets duplicate members (line ~380 hasOwnObservedAttributes is dead/undefined, so the guards at ~505 / ~760 never fire); the user's definitions silently override the injected ones, disabling inferred attribute→signal sync.
  7. Arrow event handlers with double-quoted strings emit malformed HTML (line ~177): the serialized body is placed into a double-quoted onclick="…" unescaped, and /this./g rewrites this even inside string literals — e.g. onclick={() => alert("this rocks")} breaks the attribute and turns "this rocks" into self.rocks.
  8. A JSX root of <body>/<html> produces a nested full document (lines ~31–35, getParse): parse5's full-document parse + serialize inlines a complete <html><head></head><body>…</body></html> inside the page.

Steps to reproduce

Each defect has a minimal .jsx fixture; see the corresponding commit's regression test in the PR (each proves the failure on unpatched source and passes with the fix). Example (defect 3):

  1. A .jsx component with export const inferredObservability = true that reads {this.count.get()} directly in render().
  2. Compile it (build/SSR).
  3. Observe TypeError: Cannot read properties of undefined (reading 'get') pointing at the component's render, instead of rendering the value.

Environment

  • wc-compiler: 0.22.2 (current master, src/jsx-loader.js unchanged since the release)
  • Node: 22.20
  • OS: Linux
  • Reached via @greenwood/plugin-import-jsx (Greenwood v0.34.0), but all defects are in wc-compiler itself.

Additional Context

Each fix is minimal and confined; the diagnostic-only one (defect 1) is a non-fatal console.warn, not a hard error. Defect 3 covers the direct this.<signal>.get() read; the chained {this.todos.get().length} variant is a broader change left as a follow-up.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions