@ecopages/react 0.2.0-alpha.5 → 0.2.0-alpha.51

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.
Files changed (68) hide show
  1. package/README.md +152 -29
  2. package/package.json +24 -12
  3. package/src/eco-embed.d.ts +11 -0
  4. package/src/eco-embed.js +11 -0
  5. package/src/react-hmr-strategy.d.ts +65 -43
  6. package/src/react-hmr-strategy.js +298 -145
  7. package/src/react-renderer.d.ts +169 -42
  8. package/src/react-renderer.js +484 -164
  9. package/src/react.constants.d.ts +1 -0
  10. package/src/react.constants.js +4 -0
  11. package/src/react.plugin.d.ts +40 -111
  12. package/src/react.plugin.js +136 -61
  13. package/src/react.types.d.ts +88 -0
  14. package/src/react.types.js +0 -0
  15. package/src/router-adapter.d.ts +7 -14
  16. package/src/runtime/use-sync-external-store-with-selector.d.ts +3 -0
  17. package/src/runtime/use-sync-external-store-with-selector.js +56 -0
  18. package/src/services/react-bundle.service.d.ts +22 -35
  19. package/src/services/react-bundle.service.js +41 -105
  20. package/src/services/react-hmr-page-metadata-cache.d.ts +9 -0
  21. package/src/services/react-hmr-page-metadata-cache.js +18 -2
  22. package/src/services/react-hydration-asset.service.d.ts +28 -19
  23. package/src/services/react-hydration-asset.service.js +85 -66
  24. package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
  25. package/src/services/react-mdx-config-dependency.service.js +122 -0
  26. package/src/services/react-page-module.service.d.ts +10 -2
  27. package/src/services/react-page-module.service.js +47 -39
  28. package/src/services/react-page-payload.service.d.ts +46 -0
  29. package/src/services/react-page-payload.service.js +67 -0
  30. package/src/services/react-runtime-bundle.service.d.ts +20 -13
  31. package/src/services/react-runtime-bundle.service.js +146 -179
  32. package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
  33. package/src/utils/client-graph-boundary-plugin.js +80 -3
  34. package/src/utils/component-config-traversal.d.ts +36 -0
  35. package/src/utils/component-config-traversal.js +54 -0
  36. package/src/utils/declared-modules.d.ts +1 -1
  37. package/src/utils/declared-modules.js +7 -16
  38. package/src/utils/dynamic.test.browser.d.ts +1 -0
  39. package/src/utils/dynamic.test.browser.js +33 -0
  40. package/src/utils/hydration-scripts.d.ts +27 -6
  41. package/src/utils/hydration-scripts.js +177 -44
  42. package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
  43. package/src/utils/hydration-scripts.test.browser.js +198 -0
  44. package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
  45. package/src/utils/react-dom-runtime-interop-plugin.js +38 -0
  46. package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
  47. package/src/utils/react-mdx-loader-plugin.js +13 -5
  48. package/src/utils/react-runtime-alias-map.d.ts +8 -0
  49. package/src/utils/react-runtime-alias-map.js +90 -0
  50. package/CHANGELOG.md +0 -67
  51. package/src/react-hmr-strategy.ts +0 -455
  52. package/src/react-renderer.ts +0 -403
  53. package/src/react.plugin.ts +0 -241
  54. package/src/router-adapter.ts +0 -95
  55. package/src/services/react-bundle.service.ts +0 -217
  56. package/src/services/react-hmr-page-metadata-cache.ts +0 -24
  57. package/src/services/react-hydration-asset.service.ts +0 -260
  58. package/src/services/react-page-module.service.ts +0 -214
  59. package/src/services/react-runtime-bundle.service.ts +0 -271
  60. package/src/utils/client-graph-boundary-plugin.ts +0 -710
  61. package/src/utils/client-only.ts +0 -27
  62. package/src/utils/declared-modules.ts +0 -99
  63. package/src/utils/dynamic.ts +0 -27
  64. package/src/utils/hmr-scripts.ts +0 -47
  65. package/src/utils/html-boundary.ts +0 -66
  66. package/src/utils/hydration-scripts.ts +0 -338
  67. package/src/utils/reachability-analyzer.ts +0 -593
  68. package/src/utils/react-mdx-loader-plugin.ts +0 -40
package/README.md CHANGED
@@ -1,19 +1,20 @@
1
- # Ecopages React Integration Plugin
1
+ # @ecopages/react
2
2
 
3
- The `@ecopages/react` package introduces first-class integration with [React](https://reactjs.org/) version 19, enabling developers to leverage React's robust ecosystem and component model within the Ecopages platform. This integration provides a seamless experience for using React components in your Ecopages projects, combining React's declarative UI library with the flexibility and simplicity of Ecopages.
3
+ First-class integration for [React 19](https://react.dev/) in Ecopages. This plugin enables React SSR and client hydration, allowing you to build component-level React islands or full React Single Page Applications (SPAs).
4
4
 
5
- ## Install
5
+ ## Installation
6
6
 
7
7
  ```bash
8
- bunx jsr add @ecopages/react
8
+ bun add @ecopages/react react react-dom
9
+ bun add -d @types/react @types/react-dom
9
10
  ```
10
11
 
11
12
  ## Usage
12
13
 
13
- To incorporate the React integration into your Ecopages project, configure your project as follows:
14
+ Configure the plugin in your `eco.config.ts`:
14
15
 
15
16
  ```ts
16
- import { ConfigBuilder } from '@ecopages/core';
17
+ import { ConfigBuilder } from '@ecopages/core/config-builder';
17
18
  import { reactPlugin } from '@ecopages/react';
18
19
 
19
20
  const config = await new ConfigBuilder()
@@ -24,16 +25,27 @@ const config = await new ConfigBuilder()
24
25
  export default config;
25
26
  ```
26
27
 
28
+ ## Component-Level Islands
29
+
30
+ For component-level islands, Ecopages React uses this contract:
31
+
32
+ - SSR output preserves the authored DOM structure (no unnecessary wrapper elements).
33
+ - A stable `data-eco-component-id` attribute is attached to the component SSR root.
34
+ - The island runtime replaces the SSR host with a dedicated client-owned container and mounts it with `createRoot()`. Full-page hydration paths use `hydrateRoot()`.
35
+
36
+ > [!TIP]
37
+ > **Full React SPA Routing:**
38
+ > If you are building full React pages and want client-side navigation (SPA), use [@ecopages/react-router](../../react-router/README.md) and pass it to the react plugin: `reactPlugin({ router: ecoRouter() })`.
39
+
27
40
  ## MDX Support
28
41
 
29
- The React plugin includes optional MDX support. When enabled, you can write `.mdx` pages alongside `.tsx` pages with unified client-side routing, hydration, and HMR.
42
+ The React plugin includes built-in MDX support. When enabled, you can write `.mdx` pages alongside `.tsx` pages with unified client-side routing, hydration, and HMR.
30
43
 
31
44
  ```ts
32
- import { ConfigBuilder } from '@ecopages/core';
45
+ import { ConfigBuilder } from '@ecopages/core/config-builder';
33
46
  import { reactPlugin } from '@ecopages/react';
34
47
 
35
48
  const config = await new ConfigBuilder()
36
- .setBaseUrl(import.meta.env.ECOPAGES_BASE_URL)
37
49
  .setIntegrations([
38
50
  reactPlugin({
39
51
  mdx: {
@@ -49,37 +61,148 @@ const config = await new ConfigBuilder()
49
61
  export default config;
50
62
  ```
51
63
 
52
- This approach is recommended when using a client-side router (e.g., `@ecopages/react-router`) as it ensures consistent navigation between TSX and MDX pages.
64
+ ## Mixed Rendering
53
65
 
54
- ## Component-Level Islands
66
+ The React integration can participate in mixed-renderer apps in three ways:
55
67
 
56
- Current behavior:
68
+ - React can own the page or view directly.
69
+ - React can render nested foreign subtrees inside pages owned by another integration.
70
+ - React can render through non-React page, layout, or document shells when those shell components return strings.
57
71
 
58
- - SSR output keeps the authored component DOM structure (no synthetic wrapper element).
59
- - A stable `data-eco-component-id` attribute is attached to the component SSR root when a single root element is available.
60
- - Client bootstrap resolves the component export and mounts with `createRoot()` into that root boundary.
61
- - Component assets are emitted through the shared dependency pipeline and deduplicated with other integrations.
72
+ When a non-React render pass reaches a React-owned foreign child, Ecopages hands that foreign subtree back to the React renderer. When React renders through a non-React shell, that shell must serialize to HTML so React can insert the result into the final response without escaping it.
62
73
 
63
- This design preserves global CSS/layout selectors while keeping runtime ownership isolated per island instance.
74
+ Important:
64
75
 
65
- For full React pages with client-side navigation, prefer [@ecopages/react-router](../react-router/README.md), where routing and hydration are handled by the React-specific runtime.
76
+ - Components that may render foreign children must declare those children in `config.dependencies.components`.
77
+ - Ecopages validates mixed-renderer ownership from declared dependencies during render preparation. It does not infer every foreign subtree from rendered HTML alone.
78
+ - React still keeps its own child transport and hydration rules for React-owned subtrees.
66
79
 
67
- ## Server And Client Graph Contract
80
+ ## Server and Client Graph Contract
68
81
 
69
- The React integration supports Node.js modules and server-only code, but only on the server execution graph.
82
+ The React integration supports Node.js modules and server-only code **only on the server execution graph**.
70
83
 
71
- - Server rendering can import and execute `node:*` modules, database clients, filesystem utilities, and `*.server.*` modules.
84
+ - Server rendering can safely import `node:*` modules, database clients, filesystem utilities, etc.
72
85
  - Client-hydrated React code must resolve to browser-safe modules only.
73
- - Shared files and barrel files are allowed when the exports that become client-reachable are browser-safe.
74
- - If a server-only import becomes client-reachable, the client build fails instead of silently replacing the import.
86
+ - If a server-only import crosses the boundary and becomes reachable by client code, **the client build will intentionally fail**.
75
87
 
76
- In practice, this means you can keep server helpers close to your React code, but the browser bundle boundary is strict:
88
+ Keep server helpers close, but separate them physically or logically so they do not leak into the client bundle.
77
89
 
78
- ```ts
79
- export { Button } from './button';
80
- export { db } from './db.server';
90
+ ## Client Graph Boundary Architecture
91
+
92
+ This section explains the internal contract used to keep the browser bundle minimal while preventing server-only code and request-only configuration from leaking into client output.
93
+
94
+ ### Goal
95
+
96
+ The React integration has two jobs that must hold at the same time:
97
+
98
+ - Produce a browser-safe bundle for hydrated pages and islands.
99
+ - Preserve enough page code for hydration to reconstruct the same React tree the server rendered.
100
+
101
+ That means the client bundle must keep client-safe render logic, but it must drop server-only imports and server-only `eco.page(...)` options such as middleware and build-time metadata.
102
+
103
+ ### Mental Model
104
+
105
+ Think about each React page as two related graphs:
106
+
107
+ 1. **Server graph**: everything needed to render the page on the server. This graph may include middleware, request locals, database access, filesystem access, and other server-only modules.
108
+ 2. **Client graph**: the smallest browser-safe subset needed to hydrate the rendered output in the browser.
109
+
110
+ The React integration builds the client graph conservatively. If a server-only module becomes reachable from the hydrated render path, the build should fail rather than silently shipping unsafe code.
111
+
112
+ ### What Stays and What Goes
113
+
114
+ The client bundle keeps:
115
+
116
+ - The page component render path.
117
+ - Client-safe component dependencies reachable from render.
118
+ - Layout wiring needed for hydration.
119
+ - Router runtime state needed by [@ecopages/react-router](../../react-router/README.md) when SPA mode is enabled.
120
+
121
+ The client bundle removes or excludes:
122
+
123
+ - Server-only imports that are not reachable from the hydrated render path.
124
+ - Server-only `eco.page(...)` options such as `cache`, `middleware`, `metadata`, `staticProps`, and `staticPaths`.
125
+ - Request-time configuration that has no meaning in the browser.
126
+
127
+ Important:
128
+
129
+ - `render` must stay in the client bundle, because hydration needs it to reconstruct the page tree.
130
+ - `requires` does **not** stay in the browser page config. It is used on the server to decide which `locals` keys may be serialized into the hydration payload.
131
+
132
+ ### AST Pipeline Order
133
+
134
+ The browser-bound transform in [src/utils/client-graph-boundary-plugin.ts](src/utils/client-graph-boundary-plugin.ts) follows this order:
135
+
136
+ 1. Parse the module and build a reachability view of the client render graph.
137
+ 2. Remove imports that are not allowed or not reachable from the client graph.
138
+ 3. Reparse the transformed source.
139
+ 4. Strip server-only `eco.page(...)` object properties from the reparsed AST.
140
+ 5. Return the rewritten source to the bundle step.
141
+
142
+ The reparse step is important. Once import edits change source offsets, the original AST locations are stale. Reusing them for later edits can corrupt the output or remove the wrong code.
143
+
144
+ ### Why `eco.page(...)` Options Are Stripped
145
+
146
+ Import pruning alone is not enough.
147
+
148
+ Consider a page like this:
149
+
150
+ ```tsx
151
+ import { authMiddleware } from './auth.server';
152
+
153
+ export default eco.page({
154
+ cache: 'dynamic',
155
+ middleware: [authMiddleware],
156
+ requires: ['session'] as const,
157
+ render: () => <div>Dashboard</div>,
158
+ });
81
159
  ```
82
160
 
83
- If a client entry only reaches `Button`, the `db` re-export is removed from the browser transform. If a client entry reaches `db`, the build fails because the server-only export crossed into the client graph.
161
+ If the client transform removes the `auth.server` import but leaves `middleware: [authMiddleware]` in place, the browser bundle still contains a dangling identifier. That breaks production hydration even though the import was removed correctly.
162
+
163
+ The fix is to strip server-only `eco.page(...)` options after import pruning, while keeping `render` intact.
164
+
165
+ ### Hydration Contract for `locals`
166
+
167
+ The browser must not receive arbitrary request-scoped data.
168
+
169
+ The React renderer in [src/react-renderer.ts](src/react-renderer.ts) serializes only the top-level `locals` keys explicitly declared by `Page.requires`. If a page does not declare `requires`, no `locals` are serialized for hydration.
170
+
171
+ Example:
172
+
173
+ ```tsx
174
+ export default eco.page({
175
+ requires: ['session'] as const,
176
+ render: ({ locals }) => <Dashboard user={locals?.session?.user} />,
177
+ });
178
+ ```
179
+
180
+ In this case, the hydration payload may include `locals.session`, but it will exclude unrelated request-only keys.
181
+
182
+ Important:
183
+
184
+ - This filtering is currently top-level only.
185
+ - If `locals.session` itself contains sensitive nested fields, those fields will still be serialized.
186
+ - Middleware should therefore expose a client-safe shape for any key declared in `requires`.
187
+
188
+ ### Layout Hydration Invariant
189
+
190
+ Hydration must rebuild the same tree the server rendered.
191
+
192
+ That applies to both:
193
+
194
+ - non-router hydration scripts in [src/utils/hydration-scripts.ts](src/utils/hydration-scripts.ts)
195
+ - router-backed hydration in [../../react-router/src/router.ts](../../react-router/src/router.ts)
196
+
197
+ If the page render receives `locals` on the server and the layout also depends on those values, the client must pass the same serialized `locals` into the layout during hydration. Otherwise React will detect a mismatch.
198
+
199
+ ### Tests That Guard This Contract
200
+
201
+ The main regression coverage lives in:
202
+
203
+ - [src/utils/client-graph-boundary-plugin.test.ts](src/utils/client-graph-boundary-plugin.test.ts): verifies server-only `eco.page(...)` options are stripped from browser bundles.
204
+ - [src/react-renderer.locals.test.ts](src/react-renderer.locals.test.ts): verifies only declared `requires` keys are serialized into hydration payloads.
205
+ - [src/utils/hydration-scripts.test.ts](src/utils/hydration-scripts.test.ts): verifies non-router hydration passes serialized `locals` into layouts.
206
+ - [../../react-router/test/hmr-reload.test.browser.ts](../../react-router/test/hmr-reload.test.browser.ts): verifies router-backed layout hydration receives `locals` with `persistLayouts` both enabled and disabled.
84
207
 
85
- This contract keeps SSR and server functions free to use Node.js while ensuring the final browser bundle contains no client-reachable server-only code.
208
+ If you change the AST transform or hydration flow, update the corresponding tests in the same change.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/react",
3
- "version": "0.2.0-alpha.5",
3
+ "version": "0.2.0-alpha.51",
4
4
  "description": "React integration for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -16,6 +16,10 @@
16
16
  "default": "./src/react.plugin.js",
17
17
  "types": "./src/react.plugin.d.ts"
18
18
  },
19
+ "./eco-embed": {
20
+ "default": "./src/eco-embed.js",
21
+ "types": "./src/eco-embed.d.ts"
22
+ },
19
23
  "./declarations": {
20
24
  "types": "./src/declarations.d.ts"
21
25
  },
@@ -27,10 +31,18 @@
27
31
  "types": "./src/utils/client-only.d.ts",
28
32
  "default": "./src/utils/client-only.js"
29
33
  },
34
+ "./runtime/use-sync-external-store-with-selector": {
35
+ "types": "./src/runtime/use-sync-external-store-with-selector.d.ts",
36
+ "default": "./src/runtime/use-sync-external-store-with-selector.js"
37
+ },
30
38
  "./router-adapter": {
31
39
  "types": "./src/router-adapter.d.ts",
32
40
  "default": "./src/router-adapter.js"
33
41
  },
42
+ "./eco-embed.ts": {
43
+ "default": "./src/eco-embed.js",
44
+ "types": "./src/eco-embed.d.ts"
45
+ },
34
46
  "./declarations.ts": {
35
47
  "types": "./src/declarations.d.ts"
36
48
  },
@@ -42,6 +54,10 @@
42
54
  "types": "./src/utils/client-only.d.ts",
43
55
  "default": "./src/utils/client-only.js"
44
56
  },
57
+ "./runtime/use-sync-external-store-with-selector.ts": {
58
+ "types": "./src/runtime/use-sync-external-store-with-selector.d.ts",
59
+ "default": "./src/runtime/use-sync-external-store-with-selector.js"
60
+ },
45
61
  "./router-adapter.ts": {
46
62
  "types": "./src/router-adapter.d.ts",
47
63
  "default": "./src/router-adapter.js"
@@ -53,24 +69,20 @@
53
69
  "directory": "packages/integrations/react"
54
70
  },
55
71
  "peerDependencies": {
56
- "@ecopages/core": "0.2.0-alpha.5",
72
+ "@ecopages/core": "0.2.0-alpha.51",
57
73
  "@types/react": "^19",
58
74
  "@types/react-dom": "^19",
59
75
  "react": "^19",
60
76
  "react-dom": "^19"
61
77
  },
62
78
  "dependencies": {
63
- "@ecopages/file-system": "0.2.0-alpha.5",
64
- "@ecopages/logger": "latest",
65
- "@mdx-js/esbuild": "^3.0.1",
66
- "@mdx-js/mdx": "^3.1.0",
67
- "oxc-parser": "^0.114.0",
68
- "oxc-transform": "^0.114.0",
79
+ "@ecopages/file-system": "0.2.0-alpha.51",
80
+ "@ecopages/logger": "^0.2.3",
81
+ "@mdx-js/esbuild": "^3.1.1",
82
+ "@mdx-js/mdx": "^3.1.1",
83
+ "oxc-parser": "^0.124.0",
84
+ "oxc-transform": "^0.124.0",
69
85
  "source-map": "^0.7.6",
70
86
  "vfile": "^6.0.3"
71
- },
72
- "overrides": {
73
- "react": "^19",
74
- "react-dom": "^19"
75
87
  }
76
88
  }
@@ -0,0 +1,11 @@
1
+ import type { EcoComponent, EcoEmbedProps as CoreEcoEmbedProps } from '@ecopages/core';
2
+ import type { ReactNode } from 'react';
3
+ /**
4
+ * Props for the React-owned `EcoEmbed` adapter.
5
+ */
6
+ export type EcoEmbedProps<TComponent extends EcoComponent> = CoreEcoEmbedProps<TComponent>;
7
+ /**
8
+ * Renders a foreign or same-integration eco component from a React JSX file
9
+ * without forcing inline mixed-JSX authoring.
10
+ */
11
+ export declare function EcoEmbed<TComponent extends EcoComponent>({ component, props, children, }: EcoEmbedProps<TComponent>): ReactNode;
@@ -0,0 +1,11 @@
1
+ import { eco } from "@ecopages/core";
2
+ function EcoEmbed({
3
+ component,
4
+ props,
5
+ children
6
+ }) {
7
+ return eco.embed(component, props, children);
8
+ }
9
+ export {
10
+ EcoEmbed
11
+ };
@@ -6,10 +6,20 @@
6
6
  *
7
7
  * @module
8
8
  */
9
- import { HmrStrategy, HmrStrategyType, type HmrAction } from '@ecopages/core/hmr/hmr-strategy';
9
+ import { HmrStrategy, type HmrAction } from '@ecopages/core/hmr/hmr-strategy';
10
+ import type { BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
10
11
  import type { DefaultHmrContext } from '@ecopages/core';
11
12
  import type { CompileOptions } from '@mdx-js/mdx';
12
13
  import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metadata-cache.js';
14
+ export interface ReactHmrStrategyOptions {
15
+ context: DefaultHmrContext;
16
+ pageMetadataCache: ReactHmrPageMetadataCache;
17
+ runtimeManifest: BrowserRuntimeManifest;
18
+ mdxCompilerOptions?: CompileOptions;
19
+ ownedTemplateExtensions?: string[];
20
+ allTemplateExtensions?: string[];
21
+ explicitGraphEnabled?: boolean;
22
+ }
13
23
  /**
14
24
  * Strategy for handling React component HMR updates.
15
25
  *
@@ -19,9 +29,11 @@ import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metada
19
29
  * The processing steps are:
20
30
  * 1. Check if any React entrypoints are registered
21
31
  * 2. Rebuild all React entrypoints (the changed file could be a dependency)
22
- * 3. Replace bare specifiers with runtime URLs
23
- * 4. Inject HMR acceptance handler
24
- * 5. Broadcast update events for each rebuilt entrypoint
32
+ * 3. Rebuild browser output through the shared browser bundle service while
33
+ * preserving React-specific runtime aliases and graph policy
34
+ * 4. Read page config metadata through the shared server-module loading path
35
+ * 5. Inject HMR acceptance handler
36
+ * 6. Broadcast update events for each rebuilt entrypoint
25
37
  *
26
38
  * @remarks
27
39
  * This strategy has higher priority than generic JsHmrStrategy, allowing it
@@ -37,57 +49,57 @@ import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metada
37
49
  * ```typescript
38
50
  * const context = {
39
51
  * getWatchedFiles: () => watchedFilesMap,
40
- * getSpecifierMap: () => specifierMap,
41
52
  * getDistDir: () => '/path/to/dist/_hmr',
42
53
  * getPlugins: () => [],
43
54
  * getSrcDir: () => '/path/to/src',
44
55
  * getLayoutsDir: () => '/path/to/src/layouts'
45
56
  * };
46
- * const strategy = new ReactHmrStrategy(context);
57
+ * const strategy = new ReactHmrStrategy({
58
+ * context,
59
+ * pageMetadataCache,
60
+ * runtimeManifest
61
+ * });
47
62
  * ```
48
63
  */
49
64
  export declare class ReactHmrStrategy extends HmrStrategy {
50
- private context;
51
- private pageMetadataCache;
52
- private explicitGraphEnabled;
53
- readonly type = HmrStrategyType.INTEGRATION;
65
+ readonly type: 100;
54
66
  private mdxCompilerOptions?;
55
- private readonly knownEntrypoints;
67
+ private readonly ownedTemplateExtensions;
68
+ private readonly allTemplateExtensions;
56
69
  private importNodePageModule;
57
- /**
58
- * Redirects `use-sync-external-store/shim` imports to React's built-in
59
- * `useSyncExternalStore`.
60
- *
61
- * Libraries like React Aria still list `use-sync-external-store` as a
62
- * dependency to support React 16/17. On React 18+ the `/shim` export is
63
- * already a pass-through, but without this plugin esbuild would bundle
64
- * the full CJS shim (including `process.env` branching) into the browser
65
- * bundle. The plugin short-circuits the resolution so only a single clean
66
- * ESM re-export is emitted.
67
- */
68
- private createUseSyncExternalStoreShimPlugin;
69
70
  /**
70
71
  * Creates a new React HMR strategy instance.
71
72
  *
72
- * @param context - The HMR context providing access to watched files, plugins, build directories,
73
- * and the layouts directory for detecting layout file changes that require full
74
- * page reloads instead of module-level HMR updates.
75
- * @param pageMetadataCache - React-only cache of declared browser modules discovered during
76
- * server rendering. This avoids re-importing unchanged page modules
77
- * during save-time Fast Refresh rebuilds.
78
- * @param mdxCompilerOptions - Optional MDX compiler options for processing .mdx files
79
- * @param explicitGraphEnabled - Enables explicit graph mode for React HMR bundling.
80
- * In explicit mode, HMR builds omit AST server-only stripping plugins in React paths.
73
+ * @param options - React HMR runtime services and behavior flags.
81
74
  */
82
- constructor(context: DefaultHmrContext, pageMetadataCache: ReactHmrPageMetadataCache, mdxCompilerOptions?: CompileOptions, explicitGraphEnabled?: boolean);
75
+ private context;
76
+ private pageMetadataCache;
77
+ private explicitGraphEnabled;
78
+ private readonly runtimeManifest;
79
+ constructor(options: ReactHmrStrategyOptions);
83
80
  /**
84
81
  * Returns build plugins for React HMR bundling.
85
82
  *
86
83
  * Includes the client graph boundary plugin to prevent undeclared imports
87
84
  * (including `node:*`) from breaking the browser bundle.
85
+ *
86
+ * @remarks
87
+ * HMR builds receive the React runtime manifest and rewrite manifest-owned
88
+ * runtime imports to concrete asset URLs before module resolution.
88
89
  */
89
90
  private getBuildPlugins;
90
91
  private isReactEntrypoint;
92
+ /**
93
+ * Returns true when a route file uses a compound extension like `page.foo.tsx`.
94
+ *
95
+ * @remarks
96
+ * React integration owns plain `.tsx` route templates. Compound extensions in
97
+ * pages/layouts are integration-specific route templates and should not be
98
+ * claimed by React HMR strategy.
99
+ */
100
+ private isRouteTemplate;
101
+ private resolveTemplateExtension;
102
+ private ownsWatchedEntrypoint;
91
103
  /**
92
104
  * Determines if the file is a React/MDX entrypoint that's registered for HMR.
93
105
  *
@@ -107,6 +119,21 @@ export declare class ReactHmrStrategy extends HmrStrategy {
107
119
  * @returns True if the file is in the layouts directory
108
120
  */
109
121
  private isLayoutFile;
122
+ private isPageEntrypoint;
123
+ private getEntrypointOutput;
124
+ private getGroupedTempOutputPattern;
125
+ private collectReactPageBuildTargets;
126
+ private getRequestedTargets;
127
+ /**
128
+ * Expands one HMR request into the full React page build cohort when needed.
129
+ *
130
+ * @remarks
131
+ * Page and layout changes need one shared rebuild pass so sibling routes keep
132
+ * a consistent client module graph. Non-page changes that do not touch a page
133
+ * cohort can stay scoped to the originally requested targets.
134
+ */
135
+ private resolveBuildTargets;
136
+ private partitionBuildTargets;
110
137
  /**
111
138
  * Processes a React file change by rebuilding all React entrypoints.
112
139
  *
@@ -127,13 +154,17 @@ export declare class ReactHmrStrategy extends HmrStrategy {
127
154
  * @returns True if bundling was successful
128
155
  */
129
156
  private bundleReactEntrypoint;
157
+ private bundleReactEntrypoints;
158
+ private resolveTempOutputPath;
130
159
  /**
131
160
  * Encodes dynamic route segments (brackets) in file paths.
132
161
  * Converts `[slug]` to `_slug_` to avoid filesystem issues.
133
162
  */
134
163
  private encodeDynamicSegments;
164
+ private rewriteChunkImportUrls;
165
+ private isMissingTempOutputError;
135
166
  /**
136
- * Processes bundled output by replacing specifiers and injecting HMR handler.
167
+ * Processes bundled output and injects the React HMR handler.
137
168
  * Writes to temp file first, then renames atomically to avoid conflicts.
138
169
  *
139
170
  * @param tempPath - Path to the temporary bundled file
@@ -142,13 +173,4 @@ export declare class ReactHmrStrategy extends HmrStrategy {
142
173
  * @returns True if processing was successful
143
174
  */
144
175
  private processOutput;
145
- /**
146
- * Replaces bare specifiers with runtime URLs.
147
- *
148
- * Handles both static imports and dynamic imports.
149
- *
150
- * @param code - The bundled code to transform
151
- * @returns The transformed code with runtime URLs
152
- */
153
- private replaceBareSpecifiers;
154
176
  }