@ecopages/core 0.2.0-beta.19 → 0.2.0-beta.20

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.19",
3
+ "version": "0.2.0-beta.20",
4
4
  "description": "Core package for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -17,7 +17,7 @@
17
17
  "directory": "packages/core"
18
18
  },
19
19
  "dependencies": {
20
- "@ecopages/file-system": "0.2.0-beta.19",
20
+ "@ecopages/file-system": "0.2.0-beta.20",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@oxc-project/runtime": "0.134.0",
@@ -158,9 +158,6 @@ class BunServerAdapter extends SharedServerAdapter {
158
158
  async initialize() {
159
159
  installAppRuntimeBuildExecutor(this.appConfig);
160
160
  this.staticSiteGenerator = new StaticSiteGenerator({ appConfig: this.appConfig });
161
- if (this.options?.watch) {
162
- await this.hmrManager.buildRuntime();
163
- }
164
161
  prepareRuntimePublicDir(this.appConfig);
165
162
  const staticBuilderOptions = {
166
163
  appConfig: this.appConfig,
@@ -177,6 +174,9 @@ class BunServerAdapter extends SharedServerAdapter {
177
174
  if (!this.deferRuntimeAssetSetup) {
178
175
  await this.initializeRuntimePlugins({ watch: this.options?.watch });
179
176
  }
177
+ if (this.options?.watch) {
178
+ await this.hmrManager.ensureRuntimeReady();
179
+ }
180
180
  }
181
181
  /**
182
182
  * Registers runtime plugins and propagates the final HMR manager into each
@@ -323,14 +323,9 @@ class BunServerAdapter extends SharedServerAdapter {
323
323
  }
324
324
  if (url.pathname === "/_hmr_runtime.js") {
325
325
  await waitForInit();
326
+ const runtimeReady = await hmrManager.ensureRuntimeReady();
326
327
  const runtimePath = hmrManager.getRuntimePath();
327
- if (!fileSystem.exists(runtimePath)) {
328
- appLogger.warn(
329
- `[HMR] Runtime script missing at ${runtimePath}; attempting to rebuild before serving.`
330
- );
331
- await hmrManager.buildRuntime();
332
- }
333
- if (fileSystem.exists(runtimePath)) {
328
+ if (runtimeReady && fileSystem.exists(runtimePath)) {
334
329
  return new Response(fileSystem.readFileAsBuffer(runtimePath), {
335
330
  headers: { "Content-Type": "application/javascript" }
336
331
  });
@@ -519,6 +514,9 @@ class BunServerAdapter extends SharedServerAdapter {
519
514
  staticRoutes: this.staticRoutes,
520
515
  hmrManager: this.hmrManager
521
516
  });
517
+ if (this.options?.watch) {
518
+ await this.hmrManager.ensureRuntimeReady();
519
+ }
522
520
  this.fullyInitialized = true;
523
521
  if (this.options?.watch) await this.watch();
524
522
  if (server && typeof server.reload === "function") {
@@ -278,7 +278,7 @@ class NodeServerAdapter extends SharedServerAdapter {
278
278
  this.bridge = devRuntime.bridge;
279
279
  this.hmrManager = devRuntime.hmrManager;
280
280
  this.hmrManager.setEnabled(true);
281
- await this.hmrManager.buildRuntime();
281
+ await this.hmrManager.ensureRuntimeReady();
282
282
  const hmrPreflight = (req, socket, head) => {
283
283
  const url = new URL(req.url ?? "/", this.runtimeOrigin);
284
284
  if (url.pathname !== "/_hmr") return false;
@@ -30,6 +30,7 @@ export declare abstract class SharedHmrManager implements IHmrManager {
30
30
  protected readonly browserBundleService: BrowserBundleService;
31
31
  protected readonly entrypointDependencyGraph: EntrypointDependencyGraph;
32
32
  protected readonly serverModuleTranspiler: ServerModuleTranspiler;
33
+ private runtimeBuildPromise;
33
34
  constructor({ appConfig, bridge, registrationTimeoutMs }: SharedHmrManagerParams);
34
35
  protected abstract createEntrypointDependencyGraph(existingEntrypointDependencyGraph: EntrypointDependencyGraph): EntrypointDependencyGraph;
35
36
  protected shouldSkipMissingFileChange(_filePath: string): boolean;
@@ -41,8 +42,21 @@ export declare abstract class SharedHmrManager implements IHmrManager {
41
42
  setPlugins(plugins: EcoBuildPlugin[]): void;
42
43
  setEnabled(enabled: boolean): void;
43
44
  isEnabled(): boolean;
45
+ isRuntimeReady(): boolean;
46
+ /**
47
+ * Builds the browser HMR runtime once and reuses the in-flight build for concurrent callers.
48
+ */
49
+ ensureRuntimeReady(): Promise<boolean>;
44
50
  buildRuntime(): Promise<void>;
45
51
  getRuntimePath(): string;
52
+ private buildRuntimeInternal;
53
+ /**
54
+ * @remarks
55
+ * Rolldown may emit the runtime bundle under a derived filename when the
56
+ * entrypoint lives outside the app root. The dev server always serves
57
+ * `/_hmr_runtime.js` from a stable path under `.eco/assets/_hmr/`.
58
+ */
59
+ private syncRuntimeOutput;
46
60
  broadcast(event: ClientBridgeEvent): void;
47
61
  handleFileChange(filePath: string, options?: HandleFileChangeOptions): Promise<void>;
48
62
  getOutputUrl(entrypointPath: string): string | undefined;
@@ -51,6 +51,7 @@ class SharedHmrManager {
51
51
  browserBundleService;
52
52
  entrypointDependencyGraph;
53
53
  serverModuleTranspiler;
54
+ runtimeBuildPromise = null;
54
55
  constructor({ appConfig, bridge, registrationTimeoutMs }) {
55
56
  this.appConfig = appConfig;
56
57
  this.bridge = bridge;
@@ -126,7 +127,33 @@ class SharedHmrManager {
126
127
  isEnabled() {
127
128
  return this.enabled;
128
129
  }
130
+ isRuntimeReady() {
131
+ return fileSystem.exists(this.getRuntimePath());
132
+ }
133
+ /**
134
+ * Builds the browser HMR runtime once and reuses the in-flight build for concurrent callers.
135
+ */
136
+ async ensureRuntimeReady() {
137
+ if (this.isRuntimeReady()) {
138
+ return true;
139
+ }
140
+ if (this.runtimeBuildPromise) {
141
+ return this.runtimeBuildPromise;
142
+ }
143
+ this.runtimeBuildPromise = this.buildRuntimeInternal();
144
+ try {
145
+ return await this.runtimeBuildPromise;
146
+ } finally {
147
+ this.runtimeBuildPromise = null;
148
+ }
149
+ }
129
150
  async buildRuntime() {
151
+ await this.ensureRuntimeReady();
152
+ }
153
+ getRuntimePath() {
154
+ return path.join(this.distDir, "_hmr_runtime.js");
155
+ }
156
+ async buildRuntimeInternal() {
130
157
  const runtimeSource = fileURLToPath(import.meta.resolve("@ecopages/core/hmr/client/hmr-runtime"));
131
158
  try {
132
159
  const result = await this.browserBundleService.bundle({
@@ -139,13 +166,32 @@ class SharedHmrManager {
139
166
  });
140
167
  if (!result.success) {
141
168
  this.onRuntimeBundleFailure(result.logs);
169
+ return false;
142
170
  }
171
+ this.syncRuntimeOutput(result);
172
+ return this.isRuntimeReady();
143
173
  } catch (error) {
144
174
  this.onRuntimeBundleFailure(error);
175
+ return false;
145
176
  }
146
177
  }
147
- getRuntimePath() {
148
- return path.join(this.distDir, "_hmr_runtime.js");
178
+ /**
179
+ * @remarks
180
+ * Rolldown may emit the runtime bundle under a derived filename when the
181
+ * entrypoint lives outside the app root. The dev server always serves
182
+ * `/_hmr_runtime.js` from a stable path under `.eco/assets/_hmr/`.
183
+ */
184
+ syncRuntimeOutput(result) {
185
+ const runtimePath = this.getRuntimePath();
186
+ if (fileSystem.exists(runtimePath)) {
187
+ return;
188
+ }
189
+ const emittedRuntime = result.outputs.find((output) => output.path.endsWith(".js"));
190
+ if (!emittedRuntime || emittedRuntime.path === runtimePath) {
191
+ return;
192
+ }
193
+ fileSystem.ensureDir(path.dirname(runtimePath));
194
+ fileSystem.copyFile(emittedRuntime.path, runtimePath);
149
195
  }
150
196
  broadcast(event) {
151
197
  appLogger.debug(