@ecopages/react 0.2.0-alpha.9 → 0.2.0-beta.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.
- package/README.md +30 -13
- package/package.json +23 -12
- package/src/eco-embed.d.ts +11 -0
- package/src/eco-embed.js +11 -0
- package/src/react-hmr-strategy.d.ts +102 -18
- package/src/react-hmr-strategy.js +427 -50
- package/src/react-renderer.d.ts +100 -92
- package/src/react-renderer.js +356 -340
- package/src/react.constants.d.ts +1 -0
- package/src/react.constants.js +4 -0
- package/src/react.plugin.d.ts +25 -107
- package/src/react.plugin.js +109 -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/runtime/use-sync-external-store-with-selector.d.ts +3 -0
- package/src/runtime/use-sync-external-store-with-selector.js +56 -0
- package/src/services/pages-index.d.ts +64 -0
- package/src/services/pages-index.js +73 -0
- package/src/services/react-bundle.service.d.ts +24 -9
- package/src/services/react-bundle.service.js +35 -24
- package/src/services/react-hmr-page-metadata-cache.d.ts +10 -1
- package/src/services/react-hmr-page-metadata-cache.js +18 -2
- package/src/services/react-hydration-asset.service.d.ts +28 -19
- package/src/services/react-hydration-asset.service.js +83 -64
- 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 +8 -3
- package/src/services/react-page-module.service.js +33 -26
- 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 +9 -2
- package/src/services/react-runtime-bundle.service.js +77 -16
- package/src/utils/client-graph-boundary-cache.d.ts +108 -0
- package/src/utils/client-graph-boundary-cache.js +116 -0
- package/src/utils/client-graph-boundary-plugin.d.ts +13 -5
- package/src/utils/client-graph-boundary-plugin.js +63 -5
- 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 +9 -5
- package/src/utils/hydration-scripts.js +119 -34
- package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
- package/src/utils/hydration-scripts.test.browser.js +198 -0
- package/src/utils/react-dom-runtime-interop-plugin.d.ts +1 -1
- package/src/utils/react-dom-runtime-interop-plugin.js +9 -0
- package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
- package/src/utils/{react-runtime-specifier-map.d.ts → react-runtime-alias-map.d.ts} +3 -1
- package/src/utils/react-runtime-alias-map.js +90 -0
- package/CHANGELOG.md +0 -27
- package/src/react-hmr-strategy.ts +0 -386
- package/src/react-renderer.ts +0 -803
- package/src/react.plugin.ts +0 -276
- package/src/router-adapter.ts +0 -95
- package/src/services/react-bundle.service.ts +0 -108
- package/src/services/react-hmr-page-metadata-cache.ts +0 -24
- package/src/services/react-hydration-asset.service.ts +0 -263
- package/src/services/react-page-module.service.ts +0 -224
- package/src/services/react-runtime-bundle.service.ts +0 -172
- package/src/utils/client-graph-boundary-plugin.ts +0 -831
- 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 -459
- package/src/utils/reachability-analyzer.ts +0 -593
- package/src/utils/react-dom-runtime-interop-plugin.ts +0 -33
- package/src/utils/react-mdx-loader-plugin.ts +0 -63
- package/src/utils/react-runtime-specifier-map.js +0 -37
- package/src/utils/react-runtime-specifier-map.ts +0 -45
- package/src/utils/use-sync-external-store-shim-plugin.d.ts +0 -5
- package/src/utils/use-sync-external-store-shim-plugin.js +0 -41
- package/src/utils/use-sync-external-store-shim-plugin.ts +0 -45
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
const DEFAULT_EXTENSIONS = [".tsx", ".kita.tsx", ".lit.tsx", ".eco.tsx", ".mdx", ".react.tsx"];
|
|
4
|
+
class PagesIndex {
|
|
5
|
+
pagesDir;
|
|
6
|
+
extensions;
|
|
7
|
+
isPageEntrypoint;
|
|
8
|
+
pages = /* @__PURE__ */ new Set();
|
|
9
|
+
lastRefreshAt = 0;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.pagesDir = options.pagesDir;
|
|
12
|
+
this.extensions = options.extensions ?? DEFAULT_EXTENSIONS;
|
|
13
|
+
this.isPageEntrypoint = options.isPageEntrypoint ?? (() => true);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Rescan the pages directory and rebuild the index.
|
|
17
|
+
*
|
|
18
|
+
* Cheap to call multiple times in sequence (just a glob). The
|
|
19
|
+
* real value of `PagesIndex` is the `add` / `remove` path that
|
|
20
|
+
* callers can wire to the file watcher; for now this is the
|
|
21
|
+
* fallback used on layout changes.
|
|
22
|
+
*/
|
|
23
|
+
async refresh() {
|
|
24
|
+
const files = await fileSystem.glob(
|
|
25
|
+
this.extensions.map((ext) => `**/*${ext}`),
|
|
26
|
+
{
|
|
27
|
+
cwd: this.pagesDir
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
const next = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
const absolutePath = path.join(this.pagesDir, file);
|
|
33
|
+
if (!this.isPageEntrypoint(absolutePath)) continue;
|
|
34
|
+
if (file.includes(".ecopages-node.")) continue;
|
|
35
|
+
next.add(absolutePath);
|
|
36
|
+
}
|
|
37
|
+
this.pages.clear();
|
|
38
|
+
for (const p of next) this.pages.add(p);
|
|
39
|
+
this.lastRefreshAt = Date.now();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Add a single entrypoint. Idempotent.
|
|
43
|
+
*/
|
|
44
|
+
add(absolutePath) {
|
|
45
|
+
if (!this.isPageEntrypoint(absolutePath)) return;
|
|
46
|
+
this.pages.add(absolutePath);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Remove a single entrypoint. Idempotent.
|
|
50
|
+
*/
|
|
51
|
+
remove(absolutePath) {
|
|
52
|
+
this.pages.delete(absolutePath);
|
|
53
|
+
}
|
|
54
|
+
/** True if the index contains `absolutePath`. */
|
|
55
|
+
has(absolutePath) {
|
|
56
|
+
return this.pages.has(absolutePath);
|
|
57
|
+
}
|
|
58
|
+
/** Snapshot of the current set, sorted by absolute path. */
|
|
59
|
+
list() {
|
|
60
|
+
return Array.from(this.pages).sort();
|
|
61
|
+
}
|
|
62
|
+
/** Number of indexed entrypoints. */
|
|
63
|
+
get size() {
|
|
64
|
+
return this.pages.size;
|
|
65
|
+
}
|
|
66
|
+
/** Last refresh timestamp (ms since epoch). */
|
|
67
|
+
get refreshedAt() {
|
|
68
|
+
return this.lastRefreshAt;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
PagesIndex
|
|
73
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundle configuration service for React integration.
|
|
3
3
|
*
|
|
4
|
-
* Encapsulates all
|
|
4
|
+
* Encapsulates all build plugin creation and bundle options
|
|
5
5
|
* for client-side React component builds.
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
@@ -16,9 +16,25 @@ export interface ReactBundleServiceConfig {
|
|
|
16
16
|
rootDir: string;
|
|
17
17
|
routerAdapter?: ReactRouterAdapter;
|
|
18
18
|
mdxCompilerOptions?: CompileOptions;
|
|
19
|
+
nonReactExtensions?: string[];
|
|
20
|
+
jsxImportSource?: string;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
23
|
+
* Optional flags that adjust how a React client entry is bundled.
|
|
24
|
+
*/
|
|
25
|
+
export interface ReactClientBundleOptions {
|
|
26
|
+
/**
|
|
27
|
+
* When `true`, bundle React runtime dependencies into the emitted entry instead of
|
|
28
|
+
* rewriting them to external runtime specifiers.
|
|
29
|
+
*/
|
|
30
|
+
includeRuntime?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* When set, overrides the build adapter chunk splitting mode for this entry.
|
|
33
|
+
*/
|
|
34
|
+
splitting?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Manages bundle configuration and plugin creation for React page/component builds.
|
|
22
38
|
*/
|
|
23
39
|
export declare class ReactBundleService {
|
|
24
40
|
private readonly runtimeBundleService;
|
|
@@ -29,17 +45,16 @@ export declare class ReactBundleService {
|
|
|
29
45
|
*/
|
|
30
46
|
getRuntimeImports(): ReactRuntimeImports;
|
|
31
47
|
/**
|
|
32
|
-
* Creates
|
|
48
|
+
* Creates bundle options for a page or component entry.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* React derives runtime specifier mappings from the core browser runtime manifest
|
|
52
|
+
* so ESM imports resolve to concrete runtime asset URLs during module loading.
|
|
33
53
|
*
|
|
34
54
|
* @param componentName - Generated unique component name for output naming
|
|
35
55
|
* @param isMdx - Whether the source file is an MDX file
|
|
36
56
|
* @param declaredModules - Explicitly declared browser module specifiers
|
|
37
57
|
* @returns Bundle options object for the build adapter
|
|
38
58
|
*/
|
|
39
|
-
createBundleOptions(componentName: string, isMdx: boolean, declaredModules: string[]): Promise<Record<string, unknown>>;
|
|
40
|
-
/**
|
|
41
|
-
* Creates the esbuild plugin that rewrites bare React specifiers
|
|
42
|
-
* to their runtime asset URLs.
|
|
43
|
-
*/
|
|
44
|
-
createRuntimeAliasPlugin(runtimeSpecifierMap: Record<string, string>): import("packages/core/src/build/build-types.ts").EcoBuildPlugin | null;
|
|
59
|
+
createBundleOptions(componentName: string, isMdx: boolean, declaredModules: string[], bundleOptions?: ReactClientBundleOptions): Promise<Record<string, unknown>>;
|
|
45
60
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { createClientGraphBoundaryPlugin } from "../utils/client-graph-boundary-plugin.js";
|
|
2
2
|
import {
|
|
3
|
-
buildReactRuntimeSpecifierMap,
|
|
4
3
|
getReactClientGraphAllowSpecifiers,
|
|
5
4
|
getReactRuntimeExternalSpecifiers
|
|
6
|
-
} from "../utils/react-runtime-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
} from "../utils/react-runtime-alias-map.js";
|
|
6
|
+
import { createBrowserRuntimePlugin } from "@ecopages/core/build/browser-runtime-plugin";
|
|
7
|
+
import { createForeignJsxOverridePlugin } from "@ecopages/core/plugins/foreign-jsx-override-plugin";
|
|
9
8
|
import { ReactRuntimeBundleService } from "./react-runtime-bundle.service.js";
|
|
9
|
+
import { createReactMdxLoaderPlugin } from "../utils/react-mdx-loader-plugin.js";
|
|
10
10
|
class ReactBundleService {
|
|
11
11
|
runtimeBundleService;
|
|
12
12
|
config;
|
|
13
13
|
constructor(config) {
|
|
14
14
|
this.config = config;
|
|
15
15
|
this.runtimeBundleService = new ReactRuntimeBundleService({
|
|
16
|
+
rootDir: config.rootDir,
|
|
16
17
|
routerAdapter: config.routerAdapter
|
|
17
18
|
});
|
|
18
19
|
}
|
|
@@ -23,52 +24,62 @@ class ReactBundleService {
|
|
|
23
24
|
return this.runtimeBundleService.getRuntimeImports();
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
|
-
* Creates
|
|
27
|
+
* Creates bundle options for a page or component entry.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* React derives runtime specifier mappings from the core browser runtime manifest
|
|
31
|
+
* so ESM imports resolve to concrete runtime asset URLs during module loading.
|
|
27
32
|
*
|
|
28
33
|
* @param componentName - Generated unique component name for output naming
|
|
29
34
|
* @param isMdx - Whether the source file is an MDX file
|
|
30
35
|
* @param declaredModules - Explicitly declared browser module specifiers
|
|
31
36
|
* @returns Bundle options object for the build adapter
|
|
32
37
|
*/
|
|
33
|
-
async createBundleOptions(componentName, isMdx, declaredModules) {
|
|
38
|
+
async createBundleOptions(componentName, isMdx, declaredModules, bundleOptions = {}) {
|
|
34
39
|
const runtimeImports = this.getRuntimeImports();
|
|
35
|
-
const runtimeSpecifierMap = buildReactRuntimeSpecifierMap(runtimeImports, this.config.routerAdapter);
|
|
36
40
|
const options = {
|
|
37
|
-
external: getReactRuntimeExternalSpecifiers(),
|
|
38
41
|
mainFields: ["module", "browser", "main"],
|
|
39
42
|
naming: `${componentName}.[ext]`,
|
|
40
43
|
...import.meta.env?.NODE_ENV === "production" && {
|
|
41
44
|
minify: true,
|
|
42
|
-
splitting: false,
|
|
43
45
|
treeshaking: true
|
|
44
|
-
}
|
|
46
|
+
},
|
|
47
|
+
...bundleOptions.splitting === void 0 ? {} : { splitting: bundleOptions.splitting }
|
|
45
48
|
};
|
|
49
|
+
if (!bundleOptions.includeRuntime) {
|
|
50
|
+
const reactRuntimeSpecifiers = new Set(getReactRuntimeExternalSpecifiers());
|
|
51
|
+
options.external = [
|
|
52
|
+
...Object.values(runtimeImports).filter(
|
|
53
|
+
(specifier) => Boolean(specifier) && !reactRuntimeSpecifiers.has(
|
|
54
|
+
specifier
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
];
|
|
58
|
+
}
|
|
46
59
|
const graphBoundaryPlugin = createClientGraphBoundaryPlugin({
|
|
47
60
|
absWorkingDir: this.config.rootDir,
|
|
48
61
|
declaredModules,
|
|
49
62
|
alwaysAllowSpecifiers: getReactClientGraphAllowSpecifiers([], this.config.routerAdapter)
|
|
50
63
|
});
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
const foreignJsxOverridePlugin = createForeignJsxOverridePlugin({
|
|
65
|
+
name: "react-renderer-foreign-jsx-override",
|
|
66
|
+
hostJsxImportSource: this.config.jsxImportSource ?? "react",
|
|
67
|
+
foreignExtensions: this.config.nonReactExtensions ?? []
|
|
68
|
+
});
|
|
69
|
+
const runtimeManifest = this.runtimeBundleService.getRuntimeManifest();
|
|
70
|
+
const runtimeRewritePlugin = createBrowserRuntimePlugin({
|
|
71
|
+
name: "react-renderer-runtime-import-rewrite",
|
|
72
|
+
manifest: runtimeManifest
|
|
55
73
|
});
|
|
74
|
+
const runtimePlugins = bundleOptions.includeRuntime ? [] : [runtimeRewritePlugin].filter((plugin) => plugin !== null);
|
|
56
75
|
if (isMdx && this.config.mdxCompilerOptions) {
|
|
57
|
-
const { createReactMdxLoaderPlugin } = await import("../utils/react-mdx-loader-plugin.js");
|
|
58
76
|
const mdxPlugin = createReactMdxLoaderPlugin(this.config.mdxCompilerOptions);
|
|
59
|
-
options.plugins = [graphBoundaryPlugin,
|
|
77
|
+
options.plugins = [foreignJsxOverridePlugin, graphBoundaryPlugin, ...runtimePlugins, mdxPlugin];
|
|
60
78
|
} else {
|
|
61
|
-
options.plugins = [
|
|
79
|
+
options.plugins = [foreignJsxOverridePlugin, graphBoundaryPlugin, ...runtimePlugins];
|
|
62
80
|
}
|
|
63
81
|
return options;
|
|
64
82
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Creates the esbuild plugin that rewrites bare React specifiers
|
|
67
|
-
* to their runtime asset URLs.
|
|
68
|
-
*/
|
|
69
|
-
createRuntimeAliasPlugin(runtimeSpecifierMap) {
|
|
70
|
-
return createRuntimeSpecifierAliasPlugin(runtimeSpecifierMap, { name: "react-runtime-import-alias" });
|
|
71
|
-
}
|
|
72
83
|
}
|
|
73
84
|
export {
|
|
74
85
|
ReactBundleService
|
|
@@ -6,12 +6,21 @@
|
|
|
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
|
*/
|
|
12
|
-
setDeclaredModules(entrypointPath: string, declaredModules: string[]): void;
|
|
17
|
+
setDeclaredModules(entrypointPath: string, declaredModules: readonly string[]): void;
|
|
13
18
|
/**
|
|
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,63 @@ 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
|
+
private static readonly ROUTER_PAGE_GROUPED_BUNDLE_ID;
|
|
31
32
|
constructor(config: ReactHydrationAssetServiceConfig);
|
|
33
|
+
private getIslandBundleName;
|
|
34
|
+
private getIslandHydrationName;
|
|
35
|
+
private getRouterPageGroupedEntryName;
|
|
32
36
|
/**
|
|
33
|
-
* Resolves the import path for
|
|
37
|
+
* Resolves the browser import path used for a React-owned page or island module.
|
|
34
38
|
* Uses HMR manager for development or constructs static path for production.
|
|
35
39
|
*
|
|
36
40
|
* @param pagePath - Absolute path to the page source file
|
|
37
|
-
* @param
|
|
38
|
-
* @returns The resolved import path for the
|
|
41
|
+
* @param assetName - Generated asset name
|
|
42
|
+
* @returns The resolved browser import path for the module
|
|
39
43
|
*/
|
|
40
|
-
resolveAssetImportPath(pagePath: string,
|
|
44
|
+
resolveAssetImportPath(pagePath: string, assetName: string): Promise<string>;
|
|
41
45
|
/**
|
|
42
|
-
* Creates the
|
|
46
|
+
* Creates the page-owned route entry asset for hydration and client navigation.
|
|
43
47
|
*
|
|
44
48
|
* @param pagePath - Absolute path to the page source file
|
|
45
49
|
* @param componentName - Generated unique component name
|
|
46
|
-
* @param importPath - Resolved import path
|
|
50
|
+
* @param importPath - Resolved browser import path used by development HMR
|
|
47
51
|
* @param bundleOptions - Bundle configuration options
|
|
48
52
|
* @param isDevelopment - Whether running in development mode with HMR
|
|
49
53
|
* @param isMdx - Whether the source file is an MDX file
|
|
50
|
-
* @
|
|
51
|
-
* @returns Array of asset definitions for processing
|
|
54
|
+
* @returns One page-owned asset definition for processing
|
|
52
55
|
*/
|
|
53
|
-
createPageDependencies(pagePath: string, componentName: string, importPath: string, bundleOptions: Record<string, unknown>, isDevelopment: boolean,
|
|
56
|
+
createPageDependencies(pagePath: string, componentName: string, importPath: string, pageModuleUrlExpression: string, bundleOptions: Record<string, unknown>, isDevelopment: boolean, useBrowserRuntimeImports: boolean, isMdx: boolean): AssetDefinition[];
|
|
54
57
|
/**
|
|
55
58
|
* Builds client-side assets for a React component island.
|
|
56
59
|
*
|
|
57
|
-
* Includes the bundled component entry and
|
|
60
|
+
* Includes the bundled component entry and a shared hydration bootstrap script.
|
|
58
61
|
*
|
|
59
62
|
* @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
63
|
* @param config - Optional component config with `__eco` metadata
|
|
63
64
|
* @returns Processed assets ready for injection
|
|
64
65
|
*/
|
|
65
|
-
buildComponentRenderAssets(componentFile: string,
|
|
66
|
+
buildComponentRenderAssets(componentFile: string, config?: EcoComponentConfig): Promise<ProcessedAsset[]>;
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
+
* Creates the Page Browser Graph dependency declarations for a React page.
|
|
68
69
|
*
|
|
69
70
|
* @param pagePath - Absolute file path of the page
|
|
70
71
|
* @param isMdx - Whether the page is an MDX file
|
|
71
72
|
* @param declaredModules - Explicitly declared browser module specifiers
|
|
72
|
-
* @returns
|
|
73
|
+
* @returns Declarative assets for core-owned processing
|
|
74
|
+
*/
|
|
75
|
+
createPageBrowserGraphDependencies(pagePath: string, isMdx: boolean, declaredModules: string[]): Promise<AssetDefinition[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Builds the Page Browser Graph assets for a React page.
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* Kept as a compatibility wrapper while callers migrate to core-owned page
|
|
81
|
+
* graph assembly.
|
|
73
82
|
*/
|
|
74
|
-
|
|
83
|
+
buildPageBrowserGraphAssets(pagePath: string, isMdx: boolean, declaredModules: string[]): Promise<ProcessedAsset[]>;
|
|
75
84
|
}
|
|
@@ -6,109 +6,106 @@ import {
|
|
|
6
6
|
} from "@ecopages/core/services/asset-processing-service";
|
|
7
7
|
import { createHydrationScript, createIslandHydrationScript } from "../utils/hydration-scripts.js";
|
|
8
8
|
import { collectDeclaredModulesInConfig } from "../utils/declared-modules.js";
|
|
9
|
+
function getReactIslandComponentKey(componentFile, config) {
|
|
10
|
+
return rapidhash(`${componentFile}:${config?.__eco?.id ?? ""}`).toString();
|
|
11
|
+
}
|
|
9
12
|
class ReactHydrationAssetService {
|
|
10
13
|
config;
|
|
14
|
+
static ROUTER_PAGE_GROUPED_BUNDLE_ID = "ecopages-react-router-pages";
|
|
11
15
|
constructor(config) {
|
|
12
16
|
this.config = config;
|
|
13
17
|
}
|
|
18
|
+
getIslandBundleName(componentFile) {
|
|
19
|
+
return `ecopages-react-island-${rapidhash(componentFile)}`;
|
|
20
|
+
}
|
|
21
|
+
getIslandHydrationName(bundleName, componentKey) {
|
|
22
|
+
return `${bundleName}-hydration-${componentKey}`;
|
|
23
|
+
}
|
|
24
|
+
getRouterPageGroupedEntryName(pagePath) {
|
|
25
|
+
const relativePath = path.relative(this.config.srcDir, pagePath);
|
|
26
|
+
return relativePath.replace(/\.(tsx?|jsx?|mdx?)$/, "").replace(/[\\/]+/g, "__").replace(/\[([^\]]+)\]/g, "_$1_");
|
|
27
|
+
}
|
|
14
28
|
/**
|
|
15
|
-
* Resolves the import path for
|
|
29
|
+
* Resolves the browser import path used for a React-owned page or island module.
|
|
16
30
|
* Uses HMR manager for development or constructs static path for production.
|
|
17
31
|
*
|
|
18
32
|
* @param pagePath - Absolute path to the page source file
|
|
19
|
-
* @param
|
|
20
|
-
* @returns The resolved import path for the
|
|
33
|
+
* @param assetName - Generated asset name
|
|
34
|
+
* @returns The resolved browser import path for the module
|
|
21
35
|
*/
|
|
22
|
-
async resolveAssetImportPath(pagePath,
|
|
36
|
+
async resolveAssetImportPath(pagePath, assetName) {
|
|
23
37
|
const hmrManager = this.config.assetProcessingService?.getHmrManager();
|
|
24
38
|
if (hmrManager?.isEnabled()) {
|
|
25
39
|
return hmrManager.registerEntrypoint(pagePath);
|
|
26
40
|
}
|
|
27
|
-
return `/${path.join(RESOLVED_ASSETS_DIR, path.relative(this.config.srcDir, pagePath)).replace(path.basename(pagePath), `${
|
|
41
|
+
return `/${path.join(RESOLVED_ASSETS_DIR, path.relative(this.config.srcDir, pagePath)).replace(path.basename(pagePath), `${assetName}.js`).replace(/\\/g, "/")}`;
|
|
28
42
|
}
|
|
29
43
|
/**
|
|
30
|
-
* Creates the
|
|
44
|
+
* Creates the page-owned route entry asset for hydration and client navigation.
|
|
31
45
|
*
|
|
32
46
|
* @param pagePath - Absolute path to the page source file
|
|
33
47
|
* @param componentName - Generated unique component name
|
|
34
|
-
* @param importPath - Resolved import path
|
|
48
|
+
* @param importPath - Resolved browser import path used by development HMR
|
|
35
49
|
* @param bundleOptions - Bundle configuration options
|
|
36
50
|
* @param isDevelopment - Whether running in development mode with HMR
|
|
37
51
|
* @param isMdx - Whether the source file is an MDX file
|
|
38
|
-
* @
|
|
39
|
-
* @returns Array of asset definitions for processing
|
|
52
|
+
* @returns One page-owned asset definition for processing
|
|
40
53
|
*/
|
|
41
|
-
createPageDependencies(pagePath, componentName, importPath, bundleOptions, isDevelopment,
|
|
54
|
+
createPageDependencies(pagePath, componentName, importPath, pageModuleUrlExpression, bundleOptions, isDevelopment, useBrowserRuntimeImports, isMdx) {
|
|
42
55
|
const runtimeImports = this.config.bundleService.getRuntimeImports();
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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_PAGES__=window.__ECO_PAGES__||{};window.__ECO_PAGES__.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(
|
|
56
|
+
const groupedBundle = this.config.routerAdapter ? {
|
|
57
|
+
id: ReactHydrationAssetService.ROUTER_PAGE_GROUPED_BUNDLE_ID,
|
|
58
|
+
entryName: this.getRouterPageGroupedEntryName(pagePath)
|
|
59
|
+
} : void 0;
|
|
60
|
+
return [
|
|
72
61
|
AssetFactory.createContentScript({
|
|
73
62
|
position: "head",
|
|
74
63
|
content: createHydrationScript({
|
|
75
|
-
importPath,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
importPath: isDevelopment ? importPath : pagePath,
|
|
65
|
+
pageModuleUrlExpression,
|
|
66
|
+
reactImportPath: useBrowserRuntimeImports ? runtimeImports.react : "react",
|
|
67
|
+
reactDomClientImportPath: useBrowserRuntimeImports ? runtimeImports.reactDomClient : "react-dom/client",
|
|
68
|
+
routerImportPath: useBrowserRuntimeImports ? runtimeImports.router : this.config.routerAdapter?.bundle.importPath,
|
|
79
69
|
isDevelopment,
|
|
80
70
|
isMdx,
|
|
81
|
-
router: this.config.routerAdapter
|
|
71
|
+
router: this.config.routerAdapter,
|
|
72
|
+
scriptId: componentName
|
|
82
73
|
}),
|
|
83
|
-
name:
|
|
84
|
-
|
|
74
|
+
name: componentName,
|
|
75
|
+
packageRole: "page-script",
|
|
76
|
+
bundle: !isDevelopment,
|
|
77
|
+
groupedBundle,
|
|
78
|
+
bundleOptions,
|
|
85
79
|
attributes: {
|
|
86
80
|
type: "module",
|
|
87
81
|
defer: "",
|
|
88
82
|
"data-eco-rerun": "true",
|
|
89
|
-
"data-eco-script-id":
|
|
83
|
+
"data-eco-script-id": componentName,
|
|
84
|
+
...this.config.routerAdapter ? { "data-eco-page-bootstrap": "react-router" } : {},
|
|
90
85
|
"data-eco-persist": "true"
|
|
91
86
|
}
|
|
92
87
|
})
|
|
93
|
-
|
|
94
|
-
return dependencies;
|
|
88
|
+
];
|
|
95
89
|
}
|
|
96
90
|
/**
|
|
97
91
|
* Builds client-side assets for a React component island.
|
|
98
92
|
*
|
|
99
|
-
* Includes the bundled component entry and
|
|
93
|
+
* Includes the bundled component entry and a shared hydration bootstrap script.
|
|
100
94
|
*
|
|
101
95
|
* @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
96
|
* @param config - Optional component config with `__eco` metadata
|
|
105
97
|
* @returns Processed assets ready for injection
|
|
106
98
|
*/
|
|
107
|
-
async buildComponentRenderAssets(componentFile,
|
|
108
|
-
const componentName =
|
|
109
|
-
const
|
|
99
|
+
async buildComponentRenderAssets(componentFile, config) {
|
|
100
|
+
const componentName = this.getIslandBundleName(componentFile);
|
|
101
|
+
const componentKey = getReactIslandComponentKey(componentFile, config);
|
|
102
|
+
const hydrationName = this.getIslandHydrationName(componentName, componentKey);
|
|
110
103
|
const hmrManager = this.config.assetProcessingService?.getHmrManager();
|
|
111
104
|
const isDevelopment = hmrManager?.isEnabled() ?? false;
|
|
105
|
+
if (isDevelopment) {
|
|
106
|
+
this.config.hmrPageMetadataCache?.markOwnedEntrypoint(componentFile);
|
|
107
|
+
}
|
|
108
|
+
const importPath = await this.resolveAssetImportPath(componentFile, componentName);
|
|
112
109
|
const declaredModules = collectDeclaredModulesInConfig(config);
|
|
113
110
|
const bundleOptions = await this.config.bundleService.createBundleOptions(
|
|
114
111
|
componentName,
|
|
@@ -121,6 +118,7 @@ class ReactHydrationAssetService {
|
|
|
121
118
|
position: "head",
|
|
122
119
|
filepath: componentFile,
|
|
123
120
|
name: componentName,
|
|
121
|
+
packageRole: "dynamic-chunk",
|
|
124
122
|
excludeFromHtml: true,
|
|
125
123
|
bundle: true,
|
|
126
124
|
bundleOptions,
|
|
@@ -134,21 +132,22 @@ class ReactHydrationAssetService {
|
|
|
134
132
|
position: "head",
|
|
135
133
|
content: createIslandHydrationScript({
|
|
136
134
|
importPath,
|
|
135
|
+
scriptId: hydrationName,
|
|
137
136
|
reactImportPath: runtimeImports.react,
|
|
138
137
|
reactDomClientImportPath: runtimeImports.reactDomClient,
|
|
139
|
-
targetSelector: `[data-eco-component-
|
|
140
|
-
props,
|
|
138
|
+
targetSelector: `[data-eco-component-key="${componentKey}"]`,
|
|
141
139
|
componentRef: config?.__eco?.id,
|
|
142
140
|
componentFile,
|
|
143
141
|
isDevelopment
|
|
144
142
|
}),
|
|
145
|
-
name:
|
|
143
|
+
name: hydrationName,
|
|
144
|
+
packageRole: "keep-separate",
|
|
146
145
|
bundle: false,
|
|
147
146
|
attributes: {
|
|
148
147
|
type: "module",
|
|
149
148
|
defer: "",
|
|
150
149
|
"data-eco-rerun": "true",
|
|
151
|
-
"data-eco-script-id":
|
|
150
|
+
"data-eco-script-id": hydrationName,
|
|
152
151
|
"data-eco-persist": "true"
|
|
153
152
|
}
|
|
154
153
|
})
|
|
@@ -159,34 +158,53 @@ class ReactHydrationAssetService {
|
|
|
159
158
|
return this.config.assetProcessingService.processDependencies(dependencies, componentName);
|
|
160
159
|
}
|
|
161
160
|
/**
|
|
162
|
-
*
|
|
161
|
+
* Creates the Page Browser Graph dependency declarations for a React page.
|
|
163
162
|
*
|
|
164
163
|
* @param pagePath - Absolute file path of the page
|
|
165
164
|
* @param isMdx - Whether the page is an MDX file
|
|
166
165
|
* @param declaredModules - Explicitly declared browser module specifiers
|
|
167
|
-
* @returns
|
|
166
|
+
* @returns Declarative assets for core-owned processing
|
|
168
167
|
*/
|
|
169
|
-
async
|
|
168
|
+
async createPageBrowserGraphDependencies(pagePath, isMdx, declaredModules) {
|
|
170
169
|
const componentName = `ecopages-react-${rapidhash(pagePath)}`;
|
|
171
170
|
const hmrManager = this.config.assetProcessingService?.getHmrManager();
|
|
172
171
|
const isDevelopment = hmrManager?.isEnabled() ?? false;
|
|
172
|
+
const isHostedDevelopment = !isDevelopment && process.env.NODE_ENV !== "production";
|
|
173
|
+
const usesRouterRuntime = Boolean(this.config.routerAdapter);
|
|
174
|
+
const useBrowserRuntimeImports = isDevelopment || isHostedDevelopment || usesRouterRuntime;
|
|
173
175
|
if (isDevelopment) {
|
|
174
176
|
this.config.hmrPageMetadataCache?.setDeclaredModules(pagePath, declaredModules);
|
|
175
177
|
}
|
|
176
178
|
const importPath = await this.resolveAssetImportPath(pagePath, componentName);
|
|
179
|
+
const pageModuleUrlExpression = isDevelopment ? JSON.stringify(importPath) : "import.meta.url";
|
|
177
180
|
const bundleOptions = await this.config.bundleService.createBundleOptions(
|
|
178
181
|
componentName,
|
|
179
182
|
isMdx,
|
|
180
|
-
declaredModules
|
|
183
|
+
declaredModules,
|
|
184
|
+
{ includeRuntime: !useBrowserRuntimeImports, splitting: usesRouterRuntime }
|
|
181
185
|
);
|
|
182
186
|
const dependencies = this.createPageDependencies(
|
|
183
187
|
pagePath,
|
|
184
188
|
componentName,
|
|
185
189
|
importPath,
|
|
190
|
+
pageModuleUrlExpression,
|
|
186
191
|
bundleOptions,
|
|
187
192
|
isDevelopment,
|
|
193
|
+
useBrowserRuntimeImports,
|
|
188
194
|
isMdx
|
|
189
195
|
);
|
|
196
|
+
return dependencies;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Builds the Page Browser Graph assets for a React page.
|
|
200
|
+
*
|
|
201
|
+
* @remarks
|
|
202
|
+
* Kept as a compatibility wrapper while callers migrate to core-owned page
|
|
203
|
+
* graph assembly.
|
|
204
|
+
*/
|
|
205
|
+
async buildPageBrowserGraphAssets(pagePath, isMdx, declaredModules) {
|
|
206
|
+
const componentName = `ecopages-react-${rapidhash(pagePath)}`;
|
|
207
|
+
const dependencies = await this.createPageBrowserGraphDependencies(pagePath, isMdx, declaredModules);
|
|
190
208
|
if (!this.config.assetProcessingService) {
|
|
191
209
|
throw new Error("AssetProcessingService is not set");
|
|
192
210
|
}
|
|
@@ -194,5 +212,6 @@ class ReactHydrationAssetService {
|
|
|
194
212
|
}
|
|
195
213
|
}
|
|
196
214
|
export {
|
|
197
|
-
ReactHydrationAssetService
|
|
215
|
+
ReactHydrationAssetService,
|
|
216
|
+
getReactIslandComponentKey
|
|
198
217
|
};
|