@ecopages/react 0.2.0-alpha.1

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 (59) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/LICENSE +21 -0
  3. package/README.md +65 -0
  4. package/package.json +76 -0
  5. package/src/declarations.d.ts +6 -0
  6. package/src/react-hmr-strategy.d.ts +143 -0
  7. package/src/react-hmr-strategy.js +332 -0
  8. package/src/react-hmr-strategy.ts +444 -0
  9. package/src/react-renderer.d.ts +106 -0
  10. package/src/react-renderer.js +302 -0
  11. package/src/react-renderer.ts +403 -0
  12. package/src/react.plugin.d.ts +147 -0
  13. package/src/react.plugin.js +126 -0
  14. package/src/react.plugin.ts +241 -0
  15. package/src/router-adapter.d.ts +87 -0
  16. package/src/router-adapter.js +0 -0
  17. package/src/router-adapter.ts +95 -0
  18. package/src/services/react-bundle.service.d.ts +68 -0
  19. package/src/services/react-bundle.service.js +145 -0
  20. package/src/services/react-bundle.service.ts +212 -0
  21. package/src/services/react-hmr-page-metadata-cache.d.ts +17 -0
  22. package/src/services/react-hmr-page-metadata-cache.js +19 -0
  23. package/src/services/react-hmr-page-metadata-cache.ts +24 -0
  24. package/src/services/react-hydration-asset.service.d.ts +75 -0
  25. package/src/services/react-hydration-asset.service.js +198 -0
  26. package/src/services/react-hydration-asset.service.ts +260 -0
  27. package/src/services/react-page-module.service.d.ts +80 -0
  28. package/src/services/react-page-module.service.js +155 -0
  29. package/src/services/react-page-module.service.ts +214 -0
  30. package/src/services/react-runtime-bundle.service.d.ts +38 -0
  31. package/src/services/react-runtime-bundle.service.js +207 -0
  32. package/src/services/react-runtime-bundle.service.ts +271 -0
  33. package/src/utils/client-graph-boundary-plugin.d.ts +43 -0
  34. package/src/utils/client-graph-boundary-plugin.js +356 -0
  35. package/src/utils/client-graph-boundary-plugin.ts +590 -0
  36. package/src/utils/client-only.d.ts +8 -0
  37. package/src/utils/client-only.js +19 -0
  38. package/src/utils/client-only.ts +27 -0
  39. package/src/utils/declared-modules.d.ts +42 -0
  40. package/src/utils/declared-modules.js +56 -0
  41. package/src/utils/declared-modules.ts +99 -0
  42. package/src/utils/dynamic.d.ts +15 -0
  43. package/src/utils/dynamic.js +12 -0
  44. package/src/utils/dynamic.ts +27 -0
  45. package/src/utils/hmr-scripts.d.ts +18 -0
  46. package/src/utils/hmr-scripts.js +31 -0
  47. package/src/utils/hmr-scripts.ts +47 -0
  48. package/src/utils/html-boundary.d.ts +7 -0
  49. package/src/utils/html-boundary.js +55 -0
  50. package/src/utils/html-boundary.ts +66 -0
  51. package/src/utils/hydration-scripts.d.ts +71 -0
  52. package/src/utils/hydration-scripts.js +222 -0
  53. package/src/utils/hydration-scripts.ts +338 -0
  54. package/src/utils/reachability-analyzer.d.ts +55 -0
  55. package/src/utils/reachability-analyzer.js +243 -0
  56. package/src/utils/reachability-analyzer.ts +440 -0
  57. package/src/utils/react-mdx-loader-plugin.d.ts +3 -0
  58. package/src/utils/react-mdx-loader-plugin.js +37 -0
  59. package/src/utils/react-mdx-loader-plugin.ts +40 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@ecopages/react` are documented here.
4
+
5
+ > **Note:** Changelog tracking begins at version `0.2.0`. Changes prior to this release are not recorded here but are available in the git history.
6
+
7
+ ## [UNRELEASED] — TBD
8
+
9
+ ### Features
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
27
+
28
+ - **HMR page metadata caching** — Page metadata is now cached between HMR refreshes, preventing unnecessary re-fetches during Fast Refresh (`a663788c`).
29
+ - **Stale temp module race fix** — HMR no longer incorrectly reads a stale temporary module during rapid refresh cycles (`b2cf8466`).
30
+ - **Client graph HMR stability** — HMR reloads now correctly respect client graph boundaries to avoid partial hydration mismatches (`2912d6bd`).
31
+
32
+ #### HTML Boundary Utilities
33
+
34
+ - **`html-boundary.ts`** — New utility that wraps rendered output in explicit boundary markers for the cross-integration boundary rendering policy (`ec1e4d66`).
35
+ - **`hydration-scripts.ts`** — Expanded with new helpers for generating and injecting hydration entry scripts.
36
+
37
+ ### Refactoring
38
+
39
+ - Aligned React renderer to full orchestration mode — removed legacy rendering path (`fc07bdb0`).
40
+ - Ambient module declarations cleaned up (`5f46ecc5`).
41
+ - Client graph boundaries and runtime dependency wiring corrected (`4b6cd32e`).
42
+ - Updated test suite for esbuild adapter and Node.js runtime compatibility (`31a44458`).
43
+
44
+ ### Bug Fixes
45
+
46
+ - Inlined the React MDX loader so React apps no longer need to install `@ecopages/mdx` when enabling React MDX support (`unreleased`).
47
+ - Fixed stale temp module race during Fast Refresh cycles (`b2cf8466`).
48
+ - Fixed client graph boundary wiring for runtime dependencies (`4b6cd32e`).
49
+
50
+ ### Tests
51
+
52
+ - Added `html-boundary.test.ts` covering boundary wrapping utilities.
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`).
56
+
57
+ ---
58
+
59
+ ## Migration Notes
60
+
61
+ - The React integration now requires explicit client boundary declarations. Components that should be hydrated client-side must be marked at a boundary entry point.
62
+ - The internal service layer (`ReactRuntimeBundleService`, `ReactBundleService`, etc.) is not part of the public API and may change between releases.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present Andrea Zanenghi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Ecopages React Integration Plugin
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.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bunx jsr add @ecopages/react
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ To incorporate the React integration into your Ecopages project, configure your project as follows:
14
+
15
+ ```ts
16
+ import { ConfigBuilder } from '@ecopages/core';
17
+ import { reactPlugin } from '@ecopages/react';
18
+
19
+ const config = await new ConfigBuilder()
20
+ .setBaseUrl(import.meta.env.ECOPAGES_BASE_URL)
21
+ .setIntegrations([reactPlugin()])
22
+ .build();
23
+
24
+ export default config;
25
+ ```
26
+
27
+ ## MDX Support
28
+
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.
30
+
31
+ ```ts
32
+ import { ConfigBuilder } from '@ecopages/core';
33
+ import { reactPlugin } from '@ecopages/react';
34
+
35
+ const config = await new ConfigBuilder()
36
+ .setBaseUrl(import.meta.env.ECOPAGES_BASE_URL)
37
+ .setIntegrations([
38
+ reactPlugin({
39
+ mdx: {
40
+ enabled: true,
41
+ compilerOptions: {
42
+ // Optional: remark/rehype plugins
43
+ },
44
+ },
45
+ }),
46
+ ])
47
+ .build();
48
+
49
+ export default config;
50
+ ```
51
+
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.
53
+
54
+ ## Component-Level Islands
55
+
56
+ Current behavior:
57
+
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.
62
+
63
+ This design preserves global CSS/layout selectors while keeping runtime ownership isolated per island instance.
64
+
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.
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@ecopages/react",
3
+ "version": "0.2.0-alpha.1",
4
+ "description": "React integration for Ecopages",
5
+ "keywords": [
6
+ "ecopages",
7
+ "react",
8
+ "plugin"
9
+ ],
10
+ "license": "MIT",
11
+ "main": "./src/react.plugin.js",
12
+ "types": "./src/react.plugin.d.ts",
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "default": "./src/react.plugin.js",
17
+ "types": "./src/react.plugin.d.ts"
18
+ },
19
+ "./declarations": {
20
+ "types": "./src/declarations.d.ts"
21
+ },
22
+ "./utils/dynamic": {
23
+ "types": "./src/utils/dynamic.d.ts",
24
+ "default": "./src/utils/dynamic.js"
25
+ },
26
+ "./utils/client-only": {
27
+ "types": "./src/utils/client-only.d.ts",
28
+ "default": "./src/utils/client-only.js"
29
+ },
30
+ "./router-adapter": {
31
+ "types": "./src/router-adapter.d.ts",
32
+ "default": "./src/router-adapter.js"
33
+ },
34
+ "./declarations.ts": {
35
+ "types": "./src/declarations.d.ts"
36
+ },
37
+ "./utils/dynamic.ts": {
38
+ "types": "./src/utils/dynamic.d.ts",
39
+ "default": "./src/utils/dynamic.js"
40
+ },
41
+ "./utils/client-only.ts": {
42
+ "types": "./src/utils/client-only.d.ts",
43
+ "default": "./src/utils/client-only.js"
44
+ },
45
+ "./router-adapter.ts": {
46
+ "types": "./src/router-adapter.d.ts",
47
+ "default": "./src/router-adapter.js"
48
+ }
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/ecopages/ecopages.git",
53
+ "directory": "packages/integrations/react"
54
+ },
55
+ "peerDependencies": {
56
+ "@ecopages/core": "0.2.0-alpha.1",
57
+ "@types/react": "^19",
58
+ "@types/react-dom": "^19",
59
+ "react": "^19",
60
+ "react-dom": "^19"
61
+ },
62
+ "dependencies": {
63
+ "@ecopages/file-system": "0.2.0-alpha.1",
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",
69
+ "source-map": "^0.7.6",
70
+ "vfile": "^6.0.3"
71
+ },
72
+ "overrides": {
73
+ "react": "^19",
74
+ "react-dom": "^19"
75
+ }
76
+ }
@@ -0,0 +1,6 @@
1
+ declare module '*.mdx' {
2
+ import type { ComponentType } from 'react';
3
+
4
+ const MDXComponent: ComponentType<Record<string, unknown>>;
5
+ export default MDXComponent;
6
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * React HMR Strategy
3
+ *
4
+ * Handles hot module replacement for React components.
5
+ * Triggers module invalidation on changes to ensure fresh component re-renders.
6
+ *
7
+ * @module
8
+ */
9
+ import { HmrStrategy, HmrStrategyType, type HmrAction } from '@ecopages/core/hmr/hmr-strategy';
10
+ import type { DefaultHmrContext } from '@ecopages/core';
11
+ import type { CompileOptions } from '@mdx-js/mdx';
12
+ import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metadata-cache.js';
13
+ /**
14
+ * Strategy for handling React component HMR updates.
15
+ *
16
+ * This strategy provides React-specific HMR handling by rebuilding entrypoints
17
+ * and injecting HMR acceptance handlers that trigger module invalidation.
18
+ *
19
+ * The processing steps are:
20
+ * 1. Check if any React entrypoints are registered
21
+ * 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
25
+ *
26
+ * @remarks
27
+ * This strategy has higher priority than generic JsHmrStrategy, allowing it
28
+ * to handle React files specially while falling back to generic handling for
29
+ * non-React files.
30
+ *
31
+ * Future enhancement: Track dependencies using Bun's transpiler API to only
32
+ * rebuild affected entrypoints instead of all of them.
33
+ *
34
+ * @see https://bun.sh/docs/runtime/transpiler
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const context = {
39
+ * getWatchedFiles: () => watchedFilesMap,
40
+ * getSpecifierMap: () => specifierMap,
41
+ * getDistDir: () => '/path/to/dist/_hmr',
42
+ * getPlugins: () => [],
43
+ * getSrcDir: () => '/path/to/src',
44
+ * getLayoutsDir: () => '/path/to/src/layouts'
45
+ * };
46
+ * const strategy = new ReactHmrStrategy(context);
47
+ * ```
48
+ */
49
+ export declare class ReactHmrStrategy extends HmrStrategy {
50
+ private context;
51
+ private pageMetadataCache;
52
+ private explicitGraphEnabled;
53
+ readonly type = HmrStrategyType.INTEGRATION;
54
+ private mdxCompilerOptions?;
55
+ private readonly knownEntrypoints;
56
+ private importNodePageModule;
57
+ private createUseSyncExternalStoreShimPlugin;
58
+ /**
59
+ * Creates a new React HMR strategy instance.
60
+ *
61
+ * @param context - The HMR context providing access to watched files, plugins, build directories,
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.
70
+ */
71
+ constructor(context: DefaultHmrContext, pageMetadataCache: ReactHmrPageMetadataCache, mdxCompilerOptions?: CompileOptions, explicitGraphEnabled?: boolean);
72
+ /**
73
+ * Returns build plugins for React HMR bundling.
74
+ *
75
+ * Includes the client graph boundary plugin to prevent undeclared imports
76
+ * (including `node:*`) from breaking the browser bundle.
77
+ */
78
+ private getBuildPlugins;
79
+ private isReactEntrypoint;
80
+ /**
81
+ * Determines if the file is a React/MDX entrypoint that's registered for HMR.
82
+ *
83
+ * @param filePath - Absolute path to the changed file
84
+ * @returns True if this is a registered React or MDX entrypoint
85
+ */
86
+ matches(filePath: string): boolean;
87
+ /**
88
+ * Checks if a file is a layout file.
89
+ *
90
+ * Layout files require special HMR handling because they wrap multiple pages and affect
91
+ * the entire page structure. When a layout changes, we trigger a 'layout-update' event
92
+ * instead of a regular 'update' event, which instructs the browser to perform a full
93
+ * page reload (or clear cache and re-render) rather than attempting module-level HMR.
94
+ *
95
+ * @param filePath - Absolute path to the file
96
+ * @returns True if the file is in the layouts directory
97
+ */
98
+ private isLayoutFile;
99
+ /**
100
+ * Processes a React file change by rebuilding all React entrypoints.
101
+ *
102
+ * For layout files, broadcasts a 'layout-update' event to trigger full page reload.
103
+ * For regular components/pages, broadcasts 'update' events for module-level HMR.
104
+ * When a page entrypoint is first registered, only that entrypoint is built.
105
+ * Subsequent file updates rebuild all watched React entrypoints as usual.
106
+ *
107
+ * @param _filePath - Absolute path to the changed file
108
+ * @returns Action to broadcast update events (layout-update for layouts, update for components)
109
+ */
110
+ process(_filePath: string): Promise<HmrAction>;
111
+ /**
112
+ * Bundles a single React/MDX entrypoint with HMR support.
113
+ *
114
+ * @param entrypointPath - Absolute path to the source file
115
+ * @param outputUrl - URL path for the bundled file
116
+ * @returns True if bundling was successful
117
+ */
118
+ private bundleReactEntrypoint;
119
+ /**
120
+ * Encodes dynamic route segments (brackets) in file paths.
121
+ * Converts `[slug]` to `_slug_` to avoid filesystem issues.
122
+ */
123
+ private encodeDynamicSegments;
124
+ /**
125
+ * Processes bundled output by replacing specifiers and injecting HMR handler.
126
+ * Writes to temp file first, then renames atomically to avoid conflicts.
127
+ *
128
+ * @param tempPath - Path to the temporary bundled file
129
+ * @param finalPath - Final destination path
130
+ * @param url - URL path for logging
131
+ * @returns True if processing was successful
132
+ */
133
+ 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
+ }