ProjectEvergreen / ProjectEvergreen/wcc

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

Open
#282 0 comments 0 reactions 1 assignee View on GitHub

@jstockdi is already working on this.

Since Jul 18, 2026.

Dominant language
JavaScript
Stars
126
Forks
17
PR merge metrics
No merged PRs in 30d

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.