@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.
Files changed (31) hide show
  1. package/package.json +2 -2
  2. package/src/adapters/bun/hmr-manager.d.ts +1 -2
  3. package/src/adapters/bun/hmr-manager.js +2 -2
  4. package/src/adapters/bun/server-adapter.js +5 -4
  5. package/src/adapters/node/node-hmr-manager.d.ts +1 -2
  6. package/src/adapters/node/node-hmr-manager.js +2 -2
  7. package/src/adapters/node/server-adapter.js +5 -6
  8. package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +21 -28
  9. package/src/adapters/shared/hmr-entrypoint-registrar.js +49 -31
  10. package/src/adapters/shared/runtime-server-lifecycle.d.ts +9 -1
  11. package/src/adapters/shared/runtime-server-lifecycle.js +10 -3
  12. package/src/adapters/shared/server-static-builder.js +4 -1
  13. package/src/adapters/shared/shared-hmr-manager.d.ts +29 -23
  14. package/src/adapters/shared/shared-hmr-manager.js +144 -108
  15. package/src/build/app-build-manifest-runtime.d.ts +1 -2
  16. package/src/build/app-build-manifest-runtime.js +0 -3
  17. package/src/hmr/hmr-entrypoint-output.d.ts +18 -3
  18. package/src/hmr/hmr-entrypoint-output.js +6 -11
  19. package/src/hmr/hmr-strategy.d.ts +15 -0
  20. package/src/hmr/hmr-strategy.js +19 -0
  21. package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
  22. package/src/hmr/strategies/js-hmr-strategy.js +0 -2
  23. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.d.ts +0 -1
  24. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +13 -32
  25. package/src/services/assets/browser-bundle.service.js +1 -1
  26. package/src/services/module-loading/page-module-import.service.js +0 -1
  27. package/src/services/module-loading/route-module-dependency-hasher.js +1 -0
  28. package/src/types/public-types.d.ts +10 -18
  29. package/src/watchers/project-watcher.d.ts +2 -2
  30. package/src/watchers/project-watcher.js +23 -28
  31. 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<string>;
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 lastHandledChange;
54
- private pendingChangeTimers;
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
- lastHandledChange = /* @__PURE__ */ new Map();
29
- pendingChangeTimers = /* @__PURE__ */ new Map();
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.pendingChangeTimers.get(filePath);
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.pendingChangeTimers.delete(filePath);
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.pendingChangeTimers.set(filePath, timer);
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, isRegisteredScriptEdit);
169
+ await this.prewarmBeforeHmr(resolvedFilePath, plan);
176
170
  await this.hmrManager.handleFileChange(filePath);
177
- void this.notifyProcessors(filePath, event);
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, isRegisteredScriptEdit) {
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
- if (!this.watcher) {
374
- return;
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.watcher.close();
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: () => "",