@ecopages/react 0.2.0-alpha.51 → 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 +3 -3
- package/src/react-hmr-strategy.d.ts +31 -5
- package/src/react-hmr-strategy.js +68 -12
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",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"directory": "packages/integrations/react"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@ecopages/core": "0.2.0-alpha.
|
|
72
|
+
"@ecopages/core": "0.2.0-alpha.52",
|
|
73
73
|
"@types/react": "^19",
|
|
74
74
|
"@types/react-dom": "^19",
|
|
75
75
|
"react": "^19",
|
|
76
76
|
"react-dom": "^19"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
79
|
+
"@ecopages/file-system": "0.2.0-alpha.52",
|
|
80
80
|
"@ecopages/logger": "^0.2.3",
|
|
81
81
|
"@mdx-js/esbuild": "^3.1.1",
|
|
82
82
|
"@mdx-js/mdx": "^3.1.1",
|
|
@@ -103,8 +103,15 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
103
103
|
/**
|
|
104
104
|
* Determines if the file is a React/MDX entrypoint that's registered for HMR.
|
|
105
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
|
+
*
|
|
106
113
|
* @param filePath - Absolute path to the changed file
|
|
107
|
-
* @returns True if this
|
|
114
|
+
* @returns True if this file should trigger React HMR rebuilds
|
|
108
115
|
*/
|
|
109
116
|
matches(filePath: string): boolean;
|
|
110
117
|
/**
|
|
@@ -123,7 +130,6 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
123
130
|
private getEntrypointOutput;
|
|
124
131
|
private getGroupedTempOutputPattern;
|
|
125
132
|
private collectReactPageBuildTargets;
|
|
126
|
-
private getRequestedTargets;
|
|
127
133
|
/**
|
|
128
134
|
* Expands one HMR request into the full React page build cohort when needed.
|
|
129
135
|
*
|
|
@@ -135,12 +141,17 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
135
141
|
private resolveBuildTargets;
|
|
136
142
|
private partitionBuildTargets;
|
|
137
143
|
/**
|
|
138
|
-
* 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
|
|
139
152
|
*
|
|
140
153
|
* For layout files, broadcasts a 'layout-update' event to trigger full page reload.
|
|
141
154
|
* For regular components/pages, broadcasts 'update' events for module-level HMR.
|
|
142
|
-
* When a page entrypoint is first registered, only that entrypoint is built.
|
|
143
|
-
* Subsequent file updates rebuild all watched React entrypoints as usual.
|
|
144
155
|
*
|
|
145
156
|
* @param _filePath - Absolute path to the changed file
|
|
146
157
|
* @returns Action to broadcast update events (layout-update for layouts, update for components)
|
|
@@ -149,11 +160,26 @@ export declare class ReactHmrStrategy extends HmrStrategy {
|
|
|
149
160
|
/**
|
|
150
161
|
* Bundles a single React/MDX entrypoint with HMR support.
|
|
151
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
|
+
*
|
|
152
168
|
* @param entrypointPath - Absolute path to the source file
|
|
153
169
|
* @param outputUrl - URL path for the bundled file
|
|
154
170
|
* @returns True if bundling was successful
|
|
155
171
|
*/
|
|
156
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
|
+
*/
|
|
157
183
|
private bundleReactEntrypoints;
|
|
158
184
|
private resolveTempOutputPath;
|
|
159
185
|
/**
|
|
@@ -106,8 +106,15 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
106
106
|
/**
|
|
107
107
|
* Determines if the file is a React/MDX entrypoint that's registered for HMR.
|
|
108
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
|
+
*
|
|
109
116
|
* @param filePath - Absolute path to the changed file
|
|
110
|
-
* @returns True if this
|
|
117
|
+
* @returns True if this file should trigger React HMR rebuilds
|
|
111
118
|
*/
|
|
112
119
|
matches(filePath) {
|
|
113
120
|
const watchedFiles = this.context.getWatchedFiles();
|
|
@@ -118,6 +125,15 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
118
125
|
if (watchedFiles.has(filePath)) {
|
|
119
126
|
return this.ownsWatchedEntrypoint(filePath);
|
|
120
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
|
+
}
|
|
121
137
|
return this.isReactEntrypoint(filePath);
|
|
122
138
|
}
|
|
123
139
|
/**
|
|
@@ -180,13 +196,6 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
180
196
|
(left, right) => left.entrypointPath.localeCompare(right.entrypointPath)
|
|
181
197
|
);
|
|
182
198
|
}
|
|
183
|
-
getRequestedTargets(changedFilePath, changedEntrypointOutput, watchedFiles) {
|
|
184
|
-
const requestedEntries = changedEntrypointOutput ? [[changedFilePath, changedEntrypointOutput]] : Array.from(watchedFiles.entries());
|
|
185
|
-
return requestedEntries.map(([entrypointPath, outputUrl]) => ({
|
|
186
|
-
entrypointPath,
|
|
187
|
-
outputUrl
|
|
188
|
-
}));
|
|
189
|
-
}
|
|
190
199
|
/**
|
|
191
200
|
* Expands one HMR request into the full React page build cohort when needed.
|
|
192
201
|
*
|
|
@@ -225,12 +234,17 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
225
234
|
};
|
|
226
235
|
}
|
|
227
236
|
/**
|
|
228
|
-
* 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
|
|
229
245
|
*
|
|
230
246
|
* For layout files, broadcasts a 'layout-update' event to trigger full page reload.
|
|
231
247
|
* For regular components/pages, broadcasts 'update' events for module-level HMR.
|
|
232
|
-
* When a page entrypoint is first registered, only that entrypoint is built.
|
|
233
|
-
* Subsequent file updates rebuild all watched React entrypoints as usual.
|
|
234
248
|
*
|
|
235
249
|
* @param _filePath - Absolute path to the changed file
|
|
236
250
|
* @returns Action to broadcast update events (layout-update for layouts, update for components)
|
|
@@ -251,7 +265,22 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
251
265
|
appLogger.debug(`Skipping non-React watched entrypoint: ${_filePath}`);
|
|
252
266
|
return { type: "none" };
|
|
253
267
|
}
|
|
254
|
-
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 }));
|
|
255
284
|
const groupedPageTargets = await this.resolveBuildTargets(requestedTargets, _filePath);
|
|
256
285
|
const { pageTargets, nonPageTargets } = this.partitionBuildTargets(requestedTargets, groupedPageTargets);
|
|
257
286
|
const updates = [];
|
|
@@ -311,6 +340,11 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
311
340
|
/**
|
|
312
341
|
* Bundles a single React/MDX entrypoint with HMR support.
|
|
313
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
|
+
*
|
|
314
348
|
* @param entrypointPath - Absolute path to the source file
|
|
315
349
|
* @param outputUrl - URL path for the bundled file
|
|
316
350
|
* @returns True if bundling was successful
|
|
@@ -338,6 +372,12 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
338
372
|
appLogger.error(`Failed to build ${entrypointPath}:`, result.logs);
|
|
339
373
|
return false;
|
|
340
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
|
+
}
|
|
341
381
|
const tempFile = result.outputs[0]?.path;
|
|
342
382
|
if (!tempFile) {
|
|
343
383
|
appLogger.error(`No output file generated for ${entrypointPath}`);
|
|
@@ -355,6 +395,16 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
355
395
|
return false;
|
|
356
396
|
}
|
|
357
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
|
+
*/
|
|
358
408
|
async bundleReactEntrypoints(entrypoints) {
|
|
359
409
|
try {
|
|
360
410
|
const declaredModules = /* @__PURE__ */ new Set();
|
|
@@ -387,6 +437,12 @@ class ReactHmrStrategy extends HmrStrategy {
|
|
|
387
437
|
appLogger.error(`Failed to build grouped React entrypoints:`, result.logs);
|
|
388
438
|
return [];
|
|
389
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
|
+
}
|
|
390
446
|
const updatedOutputs = [];
|
|
391
447
|
for (const { entrypointPath, outputUrl } of entrypoints) {
|
|
392
448
|
const { outputPath } = this.getEntrypointOutput(entrypointPath);
|