You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TLDR — Components using import * as React from "react" or combined imports (import React, { useState } from "react") crash at runtime because transformEsmToRuntime produces invalid JavaScript. The transform function is also duplicated across two files, risking drift. Fixing the regex and extracting a shared module resolves all three issues.
Context
wilco bundles components with esbuild using --external:react, leaving import statements in the output. At runtime, transformEsmToRuntime rewrites these into lookups on window.__MODULES__["react"]. The module registry itself is correct: useState, useEffect, etc. are available as properties.
The bug is in the regex transform, which produces invalid JS for two import patterns:
graph TD
A[Component source] -->|esbuild --external:react| B[ESM bundle with imports]
B -->|transformEsmToRuntime| C{Import pattern?}
C -->|"import { useState }"| D["const { useState } = window.__MODULES__[...] ✅"]
C -->|"import * as React"| E["const * as React = window.__MODULES__[...] ❌ invalid JS"]
C -->|"import React, { useState }"| F["Left untransformed ❌"]
// Input (esbuild output for components using React.useState style):import*asReactfrom"react";// Current transform (INVALID JS):const*asReact=window.__MODULES__["react"];// Fixed:constReact=window.__MODULES__["react"];
The regex captures * as React but the as replacement ((\w+)\s+as\s+(\w+) → $1: $2) doesn't match because * is not \w+. The raw capture is emitted as-is, producing a syntax error.
Bug 2: combined default+named imports are ignored
// Input:importReact,{useState}from"react";// Current: left untransformed (regex doesn't match this pattern)// Fixed — two statements:constReact=window.__MODULES__["react"];const{ useState }=window.__MODULES__["react"];
The regex alternation (\{[^}]+\}|\*\s+as\s+\w+|\w+) only handles one clause. Combined imports have two (React and { useState }), so the pattern fails to match entirely. Note: esbuild normalizes imports and rarely produces this pattern for externals, but it's a correctness fix.
Possible implementation
Extract transformEsmToRuntime to a shared module (src/wilcojs/react/src/loader/transform.ts), imported by both standalone.ts and useComponent.ts
Fix the regex to handle all three import patterns:
Named: import { x } from "mod" → const { x } = window.__MODULES__["mod"]
Namespace: import * as X from "mod" → const X = window.__MODULES__["mod"]
Combined: import X, { y } from "mod" → const X = window.__MODULES__["mod"]; const { y } = window.__MODULES__["mod"]
Update tests in useComponent.test.ts to fix the namespace import expectation (currently expects invalid JS) and add combined import test cases
Rebuild the standalone loader (pnpm build:loader) so loader.js includes the fix
Output and testing scenarios
Expected output:
All import patterns transform to valid JS that correctly resolves from window.__MODULES__
Existing named import behavior unchanged
Testing scenarios:
Happy path: Component with import { useState } from "react" renders and hooks work
Namespace import: Component with import * as React from "react" and React.useState() works
Combined import:import React, { useState } from "react" transforms to two valid statements
Aliased imports:import { jsx as _jsx } from "react/jsx-runtime" still works
Multiple imports: Multiple import lines from different modules all transform correctly
Source maps: Source map comments preserved after transformation
Summary
TLDR — Components using
import * as React from "react"or combined imports (import React, { useState } from "react") crash at runtime becausetransformEsmToRuntimeproduces invalid JavaScript. The transform function is also duplicated across two files, risking drift. Fixing the regex and extracting a shared module resolves all three issues.Context
wilco bundles components with esbuild using
--external:react, leavingimportstatements in the output. At runtime,transformEsmToRuntimerewrites these into lookups onwindow.__MODULES__["react"]. The module registry itself is correct:useState,useEffect, etc. are available as properties.The bug is in the regex transform, which produces invalid JS for two import patterns:
graph TD A[Component source] -->|esbuild --external:react| B[ESM bundle with imports] B -->|transformEsmToRuntime| C{Import pattern?} C -->|"import { useState }"| D["const { useState } = window.__MODULES__[...] ✅"] C -->|"import * as React"| E["const * as React = window.__MODULES__[...] ❌ invalid JS"] C -->|"import React, { useState }"| F["Left untransformed ❌"]Affected files (duplicated logic):
src/wilcojs/react/src/loader/standalone.ts(Django standalone loader)src/wilcojs/react/src/loader/useComponent.ts(Vite React app loader)Bug 1: namespace imports produce invalid JS
The regex captures
* as Reactbut theasreplacement ((\w+)\s+as\s+(\w+)→$1: $2) doesn't match because*is not\w+. The raw capture is emitted as-is, producing a syntax error.Bug 2: combined default+named imports are ignored
The regex alternation
(\{[^}]+\}|\*\s+as\s+\w+|\w+)only handles one clause. Combined imports have two (Reactand{ useState }), so the pattern fails to match entirely. Note: esbuild normalizes imports and rarely produces this pattern for externals, but it's a correctness fix.Possible implementation
transformEsmToRuntimeto a shared module (src/wilcojs/react/src/loader/transform.ts), imported by bothstandalone.tsanduseComponent.tsimport { x } from "mod"→const { x } = window.__MODULES__["mod"]import * as X from "mod"→const X = window.__MODULES__["mod"]import X, { y } from "mod"→const X = window.__MODULES__["mod"]; const { y } = window.__MODULES__["mod"]useComponent.test.tsto fix the namespace import expectation (currently expects invalid JS) and add combined import test casespnpm build:loader) soloader.jsincludes the fixOutput and testing scenarios
Expected output:
window.__MODULES__Testing scenarios:
import { useState } from "react"renders and hooks workimport * as React from "react"andReact.useState()worksimport React, { useState } from "react"transforms to two valid statementsimport { jsx as _jsx } from "react/jsx-runtime"still works