@ecopages/react 0.2.0-alpha.50 → 0.2.0-alpha.52
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 +11 -3
- package/src/react-hmr-strategy.d.ts +39 -8
- package/src/react-hmr-strategy.js +83 -27
- package/src/react.plugin.d.ts +2 -0
- package/src/react.plugin.js +5 -1
- 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/react-bundle.service.d.ts +4 -5
- package/src/services/react-bundle.service.js +12 -27
- package/src/services/react-runtime-bundle.service.d.ts +5 -0
- package/src/services/react-runtime-bundle.service.js +48 -4
- package/src/utils/react-dom-runtime-interop-plugin.js +9 -0
- package/src/utils/react-runtime-alias-map.d.ts +2 -0
- package/src/utils/react-runtime-alias-map.js +64 -7
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/react",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.52",
|
|
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.
|
|
72
|
+
"@ecopages/core": "0.2.0-alpha.52",
|
|
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.
|
|
79
|
+
"@ecopages/file-system": "0.2.0-alpha.52",
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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;
|
|
@@ -98,8 +103,15 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
98
103
|
/**
|
|
99
104
|
* Determines if the file is a React/MDX entrypoint that's registered for HMR.
|
|
100
105
|
*
|
|
106
|
+
* Uses a three-way decision strategy for selective invalidation:
|
|
107
|
+
* 1. If the file is a watched entrypoint, check if React owns it
|
|
108
|
+
* 2. If the file is a dependency of watched entrypoints (via dependency graph),
|
|
109
|
+
* check if any affected entrypoints are React-owned. Returns false if hits
|
|
110
|
+
* exist but none are owned (prevents unnecessary rebuilds).
|
|
111
|
+
* 3. Otherwise, check if the file itself is a React entrypoint template
|
|
112
|
+
*
|
|
101
113
|
* @param filePath - Absolute path to the changed file
|
|
102
|
-
* @returns True if this
|
|
114
|
+
* @returns True if this file should trigger React HMR rebuilds
|
|
103
115
|
*/
|
|
104
116
|
matches(filePath: string): boolean;
|
|
105
117
|
/**
|
|
@@ -118,7 +130,6 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
118
130
|
private getEntrypointOutput;
|
|
119
131
|
private getGroupedTempOutputPattern;
|
|
120
132
|
private collectReactPageBuildTargets;
|
|
121
|
-
private getRequestedTargets;
|
|
122
133
|
/**
|
|
123
134
|
* Expands one HMR request into the full React page build cohort when needed.
|
|
124
135
|
*
|
|
@@ -130,12 +141,17 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
130
141
|
private resolveBuildTargets;
|
|
131
142
|
private partitionBuildTargets;
|
|
132
143
|
/**
|
|
133
|
-
* Processes a React file change by rebuilding
|
|
144
|
+
* Processes a React file change by rebuilding affected React entrypoints.
|
|
145
|
+
*
|
|
146
|
+
* Uses a three-way decision strategy for selective invalidation:
|
|
147
|
+
* 1. Changed file is a watched entrypoint: rebuild only that entrypoint
|
|
148
|
+
* 2. Dependency graph has hits: rebuild only affected React-owned entrypoints.
|
|
149
|
+
* If hits exist but none map to React-owned entrypoints, return 'none' to
|
|
150
|
+
* prevent unnecessary rebuilds.
|
|
151
|
+
* 3. Dependency graph miss: fall back to rebuilding all watched entrypoints
|
|
134
152
|
*
|
|
135
153
|
* For layout files, broadcasts a 'layout-update' event to trigger full page reload.
|
|
136
154
|
* For regular components/pages, broadcasts 'update' events for module-level HMR.
|
|
137
|
-
* When a page entrypoint is first registered, only that entrypoint is built.
|
|
138
|
-
* Subsequent file updates rebuild all watched React entrypoints as usual.
|
|
139
155
|
*
|
|
140
156
|
* @param _filePath - Absolute path to the changed file
|
|
141
157
|
* @returns Action to broadcast update events (layout-update for layouts, update for components)
|
|
@@ -144,11 +160,26 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
144
160
|
/**
|
|
145
161
|
* Bundles a single React/MDX entrypoint with HMR support.
|
|
146
162
|
*
|
|
163
|
+
* After successful bundling, populates the entrypoint dependency graph with
|
|
164
|
+
* the build's dependency metadata. This enables selective invalidation on
|
|
165
|
+
* subsequent file changes, so only entrypoints affected by a changed
|
|
166
|
+
* dependency are rebuilt.
|
|
167
|
+
*
|
|
147
168
|
* @param entrypointPath - Absolute path to the source file
|
|
148
169
|
* @param outputUrl - URL path for the bundled file
|
|
149
170
|
* @returns True if bundling was successful
|
|
150
171
|
*/
|
|
151
172
|
private bundleReactEntrypoint;
|
|
173
|
+
/**
|
|
174
|
+
* Bundles multiple React/MDX entrypoints in a single build pass.
|
|
175
|
+
*
|
|
176
|
+
* Uses code splitting to share common dependencies across entrypoints.
|
|
177
|
+
* After successful bundling, populates the entrypoint dependency graph with
|
|
178
|
+
* the build's dependency metadata for selective invalidation.
|
|
179
|
+
*
|
|
180
|
+
* @param entrypoints - Array of entrypoint paths and their output URLs
|
|
181
|
+
* @returns Array of output URLs that were successfully built
|
|
182
|
+
*/
|
|
152
183
|
private bundleReactEntrypoints;
|
|
153
184
|
private resolveTempOutputPath;
|
|
154
185
|
/**
|
|
@@ -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 {
|
|
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
|
-
|
|
29
|
+
runtimeManifest;
|
|
32
30
|
constructor(options) {
|
|
33
31
|
super();
|
|
34
32
|
this.context = options.context;
|
|
35
33
|
this.pageMetadataCache = options.pageMetadataCache;
|
|
36
|
-
this.
|
|
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(
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
...
|
|
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) {
|
|
@@ -105,8 +106,15 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
105
106
|
/**
|
|
106
107
|
* Determines if the file is a React/MDX entrypoint that's registered for HMR.
|
|
107
108
|
*
|
|
109
|
+
* Uses a three-way decision strategy for selective invalidation:
|
|
110
|
+
* 1. If the file is a watched entrypoint, check if React owns it
|
|
111
|
+
* 2. If the file is a dependency of watched entrypoints (via dependency graph),
|
|
112
|
+
* check if any affected entrypoints are React-owned. Returns false if hits
|
|
113
|
+
* exist but none are owned (prevents unnecessary rebuilds).
|
|
114
|
+
* 3. Otherwise, check if the file itself is a React entrypoint template
|
|
115
|
+
*
|
|
108
116
|
* @param filePath - Absolute path to the changed file
|
|
109
|
-
* @returns True if this
|
|
117
|
+
* @returns True if this file should trigger React HMR rebuilds
|
|
110
118
|
*/
|
|
111
119
|
matches(filePath) {
|
|
112
120
|
const watchedFiles = this.context.getWatchedFiles();
|
|
@@ -117,6 +125,15 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
117
125
|
if (watchedFiles.has(filePath)) {
|
|
118
126
|
return this.ownsWatchedEntrypoint(filePath);
|
|
119
127
|
}
|
|
128
|
+
const dependencyHits = this.context.getEntrypointDependencyGraph().getDependencyEntrypoints(filePath);
|
|
129
|
+
if (dependencyHits.size > 0) {
|
|
130
|
+
for (const entrypoint of dependencyHits) {
|
|
131
|
+
if (this.ownsWatchedEntrypoint(entrypoint)) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
120
137
|
return this.isReactEntrypoint(filePath);
|
|
121
138
|
}
|
|
122
139
|
/**
|
|
@@ -179,13 +196,6 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
179
196
|
(left, right) => left.entrypointPath.localeCompare(right.entrypointPath)
|
|
180
197
|
);
|
|
181
198
|
}
|
|
182
|
-
getRequestedTargets(changedFilePath, changedEntrypointOutput, watchedFiles) {
|
|
183
|
-
const requestedEntries = changedEntrypointOutput ? [[changedFilePath, changedEntrypointOutput]] : Array.from(watchedFiles.entries());
|
|
184
|
-
return requestedEntries.map(([entrypointPath, outputUrl]) => ({
|
|
185
|
-
entrypointPath,
|
|
186
|
-
outputUrl
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
199
|
/**
|
|
190
200
|
* Expands one HMR request into the full React page build cohort when needed.
|
|
191
201
|
*
|
|
@@ -224,12 +234,17 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
224
234
|
};
|
|
225
235
|
}
|
|
226
236
|
/**
|
|
227
|
-
* Processes a React file change by rebuilding
|
|
237
|
+
* Processes a React file change by rebuilding affected React entrypoints.
|
|
238
|
+
*
|
|
239
|
+
* Uses a three-way decision strategy for selective invalidation:
|
|
240
|
+
* 1. Changed file is a watched entrypoint: rebuild only that entrypoint
|
|
241
|
+
* 2. Dependency graph has hits: rebuild only affected React-owned entrypoints.
|
|
242
|
+
* If hits exist but none map to React-owned entrypoints, return 'none' to
|
|
243
|
+
* prevent unnecessary rebuilds.
|
|
244
|
+
* 3. Dependency graph miss: fall back to rebuilding all watched entrypoints
|
|
228
245
|
*
|
|
229
246
|
* For layout files, broadcasts a 'layout-update' event to trigger full page reload.
|
|
230
247
|
* For regular components/pages, broadcasts 'update' events for module-level HMR.
|
|
231
|
-
* When a page entrypoint is first registered, only that entrypoint is built.
|
|
232
|
-
* Subsequent file updates rebuild all watched React entrypoints as usual.
|
|
233
248
|
*
|
|
234
249
|
* @param _filePath - Absolute path to the changed file
|
|
235
250
|
* @returns Action to broadcast update events (layout-update for layouts, update for components)
|
|
@@ -250,7 +265,22 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
250
265
|
appLogger.debug(`Skipping non-React watched entrypoint: ${_filePath}`);
|
|
251
266
|
return { type: "none" };
|
|
252
267
|
}
|
|
253
|
-
const
|
|
268
|
+
const dependencyHits = this.context.getEntrypointDependencyGraph().getDependencyEntrypoints(_filePath);
|
|
269
|
+
const hasDependencyHits = dependencyHits.size > 0;
|
|
270
|
+
const affectedEntrypoints = /* @__PURE__ */ new Map();
|
|
271
|
+
if (hasDependencyHits && !changedEntrypointOutput) {
|
|
272
|
+
for (const entrypoint of dependencyHits) {
|
|
273
|
+
const outputUrl = watchedFiles.get(entrypoint);
|
|
274
|
+
if (outputUrl && this.ownsWatchedEntrypoint(entrypoint)) {
|
|
275
|
+
affectedEntrypoints.set(entrypoint, outputUrl);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (affectedEntrypoints.size === 0) {
|
|
279
|
+
appLogger.debug(`Dependency hits found but none map to React-owned watched entrypoints`);
|
|
280
|
+
return { type: "none" };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const requestedTargets = changedEntrypointOutput ? [{ entrypointPath: _filePath, outputUrl: changedEntrypointOutput }] : hasDependencyHits ? Array.from(affectedEntrypoints, ([entrypointPath, outputUrl]) => ({ entrypointPath, outputUrl })) : Array.from(watchedFiles, ([entrypointPath, outputUrl]) => ({ entrypointPath, outputUrl }));
|
|
254
284
|
const groupedPageTargets = await this.resolveBuildTargets(requestedTargets, _filePath);
|
|
255
285
|
const { pageTargets, nonPageTargets } = this.partitionBuildTargets(requestedTargets, groupedPageTargets);
|
|
256
286
|
const updates = [];
|
|
@@ -310,6 +340,11 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
310
340
|
/**
|
|
311
341
|
* Bundles a single React/MDX entrypoint with HMR support.
|
|
312
342
|
*
|
|
343
|
+
* After successful bundling, populates the entrypoint dependency graph with
|
|
344
|
+
* the build's dependency metadata. This enables selective invalidation on
|
|
345
|
+
* subsequent file changes, so only entrypoints affected by a changed
|
|
346
|
+
* dependency are rebuilt.
|
|
347
|
+
*
|
|
313
348
|
* @param entrypointPath - Absolute path to the source file
|
|
314
349
|
* @param outputUrl - URL path for the bundled file
|
|
315
350
|
* @returns True if bundling was successful
|
|
@@ -337,6 +372,12 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
337
372
|
appLogger.error(`Failed to build ${entrypointPath}:`, result.logs);
|
|
338
373
|
return false;
|
|
339
374
|
}
|
|
375
|
+
if (result.dependencyGraph?.entrypoints) {
|
|
376
|
+
const dependencyGraph = this.context.getEntrypointDependencyGraph();
|
|
377
|
+
for (const [entrypoint, deps] of Object.entries(result.dependencyGraph.entrypoints)) {
|
|
378
|
+
dependencyGraph.setEntrypointDependencies(entrypoint, deps);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
340
381
|
const tempFile = result.outputs[0]?.path;
|
|
341
382
|
if (!tempFile) {
|
|
342
383
|
appLogger.error(`No output file generated for ${entrypointPath}`);
|
|
@@ -354,6 +395,16 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
354
395
|
return false;
|
|
355
396
|
}
|
|
356
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* Bundles multiple React/MDX entrypoints in a single build pass.
|
|
400
|
+
*
|
|
401
|
+
* Uses code splitting to share common dependencies across entrypoints.
|
|
402
|
+
* After successful bundling, populates the entrypoint dependency graph with
|
|
403
|
+
* the build's dependency metadata for selective invalidation.
|
|
404
|
+
*
|
|
405
|
+
* @param entrypoints - Array of entrypoint paths and their output URLs
|
|
406
|
+
* @returns Array of output URLs that were successfully built
|
|
407
|
+
*/
|
|
357
408
|
async bundleReactEntrypoints(entrypoints) {
|
|
358
409
|
try {
|
|
359
410
|
const declaredModules = /* @__PURE__ */ new Set();
|
|
@@ -386,6 +437,12 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
386
437
|
appLogger.error(`Failed to build grouped React entrypoints:`, result.logs);
|
|
387
438
|
return [];
|
|
388
439
|
}
|
|
440
|
+
if (result.dependencyGraph?.entrypoints) {
|
|
441
|
+
const dependencyGraph = this.context.getEntrypointDependencyGraph();
|
|
442
|
+
for (const [entrypoint, deps] of Object.entries(result.dependencyGraph.entrypoints)) {
|
|
443
|
+
dependencyGraph.setEntrypointDependencies(entrypoint, deps);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
389
446
|
const updatedOutputs = [];
|
|
390
447
|
for (const { entrypointPath, outputUrl } of entrypoints) {
|
|
391
448
|
const { outputPath } = this.getEntrypointOutput(entrypointPath);
|
|
@@ -469,7 +526,6 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
469
526
|
}
|
|
470
527
|
try {
|
|
471
528
|
let code = await fileSystem.readFile(tempPath);
|
|
472
|
-
code = rewriteRuntimeSpecifierAliases(code, this.runtimeAliasMap);
|
|
473
529
|
code = this.rewriteChunkImportUrls(code);
|
|
474
530
|
code = injectHmrHandler(code);
|
|
475
531
|
await fileSystem.writeAsync(finalPath, code);
|
package/src/react.plugin.d.ts
CHANGED
|
@@ -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.
|
package/src/react.plugin.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
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 ? [] : [
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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,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
|
-
};
|