@ecopages/react 0.2.0-alpha.50 → 0.2.0-alpha.51

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/react",
3
- "version": "0.2.0-alpha.50",
3
+ "version": "0.2.0-alpha.51",
4
4
  "description": "React integration for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -31,6 +31,10 @@
31
31
  "types": "./src/utils/client-only.d.ts",
32
32
  "default": "./src/utils/client-only.js"
33
33
  },
34
+ "./runtime/use-sync-external-store-with-selector": {
35
+ "types": "./src/runtime/use-sync-external-store-with-selector.d.ts",
36
+ "default": "./src/runtime/use-sync-external-store-with-selector.js"
37
+ },
34
38
  "./router-adapter": {
35
39
  "types": "./src/router-adapter.d.ts",
36
40
  "default": "./src/router-adapter.js"
@@ -50,6 +54,10 @@
50
54
  "types": "./src/utils/client-only.d.ts",
51
55
  "default": "./src/utils/client-only.js"
52
56
  },
57
+ "./runtime/use-sync-external-store-with-selector.ts": {
58
+ "types": "./src/runtime/use-sync-external-store-with-selector.d.ts",
59
+ "default": "./src/runtime/use-sync-external-store-with-selector.js"
60
+ },
53
61
  "./router-adapter.ts": {
54
62
  "types": "./src/router-adapter.d.ts",
55
63
  "default": "./src/router-adapter.js"
@@ -61,14 +69,14 @@
61
69
  "directory": "packages/integrations/react"
62
70
  },
63
71
  "peerDependencies": {
64
- "@ecopages/core": "0.2.0-alpha.50",
72
+ "@ecopages/core": "0.2.0-alpha.51",
65
73
  "@types/react": "^19",
66
74
  "@types/react-dom": "^19",
67
75
  "react": "^19",
68
76
  "react-dom": "^19"
69
77
  },
70
78
  "dependencies": {
71
- "@ecopages/file-system": "0.2.0-alpha.50",
79
+ "@ecopages/file-system": "0.2.0-alpha.51",
72
80
  "@ecopages/logger": "^0.2.3",
73
81
  "@mdx-js/esbuild": "^3.1.1",
74
82
  "@mdx-js/mdx": "^3.1.1",
@@ -7,13 +7,14 @@
7
7
  * @module
8
8
  */
9
9
  import { HmrStrategy, type HmrAction } from '@ecopages/core/hmr/hmr-strategy';
10
+ import type { BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
10
11
  import type { DefaultHmrContext } from '@ecopages/core';
11
12
  import type { CompileOptions } from '@mdx-js/mdx';
12
13
  import type { ReactHmrPageMetadataCache } from './services/react-hmr-page-metadata-cache.js';
13
14
  export interface ReactHmrStrategyOptions {
14
15
  context: DefaultHmrContext;
15
16
  pageMetadataCache: ReactHmrPageMetadataCache;
16
- runtimeAliasMap: ReadonlyMap<string, string>;
17
+ runtimeManifest: BrowserRuntimeManifest;
17
18
  mdxCompilerOptions?: CompileOptions;
18
19
  ownedTemplateExtensions?: string[];
19
20
  allTemplateExtensions?: string[];
@@ -56,7 +57,7 @@ export interface ReactHmrStrategyOptions {
56
57
  * const strategy = new ReactHmrStrategy({
57
58
  * context,
58
59
  * pageMetadataCache,
59
- * runtimeAliasMap
60
+ * runtimeManifest
60
61
  * });
61
62
  * ```
62
63
  */
@@ -74,13 +75,17 @@ export declare class ReactHmrStrategy extends HmrStrategy {
74
75
  private context;
75
76
  private pageMetadataCache;
76
77
  private explicitGraphEnabled;
77
- private readonly runtimeAliasMap;
78
+ private readonly runtimeManifest;
78
79
  constructor(options: ReactHmrStrategyOptions);
79
80
  /**
80
81
  * Returns build plugins for React HMR bundling.
81
82
  *
82
83
  * Includes the client graph boundary plugin to prevent undeclared imports
83
84
  * (including `node:*`) from breaking the browser bundle.
85
+ *
86
+ * @remarks
87
+ * HMR builds receive the React runtime manifest and rewrite manifest-owned
88
+ * runtime imports to concrete asset URLs before module resolution.
84
89
  */
85
90
  private getBuildPlugins;
86
91
  private isReactEntrypoint;
@@ -1,8 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { HmrStrategy, HmrStrategyType } from "@ecopages/core/hmr/hmr-strategy";
3
3
  import { RESOLVED_ASSETS_DIR } from "@ecopages/core/constants";
4
- import { rewriteRuntimeSpecifierAliases } from "@ecopages/core/build/runtime-specifier-aliases";
5
- import { createRuntimeSpecifierAliasPlugin } from "@ecopages/core/build/runtime-specifier-alias-plugin";
4
+ import { createBrowserRuntimeImportRewritePlugin } from "@ecopages/core/build/browser-runtime-import-rewrite-plugin";
6
5
  import { FileNotFoundError, fileSystem } from "@ecopages/file-system";
7
6
  import { Logger } from "@ecopages/logger";
8
7
  import { injectHmrHandler } from "./utils/hmr-scripts.js";
@@ -10,7 +9,6 @@ import { createClientGraphBoundaryPlugin } from "./utils/client-graph-boundary-p
10
9
  import { collectPageDeclaredModules, collectPageDeclaredModulesFromModule } from "./utils/declared-modules.js";
11
10
  import { createReactMdxLoaderPlugin } from "./utils/react-mdx-loader-plugin.js";
12
11
  import { getReactClientGraphAllowSpecifiers } from "./utils/react-runtime-alias-map.js";
13
- import { createUseSyncExternalStoreShimPlugin } from "./utils/use-sync-external-store-shim-plugin.js";
14
12
  const appLogger = new Logger("[ReactHmrStrategy]");
15
13
  class ReactHmrStrategy extends HmrStrategy {
16
14
  type = HmrStrategyType.INTEGRATION;
@@ -28,12 +26,12 @@ class ReactHmrStrategy extends HmrStrategy {
28
26
  context;
29
27
  pageMetadataCache;
30
28
  explicitGraphEnabled;
31
- runtimeAliasMap;
29
+ runtimeManifest;
32
30
  constructor(options) {
33
31
  super();
34
32
  this.context = options.context;
35
33
  this.pageMetadataCache = options.pageMetadataCache;
36
- this.runtimeAliasMap = options.runtimeAliasMap;
34
+ this.runtimeManifest = options.runtimeManifest;
37
35
  this.explicitGraphEnabled = options.explicitGraphEnabled ?? false;
38
36
  this.mdxCompilerOptions = options.mdxCompilerOptions;
39
37
  this.ownedTemplateExtensions = new Set(options.ownedTemplateExtensions ?? [".tsx"]);
@@ -46,11 +44,18 @@ class ReactHmrStrategy extends HmrStrategy {
46
44
  *
47
45
  * Includes the client graph boundary plugin to prevent undeclared imports
48
46
  * (including `node:*`) from breaking the browser bundle.
47
+ *
48
+ * @remarks
49
+ * HMR builds receive the React runtime manifest and rewrite manifest-owned
50
+ * runtime imports to concrete asset URLs before module resolution.
49
51
  */
50
52
  getBuildPlugins(declaredModules) {
51
- const allowSpecifiers = getReactClientGraphAllowSpecifiers(this.runtimeAliasMap.keys());
52
- const runtimeAliasPlugin = createRuntimeSpecifierAliasPlugin(this.runtimeAliasMap, {
53
- name: "react-hmr-runtime-specifier-alias"
53
+ const allowSpecifiers = getReactClientGraphAllowSpecifiers(
54
+ this.runtimeManifest.assets.map((asset) => asset.specifier)
55
+ );
56
+ const runtimeRewritePlugin = createBrowserRuntimeImportRewritePlugin({
57
+ name: "react-hmr-runtime-import-rewrite",
58
+ manifest: this.runtimeManifest
54
59
  });
55
60
  return [
56
61
  createClientGraphBoundaryPlugin({
@@ -58,12 +63,8 @@ class ReactHmrStrategy extends HmrStrategy {
58
63
  alwaysAllowSpecifiers: allowSpecifiers,
59
64
  declaredModules
60
65
  }),
61
- ...runtimeAliasPlugin ? [runtimeAliasPlugin] : [],
62
- ...this.context.getPlugins(),
63
- createUseSyncExternalStoreShimPlugin({
64
- name: "react-hmr-use-sync-external-store-shim",
65
- namespace: "ecopages-react-hmr-shim"
66
- })
66
+ ...runtimeRewritePlugin ? [runtimeRewritePlugin] : [],
67
+ ...this.context.getPlugins()
67
68
  ];
68
69
  }
69
70
  isReactEntrypoint(filePath) {
@@ -469,7 +470,6 @@ class ReactHmrStrategy extends HmrStrategy {
469
470
  }
470
471
  try {
471
472
  let code = await fileSystem.readFile(tempPath);
472
- code = rewriteRuntimeSpecifierAliases(code, this.runtimeAliasMap);
473
473
  code = this.rewriteChunkImportUrls(code);
474
474
  code = injectHmrHandler(code);
475
475
  await fileSystem.writeAsync(finalPath, code);
@@ -1,4 +1,5 @@
1
1
  import { IntegrationPlugin, type EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
2
+ import type { BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
2
3
  import type { HmrStrategy } from '@ecopages/core/hmr/hmr-strategy';
3
4
  import type React from 'react';
4
5
  import { ReactRenderer } from './react-renderer.js';
@@ -40,6 +41,7 @@ export declare class ReactPlugin extends IntegrationPlugin<React.ReactNode> {
40
41
  }): ReactRenderer;
41
42
  private ensureRuntimeDependencies;
42
43
  get plugins(): EcoBuildPlugin[];
44
+ get browserRuntimeManifest(): BrowserRuntimeManifest;
43
45
  /**
44
46
  * Ensures the optional React MDX loader exists before either config-time
45
47
  * manifest sealing or runtime setup needs it.
@@ -135,6 +135,10 @@ class ReactPlugin extends IntegrationPlugin {
135
135
  }
136
136
  return [];
137
137
  }
138
+ get browserRuntimeManifest() {
139
+ this.ensureRuntimeDependencies();
140
+ return this.runtimeBundleService.getRuntimeManifest();
141
+ }
138
142
  /**
139
143
  * Ensures the optional React MDX loader exists before either config-time
140
144
  * manifest sealing or runtime setup needs it.
@@ -179,7 +183,7 @@ class ReactPlugin extends IntegrationPlugin {
179
183
  return new ReactHmrStrategy({
180
184
  context,
181
185
  pageMetadataCache: this.hmrPageMetadataCache,
182
- runtimeAliasMap: new Map(Object.entries(this.runtimeBundleService.getRuntimeAliasMap("development"))),
186
+ runtimeManifest: this.runtimeBundleService.getRuntimeManifest("development"),
183
187
  mdxCompilerOptions: this.mdxCompilerOptions,
184
188
  ownedTemplateExtensions: this.extensions,
185
189
  allTemplateExtensions: this.appConfig.templatesExt,
@@ -0,0 +1,3 @@
1
+ type Subscribe = (onStoreChange: () => void) => () => void;
2
+ export declare function useSyncExternalStoreWithSelector<Snapshot, Selection>(subscribe: Subscribe, getSnapshot: () => Snapshot, getServerSnapshot: (() => Snapshot) | undefined, selector: (snapshot: Snapshot) => Selection, isEqual?: (left: Selection, right: Selection) => boolean): Selection;
3
+ export {};
@@ -0,0 +1,56 @@
1
+ import { useDebugValue, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
2
+ const objectIs = Object.is;
3
+ function useSyncExternalStoreWithSelector(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
4
+ const instRef = useRef({ hasValue: false, value: void 0 });
5
+ const memoizedSelectionRef = useMemo(() => {
6
+ let hasMemo = false;
7
+ let memoizedSnapshot;
8
+ let memoizedSelection;
9
+ const maybeGetServerSnapshot = getServerSnapshot === void 0 ? null : getServerSnapshot;
10
+ const memoizedSelector = (nextSnapshot) => {
11
+ if (!hasMemo) {
12
+ hasMemo = true;
13
+ memoizedSnapshot = nextSnapshot;
14
+ const nextSelection2 = selector(nextSnapshot);
15
+ const inst = instRef.current;
16
+ if (isEqual !== void 0 && inst.hasValue) {
17
+ const currentSelection2 = inst.value;
18
+ if (isEqual(currentSelection2, nextSelection2)) {
19
+ memoizedSelection = currentSelection2;
20
+ return currentSelection2;
21
+ }
22
+ }
23
+ memoizedSelection = nextSelection2;
24
+ return nextSelection2;
25
+ }
26
+ const currentSelection = memoizedSelection;
27
+ if (objectIs(memoizedSnapshot, nextSnapshot)) {
28
+ return currentSelection;
29
+ }
30
+ const nextSelection = selector(nextSnapshot);
31
+ if (isEqual !== void 0 && isEqual(currentSelection, nextSelection)) {
32
+ memoizedSnapshot = nextSnapshot;
33
+ return currentSelection;
34
+ }
35
+ memoizedSnapshot = nextSnapshot;
36
+ memoizedSelection = nextSelection;
37
+ return nextSelection;
38
+ };
39
+ return [
40
+ () => memoizedSelector(getSnapshot()),
41
+ maybeGetServerSnapshot === null ? void 0 : () => memoizedSelector(maybeGetServerSnapshot())
42
+ ];
43
+ }, [getSnapshot, getServerSnapshot, selector, isEqual]);
44
+ const value = useSyncExternalStore(subscribe, memoizedSelectionRef[0], memoizedSelectionRef[1]);
45
+ useEffect(() => {
46
+ instRef.current = {
47
+ hasValue: true,
48
+ value
49
+ };
50
+ }, [value]);
51
+ useDebugValue(value);
52
+ return value;
53
+ }
54
+ export {
55
+ useSyncExternalStoreWithSelector
56
+ };
@@ -47,15 +47,14 @@ export declare class ReactBundleService {
47
47
  /**
48
48
  * Creates esbuild bundle options for a page or component entry.
49
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.
53
+ *
50
54
  * @param componentName - Generated unique component name for output naming
51
55
  * @param isMdx - Whether the source file is an MDX file
52
56
  * @param declaredModules - Explicitly declared browser module specifiers
53
57
  * @returns Bundle options object for the build adapter
54
58
  */
55
59
  createBundleOptions(componentName: string, isMdx: boolean, declaredModules: string[], bundleOptions?: ReactClientBundleOptions): Promise<Record<string, unknown>>;
56
- /**
57
- * Creates the esbuild plugin that rewrites bare React specifiers
58
- * to their runtime asset URLs.
59
- */
60
- createRuntimeAliasPlugin(runtimeAliasMap: Record<string, string>): import("@ecopages/core/build/build-types").EcoBuildPlugin | null;
61
60
  }
@@ -1,11 +1,9 @@
1
1
  import { createClientGraphBoundaryPlugin } from "../utils/client-graph-boundary-plugin.js";
2
2
  import {
3
- buildReactRuntimeAliasMap,
4
3
  getReactClientGraphAllowSpecifiers,
5
4
  getReactRuntimeExternalSpecifiers
6
5
  } 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";
6
+ import { createBrowserRuntimeImportRewritePlugin } from "@ecopages/core/build/browser-runtime-import-rewrite-plugin";
9
7
  import { createForeignJsxOverridePlugin } from "@ecopages/core/plugins/foreign-jsx-override-plugin";
10
8
  import { ReactRuntimeBundleService } from "./react-runtime-bundle.service.js";
11
9
  import { createReactMdxLoaderPlugin } from "../utils/react-mdx-loader-plugin.js";
@@ -28,6 +26,10 @@ class ReactBundleService {
28
26
  /**
29
27
  * Creates esbuild bundle options for a page or component entry.
30
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.
32
+ *
31
33
  * @param componentName - Generated unique component name for output naming
32
34
  * @param isMdx - Whether the source file is an MDX file
33
35
  * @param declaredModules - Explicitly declared browser module specifiers
@@ -64,37 +66,20 @@ class ReactBundleService {
64
66
  hostJsxImportSource: this.config.jsxImportSource ?? "react",
65
67
  foreignExtensions: this.config.nonReactExtensions ?? []
66
68
  });
67
- const useSyncExternalStoreShimPlugin = createUseSyncExternalStoreShimPlugin({
68
- name: "react-renderer-use-sync-external-store-shim",
69
- namespace: "ecopages-react-renderer-shim"
69
+ const runtimeManifest = this.runtimeBundleService.getRuntimeManifest();
70
+ const runtimeRewritePlugin = createBrowserRuntimeImportRewritePlugin({
71
+ name: "react-renderer-runtime-import-rewrite",
72
+ manifest: runtimeManifest
70
73
  });
71
- const runtimePlugins = bundleOptions.includeRuntime ? [] : [this.createRuntimeAliasPlugin(buildReactRuntimeAliasMap(runtimeImports))];
74
+ const runtimePlugins = bundleOptions.includeRuntime ? [] : [runtimeRewritePlugin].filter((plugin) => plugin !== null);
72
75
  if (isMdx && this.config.mdxCompilerOptions) {
73
76
  const mdxPlugin = createReactMdxLoaderPlugin(this.config.mdxCompilerOptions);
74
- options.plugins = [
75
- foreignJsxOverridePlugin,
76
- graphBoundaryPlugin,
77
- ...runtimePlugins,
78
- mdxPlugin,
79
- useSyncExternalStoreShimPlugin
80
- ];
77
+ options.plugins = [foreignJsxOverridePlugin, graphBoundaryPlugin, ...runtimePlugins, mdxPlugin];
81
78
  } else {
82
- options.plugins = [
83
- foreignJsxOverridePlugin,
84
- graphBoundaryPlugin,
85
- ...runtimePlugins,
86
- useSyncExternalStoreShimPlugin
87
- ];
79
+ options.plugins = [foreignJsxOverridePlugin, graphBoundaryPlugin, ...runtimePlugins];
88
80
  }
89
81
  return options;
90
82
  }
91
- /**
92
- * Creates the esbuild plugin that rewrites bare React specifiers
93
- * to their runtime asset URLs.
94
- */
95
- createRuntimeAliasPlugin(runtimeAliasMap) {
96
- return createRuntimeSpecifierAliasPlugin(runtimeAliasMap, { name: "react-runtime-import-alias" });
97
- }
98
83
  }
99
84
  export {
100
85
  ReactBundleService
@@ -9,12 +9,14 @@
9
9
  import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
10
10
  import { type AssetDefinition } from '@ecopages/core/services/asset-processing-service';
11
11
  import type { ReactRouterAdapter } from '../router-adapter.js';
12
+ import { type BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
12
13
  export type ReactRuntimeImports = {
13
14
  react: string;
14
15
  reactDomClient: string;
15
16
  reactJsxRuntime: string;
16
17
  reactJsxDevRuntime: string;
17
18
  reactDom: string;
19
+ useSyncExternalStoreWithSelector: string;
18
20
  router?: string;
19
21
  };
20
22
  export interface ReactRuntimeBundleServiceConfig {
@@ -32,8 +34,11 @@ export declare class ReactRuntimeBundleService {
32
34
  private getReactVendorFileName;
33
35
  private getReactDomVendorFileName;
34
36
  private getRouterVendorFileName;
37
+ private getUseSyncExternalStoreWithSelectorVendorFileName;
38
+ private createReactVendorImportRewritePlugin;
35
39
  getRuntimeImports(mode?: RuntimeMode): ReactRuntimeImports;
36
40
  getRuntimeAliasMap(mode?: RuntimeMode): Record<string, string>;
41
+ getRuntimeManifest(mode?: RuntimeMode): BrowserRuntimeManifest;
37
42
  getDependencies(): AssetDefinition[];
38
43
  createRuntimeAliasPlugin(mode?: RuntimeMode): EcoBuildPlugin;
39
44
  }
@@ -1,3 +1,7 @@
1
+ import {
2
+ createBrowserRuntimeImportRewritePlugin,
3
+ DEFAULT_BROWSER_RUNTIME_IMPORT_REWRITE_PLUGIN_NAME
4
+ } from "@ecopages/core/build/browser-runtime-import-rewrite-plugin";
1
5
  import { createRuntimeSpecifierAliasPlugin } from "@ecopages/core/build/runtime-specifier-alias-plugin";
2
6
  import {
3
7
  buildBrowserRuntimeAssetUrl,
@@ -5,7 +9,10 @@ import {
5
9
  createBrowserRuntimeScriptAsset
6
10
  } from "@ecopages/core/services/asset-processing-service";
7
11
  import { createReactDomRuntimeInteropPlugin } from "../utils/react-dom-runtime-interop-plugin.js";
8
- import { buildReactRuntimeAliasMap } from "../utils/react-runtime-alias-map.js";
12
+ import { buildReactRuntimeAliasMap, buildReactRuntimeManifest } from "../utils/react-runtime-alias-map.js";
13
+ import {
14
+ createBrowserRuntimeManifest
15
+ } from "@ecopages/core/build/browser-runtime-manifest";
9
16
  class ReactRuntimeBundleService {
10
17
  config;
11
18
  constructor(config) {
@@ -39,6 +46,22 @@ class ReactRuntimeBundleService {
39
46
  }
40
47
  return mode === "development" ? `${this.config.routerAdapter.bundle.outputName}.development.js` : `${this.config.routerAdapter.bundle.outputName}.js`;
41
48
  }
49
+ getUseSyncExternalStoreWithSelectorVendorFileName(mode) {
50
+ return mode === "development" ? "use-sync-external-store-with-selector.development.js" : "use-sync-external-store-with-selector.js";
51
+ }
52
+ createReactVendorImportRewritePlugin(mode) {
53
+ return createBrowserRuntimeImportRewritePlugin({
54
+ name: `react-plugin-vendor-runtime-import-rewrite-${mode}`,
55
+ manifest: createBrowserRuntimeManifest([
56
+ {
57
+ specifier: "react",
58
+ owner: "@ecopages/react",
59
+ importPath: "react",
60
+ publicPath: buildBrowserRuntimeAssetUrl(this.getReactVendorFileName(mode))
61
+ }
62
+ ])
63
+ });
64
+ }
42
65
  getRuntimeImports(mode = this.getCurrentRuntimeMode()) {
43
66
  const reactVendorFileName = this.getReactVendorFileName(mode);
44
67
  const reactDomVendorFileName = this.getReactDomVendorFileName(mode);
@@ -47,7 +70,10 @@ class ReactRuntimeBundleService {
47
70
  reactDomClient: buildBrowserRuntimeAssetUrl(reactDomVendorFileName),
48
71
  reactJsxRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
49
72
  reactJsxDevRuntime: buildBrowserRuntimeAssetUrl(reactVendorFileName),
50
- reactDom: buildBrowserRuntimeAssetUrl(reactDomVendorFileName)
73
+ reactDom: buildBrowserRuntimeAssetUrl(reactDomVendorFileName),
74
+ useSyncExternalStoreWithSelector: buildBrowserRuntimeAssetUrl(
75
+ this.getUseSyncExternalStoreWithSelectorVendorFileName(mode)
76
+ )
51
77
  };
52
78
  if (this.config.routerAdapter) {
53
79
  runtimeImports.router = buildBrowserRuntimeAssetUrl(this.getRouterVendorFileName(mode));
@@ -57,10 +83,16 @@ class ReactRuntimeBundleService {
57
83
  getRuntimeAliasMap(mode = this.getCurrentRuntimeMode()) {
58
84
  return buildReactRuntimeAliasMap(this.getRuntimeImports(mode));
59
85
  }
86
+ getRuntimeManifest(mode = this.getCurrentRuntimeMode()) {
87
+ return buildReactRuntimeManifest(this.getRuntimeImports(mode));
88
+ }
60
89
  getDependencies() {
61
- const reactDomRuntimeInteropPlugin = createReactDomRuntimeInteropPlugin();
62
90
  const dependencies = [];
63
91
  for (const mode of ["production", "development"]) {
92
+ const reactVendorImportRewritePlugin = this.createReactVendorImportRewritePlugin(mode);
93
+ const reactDomRuntimeInteropPlugin = createReactDomRuntimeInteropPlugin({
94
+ reactSpecifier: buildBrowserRuntimeAssetUrl(this.getReactVendorFileName(mode))
95
+ });
64
96
  const reactRuntimeAliasPlugin = createRuntimeSpecifierAliasPlugin(
65
97
  {
66
98
  react: buildBrowserRuntimeAssetUrl(this.getReactVendorFileName(mode))
@@ -84,7 +116,8 @@ class ReactRuntimeBundleService {
84
116
  cacheDirName: `ecopages-react-runtime-${mode}`,
85
117
  rootDir: this.config.rootDir,
86
118
  bundleOptions: {
87
- define: this.createRuntimeDefines(mode)
119
+ define: this.createRuntimeDefines(mode),
120
+ excludeAppBuildPlugins: [DEFAULT_BROWSER_RUNTIME_IMPORT_REWRITE_PLUGIN_NAME]
88
121
  }
89
122
  }),
90
123
  createBrowserRuntimeModuleAsset({
@@ -95,8 +128,19 @@ class ReactRuntimeBundleService {
95
128
  rootDir: this.config.rootDir,
96
129
  bundleOptions: {
97
130
  define: this.createRuntimeDefines(mode),
131
+ excludeAppBuildPlugins: [DEFAULT_BROWSER_RUNTIME_IMPORT_REWRITE_PLUGIN_NAME],
98
132
  plugins: reactDomBundlePlugins
99
133
  }
134
+ }),
135
+ createBrowserRuntimeScriptAsset({
136
+ importPath: "@ecopages/react/runtime/use-sync-external-store-with-selector",
137
+ name: "use-sync-external-store-with-selector",
138
+ fileName: this.getUseSyncExternalStoreWithSelectorVendorFileName(mode),
139
+ bundleOptions: {
140
+ define: this.createRuntimeDefines(mode),
141
+ excludeAppBuildPlugins: [DEFAULT_BROWSER_RUNTIME_IMPORT_REWRITE_PLUGIN_NAME],
142
+ plugins: [reactVendorImportRewritePlugin]
143
+ }
100
144
  })
101
145
  );
102
146
  if (this.config.routerAdapter) {
@@ -1,5 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ function escapeRegExp(value) {
4
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
3
6
  function createReactDomRuntimeInteropPlugin(options) {
4
7
  const reactDomFileFilter = /[\\/]react-dom[\\/].*\.js$/;
5
8
  const reactRequirePattern = /\brequire\((['"])react\1\)/g;
@@ -7,6 +10,12 @@ function createReactDomRuntimeInteropPlugin(options) {
7
10
  return {
8
11
  name: options?.name ?? "react-dom-runtime-interop",
9
12
  setup(build) {
13
+ if (reactSpecifier.startsWith("/")) {
14
+ build.onResolve({ filter: new RegExp(`^${escapeRegExp(reactSpecifier)}$`) }, (args) => ({
15
+ path: args.path,
16
+ external: true
17
+ }));
18
+ }
10
19
  build.onLoad({ filter: reactDomFileFilter }, (args) => {
11
20
  const content = fs.readFileSync(args.path, "utf-8");
12
21
  if (!reactRequirePattern.test(content)) {
@@ -1,6 +1,8 @@
1
1
  import type { ReactRouterAdapter } from '../router-adapter.js';
2
2
  import type { ReactRuntimeImports } from '../services/react-runtime-bundle.service.js';
3
+ import { type BrowserRuntimeManifest } from '@ecopages/core/build/browser-runtime-manifest';
3
4
  export declare const REACT_RUNTIME_SPECIFIERS: readonly ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime", "react-dom/client"];
4
5
  export declare function buildReactRuntimeAliasMap(runtimeImports: ReactRuntimeImports): Record<string, string>;
6
+ export declare function buildReactRuntimeManifest(runtimeImports: ReactRuntimeImports): BrowserRuntimeManifest;
5
7
  export declare function getReactRuntimeExternalSpecifiers(): string[];
6
8
  export declare function getReactClientGraphAllowSpecifiers(runtimeSpecifiers: Iterable<string>, routerAdapter?: ReactRouterAdapter): string[];
@@ -1,3 +1,7 @@
1
+ import {
2
+ createBrowserRuntimeManifest,
3
+ getBrowserRuntimeSpecifierMap
4
+ } from "@ecopages/core/build/browser-runtime-manifest";
1
5
  const REACT_RUNTIME_SPECIFIERS = [
2
6
  "react",
3
7
  "react-dom",
@@ -6,13 +10,65 @@ const REACT_RUNTIME_SPECIFIERS = [
6
10
  "react-dom/client"
7
11
  ];
8
12
  function buildReactRuntimeAliasMap(runtimeImports) {
9
- return {
10
- react: runtimeImports.react,
11
- "react/jsx-runtime": runtimeImports.reactJsxRuntime,
12
- "react/jsx-dev-runtime": runtimeImports.reactJsxDevRuntime,
13
- "react-dom": runtimeImports.reactDom,
14
- "react-dom/client": runtimeImports.reactDomClient
15
- };
13
+ return Object.fromEntries(getBrowserRuntimeSpecifierMap(buildReactRuntimeManifest(runtimeImports)));
14
+ }
15
+ function buildReactRuntimeManifest(runtimeImports) {
16
+ return createBrowserRuntimeManifest([
17
+ {
18
+ specifier: "react",
19
+ owner: "@ecopages/react",
20
+ importPath: "react",
21
+ publicPath: runtimeImports.react
22
+ },
23
+ {
24
+ specifier: "react/jsx-runtime",
25
+ owner: "@ecopages/react",
26
+ importPath: "react/jsx-runtime",
27
+ publicPath: runtimeImports.reactJsxRuntime
28
+ },
29
+ {
30
+ specifier: "react/jsx-dev-runtime",
31
+ owner: "@ecopages/react",
32
+ importPath: "react/jsx-dev-runtime",
33
+ publicPath: runtimeImports.reactJsxDevRuntime
34
+ },
35
+ {
36
+ specifier: "react-dom",
37
+ owner: "@ecopages/react",
38
+ importPath: "react-dom",
39
+ publicPath: runtimeImports.reactDom
40
+ },
41
+ {
42
+ specifier: "react-dom/client",
43
+ owner: "@ecopages/react",
44
+ importPath: "react-dom/client",
45
+ publicPath: runtimeImports.reactDomClient
46
+ },
47
+ {
48
+ specifier: "use-sync-external-store/shim",
49
+ owner: "@ecopages/react",
50
+ importPath: "use-sync-external-store/shim",
51
+ publicPath: runtimeImports.react
52
+ },
53
+ {
54
+ specifier: "use-sync-external-store/shim/index.js",
55
+ owner: "@ecopages/react",
56
+ importPath: "use-sync-external-store/shim/index.js",
57
+ publicPath: runtimeImports.react
58
+ },
59
+ {
60
+ specifier: "use-sync-external-store/shim/with-selector",
61
+ owner: "@ecopages/react",
62
+ importPath: "use-sync-external-store/shim/with-selector",
63
+ publicPath: runtimeImports.useSyncExternalStoreWithSelector
64
+ },
65
+ {
66
+ specifier: "use-sync-external-store/shim/with-selector.js",
67
+ owner: "@ecopages/react",
68
+ importPath: "use-sync-external-store/shim/with-selector.js",
69
+ publicPath: runtimeImports.useSyncExternalStoreWithSelector
70
+ }
71
+ ]);
16
72
  }
17
73
  function getReactRuntimeExternalSpecifiers() {
18
74
  return [...REACT_RUNTIME_SPECIFIERS];
@@ -28,6 +84,7 @@ function getReactClientGraphAllowSpecifiers(runtimeSpecifiers, routerAdapter) {
28
84
  export {
29
85
  REACT_RUNTIME_SPECIFIERS,
30
86
  buildReactRuntimeAliasMap,
87
+ buildReactRuntimeManifest,
31
88
  getReactClientGraphAllowSpecifiers,
32
89
  getReactRuntimeExternalSpecifiers
33
90
  };
@@ -1,5 +0,0 @@
1
- import type { EcoBuildPlugin } from '@ecopages/core/plugins/integration-plugin';
2
- export declare function createUseSyncExternalStoreShimPlugin(options?: {
3
- name?: string;
4
- namespace?: string;
5
- }): EcoBuildPlugin;
@@ -1,41 +0,0 @@
1
- function createUseSyncExternalStoreShimPlugin(options) {
2
- const namespace = options?.namespace ?? "ecopages-react-use-sync-external-store-shim";
3
- return {
4
- name: options?.name ?? "react-use-sync-external-store-shim",
5
- setup(build) {
6
- build.onResolve({ filter: /^use-sync-external-store\/shim(?:\/index\.js)?$/ }, () => ({
7
- path: "use-sync-external-store/shim",
8
- namespace
9
- }));
10
- build.onLoad({ filter: /^use-sync-external-store\/shim$/, namespace }, () => ({
11
- contents: "export { useSyncExternalStore } from 'react';",
12
- loader: "js"
13
- }));
14
- build.onLoad({ filter: /[\\/]use-sync-external-store[\\/]shim[\\/]index\.js$/ }, () => ({
15
- contents: "export { useSyncExternalStore } from 'react';",
16
- loader: "js"
17
- }));
18
- build.onLoad(
19
- {
20
- filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.development\.js$/
21
- },
22
- () => ({
23
- contents: "export { useSyncExternalStore } from 'react';",
24
- loader: "js"
25
- })
26
- );
27
- build.onLoad(
28
- {
29
- filter: /[\\/]use-sync-external-store[\\/]cjs[\\/]use-sync-external-store-shim\.production\.js$/
30
- },
31
- () => ({
32
- contents: "export { useSyncExternalStore } from 'react';",
33
- loader: "js"
34
- })
35
- );
36
- }
37
- };
38
- }
39
- export {
40
- createUseSyncExternalStoreShimPlugin
41
- };