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:
- 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).
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).
{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.
- 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.
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.
- 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.
- 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.
- 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):
- A
.jsx component with export const inferredObservability = true that reads {this.count.get()} directly in render().
- Compile it (build/SSR).
- 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.
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 insrc/jsx-loader.js. Each is individually reproducible with a minimal.jsxcomponent; 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:
~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 aconsole.warnfrom the fall-through so the drop is visible (also handles the{' '}/{'text'}child-Literalidiom, which was itself dropped).class X extends HTMLElement {}; customElements.define(...); export default X;shipsundefined.$$tmpl0(...)(lines ~417–427 / 770 / 819).componentNameis only captured for the inlineexport default class Nameform, so the effect-append pass emitsundefined.$$tmpl0(...)→ clientTypeErroron connect (build green, SSR correct).{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.class Foo {}(noextends) in a.jsxfile crashes compilation withCannot read properties of null (reading 'name')(lines ~431 / 698;node.superClassisnull), with no file named.export const inferredObservability = falsestill enables observability (line ~413):Boolean(node.declaration.declarations[0].init.raw)runs on the source text"false", andBoolean("false") === true.observedAttributes/attributeChangedCallbackgets duplicate members (line ~380hasOwnObservedAttributesis dead/undefined, so the guards at ~505 / ~760 never fire); the user's definitions silently override the injected ones, disabling inferred attribute→signal sync.onclick="…"unescaped, and/this./grewritesthiseven inside string literals — e.g.onclick={() => alert("this rocks")}breaks the attribute and turns"this rocks"intoself.rocks.<body>/<html>produces a nested full document (lines ~31–35,getParse): parse5's full-document parse +serializeinlines a complete<html><head></head><body>…</body></html>inside the page.Steps to reproduce
Each defect has a minimal
.jsxfixture; 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):.jsxcomponent withexport const inferredObservability = truethat reads{this.count.get()}directly inrender().TypeError: Cannot read properties of undefined (reading 'get')pointing at the component'srender, instead of rendering the value.Environment
master,src/jsx-loader.jsunchanged since the release)@greenwood/plugin-import-jsx(Greenwood v0.34.0), but all defects are inwc-compileritself.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 directthis.<signal>.get()read; the chained{this.todos.get().length}variant is a broader change left as a follow-up.