@ecopages/core 0.2.0-beta.20 → 0.2.0-beta.21
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 +2 -2
- package/src/adapters/bun/hmr-manager.d.ts +1 -2
- package/src/adapters/bun/hmr-manager.js +2 -2
- package/src/adapters/bun/server-adapter.js +5 -4
- package/src/adapters/node/node-hmr-manager.d.ts +1 -2
- package/src/adapters/node/node-hmr-manager.js +2 -2
- package/src/adapters/node/server-adapter.js +5 -6
- package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +21 -28
- package/src/adapters/shared/hmr-entrypoint-registrar.js +49 -31
- package/src/adapters/shared/runtime-server-lifecycle.d.ts +9 -1
- package/src/adapters/shared/runtime-server-lifecycle.js +10 -3
- package/src/adapters/shared/server-static-builder.js +4 -1
- package/src/adapters/shared/shared-hmr-manager.d.ts +29 -23
- package/src/adapters/shared/shared-hmr-manager.js +144 -108
- package/src/build/app-build-manifest-runtime.d.ts +1 -2
- package/src/build/app-build-manifest-runtime.js +0 -3
- package/src/hmr/hmr-entrypoint-output.d.ts +18 -3
- package/src/hmr/hmr-entrypoint-output.js +6 -11
- package/src/hmr/hmr-strategy.d.ts +15 -0
- package/src/hmr/hmr-strategy.js +19 -0
- package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
- package/src/hmr/strategies/js-hmr-strategy.js +0 -2
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.d.ts +0 -1
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +13 -32
- package/src/services/assets/browser-bundle.service.js +1 -1
- package/src/services/module-loading/page-module-import.service.js +0 -1
- package/src/services/module-loading/route-module-dependency-hasher.js +1 -0
- package/src/types/public-types.d.ts +10 -18
- package/src/watchers/project-watcher.d.ts +2 -2
- package/src/watchers/project-watcher.js +23 -28
- package/src/watchers/project-watcher.test-helpers.js +5 -5
|
@@ -109,7 +109,6 @@ class PageModuleImportService {
|
|
|
109
109
|
if (!cachedModule.dependencyHashes) {
|
|
110
110
|
return await cachedModule.promise;
|
|
111
111
|
}
|
|
112
|
-
this.dependencyHasher.clearMemo();
|
|
113
112
|
if (this.dependencyHasher.matchesStoredHashes(cachedModule.dependencyHashes, filePath, fileHash)) {
|
|
114
113
|
return await cachedModule.promise;
|
|
115
114
|
}
|
|
@@ -55,6 +55,7 @@ class RouteModuleDependencyHasher {
|
|
|
55
55
|
* @returns `true` when all stored hashes are present and still valid.
|
|
56
56
|
*/
|
|
57
57
|
matchesStoredHashes(storedHashes, entrypointPath, entrypointSourceHash) {
|
|
58
|
+
this.clearMemo();
|
|
58
59
|
if (!storedHashes || Object.keys(storedHashes).length === 0) {
|
|
59
60
|
return false;
|
|
60
61
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Readable } from 'node:stream';
|
|
2
2
|
import type { ApiResponseBuilder } from '../adapters/shared/api-response.js';
|
|
3
3
|
import type { BuildExecutor } from '../build/build-adapter.js';
|
|
4
|
-
import type { EcoBuildPlugin } from '../build/build-types.js';
|
|
5
4
|
import type { ForeignChildRuntime } from '../route-renderer/orchestration/component-render-context.js';
|
|
6
5
|
import type { EcoPageComponent } from '../eco/eco.types.js';
|
|
7
6
|
import type { EcoPagesAppConfig } from './internal-types.js';
|
|
@@ -175,10 +174,6 @@ export interface DefaultHmrContext {
|
|
|
175
174
|
* Directory where HMR bundles are written.
|
|
176
175
|
*/
|
|
177
176
|
getDistDir(): string;
|
|
178
|
-
/**
|
|
179
|
-
* Build plugins to use during bundling.
|
|
180
|
-
*/
|
|
181
|
-
getPlugins(): EcoBuildPlugin[];
|
|
182
177
|
/**
|
|
183
178
|
* Absolute path to the source directory.
|
|
184
179
|
*/
|
|
@@ -243,6 +238,14 @@ export interface IClientBridge {
|
|
|
243
238
|
error(message: string): void;
|
|
244
239
|
subscriberCount: number;
|
|
245
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Verified HMR entrypoint artifact produced by registration.
|
|
243
|
+
*/
|
|
244
|
+
export interface ResolvedHmrEntrypoint {
|
|
245
|
+
sourcePath: string;
|
|
246
|
+
outputPath: string;
|
|
247
|
+
outputUrl: string;
|
|
248
|
+
}
|
|
246
249
|
/**
|
|
247
250
|
* Interface for the HMR Manager.
|
|
248
251
|
* Used by integration plugins to register entrypoints and strategies.
|
|
@@ -264,15 +267,11 @@ export interface IHmrManager {
|
|
|
264
267
|
* framework integration. Unlike `registerEntrypoint()`, it may use the generic
|
|
265
268
|
* script bundling path.
|
|
266
269
|
*/
|
|
267
|
-
registerScriptEntrypoint(entrypointPath: string): Promise<
|
|
270
|
+
registerScriptEntrypoint(entrypointPath: string): Promise<ResolvedHmrEntrypoint>;
|
|
268
271
|
/**
|
|
269
272
|
* Registers a custom HMR strategy.
|
|
270
273
|
*/
|
|
271
274
|
registerStrategy(strategy: HmrStrategy): void;
|
|
272
|
-
/**
|
|
273
|
-
* Sets the build plugins to use during bundling.
|
|
274
|
-
*/
|
|
275
|
-
setPlugins(plugins: EcoBuildPlugin[]): void;
|
|
276
275
|
/**
|
|
277
276
|
* Enables or disables HMR.
|
|
278
277
|
*/
|
|
@@ -296,10 +295,7 @@ export interface IHmrManager {
|
|
|
296
295
|
/**
|
|
297
296
|
* Returns an existing emitted HMR script artifact without registering it.
|
|
298
297
|
*/
|
|
299
|
-
getResolvedScriptOutput?(entrypointPath: string):
|
|
300
|
-
outputUrl: string;
|
|
301
|
-
outputPath: string;
|
|
302
|
-
} | undefined;
|
|
298
|
+
getResolvedScriptOutput?(entrypointPath: string): ResolvedHmrEntrypoint | undefined;
|
|
303
299
|
/**
|
|
304
300
|
* Gets the map of watched files.
|
|
305
301
|
*/
|
|
@@ -308,10 +304,6 @@ export interface IHmrManager {
|
|
|
308
304
|
* Gets the HMR dist directory.
|
|
309
305
|
*/
|
|
310
306
|
getDistDir(): string;
|
|
311
|
-
/**
|
|
312
|
-
* Gets the build plugins.
|
|
313
|
-
*/
|
|
314
|
-
getPlugins(): EcoBuildPlugin[];
|
|
315
307
|
/**
|
|
316
308
|
* Gets the default HMR context.
|
|
317
309
|
*/
|
|
@@ -50,8 +50,8 @@ export declare class ProjectWatcher {
|
|
|
50
50
|
private readonly invalidationService;
|
|
51
51
|
private readonly changeDebounceMs;
|
|
52
52
|
private watcher;
|
|
53
|
-
private
|
|
54
|
-
private
|
|
53
|
+
private closed;
|
|
54
|
+
private pendingChangeEvents;
|
|
55
55
|
private changeQueue;
|
|
56
56
|
constructor({ config, refreshRouterRoutesCallback, hmrManager, bridge, hostOwnsDevClient, changeDebounceMs, }: ProjectWatcherConfig);
|
|
57
57
|
/**
|
|
@@ -25,8 +25,8 @@ class ProjectWatcher {
|
|
|
25
25
|
invalidationService;
|
|
26
26
|
changeDebounceMs;
|
|
27
27
|
watcher = null;
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
closed = false;
|
|
29
|
+
pendingChangeEvents = /* @__PURE__ */ new Map();
|
|
30
30
|
changeQueue = Promise.resolve();
|
|
31
31
|
constructor({
|
|
32
32
|
config,
|
|
@@ -123,28 +123,25 @@ class ProjectWatcher {
|
|
|
123
123
|
* @param event - The type of file system event
|
|
124
124
|
*/
|
|
125
125
|
handleFileChange(rawPath, event = "change") {
|
|
126
|
+
if (this.closed) {
|
|
127
|
+
return Promise.resolve();
|
|
128
|
+
}
|
|
126
129
|
const filePath = path.resolve(rawPath);
|
|
127
130
|
if (this.changeDebounceMs === 0) {
|
|
128
131
|
return this.enqueueChange(() => this.processFileChange(filePath, event));
|
|
129
132
|
}
|
|
130
|
-
const existing = this.
|
|
133
|
+
const existing = this.pendingChangeEvents.get(filePath);
|
|
131
134
|
if (existing) {
|
|
132
|
-
clearTimeout(existing);
|
|
135
|
+
clearTimeout(existing.timer);
|
|
133
136
|
}
|
|
134
137
|
const timer = setTimeout(() => {
|
|
135
|
-
this.
|
|
136
|
-
this.enqueueChange(() => this.processFileChange(filePath, event));
|
|
138
|
+
this.pendingChangeEvents.delete(filePath);
|
|
139
|
+
void this.enqueueChange(() => this.processFileChange(filePath, event));
|
|
137
140
|
}, this.changeDebounceMs);
|
|
138
|
-
this.
|
|
141
|
+
this.pendingChangeEvents.set(filePath, { event, timer });
|
|
139
142
|
return Promise.resolve();
|
|
140
143
|
}
|
|
141
144
|
async processFileChange(filePath, event) {
|
|
142
|
-
const now = Date.now();
|
|
143
|
-
const lastHandledAt = this.lastHandledChange.get(filePath);
|
|
144
|
-
if (lastHandledAt !== void 0 && now - lastHandledAt < ProjectWatcher.duplicateChangeWindowMs) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
this.lastHandledChange.set(filePath, now);
|
|
148
145
|
try {
|
|
149
146
|
const plan = this.invalidationService.planFileChange(filePath);
|
|
150
147
|
if (plan.category === "public-asset") {
|
|
@@ -157,11 +154,8 @@ class ProjectWatcher {
|
|
|
157
154
|
this.hmrManager.getWatchedFiles(),
|
|
158
155
|
resolvedFilePath
|
|
159
156
|
);
|
|
160
|
-
if (plan.invalidateServerModules) {
|
|
157
|
+
if (plan.invalidateServerModules && !isRegisteredScriptEdit) {
|
|
161
158
|
this.invalidationService.invalidateServerModules([filePath]);
|
|
162
|
-
if (isRegisteredScriptEdit) {
|
|
163
|
-
await this.invalidationService.notifyRegisteredScriptEntrypointChange(resolvedFilePath);
|
|
164
|
-
}
|
|
165
159
|
}
|
|
166
160
|
if (plan.refreshRoutes) {
|
|
167
161
|
await this.refreshRouterRoutesCallback();
|
|
@@ -172,9 +166,9 @@ class ProjectWatcher {
|
|
|
172
166
|
}
|
|
173
167
|
const deferProcessorNotifications = plan.category === "include-source" || plan.category === "explicit-server-view" || isRegisteredScriptEdit;
|
|
174
168
|
if (deferProcessorNotifications && plan.delegateToHmr) {
|
|
175
|
-
await this.prewarmBeforeHmr(resolvedFilePath, plan
|
|
169
|
+
await this.prewarmBeforeHmr(resolvedFilePath, plan);
|
|
176
170
|
await this.hmrManager.handleFileChange(filePath);
|
|
177
|
-
|
|
171
|
+
await this.notifyProcessors(filePath, event);
|
|
178
172
|
return;
|
|
179
173
|
}
|
|
180
174
|
await this.notifyProcessors(filePath, event);
|
|
@@ -195,11 +189,7 @@ class ProjectWatcher {
|
|
|
195
189
|
* Re-imports server modules before HMR broadcast so reload/refetch does not
|
|
196
190
|
* race stale in-memory imports or custom-element registry state.
|
|
197
191
|
*/
|
|
198
|
-
async prewarmBeforeHmr(filePath, plan
|
|
199
|
-
if (isRegisteredScriptEdit) {
|
|
200
|
-
await this.prewarmServerModuleImports([filePath], { bypassCache: true, scope: "registered script" });
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
192
|
+
async prewarmBeforeHmr(filePath, plan) {
|
|
203
193
|
if (plan.category !== "include-source" && plan.category !== "explicit-server-view") {
|
|
204
194
|
return;
|
|
205
195
|
}
|
|
@@ -370,11 +360,16 @@ class ProjectWatcher {
|
|
|
370
360
|
* and other short-lived Ecopages runtimes.
|
|
371
361
|
*/
|
|
372
362
|
async close() {
|
|
373
|
-
|
|
374
|
-
|
|
363
|
+
this.closed = true;
|
|
364
|
+
for (const pending of this.pendingChangeEvents.values()) {
|
|
365
|
+
clearTimeout(pending.timer);
|
|
366
|
+
}
|
|
367
|
+
this.pendingChangeEvents.clear();
|
|
368
|
+
if (this.watcher) {
|
|
369
|
+
await this.watcher.close();
|
|
370
|
+
this.watcher = null;
|
|
375
371
|
}
|
|
376
|
-
await this.
|
|
377
|
-
this.watcher = null;
|
|
372
|
+
await this.changeQueue.catch(() => void 0);
|
|
378
373
|
}
|
|
379
374
|
}
|
|
380
375
|
export {
|
|
@@ -6,21 +6,21 @@ const createMockHmrManager = () => ({
|
|
|
6
6
|
}),
|
|
7
7
|
setEnabled: vi.fn(() => {
|
|
8
8
|
}),
|
|
9
|
-
setPlugins: vi.fn(() => {
|
|
10
|
-
}),
|
|
11
9
|
registerEntrypoint: vi.fn(async () => ""),
|
|
12
|
-
registerScriptEntrypoint: vi.fn(async () =>
|
|
10
|
+
registerScriptEntrypoint: vi.fn(async () => ({
|
|
11
|
+
sourcePath: "",
|
|
12
|
+
outputPath: "",
|
|
13
|
+
outputUrl: ""
|
|
14
|
+
})),
|
|
13
15
|
registerStrategy: vi.fn(() => {
|
|
14
16
|
}),
|
|
15
17
|
isEnabled: vi.fn(() => true),
|
|
16
18
|
getOutputUrl: vi.fn(() => void 0),
|
|
17
19
|
getWatchedFiles: vi.fn(() => /* @__PURE__ */ new Map()),
|
|
18
20
|
getDistDir: vi.fn(() => ""),
|
|
19
|
-
getPlugins: vi.fn(() => []),
|
|
20
21
|
getDefaultContext: vi.fn(() => ({
|
|
21
22
|
getWatchedFiles: () => /* @__PURE__ */ new Map(),
|
|
22
23
|
getDistDir: () => "",
|
|
23
|
-
getPlugins: () => [],
|
|
24
24
|
getSrcDir: () => "",
|
|
25
25
|
getLayoutsDir: () => "",
|
|
26
26
|
getPagesDir: () => "",
|