@ecopages/core 0.2.0-rc.5 → 0.2.0-rc.6

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/README.md CHANGED
@@ -53,7 +53,10 @@ flowchart TD
53
53
  B --> C[DevelopmentInvalidationService]
54
54
  C --> D{Change kind}
55
55
  D -->|Route or server source| E[Invalidate server modules]
56
- D -->|Public or include| F[Reload browser]
56
+ D -->|Additional watch| E
57
+ E --> N[Notify processors]
58
+ N --> F[Reload browser]
59
+ D -->|Public asset| F
57
60
  D -->|Processor-owned asset| G[Notify processor only]
58
61
  D -->|HMR-eligible source| H[Core HMR manager]
59
62
  H --> I[Strategy selection]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-rc.5",
3
+ "version": "0.2.0-rc.6",
4
4
  "description": "Core package for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -20,7 +20,7 @@
20
20
  "directory": "packages/core"
21
21
  },
22
22
  "dependencies": {
23
- "@ecopages/file-system": "0.2.0-rc.5",
23
+ "@ecopages/file-system": "0.2.0-rc.6",
24
24
  "@ecopages/logger": "^0.2.3",
25
25
  "@ecopages/scripts-injector": "^0.1.5",
26
26
  "@oxc-project/runtime": "0.141.0",
@@ -34,7 +34,7 @@
34
34
  "@standard-schema/utils": "^0.3.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "@ecopages/dev-toolbar": "0.2.0-rc.5"
37
+ "@ecopages/dev-toolbar": "0.2.0-rc.6"
38
38
  },
39
39
  "peerDependenciesMeta": {
40
40
  "@ecopages/dev-toolbar": {
@@ -37,6 +37,7 @@ These contracts are responsible for:
37
37
  2. Core seals the app-owned build manifest.
38
38
  3. Runtime startup calls runtime-only setup hooks.
39
39
  4. Request-time rendering and development invalidation reuse those finalized contracts.
40
+ 5. `DevelopmentInvalidationService.invalidateServerModules()` calls `Processor.invalidateServerArtifacts()` so processors can discard compiled server artifacts that are not part of the route-module graph.
40
41
 
41
42
  ## Discovered dependency metadata
42
43
 
@@ -147,6 +147,10 @@ export declare abstract class Processor<TOptions = Record<string, unknown>> {
147
147
  didChange(): boolean;
148
148
  abstract setup(): Promise<void>;
149
149
  abstract process(input: unknown, filePath?: string): Promise<unknown>;
150
+ /**
151
+ * Discards cached server build artifacts when server modules are invalidated.
152
+ */
153
+ invalidateServerArtifacts(): void;
150
154
  /**
151
155
  * Releases runtime resources owned by the processor.
152
156
  *
@@ -108,6 +108,10 @@ export class Processor {
108
108
  didChange() {
109
109
  return false;
110
110
  }
111
+ /**
112
+ * Discards cached server build artifacts when server modules are invalidated.
113
+ */
114
+ invalidateServerArtifacts() { }
111
115
  /**
112
116
  * Releases runtime resources owned by the processor.
113
117
  *
@@ -2,6 +2,7 @@ import path from 'node:path';
2
2
  import { getAppServerInvalidationState } from '../runtime-state/server-invalidation-state.service.js';
3
3
  import { appLogger } from '../../global/app-logger.js';
4
4
  import { clearAppDevelopmentRouteModuleBuildCaches } from '../module-loading/route-module-build-cache-registry.js';
5
+ import { clearCollectionServerBuildArtifacts } from '../module-loading/collection-server-module-build.service.js';
5
6
  /**
6
7
  * Framework-owned development invalidation service.
7
8
  *
@@ -31,6 +32,10 @@ export class DevelopmentInvalidationService {
31
32
  getAppServerInvalidationState(this.appConfig).invalidateServerModules(changedFiles);
32
33
  this.appConfig.runtime?.appModuleLoader?.invalidateDevelopmentGraph();
33
34
  clearAppDevelopmentRouteModuleBuildCaches(this.appConfig);
35
+ clearCollectionServerBuildArtifacts(this.appConfig);
36
+ for (const processor of this.appConfig.processors.values()) {
37
+ processor.invalidateServerArtifacts?.();
38
+ }
34
39
  }
35
40
  /**
36
41
  * Registers an integration-owned handler for registered script entrypoint edits.
@@ -80,7 +85,7 @@ export class DevelopmentInvalidationService {
80
85
  if (this.matchesAdditionalWatchPaths(filePath)) {
81
86
  return {
82
87
  category: 'additional-watch',
83
- invalidateServerModules: false,
88
+ invalidateServerModules: true,
84
89
  refreshRoutes: false,
85
90
  reloadBrowser: true,
86
91
  delegateToHmr: false,
@@ -162,11 +167,13 @@ export class DevelopmentInvalidationService {
162
167
  return false;
163
168
  for (const pattern of patterns) {
164
169
  if (pattern.includes('*')) {
165
- const ext = pattern.replace(/\*\*?\/\*/, '');
170
+ const ext = pattern.replace(/\*\*?\/\*|\*+/g, '');
166
171
  if (normalizedPath.endsWith(ext))
167
172
  return true;
173
+ continue;
168
174
  }
169
- else if (normalizedPath.endsWith(pattern) || normalizedPath === path.resolve(pattern)) {
175
+ const resolvedPattern = path.isAbsolute(pattern) ? pattern : path.resolve(this.appConfig.rootDir, pattern);
176
+ if (normalizedPath === resolvedPattern || normalizedPath.startsWith(`${resolvedPattern}${path.sep}`)) {
170
177
  return true;
171
178
  }
172
179
  }
@@ -35,6 +35,8 @@ Call site (route scan, renderer, SSG, API)
35
35
 
36
36
  Development import URLs use `sourceHash` plus a per-service import generation counter. Node uses that value in its `?update=` query. Bun also receives a generation-specific compiled output filename because it retains a previously imported file when only its query changes. Both paths advance after `invalidateDevelopmentGraph()` without a process-wide invalidation version in reuse keys.
37
37
 
38
+ Compiled collection server modules also include the app-owned server invalidation version in their output filename (`<collection>-<buildIdentity>-<invalidationVersion>.mjs`) so Node's ESM loader treats recompiled bundles as new modules after co-located helper files change.
39
+
38
40
  ## Files
39
41
 
40
42
  | File | Role |
@@ -5,6 +5,7 @@ export type CollectionServerBuildArtifact = {
5
5
  outputUrl: string;
6
6
  sourcePaths: string[];
7
7
  buildIdentity: string;
8
+ invalidationVersion: number;
8
9
  };
9
10
  export declare function getCollectionServerBuildArtifact(appConfig: EcoPagesAppConfig, collectionName: string): CollectionServerBuildArtifact | undefined;
10
11
  export declare function clearCollectionServerBuildArtifacts(appConfig: EcoPagesAppConfig): void;
@@ -7,6 +7,7 @@ import { createServerBuildRequest } from '../../build/runtime/build-request-poli
7
7
  import { recordCollectionBuild } from '../../diagnostics/request-pipeline-metrics.js';
8
8
  import { resolveInternalExecutionDir } from '../../utils/resolve-work-dir.js';
9
9
  import { fileSystem } from '@ecopages/file-system';
10
+ import { getAppServerInvalidationState } from '../runtime-state/server-invalidation-state.service.js';
10
11
  const artifactsByCollection = new WeakMap();
11
12
  function getArtifactMap(appConfig) {
12
13
  let artifacts = artifactsByCollection.get(appConfig);
@@ -51,13 +52,14 @@ export async function buildCollectionServerModule(input) {
51
52
  splitting: false,
52
53
  externalPackages: true,
53
54
  });
55
+ const invalidationVersion = getAppServerInvalidationState(appConfig).getServerInvalidationVersion();
54
56
  const buildIdentity = createCollectionBuildIdentity(createBuildRequestIdentity(buildRequest), sourceFilePath, ownedSourcePaths);
55
57
  const artifacts = getArtifactMap(appConfig);
56
58
  const existing = artifacts.get(collectionName);
57
- if (existing?.buildIdentity === buildIdentity) {
59
+ if (existing?.buildIdentity === buildIdentity && existing?.invalidationVersion === invalidationVersion) {
58
60
  return existing;
59
61
  }
60
- const outputFileName = `${collectionName}-${buildIdentity}.mjs`;
62
+ const outputFileName = `${collectionName}-${buildIdentity}-${invalidationVersion}.mjs`;
61
63
  const buildOptions = { ...buildRequest, naming: outputFileName.replace(/\.mjs$/u, '.[ext]') };
62
64
  recordCollectionBuild();
63
65
  const buildResult = await build(buildOptions);
@@ -77,6 +79,7 @@ export async function buildCollectionServerModule(input) {
77
79
  outputUrl: pathToFileURL(compiledOutput).href,
78
80
  sourcePaths: [...ownedSourcePaths, sourceFilePath],
79
81
  buildIdentity,
82
+ invalidationVersion,
80
83
  };
81
84
  artifacts.set(collectionName, artifact);
82
85
  return artifact;
@@ -184,6 +184,7 @@ export class ProjectWatcher {
184
184
  await this.refreshRouterRoutesCallback();
185
185
  }
186
186
  if (plan.reloadBrowser) {
187
+ await this.notifyProcessors(filePath, event);
187
188
  this.requestBrowserReload();
188
189
  return;
189
190
  }
@@ -364,7 +365,10 @@ export class ProjectWatcher {
364
365
  processorPaths.add(this.appConfig.absolutePaths.publicDir);
365
366
  }
366
367
  for (const watchPath of this.appConfig.additionalWatchPaths) {
367
- processorPaths.add(watchPath);
368
+ const resolvedWatchPath = path.isAbsolute(watchPath) || watchPath.includes('*')
369
+ ? watchPath
370
+ : path.resolve(this.appConfig.rootDir, watchPath);
371
+ processorPaths.add(resolvedWatchPath);
368
372
  }
369
373
  const ignored = createProjectWatcherIgnorePredicate(this.appConfig.absolutePaths);
370
374
  this.watcher = chokidar.watch(Array.from(processorPaths), {