@wix/zero-config-implementation 1.59.0 → 1.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "1.59.0",
7
+ "version": "1.60.0",
8
8
  "description": "Core library for extracting component manifests from JS and CSS files",
9
9
  "type": "module",
10
10
  "main": "dist/index.js",
@@ -13,12 +13,6 @@
13
13
  ".": {
14
14
  "import": "./dist/index.js",
15
15
  "types": "./dist/index.d.ts"
16
- },
17
- "./jsx-runtime-loader": {
18
- "import": "./dist/jsx-runtime-loader.js"
19
- },
20
- "./jsx-runtime-interceptor": {
21
- "import": "./dist/jsx-runtime-interceptor.js"
22
16
  }
23
17
  },
24
18
  "scripts": {
@@ -85,5 +79,5 @@
85
79
  ]
86
80
  }
87
81
  },
88
- "falconPackageHash": "3762cf48efbed240f0744fc678539dd9e70ea2021dc6171c70fb714b"
82
+ "falconPackageHash": "773d13f574bda240374a4a8e8347af42c6022e40e7d842e98c15a6cf"
89
83
  }
@@ -10,18 +10,22 @@
10
10
  * Four complementary layers ensure interception regardless of how JSX is compiled
11
11
  * AND regardless of how many React module instances are loaded in the process:
12
12
  *
13
- * 1. **Vite alias** — `react/jsx-runtime` is aliased to `jsx-runtime-interceptor.ts`
14
- * in vite.config.ts. This handles ESM imports resolved at build time.
13
+ * 1. **Node.js ESM loader hook** — registered by `registerJsxLoaderHook()` in
14
+ * `module-loader.ts` before any user bundle is imported. Redirects
15
+ * `react/jsx-runtime` and `react/jsx-dev-runtime` bare-specifier imports to
16
+ * an in-memory `data:` URL interceptor that reads mutable state from
17
+ * `globalThis`. This handles pre-built ESM bundles with static
18
+ * `import ... from 'react/jsx-runtime'` at the top.
15
19
  *
16
20
  * 2. **`React.createElement` monkey-patch** — Classic JSX transform and explicit
17
21
  * `React.createElement()` calls are intercepted by temporarily replacing the
18
22
  * function on the React module object.
19
23
  *
20
- * 3. **CJS module export patch** — Pre-built CJS bundles call
21
- * `require('react/jsx-runtime')` at runtime, bypassing the Vite alias. We use
22
- * `createRequire` to obtain the real CJS module objects and temporarily replace
23
- * their `jsx`/`jsxs`/`jsxDEV` exports during rendering. This is safe because
24
- * CJS module exports are mutable (`writable: true`).
24
+ * 3. **CJS module export patch** — Pre-built CJS bundles that call
25
+ * `require('react/jsx-runtime')` at runtime. We use `createRequire` to obtain
26
+ * the real CJS module objects and temporarily replace their `jsx`/`jsxs`/`jsxDEV`
27
+ * exports during rendering. This is safe because CJS module exports are mutable
28
+ * (`writable: true`).
25
29
  *
26
30
  * 4. **User-bundle React patch** — When the renderer is hosted inside a process
27
31
  * that bundled its own React (e.g. `@wix/cli` ships a baked-in copy of
@@ -38,20 +42,19 @@
38
42
  *
39
43
  * ## Intercepted Functions
40
44
  *
41
- * - `React.createElement` - Classic JSX transform (monkey-patched)
42
- * - `jsx` / `jsxs` from `react/jsx-runtime` - New JSX transform via interceptor module
43
- * - `jsx` / `jsxs` / `jsxDEV` on the real CJS `react/jsx-runtime` and
44
- * `react/jsx-dev-runtime` module objects (monkey-patched for CJS consumers)
45
- * - The same exports on the React modules resolved from the user component
46
- * bundle's own location (when a different module instance from the host's)
45
+ * - `React.createElement` classic JSX transform (monkey-patched)
46
+ * - `jsx` / `jsxs` / `jsxDEV` from `react/jsx-runtime` and `react/jsx-dev-runtime`
47
+ * new JSX transform, intercepted via the ESM loader hook's `data:` URL interceptor
48
+ * - `jsx` / `jsxs` / `jsxDEV` on the real CJS module objects (monkey-patched for
49
+ * CJS consumers)
47
50
  *
48
51
  * ## Known Limitations
49
52
  *
50
- * 1. **Global state** - During render, we temporarily replace element creation
53
+ * 1. **Global state** During render, we temporarily replace element creation
51
54
  * functions globally. This is not thread-safe and could cause issues if multiple
52
55
  * renders happen concurrently. Interceptors are restored in a finally block.
53
56
  *
54
- * 2. **React version compatibility** - This approach may break with future React versions
57
+ * 2. **React version compatibility** This approach may break with future React versions
55
58
  * if the internal rendering pipeline changes. Tested with React 18.x.
56
59
  *
57
60
  * @module component-renderer
@@ -1,42 +1,29 @@
1
1
  /**
2
- * Interceptable JSX Runtime
2
+ * JSX Interception
3
3
  *
4
- * This module wraps React's jsx-runtime and allows dynamic interception
5
- * of jsx/jsxs/jsxDEV calls. This is necessary because ESM imports are immutable,
6
- * so we can't monkey-patch react/jsx-runtime directly.
4
+ * Provides mutable interception of React's jsx-runtime functions and registers
5
+ * the Node.js ESM loader hook that redirects `react/jsx-runtime` and
6
+ * `react/jsx-dev-runtime` imports to an in-memory data: URL interceptor.
7
7
  *
8
- * ## Usage contexts
9
- *
10
- * This module runs in two contexts that must share mutable state:
11
- *
12
- * 1. **Vite alias (tests)** — aliased to `react/jsx-runtime` in vite.config.ts
13
- * 2. **Node.js ESM loader hook (CLI)** — redirected via `module.register()` in cli.ts
14
- *
15
- * In both cases the interceptor may be loaded as a separate module instance from
16
- * the copy bundled into the main dist files. To ensure `setJsxInterceptors()`
17
- * (called from component-renderer.ts) affects the `jsx`/`jsxs` that user
18
- * components call, mutable state lives on `globalThis` via `Symbol.for()`.
8
+ * Mutable state lives on `globalThis` via `Symbol.for()` so that the bundled
9
+ * copy of this module and the data: URL interceptor module share the same state.
19
10
  */
20
11
 
21
- import { createRequire } from 'node:module'
12
+ import { createRequire, register } from 'node:module'
22
13
 
23
14
  // Use require() to load the real React modules, bypassing Vite alias / ESM loader hook
24
15
  const require = createRequire(import.meta.url)
25
16
  const originalRuntime = require('react/jsx-runtime') as typeof import('react/jsx-runtime')
26
17
  const originalDevRuntime = require('react/jsx-dev-runtime') as typeof import('react/jsx-dev-runtime')
27
18
 
28
- // Re-export Fragment unchanged
29
- export const Fragment = originalRuntime.Fragment
30
-
31
19
  // Type for jsxDEV function
32
20
  type JsxDevFn = typeof originalDevRuntime.jsxDEV
33
21
 
34
22
  // ─────────────────────────────────────────────────────────────────────────────
35
23
  // Shared state via globalThis
36
24
  //
37
- // The bundled copy (inside cli.js / index.js) and the standalone copy (loaded
38
- // by the ESM loader hook or Vite alias) must share the same mutable state.
39
- // Symbol.for() guarantees a process-wide singleton key.
25
+ // The bundled copy (inside index.js) and the data: URL interceptor module share
26
+ // mutable state via globalThis. Symbol.for() guarantees a process-wide key.
40
27
  // ─────────────────────────────────────────────────────────────────────────────
41
28
 
42
29
  const STATE_KEY = Symbol.for('zero-config:jsx-interceptor')
@@ -87,60 +74,94 @@ export function getOriginals() {
87
74
  }
88
75
  }
89
76
 
90
- // Export interceptable jsx/jsxs - these delegate to the shared state
91
- export function jsx(
92
- type: Parameters<typeof originalRuntime.jsx>[0],
93
- props: Parameters<typeof originalRuntime.jsx>[1],
94
- key?: Parameters<typeof originalRuntime.jsx>[2],
95
- ) {
96
- const state = getState()
97
- if (state.isInsideOriginal) {
98
- return originalRuntime.jsx(type, props, key)
99
- }
100
- state.isInsideOriginal = true
101
- try {
102
- return state.currentJsx(type, props, key)
103
- } finally {
104
- state.isInsideOriginal = false
105
- }
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+ // Hook Registration
79
+ // ─────────────────────────────────────────────────────────────────────────────
80
+
81
+ const LOADER_REGISTERED_KEY = Symbol.for('zero-config:jsx-loader-registered')
82
+ const ORIGINALS_KEY = Symbol.for('zero-config:jsx-originals')
83
+
84
+ interface JsxOriginals {
85
+ Fragment: typeof originalRuntime.Fragment
86
+ jsx: typeof originalRuntime.jsx
87
+ jsxs: typeof originalRuntime.jsxs
88
+ jsxDEV: JsxDevFn
106
89
  }
107
90
 
108
- export function jsxs(
109
- type: Parameters<typeof originalRuntime.jsxs>[0],
110
- props: Parameters<typeof originalRuntime.jsxs>[1],
111
- key?: Parameters<typeof originalRuntime.jsxs>[2],
112
- ) {
113
- const state = getState()
114
- if (state.isInsideOriginal) {
115
- return originalRuntime.jsxs(type, props, key)
116
- }
117
- state.isInsideOriginal = true
118
- try {
119
- return state.currentJsxs(type, props, key)
120
- } finally {
121
- state.isInsideOriginal = false
91
+ /**
92
+ * Registers a Node.js ESM loader hook that redirects `react/jsx-runtime` and
93
+ * `react/jsx-dev-runtime` imports to an in-memory interceptor built from
94
+ * `data:` URLs — no separate files required, safe for bundled consumers.
95
+ *
96
+ * Must be called before any dynamic `import()` of user components.
97
+ * Safe to call multiple times; subsequent calls are no-ops.
98
+ */
99
+ export function registerJsxLoaderHook(): void {
100
+ const globalRecord = globalThis as unknown as Record<symbol, unknown>
101
+ if (globalRecord[LOADER_REGISTERED_KEY]) return
102
+ globalRecord[LOADER_REGISTERED_KEY] = true
103
+
104
+ const cjsRequire = createRequire(import.meta.url)
105
+ const realRuntime = cjsRequire('react/jsx-runtime') as typeof originalRuntime
106
+ const realDevRuntime = cjsRequire('react/jsx-dev-runtime') as typeof originalDevRuntime
107
+
108
+ // Stash originals so the data: interceptor module can read them without needing
109
+ // createRequire (data: modules have no filesystem context for CJS resolution)
110
+ globalRecord[ORIGINALS_KEY] = {
111
+ Fragment: realRuntime.Fragment,
112
+ jsx: realRuntime.jsx,
113
+ jsxs: realRuntime.jsxs,
114
+ jsxDEV: realDevRuntime.jsxDEV,
115
+ } satisfies JsxOriginals
116
+
117
+ const interceptorSource = `
118
+ const ORIGINALS_KEY = Symbol.for('zero-config:jsx-originals');
119
+ const STATE_KEY = Symbol.for('zero-config:jsx-interceptor');
120
+ const originals = globalThis[ORIGINALS_KEY];
121
+ function getState() {
122
+ if (!globalThis[STATE_KEY]) {
123
+ globalThis[STATE_KEY] = {
124
+ currentJsx: originals.jsx, currentJsxs: originals.jsxs,
125
+ currentJsxDEV: originals.jsxDEV, isInsideOriginal: false,
126
+ };
122
127
  }
128
+ return globalThis[STATE_KEY];
129
+ }
130
+ export const Fragment = originals.Fragment;
131
+ export function jsx(type, props, key) {
132
+ const state = getState();
133
+ if (state.isInsideOriginal) return originals.jsx(type, props, key);
134
+ state.isInsideOriginal = true;
135
+ try { return state.currentJsx(type, props, key); }
136
+ finally { state.isInsideOriginal = false; }
123
137
  }
138
+ export function jsxs(type, props, key) {
139
+ const state = getState();
140
+ if (state.isInsideOriginal) return originals.jsxs(type, props, key);
141
+ state.isInsideOriginal = true;
142
+ try { return state.currentJsxs(type, props, key); }
143
+ finally { state.isInsideOriginal = false; }
144
+ }
145
+ export function jsxDEV(type, props, key, isStaticChildren, source, self) {
146
+ const state = getState();
147
+ if (state.isInsideOriginal) return originals.jsxDEV(type, props, key, isStaticChildren, source, self);
148
+ state.isInsideOriginal = true;
149
+ try { return state.currentJsxDEV(type, props, key, isStaticChildren, source, self); }
150
+ finally { state.isInsideOriginal = false; }
151
+ }
152
+ `
124
153
 
125
- // Export interceptable jsxDEV for development mode
126
- export function jsxDEV(
127
- type: Parameters<JsxDevFn>[0],
128
- props: Parameters<JsxDevFn>[1],
129
- key: Parameters<JsxDevFn>[2],
130
- isStaticChildren: Parameters<JsxDevFn>[3],
131
- source: Parameters<JsxDevFn>[4],
132
- self: Parameters<JsxDevFn>[5],
133
- ) {
134
- const state = getState()
135
- // Prevent recursion when React's internal jsxDEV calls other jsx functions
136
- if (state.isInsideOriginal) {
137
- return originalDevRuntime.jsxDEV(type, props, key, isStaticChildren, source, self)
138
- }
154
+ const interceptorDataUrl = `data:text/javascript,${encodeURIComponent(interceptorSource)}`
139
155
 
140
- state.isInsideOriginal = true
141
- try {
142
- return state.currentJsxDEV(type, props, key, isStaticChildren, source, self)
143
- } finally {
144
- state.isInsideOriginal = false
156
+ const loaderSource = `
157
+ const INTERCEPTOR_URL = ${JSON.stringify(interceptorDataUrl)};
158
+ export async function resolve(specifier, context, nextResolve) {
159
+ if (specifier === 'react/jsx-runtime' || specifier === 'react/jsx-dev-runtime') {
160
+ return { shortCircuit: true, url: INTERCEPTOR_URL };
145
161
  }
162
+ return nextResolve(specifier, context);
163
+ }
164
+ `
165
+
166
+ register(`data:text/javascript,${encodeURIComponent(loaderSource)}`)
146
167
  }
@@ -5,6 +5,8 @@ import React from 'react'
5
5
  import type { ComponentType } from 'react'
6
6
  import ReactDOM from 'react-dom'
7
7
 
8
+ import { registerJsxLoaderHook } from './jsx-runtime-interceptor'
9
+
8
10
  /**
9
11
  * Structured failure from `loadModule` when both ESM import and CJS require fail.
10
12
  * Carries each error separately so callers can report them independently.
@@ -38,10 +40,10 @@ function setupWindowGlobals(): void {
38
40
  * Attempts to load a module, first via ESM `import()`, then via CJS `require`.
39
41
  *
40
42
  * ESM `import()` is preferred because it participates in the ESM loader hook
41
- * pipeline (registered via `module.register()` in the CLI). This is required
42
- * for JSX interception to work — the loader hook redirects `react/jsx-runtime`
43
- * to the interceptable version. CJS `require()` bypasses ESM hooks even when
44
- * loading ESM modules (Node 22+), so it's only used as a fallback.
43
+ * pipeline. The hook is registered here before the import, redirecting
44
+ * `react/jsx-runtime` to the interceptable version. CJS `require()` bypasses
45
+ * ESM hooks even when loading ESM modules (Node 22+), so it's only used as a
46
+ * fallback.
45
47
  *
46
48
  * @param entryPath - Absolute path to the module entry point.
47
49
  * @returns A `ResultAsync` containing the module exports on success.
@@ -53,6 +55,8 @@ export function loadModule(entryPath: string): ResultAsync<Record<string, unknow
53
55
  }
54
56
 
55
57
  setupWindowGlobals()
58
+ registerJsxLoaderHook()
59
+
56
60
  return ResultAsync.fromPromise(
57
61
  import(entryPath) as Promise<Record<string, unknown>>,
58
62
  (esmErr): Error => (esmErr instanceof Error ? esmErr : new Error(String(esmErr))),
package/vite.config.ts CHANGED
@@ -27,8 +27,6 @@ export default defineConfig(({ mode }) => ({
27
27
  lib: {
28
28
  entry: {
29
29
  index: resolve(__dirname, 'src/index.ts'),
30
- 'jsx-runtime-interceptor': resolve(__dirname, 'src/jsx-runtime-interceptor.ts'),
31
- 'jsx-runtime-loader': resolve(__dirname, 'src/jsx-runtime-loader.ts'),
32
30
  },
33
31
  formats: ['es'],
34
32
  },
@@ -52,11 +50,6 @@ export default defineConfig(({ mode }) => ({
52
50
  resolve: {
53
51
  // Ensure Node.js-specific export conditions are used when bundling for Node
54
52
  conditions: ['node', 'import', 'module', 'default'],
55
- alias: {
56
- // Alias jsx-runtime to our interceptable version for tests
57
- 'react/jsx-runtime': resolve(__dirname, 'src/jsx-runtime-interceptor.ts'),
58
- 'react/jsx-dev-runtime': resolve(__dirname, 'src/jsx-runtime-interceptor.ts'),
59
- },
60
53
  },
61
54
  test: {
62
55
  globals: true,
@@ -1,58 +0,0 @@
1
- import { ExoticComponent } from 'react';
2
- import { JSXElementConstructor } from 'react';
3
- import { ReactElement } from 'react';
4
- import { reactJsxDevRuntime } from 'react/jsx-dev-runtime';
5
- import { reactJsxRuntime } from 'react/jsx-runtime';
6
- import { ReactNode } from 'react';
7
-
8
- export declare const Fragment: ExoticComponent< {
9
- children?: ReactNode | undefined;
10
- }>;
11
-
12
- /**
13
- * Gets the original jsx/jsxs/jsxDEV functions.
14
- */
15
- export declare function getOriginals(): {
16
- jsx: jsx;
17
- jsxs: jsxs;
18
- jsxDEV: jsxDEV;
19
- };
20
-
21
- export declare function jsx(type: Parameters<typeof originalRuntime.jsx>[0], props: Parameters<typeof originalRuntime.jsx>[1], key?: Parameters<typeof originalRuntime.jsx>[2]): ReactElement<any, string | JSXElementConstructor<any>>;
22
-
23
- export declare function jsxDEV(type: Parameters<JsxDevFn>[0], props: Parameters<JsxDevFn>[1], key: Parameters<JsxDevFn>[2], isStaticChildren: Parameters<JsxDevFn>[3], source: Parameters<JsxDevFn>[4], self: Parameters<JsxDevFn>[5]): ReactElement<any, string | JSXElementConstructor<any>>;
24
-
25
- declare type JsxDevFn = typeof originalDevRuntime.jsxDEV;
26
-
27
- export declare function jsxs(type: Parameters<typeof originalRuntime.jsxs>[0], props: Parameters<typeof originalRuntime.jsxs>[1], key?: Parameters<typeof originalRuntime.jsxs>[2]): ReactElement<any, string | JSXElementConstructor<any>>;
28
-
29
- declare const originalDevRuntime: reactJsxDevRuntime;
30
-
31
- /**
32
- * Interceptable JSX Runtime
33
- *
34
- * This module wraps React's jsx-runtime and allows dynamic interception
35
- * of jsx/jsxs/jsxDEV calls. This is necessary because ESM imports are immutable,
36
- * so we can't monkey-patch react/jsx-runtime directly.
37
- *
38
- * ## Usage contexts
39
- *
40
- * This module runs in two contexts that must share mutable state:
41
- *
42
- * 1. **Vite alias (tests)** — aliased to `react/jsx-runtime` in vite.config.ts
43
- * 2. **Node.js ESM loader hook (CLI)** — redirected via `module.register()` in cli.ts
44
- *
45
- * In both cases the interceptor may be loaded as a separate module instance from
46
- * the copy bundled into the main dist files. To ensure `setJsxInterceptors()`
47
- * (called from component-renderer.ts) affects the `jsx`/`jsxs` that user
48
- * components call, mutable state lives on `globalThis` via `Symbol.for()`.
49
- */
50
- declare const originalRuntime: reactJsxRuntime;
51
-
52
- /**
53
- * Sets custom jsx/jsxs/jsxDEV implementations for interception.
54
- * Call with no arguments to restore originals.
55
- */
56
- export declare function setJsxInterceptors(jsx?: typeof originalRuntime.jsx, jsxs?: typeof originalRuntime.jsxs, jsxDEV?: JsxDevFn): void;
57
-
58
- export { }
@@ -1,63 +0,0 @@
1
- import { createRequire as j } from "node:module";
2
- const o = j(import.meta.url), t = o("react/jsx-runtime"), u = o("react/jsx-dev-runtime"), f = t.Fragment, a = /* @__PURE__ */ Symbol.for("zero-config:jsx-interceptor");
3
- function x() {
4
- const n = globalThis;
5
- return n[a] || (n[a] = {
6
- currentJsx: t.jsx,
7
- currentJsxs: t.jsxs,
8
- currentJsxDEV: u.jsxDEV,
9
- isInsideOriginal: !1
10
- }), n[a];
11
- }
12
- function m(n, r, i) {
13
- const s = x();
14
- s.currentJsx = n ?? t.jsx, s.currentJsxs = r ?? t.jsxs, s.currentJsxDEV = i ?? u.jsxDEV;
15
- }
16
- function E() {
17
- return {
18
- jsx: t.jsx,
19
- jsxs: t.jsxs,
20
- jsxDEV: u.jsxDEV
21
- };
22
- }
23
- function I(n, r, i) {
24
- const s = x();
25
- if (s.isInsideOriginal)
26
- return t.jsx(n, r, i);
27
- s.isInsideOriginal = !0;
28
- try {
29
- return s.currentJsx(n, r, i);
30
- } finally {
31
- s.isInsideOriginal = !1;
32
- }
33
- }
34
- function O(n, r, i) {
35
- const s = x();
36
- if (s.isInsideOriginal)
37
- return t.jsxs(n, r, i);
38
- s.isInsideOriginal = !0;
39
- try {
40
- return s.currentJsxs(n, r, i);
41
- } finally {
42
- s.isInsideOriginal = !1;
43
- }
44
- }
45
- function d(n, r, i, s, c, l) {
46
- const e = x();
47
- if (e.isInsideOriginal)
48
- return u.jsxDEV(n, r, i, s, c, l);
49
- e.isInsideOriginal = !0;
50
- try {
51
- return e.currentJsxDEV(n, r, i, s, c, l);
52
- } finally {
53
- e.isInsideOriginal = !1;
54
- }
55
- }
56
- export {
57
- f as Fragment,
58
- E as getOriginals,
59
- I as jsx,
60
- d as jsxDEV,
61
- O as jsxs,
62
- m as setJsxInterceptors
63
- };
@@ -1,27 +0,0 @@
1
- declare type NextResolve = (specifier: string, context: ResolveContext) => Promise<ResolveResult>;
2
-
3
- export declare function resolve(specifier: string, context: ResolveContext, nextResolve: NextResolve): Promise<ResolveResult>;
4
-
5
- declare interface ResolveContext {
6
- parentURL?: string;
7
- }
8
-
9
- /**
10
- * Node.js ESM Customization Hook
11
- *
12
- * Redirects ESM `import 'react/jsx-runtime'` and `import 'react/jsx-dev-runtime'`
13
- * to the interceptable jsx-runtime-interceptor module.
14
- *
15
- * Registered via `module.register()` in the CLI before loading user components.
16
- * This ensures that dynamically imported user components use the interceptable
17
- * jsx-runtime, enabling prop tracking and element tracing.
18
- *
19
- * CJS `require()` calls are not affected by this hook — the interceptor uses
20
- * `createRequire` internally, so it always loads the real React runtime.
21
- */
22
- declare interface ResolveResult {
23
- url: string;
24
- shortCircuit?: boolean;
25
- }
26
-
27
- export { }
@@ -1,7 +0,0 @@
1
- const n = import.meta.url.replace(/jsx-runtime-loader\.js$/, "jsx-runtime-interceptor.js");
2
- async function u(r, t, e) {
3
- return r === "react/jsx-runtime" || r === "react/jsx-dev-runtime" ? { shortCircuit: !0, url: n } : e(r, t);
4
- }
5
- export {
6
- u as resolve
7
- };
@@ -1,38 +0,0 @@
1
- /**
2
- * Node.js ESM Customization Hook
3
- *
4
- * Redirects ESM `import 'react/jsx-runtime'` and `import 'react/jsx-dev-runtime'`
5
- * to the interceptable jsx-runtime-interceptor module.
6
- *
7
- * Registered via `module.register()` in the CLI before loading user components.
8
- * This ensures that dynamically imported user components use the interceptable
9
- * jsx-runtime, enabling prop tracking and element tracing.
10
- *
11
- * CJS `require()` calls are not affected by this hook — the interceptor uses
12
- * `createRequire` internally, so it always loads the real React runtime.
13
- */
14
-
15
- // Compute sibling file URL at runtime — avoids Vite's static `new URL()` asset transform
16
- const INTERCEPTOR_URL = import.meta.url.replace(/jsx-runtime-loader\.js$/, 'jsx-runtime-interceptor.js')
17
-
18
- interface ResolveResult {
19
- url: string
20
- shortCircuit?: boolean
21
- }
22
-
23
- interface ResolveContext {
24
- parentURL?: string
25
- }
26
-
27
- type NextResolve = (specifier: string, context: ResolveContext) => Promise<ResolveResult>
28
-
29
- export async function resolve(
30
- specifier: string,
31
- context: ResolveContext,
32
- nextResolve: NextResolve,
33
- ): Promise<ResolveResult> {
34
- if (specifier === 'react/jsx-runtime' || specifier === 'react/jsx-dev-runtime') {
35
- return { shortCircuit: true, url: INTERCEPTOR_URL }
36
- }
37
- return nextResolve(specifier, context)
38
- }