@ecopages/react 0.2.0-alpha.4 → 0.2.0-alpha.40
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/README.md +161 -18
- package/package.json +16 -12
- package/src/eco-embed.d.ts +11 -0
- package/src/eco-embed.js +11 -0
- package/src/react-hmr-strategy.d.ts +42 -32
- package/src/react-hmr-strategy.js +103 -124
- package/src/react-renderer.d.ts +169 -42
- package/src/react-renderer.js +484 -164
- 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 +45 -93
- 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 +26 -19
- package/src/services/react-hydration-asset.service.js +72 -66
- 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 +47 -39
- 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/CHANGELOG.md +0 -62
- 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
|
@@ -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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
51
|
-
|
|
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 = [
|
|
70
|
+
options.plugins = [
|
|
71
|
+
foreignJsxOverridePlugin,
|
|
72
|
+
graphBoundaryPlugin,
|
|
73
|
+
...runtimePlugins,
|
|
74
|
+
mdxPlugin,
|
|
75
|
+
useSyncExternalStoreShimPlugin
|
|
76
|
+
];
|
|
56
77
|
} else {
|
|
57
|
-
options.plugins = [
|
|
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(
|
|
66
|
-
|
|
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
|
-
|
|
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
|
|
5
|
-
*
|
|
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
|
|
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
|
|
38
|
-
* @returns The resolved import path for the
|
|
39
|
+
* @param assetName - Generated asset name
|
|
40
|
+
* @returns The resolved browser import path for the module
|
|
39
41
|
*/
|
|
40
|
-
resolveAssetImportPath(pagePath: string,
|
|
42
|
+
resolveAssetImportPath(pagePath: string, assetName: string): Promise<string>;
|
|
41
43
|
/**
|
|
42
|
-
* Creates the
|
|
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
|
|
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
|
-
* @
|
|
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,
|
|
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
|
|
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,
|
|
64
|
+
buildComponentRenderAssets(componentFile: string, config?: EcoComponentConfig): Promise<ProcessedAsset[]>;
|
|
66
65
|
/**
|
|
67
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
20
|
-
* @returns The resolved import path for the
|
|
28
|
+
* @param assetName - Generated asset name
|
|
29
|
+
* @returns The resolved browser import path for the module
|
|
21
30
|
*/
|
|
22
|
-
async resolveAssetImportPath(pagePath,
|
|
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), `${
|
|
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
|
|
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
|
|
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
|
-
* @
|
|
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,
|
|
49
|
+
createPageDependencies(pagePath, componentName, importPath, pageModuleUrlExpression, bundleOptions, isDevelopment, useBrowserRuntimeImports, isMdx) {
|
|
42
50
|
const runtimeImports = this.config.bundleService.getRuntimeImports();
|
|
43
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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:
|
|
84
|
-
|
|
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":
|
|
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
|
|
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,
|
|
108
|
-
const componentName =
|
|
109
|
-
const
|
|
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-
|
|
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:
|
|
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":
|
|
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
|
-
*
|
|
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
|
|
154
|
+
* @returns Declarative assets for core-owned processing
|
|
168
155
|
*/
|
|
169
|
-
async
|
|
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 {};
|