@ecopages/react 0.2.0-alpha.4 → 0.2.0-alpha.41

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 (70) hide show
  1. package/README.md +161 -18
  2. package/package.json +16 -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 +42 -32
  6. package/src/react-hmr-strategy.js +103 -124
  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 +38 -111
  12. package/src/react.plugin.js +132 -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/services/react-bundle.service.d.ts +15 -26
  17. package/src/services/react-bundle.service.js +45 -93
  18. package/src/services/react-hmr-page-metadata-cache.d.ts +9 -0
  19. package/src/services/react-hmr-page-metadata-cache.js +18 -2
  20. package/src/services/react-hydration-asset.service.d.ts +26 -19
  21. package/src/services/react-hydration-asset.service.js +72 -66
  22. package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
  23. package/src/services/react-mdx-config-dependency.service.js +122 -0
  24. package/src/services/react-page-module.service.d.ts +10 -2
  25. package/src/services/react-page-module.service.js +47 -39
  26. package/src/services/react-page-payload.service.d.ts +46 -0
  27. package/src/services/react-page-payload.service.js +67 -0
  28. package/src/services/react-runtime-bundle.service.d.ts +15 -13
  29. package/src/services/react-runtime-bundle.service.js +103 -180
  30. package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
  31. package/src/utils/client-graph-boundary-plugin.js +149 -11
  32. package/src/utils/component-config-traversal.d.ts +36 -0
  33. package/src/utils/component-config-traversal.js +54 -0
  34. package/src/utils/declared-modules.d.ts +1 -1
  35. package/src/utils/declared-modules.js +7 -16
  36. package/src/utils/dynamic.test.browser.d.ts +1 -0
  37. package/src/utils/dynamic.test.browser.js +33 -0
  38. package/src/utils/hydration-scripts.d.ts +25 -6
  39. package/src/utils/hydration-scripts.js +150 -44
  40. package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
  41. package/src/utils/hydration-scripts.test.browser.js +198 -0
  42. package/src/utils/reachability-analyzer.d.ts +12 -1
  43. package/src/utils/reachability-analyzer.js +101 -5
  44. package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
  45. package/src/utils/react-dom-runtime-interop-plugin.js +29 -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 +6 -0
  49. package/src/utils/react-runtime-alias-map.js +33 -0
  50. package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
  51. package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
  52. package/CHANGELOG.md +0 -62
  53. package/src/react-hmr-strategy.ts +0 -444
  54. package/src/react-renderer.ts +0 -403
  55. package/src/react.plugin.ts +0 -241
  56. package/src/router-adapter.ts +0 -95
  57. package/src/services/react-bundle.service.ts +0 -212
  58. package/src/services/react-hmr-page-metadata-cache.ts +0 -24
  59. package/src/services/react-hydration-asset.service.ts +0 -260
  60. package/src/services/react-page-module.service.ts +0 -214
  61. package/src/services/react-runtime-bundle.service.ts +0 -271
  62. package/src/utils/client-graph-boundary-plugin.ts +0 -590
  63. package/src/utils/client-only.ts +0 -27
  64. package/src/utils/declared-modules.ts +0 -99
  65. package/src/utils/dynamic.ts +0 -27
  66. package/src/utils/hmr-scripts.ts +0 -47
  67. package/src/utils/html-boundary.ts +0 -66
  68. package/src/utils/hydration-scripts.ts +0 -338
  69. package/src/utils/reachability-analyzer.ts +0 -440
  70. package/src/utils/react-mdx-loader-plugin.ts +0 -40
@@ -1,13 +1,24 @@
1
1
  import { createClientGraphBoundaryPlugin } from "../utils/client-graph-boundary-plugin.js";
2
+ import {
3
+ buildReactRuntimeAliasMap,
4
+ getReactClientGraphAllowSpecifiers,
5
+ getReactRuntimeExternalSpecifiers
6
+ } from "../utils/react-runtime-alias-map.js";
7
+ import { createUseSyncExternalStoreShimPlugin } from "../utils/use-sync-external-store-shim-plugin.js";
8
+ import { createRuntimeSpecifierAliasPlugin } from "@ecopages/core/build/runtime-specifier-alias-plugin";
9
+ import { createForeignJsxOverridePlugin } from "@ecopages/core/plugins/foreign-jsx-override-plugin";
2
10
  import { ReactRuntimeBundleService } from "./react-runtime-bundle.service.js";
11
+ import { createReactMdxLoaderPlugin } from "../utils/react-mdx-loader-plugin.js";
3
12
  class ReactBundleService {
13
+ runtimeBundleService;
14
+ config;
4
15
  constructor(config) {
5
16
  this.config = config;
6
17
  this.runtimeBundleService = new ReactRuntimeBundleService({
18
+ rootDir: config.rootDir,
7
19
  routerAdapter: config.routerAdapter
8
20
  });
9
21
  }
10
- runtimeBundleService;
11
22
  /**
12
23
  * Returns resolved runtime import paths for the React runtime.
13
24
  */
@@ -22,10 +33,9 @@ class ReactBundleService {
22
33
  * @param declaredModules - Explicitly declared browser module specifiers
23
34
  * @returns Bundle options object for the build adapter
24
35
  */
25
- async createBundleOptions(componentName, isMdx, declaredModules) {
36
+ async createBundleOptions(componentName, isMdx, declaredModules, bundleOptions = {}) {
26
37
  const runtimeImports = this.getRuntimeImports();
27
38
  const options = {
28
- external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime", "react-dom/client"],
29
39
  mainFields: ["module", "browser", "main"],
30
40
  naming: `${componentName}.[ext]`,
31
41
  ...import.meta.env?.NODE_ENV === "production" && {
@@ -34,27 +44,43 @@ class ReactBundleService {
34
44
  treeshaking: true
35
45
  }
36
46
  };
47
+ if (!bundleOptions.includeRuntime) {
48
+ options.external = [
49
+ ...getReactRuntimeExternalSpecifiers(),
50
+ ...Object.values(runtimeImports).filter((specifier) => Boolean(specifier))
51
+ ];
52
+ }
37
53
  const graphBoundaryPlugin = createClientGraphBoundaryPlugin({
38
54
  absWorkingDir: this.config.rootDir,
39
55
  declaredModules,
40
- alwaysAllowSpecifiers: [
41
- "@ecopages/core",
42
- "react",
43
- "react-dom",
44
- "react/jsx-runtime",
45
- "react/jsx-dev-runtime",
46
- "react-dom/client",
47
- ...this.config.routerAdapter ? [this.config.routerAdapter.importMapKey] : []
48
- ]
56
+ alwaysAllowSpecifiers: getReactClientGraphAllowSpecifiers([], this.config.routerAdapter)
57
+ });
58
+ const foreignJsxOverridePlugin = createForeignJsxOverridePlugin({
59
+ name: "react-renderer-foreign-jsx-override",
60
+ hostJsxImportSource: this.config.jsxImportSource ?? "react",
61
+ foreignExtensions: this.config.nonReactExtensions ?? []
49
62
  });
50
- const runtimeAliasPlugin = this.createRuntimeAliasPlugin(runtimeImports);
51
- const useSyncExternalStoreShimPlugin = this.createSyncExternalStorePlugin();
63
+ const useSyncExternalStoreShimPlugin = createUseSyncExternalStoreShimPlugin({
64
+ name: "react-renderer-use-sync-external-store-shim",
65
+ namespace: "ecopages-react-renderer-shim"
66
+ });
67
+ const runtimePlugins = bundleOptions.includeRuntime ? [] : [this.createRuntimeAliasPlugin(buildReactRuntimeAliasMap(runtimeImports))];
52
68
  if (isMdx && this.config.mdxCompilerOptions) {
53
- const { createReactMdxLoaderPlugin } = await import("../utils/react-mdx-loader-plugin.js");
54
69
  const mdxPlugin = createReactMdxLoaderPlugin(this.config.mdxCompilerOptions);
55
- options.plugins = [runtimeAliasPlugin, mdxPlugin, useSyncExternalStoreShimPlugin, graphBoundaryPlugin];
70
+ options.plugins = [
71
+ foreignJsxOverridePlugin,
72
+ graphBoundaryPlugin,
73
+ ...runtimePlugins,
74
+ mdxPlugin,
75
+ useSyncExternalStoreShimPlugin
76
+ ];
56
77
  } else {
57
- options.plugins = [runtimeAliasPlugin, useSyncExternalStoreShimPlugin, graphBoundaryPlugin];
78
+ options.plugins = [
79
+ foreignJsxOverridePlugin,
80
+ graphBoundaryPlugin,
81
+ ...runtimePlugins,
82
+ useSyncExternalStoreShimPlugin
83
+ ];
58
84
  }
59
85
  return options;
60
86
  }
@@ -62,82 +88,8 @@ class ReactBundleService {
62
88
  * Creates the esbuild plugin that rewrites bare React specifiers
63
89
  * to their runtime asset URLs.
64
90
  */
65
- createRuntimeAliasPlugin(runtimeImports) {
66
- const aliases = /* @__PURE__ */ new Map([
67
- ["react", runtimeImports.react],
68
- ["react-dom/client", runtimeImports.reactDomClient],
69
- ["react/jsx-runtime", runtimeImports.reactJsxRuntime],
70
- ["react/jsx-dev-runtime", runtimeImports.reactJsxDevRuntime],
71
- ["react-dom", runtimeImports.reactDom]
72
- ]);
73
- if (this.config.routerAdapter && runtimeImports.router) {
74
- aliases.set(this.config.routerAdapter.importMapKey, runtimeImports.router);
75
- }
76
- const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
77
- const pattern = new RegExp(
78
- `^(${Array.from(aliases.keys()).map((key) => escapeRegExp(key)).join("|")})$`
79
- );
80
- return {
81
- name: "react-runtime-import-alias",
82
- setup(build) {
83
- build.onResolve({ filter: pattern }, (args) => {
84
- const mappedPath = aliases.get(args.path);
85
- if (!mappedPath) {
86
- return void 0;
87
- }
88
- return {
89
- path: mappedPath,
90
- external: true
91
- };
92
- });
93
- }
94
- };
95
- }
96
- /**
97
- * Creates the esbuild plugin that shims `use-sync-external-store/shim`
98
- * to re-export from React's built-in `useSyncExternalStore`.
99
- * This is needed because some packages use `use-sync-external-store/shim`
100
- * but React 18+ has built-in `useSyncExternalStore`.
101
- */
102
- createSyncExternalStorePlugin() {
103
- return {
104
- name: "react-renderer-use-sync-external-store-shim",
105
- setup(build) {
106
- build.onResolve({ filter: /^use-sync-external-store\/shim(?:\/index\.js)?$/ }, () => ({
107
- path: "use-sync-external-store/shim",
108
- namespace: "ecopages-react-renderer-shim"
109
- }));
110
- build.onLoad(
111
- { filter: /^use-sync-external-store\/shim$/, namespace: "ecopages-react-renderer-shim" },
112
- () => ({
113
- contents: "export { useSyncExternalStore } from 'react';",
114
- loader: "js"
115
- })
116
- );
117
- build.onLoad({ filter: /[\\/]use-sync-external-store[\\/]shim[\\/]index\.js$/ }, () => ({
118
- contents: "export { useSyncExternalStore } from 'react';",
119
- loader: "js"
120
- }));
121
- build.onLoad(
122
- {
123
- filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.development\.js$/
124
- },
125
- () => ({
126
- contents: "export { useSyncExternalStore } from 'react';",
127
- loader: "js"
128
- })
129
- );
130
- build.onLoad(
131
- {
132
- filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.production\.js$/
133
- },
134
- () => ({
135
- contents: "export { useSyncExternalStore } from 'react';",
136
- loader: "js"
137
- })
138
- );
139
- }
140
- };
91
+ createRuntimeAliasPlugin(runtimeAliasMap) {
92
+ return createRuntimeSpecifierAliasPlugin(runtimeAliasMap, { name: "react-runtime-import-alias" });
141
93
  }
142
94
  }
143
95
  export {
@@ -6,6 +6,11 @@
6
6
  */
7
7
  export declare class ReactHmrPageMetadataCache {
8
8
  private readonly declaredModulesByEntrypoint;
9
+ private readonly ownedEntrypoints;
10
+ /**
11
+ * Marks an HMR entrypoint as React-owned.
12
+ */
13
+ markOwnedEntrypoint(entrypointPath: string): void;
9
14
  /**
10
15
  * Stores the declared browser modules for a page entrypoint.
11
16
  */
@@ -14,4 +19,8 @@ export declare class ReactHmrPageMetadataCache {
14
19
  * Returns the last known declared browser modules for a page entrypoint.
15
20
  */
16
21
  getDeclaredModules(entrypointPath: string): string[] | undefined;
22
+ /**
23
+ * Returns true when the watched entrypoint is owned by the React integration.
24
+ */
25
+ ownsEntrypoint(entrypointPath: string): boolean;
17
26
  }
@@ -1,18 +1,34 @@
1
+ import path from "node:path";
1
2
  class ReactHmrPageMetadataCache {
2
3
  declaredModulesByEntrypoint = /* @__PURE__ */ new Map();
4
+ ownedEntrypoints = /* @__PURE__ */ new Set();
5
+ /**
6
+ * Marks an HMR entrypoint as React-owned.
7
+ */
8
+ markOwnedEntrypoint(entrypointPath) {
9
+ this.ownedEntrypoints.add(path.resolve(entrypointPath));
10
+ }
3
11
  /**
4
12
  * Stores the declared browser modules for a page entrypoint.
5
13
  */
6
14
  setDeclaredModules(entrypointPath, declaredModules) {
7
- this.declaredModulesByEntrypoint.set(entrypointPath, [...declaredModules]);
15
+ const resolvedEntrypointPath = path.resolve(entrypointPath);
16
+ this.markOwnedEntrypoint(resolvedEntrypointPath);
17
+ this.declaredModulesByEntrypoint.set(resolvedEntrypointPath, [...declaredModules]);
8
18
  }
9
19
  /**
10
20
  * Returns the last known declared browser modules for a page entrypoint.
11
21
  */
12
22
  getDeclaredModules(entrypointPath) {
13
- const declaredModules = this.declaredModulesByEntrypoint.get(entrypointPath);
23
+ const declaredModules = this.declaredModulesByEntrypoint.get(path.resolve(entrypointPath));
14
24
  return declaredModules ? [...declaredModules] : void 0;
15
25
  }
26
+ /**
27
+ * Returns true when the watched entrypoint is owned by the React integration.
28
+ */
29
+ ownsEntrypoint(entrypointPath) {
30
+ return this.ownedEntrypoints.has(path.resolve(entrypointPath));
31
+ }
16
32
  }
17
33
  export {
18
34
  ReactHmrPageMetadataCache
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Hydration asset creation service for React integration.
3
3
  *
4
- * Builds the asset definitions (bundled component scripts + hydration bootstrap scripts)
5
- * required for client-side React rendering — both at the page level and the component
6
- * island level.
4
+ * Builds the asset definitions required for client-side React rendering both at
5
+ * the page level and the component island level.
7
6
  *
8
7
  * @module
9
8
  */
@@ -23,53 +22,61 @@ export interface ReactHydrationAssetServiceConfig {
23
22
  bundleService: ReactBundleService;
24
23
  hmrPageMetadataCache?: ReactHmrPageMetadataCache;
25
24
  }
25
+ export declare function getReactIslandComponentKey(componentFile: string, config?: EcoComponentConfig): string;
26
26
  /**
27
27
  * Manages the creation of client-side hydration assets for React pages and component islands.
28
28
  */
29
29
  export declare class ReactHydrationAssetService {
30
30
  private readonly config;
31
31
  constructor(config: ReactHydrationAssetServiceConfig);
32
+ private getIslandBundleName;
33
+ private getIslandHydrationName;
32
34
  /**
33
- * Resolves the import path for the bundled page component.
35
+ * Resolves the browser import path used for a React-owned page or island module.
34
36
  * Uses HMR manager for development or constructs static path for production.
35
37
  *
36
38
  * @param pagePath - Absolute path to the page source file
37
- * @param componentName - Generated unique component name
38
- * @returns The resolved import path for the bundled component
39
+ * @param assetName - Generated asset name
40
+ * @returns The resolved browser import path for the module
39
41
  */
40
- resolveAssetImportPath(pagePath: string, componentName: string): Promise<string>;
42
+ resolveAssetImportPath(pagePath: string, assetName: string): Promise<string>;
41
43
  /**
42
- * Creates the asset dependencies for a page: the bundled component and hydration script.
44
+ * Creates the page-owned route entry asset for hydration and client navigation.
43
45
  *
44
46
  * @param pagePath - Absolute path to the page source file
45
47
  * @param componentName - Generated unique component name
46
- * @param importPath - Resolved import path for the bundled component
48
+ * @param importPath - Resolved browser import path used by development HMR
47
49
  * @param bundleOptions - Bundle configuration options
48
50
  * @param isDevelopment - Whether running in development mode with HMR
49
51
  * @param isMdx - Whether the source file is an MDX file
50
- * @param props - Optional page props for client serialization
51
- * @returns Array of asset definitions for processing
52
+ * @returns One page-owned asset definition for processing
52
53
  */
53
- createPageDependencies(pagePath: string, componentName: string, importPath: string, bundleOptions: Record<string, unknown>, isDevelopment: boolean, isMdx: boolean, props?: Record<string, unknown>): AssetDefinition[];
54
+ createPageDependencies(pagePath: string, componentName: string, importPath: string, pageModuleUrlExpression: string, bundleOptions: Record<string, unknown>, isDevelopment: boolean, useBrowserRuntimeImports: boolean, isMdx: boolean): AssetDefinition[];
54
55
  /**
55
56
  * Builds client-side assets for a React component island.
56
57
  *
57
- * Includes the bundled component entry and an inline hydration bootstrap script.
58
+ * Includes the bundled component entry and a shared hydration bootstrap script.
58
59
  *
59
60
  * @param componentFile - Absolute path to the component source file
60
- * @param componentInstanceId - Unique instance ID for DOM targeting
61
- * @param props - Serialized props for client-side hydration
62
61
  * @param config - Optional component config with `__eco` metadata
63
62
  * @returns Processed assets ready for injection
64
63
  */
65
- buildComponentRenderAssets(componentFile: string, componentInstanceId: string, props: Record<string, unknown>, config?: EcoComponentConfig): Promise<ProcessedAsset[]>;
64
+ buildComponentRenderAssets(componentFile: string, config?: EcoComponentConfig): Promise<ProcessedAsset[]>;
66
65
  /**
67
- * Builds all client-side route assets for a page.
66
+ * Creates the Page Browser Graph dependency declarations for a React page.
68
67
  *
69
68
  * @param pagePath - Absolute file path of the page
70
69
  * @param isMdx - Whether the page is an MDX file
71
70
  * @param declaredModules - Explicitly declared browser module specifiers
72
- * @returns Processed assets for the route
71
+ * @returns Declarative assets for core-owned processing
72
+ */
73
+ createPageBrowserGraphDependencies(pagePath: string, isMdx: boolean, declaredModules: string[]): Promise<AssetDefinition[]>;
74
+ /**
75
+ * Builds the Page Browser Graph assets for a React page.
76
+ *
77
+ * @remarks
78
+ * Kept as a compatibility wrapper while callers migrate to core-owned page
79
+ * graph assembly.
73
80
  */
74
- buildRouteRenderAssets(pagePath: string, isMdx: boolean, declaredModules: string[]): Promise<ProcessedAsset[]>;
81
+ buildPageBrowserGraphAssets(pagePath: string, isMdx: boolean, declaredModules: string[]): Promise<ProcessedAsset[]>;
75
82
  }
@@ -4,111 +4,97 @@ import { RESOLVED_ASSETS_DIR } from "@ecopages/core/constants";
4
4
  import {
5
5
  AssetFactory
6
6
  } from "@ecopages/core/services/asset-processing-service";
7
- import { createHydrationScript } from "../utils/hydration-scripts.js";
8
- import { createIslandHydrationScript } from "../utils/hydration-scripts.js";
7
+ import { createHydrationScript, createIslandHydrationScript } from "../utils/hydration-scripts.js";
9
8
  import { collectDeclaredModulesInConfig } from "../utils/declared-modules.js";
9
+ function getReactIslandComponentKey(componentFile, config) {
10
+ return rapidhash(`${componentFile}:${config?.__eco?.id ?? ""}`).toString();
11
+ }
10
12
  class ReactHydrationAssetService {
13
+ config;
11
14
  constructor(config) {
12
15
  this.config = config;
13
16
  }
17
+ getIslandBundleName(componentFile) {
18
+ return `ecopages-react-island-${rapidhash(componentFile)}`;
19
+ }
20
+ getIslandHydrationName(bundleName, componentKey) {
21
+ return `${bundleName}-hydration-${componentKey}`;
22
+ }
14
23
  /**
15
- * Resolves the import path for the bundled page component.
24
+ * Resolves the browser import path used for a React-owned page or island module.
16
25
  * Uses HMR manager for development or constructs static path for production.
17
26
  *
18
27
  * @param pagePath - Absolute path to the page source file
19
- * @param componentName - Generated unique component name
20
- * @returns The resolved import path for the bundled component
28
+ * @param assetName - Generated asset name
29
+ * @returns The resolved browser import path for the module
21
30
  */
22
- async resolveAssetImportPath(pagePath, componentName) {
31
+ async resolveAssetImportPath(pagePath, assetName) {
23
32
  const hmrManager = this.config.assetProcessingService?.getHmrManager();
24
33
  if (hmrManager?.isEnabled()) {
25
34
  return hmrManager.registerEntrypoint(pagePath);
26
35
  }
27
- return `/${path.join(RESOLVED_ASSETS_DIR, path.relative(this.config.srcDir, pagePath)).replace(path.basename(pagePath), `${componentName}.js`).replace(/\\/g, "/")}`;
36
+ return `/${path.join(RESOLVED_ASSETS_DIR, path.relative(this.config.srcDir, pagePath)).replace(path.basename(pagePath), `${assetName}.js`).replace(/\\/g, "/")}`;
28
37
  }
29
38
  /**
30
- * Creates the asset dependencies for a page: the bundled component and hydration script.
39
+ * Creates the page-owned route entry asset for hydration and client navigation.
31
40
  *
32
41
  * @param pagePath - Absolute path to the page source file
33
42
  * @param componentName - Generated unique component name
34
- * @param importPath - Resolved import path for the bundled component
43
+ * @param importPath - Resolved browser import path used by development HMR
35
44
  * @param bundleOptions - Bundle configuration options
36
45
  * @param isDevelopment - Whether running in development mode with HMR
37
46
  * @param isMdx - Whether the source file is an MDX file
38
- * @param props - Optional page props for client serialization
39
- * @returns Array of asset definitions for processing
47
+ * @returns One page-owned asset definition for processing
40
48
  */
41
- createPageDependencies(pagePath, componentName, importPath, bundleOptions, isDevelopment, isMdx, props) {
49
+ createPageDependencies(pagePath, componentName, importPath, pageModuleUrlExpression, bundleOptions, isDevelopment, useBrowserRuntimeImports, isMdx) {
42
50
  const runtimeImports = this.config.bundleService.getRuntimeImports();
43
- const dependencies = [
44
- AssetFactory.createFileScript({
45
- position: "head",
46
- filepath: pagePath,
47
- name: componentName,
48
- excludeFromHtml: true,
49
- bundle: true,
50
- bundleOptions,
51
- attributes: {
52
- type: "module",
53
- defer: "",
54
- "data-eco-persist": "true"
55
- }
56
- })
57
- ];
58
- if (props && Object.keys(props).length > 0) {
59
- dependencies.push(
60
- AssetFactory.createContentScript({
61
- position: "head",
62
- content: `window.__ECO_PAGE__={module:"${importPath}",props:${JSON.stringify(props)}};`,
63
- name: `${componentName}-props`,
64
- bundle: false,
65
- attributes: {
66
- type: "module"
67
- }
68
- })
69
- );
70
- }
71
- dependencies.push(
51
+ return [
72
52
  AssetFactory.createContentScript({
73
53
  position: "head",
74
54
  content: createHydrationScript({
75
- importPath,
76
- reactImportPath: runtimeImports.react,
77
- reactDomClientImportPath: runtimeImports.reactDomClient,
78
- routerImportPath: runtimeImports.router,
55
+ importPath: isDevelopment ? importPath : pagePath,
56
+ pageModuleUrlExpression,
57
+ reactImportPath: useBrowserRuntimeImports ? runtimeImports.react : "react",
58
+ reactDomClientImportPath: useBrowserRuntimeImports ? runtimeImports.reactDomClient : "react-dom/client",
59
+ routerImportPath: useBrowserRuntimeImports ? runtimeImports.router : this.config.routerAdapter?.bundle.importPath,
79
60
  isDevelopment,
80
61
  isMdx,
81
- router: this.config.routerAdapter
62
+ router: this.config.routerAdapter,
63
+ scriptId: componentName
82
64
  }),
83
- name: `${componentName}-hydration`,
84
- bundle: false,
65
+ name: componentName,
66
+ packageRole: "page-script",
67
+ bundle: !isDevelopment,
68
+ bundleOptions,
85
69
  attributes: {
86
70
  type: "module",
87
71
  defer: "",
88
72
  "data-eco-rerun": "true",
89
- "data-eco-script-id": `${componentName}-hydration`,
73
+ "data-eco-script-id": componentName,
90
74
  "data-eco-persist": "true"
91
75
  }
92
76
  })
93
- );
94
- return dependencies;
77
+ ];
95
78
  }
96
79
  /**
97
80
  * Builds client-side assets for a React component island.
98
81
  *
99
- * Includes the bundled component entry and an inline hydration bootstrap script.
82
+ * Includes the bundled component entry and a shared hydration bootstrap script.
100
83
  *
101
84
  * @param componentFile - Absolute path to the component source file
102
- * @param componentInstanceId - Unique instance ID for DOM targeting
103
- * @param props - Serialized props for client-side hydration
104
85
  * @param config - Optional component config with `__eco` metadata
105
86
  * @returns Processed assets ready for injection
106
87
  */
107
- async buildComponentRenderAssets(componentFile, componentInstanceId, props, config) {
108
- const componentName = `ecopages-react-island-${rapidhash(`${componentFile}:${componentInstanceId}`)}`;
109
- const importPath = await this.resolveAssetImportPath(componentFile, componentName);
88
+ async buildComponentRenderAssets(componentFile, config) {
89
+ const componentName = this.getIslandBundleName(componentFile);
90
+ const componentKey = getReactIslandComponentKey(componentFile, config);
91
+ const hydrationName = this.getIslandHydrationName(componentName, componentKey);
110
92
  const hmrManager = this.config.assetProcessingService?.getHmrManager();
111
93
  const isDevelopment = hmrManager?.isEnabled() ?? false;
94
+ if (isDevelopment) {
95
+ this.config.hmrPageMetadataCache?.markOwnedEntrypoint(componentFile);
96
+ }
97
+ const importPath = await this.resolveAssetImportPath(componentFile, componentName);
112
98
  const declaredModules = collectDeclaredModulesInConfig(config);
113
99
  const bundleOptions = await this.config.bundleService.createBundleOptions(
114
100
  componentName,
@@ -121,6 +107,7 @@ class ReactHydrationAssetService {
121
107
  position: "head",
122
108
  filepath: componentFile,
123
109
  name: componentName,
110
+ packageRole: "dynamic-chunk",
124
111
  excludeFromHtml: true,
125
112
  bundle: true,
126
113
  bundleOptions,
@@ -136,19 +123,19 @@ class ReactHydrationAssetService {
136
123
  importPath,
137
124
  reactImportPath: runtimeImports.react,
138
125
  reactDomClientImportPath: runtimeImports.reactDomClient,
139
- targetSelector: `[data-eco-component-id="${componentInstanceId}"]`,
140
- props,
126
+ targetSelector: `[data-eco-component-key="${componentKey}"]`,
141
127
  componentRef: config?.__eco?.id,
142
128
  componentFile,
143
129
  isDevelopment
144
130
  }),
145
- name: `${componentName}-hydration`,
131
+ name: hydrationName,
132
+ packageRole: "keep-separate",
146
133
  bundle: false,
147
134
  attributes: {
148
135
  type: "module",
149
136
  defer: "",
150
137
  "data-eco-rerun": "true",
151
- "data-eco-script-id": `${componentName}-hydration`,
138
+ "data-eco-script-id": hydrationName,
152
139
  "data-eco-persist": "true"
153
140
  }
154
141
  })
@@ -159,34 +146,52 @@ class ReactHydrationAssetService {
159
146
  return this.config.assetProcessingService.processDependencies(dependencies, componentName);
160
147
  }
161
148
  /**
162
- * Builds all client-side route assets for a page.
149
+ * Creates the Page Browser Graph dependency declarations for a React page.
163
150
  *
164
151
  * @param pagePath - Absolute file path of the page
165
152
  * @param isMdx - Whether the page is an MDX file
166
153
  * @param declaredModules - Explicitly declared browser module specifiers
167
- * @returns Processed assets for the route
154
+ * @returns Declarative assets for core-owned processing
168
155
  */
169
- async buildRouteRenderAssets(pagePath, isMdx, declaredModules) {
156
+ async createPageBrowserGraphDependencies(pagePath, isMdx, declaredModules) {
170
157
  const componentName = `ecopages-react-${rapidhash(pagePath)}`;
171
158
  const hmrManager = this.config.assetProcessingService?.getHmrManager();
172
159
  const isDevelopment = hmrManager?.isEnabled() ?? false;
160
+ const isHostedDevelopment = !isDevelopment && process.env.NODE_ENV !== "production";
161
+ const useBrowserRuntimeImports = isDevelopment || isHostedDevelopment;
173
162
  if (isDevelopment) {
174
163
  this.config.hmrPageMetadataCache?.setDeclaredModules(pagePath, declaredModules);
175
164
  }
176
165
  const importPath = await this.resolveAssetImportPath(pagePath, componentName);
166
+ const pageModuleUrlExpression = "import.meta.url";
177
167
  const bundleOptions = await this.config.bundleService.createBundleOptions(
178
168
  componentName,
179
169
  isMdx,
180
- declaredModules
170
+ declaredModules,
171
+ { includeRuntime: !useBrowserRuntimeImports }
181
172
  );
182
173
  const dependencies = this.createPageDependencies(
183
174
  pagePath,
184
175
  componentName,
185
176
  importPath,
177
+ pageModuleUrlExpression,
186
178
  bundleOptions,
187
179
  isDevelopment,
180
+ useBrowserRuntimeImports,
188
181
  isMdx
189
182
  );
183
+ return dependencies;
184
+ }
185
+ /**
186
+ * Builds the Page Browser Graph assets for a React page.
187
+ *
188
+ * @remarks
189
+ * Kept as a compatibility wrapper while callers migrate to core-owned page
190
+ * graph assembly.
191
+ */
192
+ async buildPageBrowserGraphAssets(pagePath, isMdx, declaredModules) {
193
+ const componentName = `ecopages-react-${rapidhash(pagePath)}`;
194
+ const dependencies = await this.createPageBrowserGraphDependencies(pagePath, isMdx, declaredModules);
190
195
  if (!this.config.assetProcessingService) {
191
196
  throw new Error("AssetProcessingService is not set");
192
197
  }
@@ -194,5 +199,6 @@ class ReactHydrationAssetService {
194
199
  }
195
200
  }
196
201
  export {
197
- ReactHydrationAssetService
202
+ ReactHydrationAssetService,
203
+ getReactIslandComponentKey
198
204
  };
@@ -0,0 +1,36 @@
1
+ import type { EcoComponent, EcoComponentConfig } from '@ecopages/core';
2
+ import { type AssetProcessingService, type ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
3
+ import type { ReactPageModuleService } from './react-page-module.service.js';
4
+ type MdxConfigDependencyProcessor = (components: Partial<EcoComponent>[]) => Promise<ProcessedAsset[]>;
5
+ export interface ReactMdxConfigDependencyServiceConfig {
6
+ integrationName: string;
7
+ pageModuleService: Pick<ReactPageModuleService, 'ensureConfigFileMetadata'>;
8
+ assetProcessingService?: Pick<AssetProcessingService, 'processDependencies'>;
9
+ }
10
+ /**
11
+ * Resolves MDX-owned config dependencies that live outside the normal React component tree.
12
+ *
13
+ * React MDX pages can declare dependencies on the page config itself or on a
14
+ * resolved layout config. Those roots need to be materialized as synthetic
15
+ * component configs so the shared dependency pipeline can process them without
16
+ * growing more MDX-specific logic inside the renderer.
17
+ */
18
+ export declare class ReactMdxConfigDependencyService {
19
+ private readonly config;
20
+ constructor(config: ReactMdxConfigDependencyServiceConfig);
21
+ /**
22
+ * Processes MDX-owned config dependencies and eagerly emits any SSR-marked lazy scripts.
23
+ */
24
+ processMdxConfigDependencies(options: {
25
+ pagePath: string;
26
+ config?: EcoComponentConfig;
27
+ processComponentDependencies: MdxConfigDependencyProcessor;
28
+ }): Promise<ProcessedAsset[]>;
29
+ private createOwnedConfigComponents;
30
+ private processDeclaredSsrLazyDependencies;
31
+ /**
32
+ * Collects `lazy` script dependencies that also opt into SSR from an MDX config graph.
33
+ */
34
+ private collectDeclaredSsrLazyDependencies;
35
+ }
36
+ export {};