@ecopages/react 0.2.0-alpha.3 → 0.2.0-alpha.31
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/CHANGELOG.md +27 -39
- package/README.md +161 -18
- package/package.json +6 -6
- package/src/react-hmr-strategy.d.ts +42 -32
- package/src/react-hmr-strategy.js +99 -123
- package/src/react-renderer.d.ts +168 -41
- package/src/react-renderer.js +466 -163
- package/src/react.constants.d.ts +1 -0
- package/src/react.constants.js +4 -0
- package/src/react.plugin.d.ts +38 -111
- package/src/react.plugin.js +132 -61
- package/src/react.types.d.ts +88 -0
- package/src/react.types.js +0 -0
- package/src/router-adapter.d.ts +7 -14
- package/src/services/react-bundle.service.d.ts +15 -26
- package/src/services/react-bundle.service.js +44 -92
- package/src/services/react-hmr-page-metadata-cache.d.ts +9 -0
- package/src/services/react-hmr-page-metadata-cache.js +18 -2
- package/src/services/react-hydration-asset.service.d.ts +17 -18
- package/src/services/react-hydration-asset.service.js +59 -65
- package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
- package/src/services/react-mdx-config-dependency.service.js +122 -0
- package/src/services/react-page-module.service.d.ts +10 -2
- package/src/services/react-page-module.service.js +44 -37
- package/src/services/react-page-payload.service.d.ts +46 -0
- package/src/services/react-page-payload.service.js +67 -0
- package/src/services/react-runtime-bundle.service.d.ts +15 -13
- package/src/services/react-runtime-bundle.service.js +103 -180
- package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
- package/src/utils/client-graph-boundary-plugin.js +149 -11
- package/src/utils/component-config-traversal.d.ts +36 -0
- package/src/utils/component-config-traversal.js +54 -0
- package/src/utils/declared-modules.d.ts +1 -1
- package/src/utils/declared-modules.js +7 -16
- package/src/utils/dynamic.test.browser.d.ts +1 -0
- package/src/utils/dynamic.test.browser.js +33 -0
- package/src/utils/hydration-scripts.d.ts +25 -6
- package/src/utils/hydration-scripts.js +150 -44
- package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
- package/src/utils/hydration-scripts.test.browser.js +198 -0
- package/src/utils/reachability-analyzer.d.ts +12 -1
- package/src/utils/reachability-analyzer.js +101 -5
- package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
- package/src/utils/react-dom-runtime-interop-plugin.js +29 -0
- package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
- package/src/utils/react-mdx-loader-plugin.js +13 -5
- package/src/utils/react-runtime-alias-map.d.ts +6 -0
- package/src/utils/react-runtime-alias-map.js +33 -0
- package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
- package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
- package/src/react-hmr-strategy.ts +0 -444
- package/src/react-renderer.ts +0 -403
- package/src/react.plugin.ts +0 -241
- package/src/router-adapter.ts +0 -95
- package/src/services/react-bundle.service.ts +0 -212
- package/src/services/react-hmr-page-metadata-cache.ts +0 -24
- package/src/services/react-hydration-asset.service.ts +0 -260
- package/src/services/react-page-module.service.ts +0 -214
- package/src/services/react-runtime-bundle.service.ts +0 -271
- package/src/utils/client-graph-boundary-plugin.ts +0 -590
- package/src/utils/client-only.ts +0 -27
- package/src/utils/declared-modules.ts +0 -99
- package/src/utils/dynamic.ts +0 -27
- package/src/utils/hmr-scripts.ts +0 -47
- package/src/utils/html-boundary.ts +0 -66
- package/src/utils/hydration-scripts.ts +0 -338
- package/src/utils/reachability-analyzer.ts +0 -440
- package/src/utils/react-mdx-loader-plugin.ts +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -6,57 +6,45 @@ All notable changes to `@ecopages/react` are documented here.
|
|
|
6
6
|
|
|
7
7
|
## [UNRELEASED] — TBD
|
|
8
8
|
|
|
9
|
-
###
|
|
10
|
-
|
|
11
|
-
#### Render Reachability Analysis
|
|
12
|
-
|
|
13
|
-
- **Client render graph (Phase 1)** — Introduces a static reachability analysis step that builds an explicit graph of which components are rendered client-side (`cdfbd69e`).
|
|
14
|
-
- **OXC-powered reachability analyzer** — `reachability-analyzer.ts` uses OXC to parse and walk component ASTs, building a `ClientRenderGraph` that maps exported components to their client-side reach (`5412df6b`).
|
|
15
|
-
- **Explicit client graph boundaries** — Components must now declare explicit boundaries; the analyser enforces these to prevent over-hydration (`2912d6bd`).
|
|
16
|
-
- **Declared modules utility** — `declared-modules.ts` tracks which modules are declared as client boundaries.
|
|
17
|
-
|
|
18
|
-
#### Service Architecture Refactor
|
|
19
|
-
|
|
20
|
-
- **`ReactRuntimeBundleService`** — Manages runtime assets and specifier mapping for the React integration (`cfd3cb05`).
|
|
21
|
-
- **`ReactHydrationAssetService`** — Creates and manages hydration assets for client-side rendering (`cfd3cb05`).
|
|
22
|
-
- **`ReactBundleService`** — Handles esbuild bundle configuration for React components (`cfd3cb05`).
|
|
23
|
-
- **`ReactPageModuleService`** — Loads and compiles MDX/TSX page modules, including config resolution (`cfd3cb05`).
|
|
24
|
-
- The integration no longer builds a monolithic renderer — each concern is handled by a focused service.
|
|
25
|
-
|
|
26
|
-
#### HMR Improvements
|
|
9
|
+
### Bug Fixes
|
|
27
10
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
11
|
+
- Fixed router-managed React HMR page entries to reload the active route with a cleared persisted-layout cache so shared layout edits apply while the current page stays mounted.
|
|
12
|
+
- Fixed router-managed React HMR handlers to forward the active page HMR entry when reloading the current route through React Router.
|
|
13
|
+
- Fixed production React route hydration bundles to inline React runtime dependencies and import the router through the emitted page browser graph instead of a published import-map key.
|
|
14
|
+
- Removed the redundant React page props bootstrap script so route hydration relies on the canonical `__ECO_PAGE_DATA__` payload.
|
|
15
|
+
- Fixed React hydration, Fast Refresh, module loading, doctype handling, island asset reuse, and mixed-renderer foreign-subtree resolution across Bun, Vite, and Nitro flows.
|
|
16
|
+
- Restored direct `ReactPlugin` construction so the exported class still accepts the public plugin options shape.
|
|
17
|
+
- Fixed React foreign-subtree payload compatibility coverage and removed the plugin/renderer integration-name import cycle.
|
|
31
18
|
|
|
32
|
-
|
|
19
|
+
### Features
|
|
33
20
|
|
|
34
|
-
-
|
|
35
|
-
- **`hydration-scripts.ts`** — Expanded with new helpers for generating and injecting hydration entry scripts.
|
|
21
|
+
- Added built-in React MDX support and reachability-based hydration analysis for React page bundles.
|
|
36
22
|
|
|
37
23
|
### Refactoring
|
|
38
24
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
25
|
+
- Collapsed React route hydration into one page-owned entry module that re-exports the page component and bundles runtime dependencies in production.
|
|
26
|
+
- Removed the router adapter `importMapKey` contract so both development and production route hydration follow the router bundle import path instead of split import-map and bundle-path models.
|
|
27
|
+
- Replaced the positional `ReactHmrStrategy` constructor with an options object so React HMR wiring can evolve without argument-order churn.
|
|
28
|
+
- Renamed the remaining React runtime alias internals away from `specifierMap` terminology now that import-map-era core seams are gone.
|
|
29
|
+
- Consolidated React bundling, hydration, and runtime state behind shared service boundaries and `window.__ECO_PAGES__`.
|
|
30
|
+
- Moved React plugin option/default resolution into the factory and replaced renderer static config with instance-owned runtime wiring.
|
|
31
|
+
- Extracted React page-payload and locals serialization into a dedicated service to keep the renderer focused on orchestration.
|
|
32
|
+
- Centralized recursive React component-config traversal so module discovery and MDX SSR-lazy asset collection no longer reimplement graph walking.
|
|
33
|
+
- Moved MDX config dependency resolution out of the renderer into a dedicated React service.
|
|
34
|
+
- Collected shared React plugin and renderer config types into a dedicated module while keeping renderer-local runtime types close to implementation.
|
|
43
35
|
|
|
44
|
-
###
|
|
36
|
+
### Tests
|
|
45
37
|
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
38
|
+
- Added Vitest browser coverage for the React `dynamic()` utility using React Testing Library.
|
|
39
|
+
- Added browser execution coverage for the generated React hydration bootstrap, including router ownership registration and page-root cleanup.
|
|
40
|
+
- Added renderer-level coverage for the foreign-subtree payload compatibility contract, including non-attachable fragment roots.
|
|
49
41
|
|
|
50
|
-
###
|
|
42
|
+
### Documentation
|
|
51
43
|
|
|
52
|
-
-
|
|
53
|
-
- Added `hydration-scripts.test.ts` with hydration script generation coverage.
|
|
54
|
-
- Added `reachability-analyzer.test.ts` (187 lines) covering export declaration reachability.
|
|
55
|
-
- Updated integration tests for esbuild adapter compatibility (`31a44458`).
|
|
44
|
+
- Updated the README to document React-owned mixed boundaries and React MDX setup.
|
|
56
45
|
|
|
57
46
|
---
|
|
58
47
|
|
|
59
48
|
## Migration Notes
|
|
60
49
|
|
|
61
|
-
-
|
|
62
|
-
- The internal service layer (`ReactRuntimeBundleService`, `ReactBundleService`, etc.) is not part of the public API and may change between releases.
|
|
50
|
+
- React MDX support is built in and no longer requires installing `@ecopages/mdx` just to enable React MDX routes.
|
package/README.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ecopages/react
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
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,17 +61,148 @@ const config = await new ConfigBuilder()
|
|
|
49
61
|
export default config;
|
|
50
62
|
```
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
## Mixed Rendering
|
|
53
65
|
|
|
54
|
-
|
|
66
|
+
The React integration can participate in mixed-renderer apps in three ways:
|
|
67
|
+
|
|
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.
|
|
71
|
+
|
|
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.
|
|
73
|
+
|
|
74
|
+
Important:
|
|
75
|
+
|
|
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.
|
|
79
|
+
|
|
80
|
+
## Server and Client Graph Contract
|
|
81
|
+
|
|
82
|
+
The React integration supports Node.js modules and server-only code **only on the server execution graph**.
|
|
83
|
+
|
|
84
|
+
- Server rendering can safely import `node:*` modules, database clients, filesystem utilities, etc.
|
|
85
|
+
- Client-hydrated React code must resolve to browser-safe modules only.
|
|
86
|
+
- If a server-only import crosses the boundary and becomes reachable by client code, **the client build will intentionally fail**.
|
|
87
|
+
|
|
88
|
+
Keep server helpers close, but separate them physically or logically so they do not leak into the client bundle.
|
|
89
|
+
|
|
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
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
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.
|
|
55
198
|
|
|
56
|
-
|
|
199
|
+
### Tests That Guard This Contract
|
|
57
200
|
|
|
58
|
-
|
|
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.
|
|
201
|
+
The main regression coverage lives in:
|
|
62
202
|
|
|
63
|
-
|
|
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.
|
|
64
207
|
|
|
65
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.0-alpha.31",
|
|
4
4
|
"description": "React integration for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -53,19 +53,19 @@
|
|
|
53
53
|
"directory": "packages/integrations/react"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@ecopages/core": "0.2.0-alpha.
|
|
56
|
+
"@ecopages/core": "0.2.0-alpha.31",
|
|
57
57
|
"@types/react": "^19",
|
|
58
58
|
"@types/react-dom": "^19",
|
|
59
59
|
"react": "^19",
|
|
60
60
|
"react-dom": "^19"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
64
|
-
"@ecopages/logger": "
|
|
63
|
+
"@ecopages/file-system": "0.2.0-alpha.31",
|
|
64
|
+
"@ecopages/logger": "^0.2.3",
|
|
65
65
|
"@mdx-js/esbuild": "^3.0.1",
|
|
66
66
|
"@mdx-js/mdx": "^3.1.0",
|
|
67
|
-
"oxc-parser": "^0.
|
|
68
|
-
"oxc-transform": "^0.
|
|
67
|
+
"oxc-parser": "^0.124.0",
|
|
68
|
+
"oxc-transform": "^0.124.0",
|
|
69
69
|
"source-map": "^0.7.6",
|
|
70
70
|
"vfile": "^6.0.3"
|
|
71
71
|
},
|
|
@@ -6,10 +6,19 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import { HmrStrategy,
|
|
9
|
+
import { HmrStrategy, type HmrAction } from '@ecopages/core/hmr/hmr-strategy';
|
|
10
10
|
import type { DefaultHmrContext } from '@ecopages/core';
|
|
11
11
|
import type { CompileOptions } from '@mdx-js/mdx';
|
|
12
12
|
import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metadata-cache.js';
|
|
13
|
+
export interface ReactHmrStrategyOptions {
|
|
14
|
+
context: DefaultHmrContext;
|
|
15
|
+
pageMetadataCache: ReactHmrPageMetadataCache;
|
|
16
|
+
runtimeAliasMap: ReadonlyMap<string, string>;
|
|
17
|
+
mdxCompilerOptions?: CompileOptions;
|
|
18
|
+
ownedTemplateExtensions?: string[];
|
|
19
|
+
allTemplateExtensions?: string[];
|
|
20
|
+
explicitGraphEnabled?: boolean;
|
|
21
|
+
}
|
|
13
22
|
/**
|
|
14
23
|
* Strategy for handling React component HMR updates.
|
|
15
24
|
*
|
|
@@ -19,9 +28,11 @@ import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metada
|
|
|
19
28
|
* The processing steps are:
|
|
20
29
|
* 1. Check if any React entrypoints are registered
|
|
21
30
|
* 2. Rebuild all React entrypoints (the changed file could be a dependency)
|
|
22
|
-
* 3.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
31
|
+
* 3. Rebuild browser output through the shared browser bundle service while
|
|
32
|
+
* preserving React-specific runtime aliases and graph policy
|
|
33
|
+
* 4. Read page config metadata through the shared server-module loading path
|
|
34
|
+
* 5. Inject HMR acceptance handler
|
|
35
|
+
* 6. Broadcast update events for each rebuilt entrypoint
|
|
25
36
|
*
|
|
26
37
|
* @remarks
|
|
27
38
|
* This strategy has higher priority than generic JsHmrStrategy, allowing it
|
|
@@ -37,38 +48,34 @@ import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metada
|
|
|
37
48
|
* ```typescript
|
|
38
49
|
* const context = {
|
|
39
50
|
* getWatchedFiles: () => watchedFilesMap,
|
|
40
|
-
* getSpecifierMap: () => specifierMap,
|
|
41
51
|
* getDistDir: () => '/path/to/dist/_hmr',
|
|
42
52
|
* getPlugins: () => [],
|
|
43
53
|
* getSrcDir: () => '/path/to/src',
|
|
44
54
|
* getLayoutsDir: () => '/path/to/src/layouts'
|
|
45
55
|
* };
|
|
46
|
-
* const strategy = new ReactHmrStrategy(
|
|
56
|
+
* const strategy = new ReactHmrStrategy({
|
|
57
|
+
* context,
|
|
58
|
+
* pageMetadataCache,
|
|
59
|
+
* runtimeAliasMap
|
|
60
|
+
* });
|
|
47
61
|
* ```
|
|
48
62
|
*/
|
|
49
63
|
export declare class ReactHmrStrategy extends HmrStrategy {
|
|
50
|
-
|
|
51
|
-
private pageMetadataCache;
|
|
52
|
-
private explicitGraphEnabled;
|
|
53
|
-
readonly type = HmrStrategyType.INTEGRATION;
|
|
64
|
+
readonly type: 100;
|
|
54
65
|
private mdxCompilerOptions?;
|
|
55
|
-
private readonly
|
|
66
|
+
private readonly ownedTemplateExtensions;
|
|
67
|
+
private readonly allTemplateExtensions;
|
|
56
68
|
private importNodePageModule;
|
|
57
|
-
private createUseSyncExternalStoreShimPlugin;
|
|
58
69
|
/**
|
|
59
70
|
* Creates a new React HMR strategy instance.
|
|
60
71
|
*
|
|
61
|
-
* @param
|
|
62
|
-
* and the layouts directory for detecting layout file changes that require full
|
|
63
|
-
* page reloads instead of module-level HMR updates.
|
|
64
|
-
* @param pageMetadataCache - React-only cache of declared browser modules discovered during
|
|
65
|
-
* server rendering. This avoids re-importing unchanged page modules
|
|
66
|
-
* during save-time Fast Refresh rebuilds.
|
|
67
|
-
* @param mdxCompilerOptions - Optional MDX compiler options for processing .mdx files
|
|
68
|
-
* @param explicitGraphEnabled - Enables explicit graph mode for React HMR bundling.
|
|
69
|
-
* In explicit mode, HMR builds omit AST server-only stripping plugins in React paths.
|
|
72
|
+
* @param options - React HMR runtime services and behavior flags.
|
|
70
73
|
*/
|
|
71
|
-
|
|
74
|
+
private context;
|
|
75
|
+
private pageMetadataCache;
|
|
76
|
+
private explicitGraphEnabled;
|
|
77
|
+
private readonly runtimeAliasMap;
|
|
78
|
+
constructor(options: ReactHmrStrategyOptions);
|
|
72
79
|
/**
|
|
73
80
|
* Returns build plugins for React HMR bundling.
|
|
74
81
|
*
|
|
@@ -77,6 +84,17 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
77
84
|
*/
|
|
78
85
|
private getBuildPlugins;
|
|
79
86
|
private isReactEntrypoint;
|
|
87
|
+
/**
|
|
88
|
+
* Returns true when a route file uses a compound extension like `page.foo.tsx`.
|
|
89
|
+
*
|
|
90
|
+
* @remarks
|
|
91
|
+
* React integration owns plain `.tsx` route templates. Compound extensions in
|
|
92
|
+
* pages/layouts are integration-specific route templates and should not be
|
|
93
|
+
* claimed by React HMR strategy.
|
|
94
|
+
*/
|
|
95
|
+
private isRouteTemplate;
|
|
96
|
+
private resolveTemplateExtension;
|
|
97
|
+
private ownsWatchedEntrypoint;
|
|
80
98
|
/**
|
|
81
99
|
* Determines if the file is a React/MDX entrypoint that's registered for HMR.
|
|
82
100
|
*
|
|
@@ -116,13 +134,14 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
116
134
|
* @returns True if bundling was successful
|
|
117
135
|
*/
|
|
118
136
|
private bundleReactEntrypoint;
|
|
137
|
+
private resolveTempOutputPath;
|
|
119
138
|
/**
|
|
120
139
|
* Encodes dynamic route segments (brackets) in file paths.
|
|
121
140
|
* Converts `[slug]` to `_slug_` to avoid filesystem issues.
|
|
122
141
|
*/
|
|
123
142
|
private encodeDynamicSegments;
|
|
124
143
|
/**
|
|
125
|
-
* Processes bundled output
|
|
144
|
+
* Processes bundled output and injects the React HMR handler.
|
|
126
145
|
* Writes to temp file first, then renames atomically to avoid conflicts.
|
|
127
146
|
*
|
|
128
147
|
* @param tempPath - Path to the temporary bundled file
|
|
@@ -131,13 +150,4 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
131
150
|
* @returns True if processing was successful
|
|
132
151
|
*/
|
|
133
152
|
private processOutput;
|
|
134
|
-
/**
|
|
135
|
-
* Replaces bare specifiers with runtime URLs.
|
|
136
|
-
*
|
|
137
|
-
* Handles both static imports and dynamic imports.
|
|
138
|
-
*
|
|
139
|
-
* @param code - The bundled code to transform
|
|
140
|
-
* @returns The transformed code with runtime URLs
|
|
141
|
-
*/
|
|
142
|
-
private replaceBareSpecifiers;
|
|
143
153
|
}
|