@astroscope/boot 0.5.0 → 0.6.1

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
@@ -89,45 +89,47 @@ Called when the server is shutting down (SIGTERM in production, server close in
89
89
  - Cleaning up resources
90
90
  - Graceful shutdown of external services
91
91
 
92
- ## V8 Warmup
92
+ ## Warmup
93
93
 
94
- The package includes a warmup utility that pre-imports all page modules and middleware to warm up the V8 JIT compiler, reducing cold start latency for the first requests.
94
+ Pre-imports server modules at startup to eliminate cold-start latency on the first request. At build time, a virtual module is generated that statically imports all matched files. At runtime, this module is loaded in parallel with `onStartup`, so warmup doesn't delay server readiness.
95
+
96
+ ### Enable warmup
95
97
 
96
98
  ```ts
97
- // src/boot.ts
98
- import type { BootContext } from "@astroscope/boot";
99
- import { warmup } from "@astroscope/boot/warmup";
99
+ boot({ warmup: true });
100
+ ```
100
101
 
101
- export async function onStartup({ host, port }: BootContext) {
102
- const result = await warmup();
102
+ This uses the default glob patterns (`WARMUP_MODULES`) which cover:
103
103
 
104
- if (result.success.length > 0) {
105
- console.log(`Warmed up ${result.success.length} modules in ${result.duration}ms`);
106
- }
104
+ - `src/pages/**/*.{astro,ts,tsx,js,jsx,md,mdx}`
105
+ - `src/middleware.{ts,js}` / `src/middleware/index.{ts,js}`
107
106
 
108
- if (result.failed.length > 0) {
109
- console.warn(`Failed to warm up: ${result.failed.join(", ")}`);
110
- }
107
+ ### Custom patterns
111
108
 
112
- console.log(`Server ready at ${host}:${port}`);
113
- }
109
+ Pass an array of glob patterns to control exactly which files are warmed up:
110
+
111
+ ```ts
112
+ import { WARMUP_MODULES } from "@astroscope/boot";
113
+
114
+ // defaults + custom patterns
115
+ boot({ warmup: [...WARMUP_MODULES, "src/components/**/*.tsx"] });
116
+
117
+ // only custom patterns (no defaults)
118
+ boot({ warmup: ["src/lib/heavy-module.ts"] });
114
119
  ```
115
120
 
116
- ### `WarmupResult`
121
+ ### Exported constants
122
+
123
+ The default glob patterns are exported for composition:
117
124
 
118
125
  ```ts
119
- interface WarmupResult {
120
- /** Modules that were successfully loaded */
121
- success: string[];
122
- /** Modules that failed to load */
123
- failed: string[];
124
- /** Time taken in milliseconds */
125
- duration: number;
126
- }
126
+ import {
127
+ WARMUP_PAGE_MODULES,
128
+ WARMUP_MIDDLEWARE_MODULES,
129
+ WARMUP_MODULES,
130
+ } from "@astroscope/boot";
127
131
  ```
128
132
 
129
- In development mode, `warmup()` is a no-op that returns empty results. In production, it reads a manifest generated during the build and imports all discovered page modules and middleware in parallel.
130
-
131
133
  ## Options
132
134
 
133
135
  ### `entry`
@@ -152,14 +154,30 @@ Re-run `onStartup` when the boot file changes during development. This is disabl
152
154
  boot({ hmr: true });
153
155
  ```
154
156
 
157
+ ### `warmup`
158
+
159
+ Pre-import server modules on startup to eliminate cold-start latency.
160
+
161
+ - **Type**: `boolean | string[]`
162
+ - **Default**: `false`
163
+
164
+ ```ts
165
+ // use default patterns
166
+ boot({ warmup: true });
167
+
168
+ // custom patterns
169
+ boot({ warmup: [...WARMUP_MODULES, "src/components/**/*.tsx"] });
170
+ ```
171
+
155
172
  ## How it works
156
173
 
157
174
  - **Development**: The boot file runs _after_ the dev server starts listening (Vite limitation). `onShutdown` is called when the dev server closes.
158
- - **Production**: `onStartup` runs _before_ the server starts handling requests. `onShutdown` is called on SIGTERM.
175
+ - **Production**: `onStartup` runs _before_ the server starts handling requests. `onShutdown` is called on SIGTERM. When warmup is enabled, module pre-loading runs in parallel with `onStartup`.
159
176
 
160
177
  ## Requirements
161
178
 
162
- - Only works with SSR output mode (`output: "server"`)
179
+ - Node.js runtime
180
+ - SSR output mode (`output: "server"`)
163
181
 
164
182
  ## License
165
183
 
package/dist/events.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as BootContext } from './types-CxpusND2.cjs';
1
+ import { B as BootContext } from './types-Ccbd5qoU.cjs';
2
2
 
3
3
  type BootEventName = 'beforeOnStartup' | 'afterOnStartup' | 'beforeOnShutdown' | 'afterOnShutdown';
4
4
  type BootEventHandler = (context: BootContext) => Promise<void> | void;
package/dist/events.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { B as BootContext } from './types-CxpusND2.js';
1
+ import { B as BootContext } from './types-Ccbd5qoU.js';
2
2
 
3
3
  type BootEventName = 'beforeOnStartup' | 'afterOnStartup' | 'beforeOnShutdown' | 'afterOnShutdown';
4
4
  type BootEventHandler = (context: BootContext) => Promise<void> | void;
package/dist/index.cjs CHANGED
@@ -30,6 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ WARMUP_MIDDLEWARE_MODULES: () => WARMUP_MIDDLEWARE_MODULES,
34
+ WARMUP_MODULES: () => WARMUP_MODULES,
35
+ WARMUP_PAGE_MODULES: () => WARMUP_PAGE_MODULES,
33
36
  boot: () => boot,
34
37
  default: () => boot,
35
38
  prepend: () => prepend
@@ -38,6 +41,7 @@ module.exports = __toCommonJS(index_exports);
38
41
 
39
42
  // src/integration.ts
40
43
  var import_node_fs2 = __toESM(require("fs"), 1);
44
+ var import_node_url = require("url");
41
45
  var import_magic_string = __toESM(require("magic-string"), 1);
42
46
  var import_vite2 = require("vite");
43
47
 
@@ -194,58 +198,33 @@ function getPrependCode() {
194
198
  return code;
195
199
  }
196
200
 
197
- // src/warmup-manifest.ts
201
+ // src/warmup.ts
198
202
  var import_node_fs = __toESM(require("fs"), 1);
199
203
  var import_node_path2 = __toESM(require("path"), 1);
200
- var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
201
- var MIDDLEWARE_VIRTUAL_ID = "\0virtual:astro:middleware";
202
- function collectWarmupModules(bundle) {
203
- const pageModules = [];
204
- let middlewarePath = null;
205
- for (const [fileName, chunk] of Object.entries(bundle)) {
206
- if (chunk.type === "chunk" && chunk.facadeModuleId === MIDDLEWARE_VIRTUAL_ID) {
207
- middlewarePath = fileName;
208
- break;
204
+ var WARMUP_PAGE_MODULES = ["src/pages/**/*.{astro,ts,tsx,js,jsx,md,mdx}"];
205
+ var WARMUP_MIDDLEWARE_MODULES = ["src/middleware.{ts,js}", "src/middleware/index.{ts,js}"];
206
+ var WARMUP_MODULES = [...WARMUP_PAGE_MODULES, ...WARMUP_MIDDLEWARE_MODULES];
207
+ var VIRTUAL_MODULE_ID = "virtual:@astroscope/boot/warmup";
208
+ var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
209
+ async function resolveWarmupFiles(patterns, projectRoot) {
210
+ const files = [];
211
+ for (const pattern of patterns) {
212
+ for await (const entry of import_node_fs.default.promises.glob(pattern, {
213
+ cwd: projectRoot,
214
+ exclude: (name) => name === "node_modules"
215
+ })) {
216
+ files.push(import_node_path2.default.resolve(projectRoot, entry));
209
217
  }
210
218
  }
211
- const entryChunk = bundle["entry.mjs"];
212
- if (entryChunk?.type !== "chunk") {
213
- return { pageModules, middlewarePath };
214
- }
215
- let serverChunk = null;
216
- for (const imp of entryChunk.imports) {
217
- const chunk = bundle[imp];
218
- if (chunk?.type !== "chunk") continue;
219
- if (!serverChunk || chunk.dynamicImports.length > serverChunk.dynamicImports.length) {
220
- serverChunk = chunk;
221
- }
222
- }
223
- if (serverChunk) {
224
- const skip = /* @__PURE__ */ new Set([middlewarePath]);
225
- for (const dynImport of serverChunk.dynamicImports) {
226
- if (skip.has(dynImport)) continue;
227
- const chunk = bundle[dynImport];
228
- if (chunk?.type !== "chunk") continue;
229
- const facadeId = chunk.facadeModuleId ?? "";
230
- if (facadeId.includes("noop-") || facadeId.includes("virtual:astro:server-island") || facadeId.includes("virtual:astro:session")) {
231
- continue;
232
- }
233
- pageModules.push(dynImport);
234
- }
235
- }
236
- return { pageModules, middlewarePath };
219
+ return [...new Set(files)];
237
220
  }
238
- function writeWarmupManifest(outDir, { pageModules, middlewarePath }, logger) {
239
- const modules = [];
240
- if (middlewarePath) {
241
- modules.push(`./${middlewarePath}`);
242
- }
243
- for (const page of pageModules) {
244
- modules.push(`./${page}`);
245
- }
246
- const manifestPath = import_node_path2.default.join(outDir, "chunks", WARMUP_MANIFEST_FILE);
247
- import_node_fs.default.writeFileSync(manifestPath, JSON.stringify({ modules }));
248
- logger.info(`generated warmup for ${pageModules.length} pages`);
221
+ function generateWarmupCode(files) {
222
+ if (files.length === 0) return "";
223
+ const imports = files.map((f) => ` import(${JSON.stringify(f)})`).join(",\n");
224
+ return `await Promise.allSettled([
225
+ ${imports},
226
+ ]);
227
+ `;
249
228
  }
250
229
 
251
230
  // src/integration.ts
@@ -269,12 +248,20 @@ function getBootContext(server, config) {
269
248
  const { host, port } = getServerDefaults(config);
270
249
  return { dev: true, host, port };
271
250
  }
272
- var getState = (0, import_vite2.perEnvironmentState)(() => ({ bootChunkRef: null, warmupModules: null }));
251
+ function resolveWarmupPatterns(warmup) {
252
+ if (!warmup) return null;
253
+ if (Array.isArray(warmup)) {
254
+ return warmup;
255
+ }
256
+ return WARMUP_MODULES;
257
+ }
258
+ var getState = (0, import_vite2.perEnvironmentState)(() => ({ bootChunkRef: null, warmupChunkRef: null }));
273
259
  function boot(options = {}) {
274
260
  const entry = resolveEntry(options.entry);
275
261
  const hmr = options.hmr ?? false;
276
- const enableWarmup = options.warmup ?? false;
262
+ const warmupPatterns = resolveWarmupPatterns(options.warmup);
277
263
  let astroConfig = null;
264
+ let warmupCode = null;
278
265
  return {
279
266
  name: "@astroscope/boot",
280
267
  hooks: {
@@ -283,16 +270,38 @@ function boot(options = {}) {
283
270
  updateConfig({
284
271
  vite: {
285
272
  plugins: [
286
- // build plugin: handles entry.mjs injection, warmup manifest
273
+ // build plugin: handles entry.mjs injection and warmup virtual module
287
274
  {
288
275
  name: "@astroscope/boot",
289
- buildStart() {
276
+ resolveId(id) {
277
+ if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;
278
+ },
279
+ load(id) {
280
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) return warmupCode ?? "";
281
+ },
282
+ async buildStart() {
290
283
  if (this.environment.name !== "ssr") return;
291
284
  const state = getState(this);
292
285
  try {
293
286
  state.bootChunkRef = this.emitFile({ type: "chunk", id: entry, name: "boot" });
294
287
  } catch {
295
288
  }
289
+ if (warmupPatterns) {
290
+ const projectRoot = astroConfig?.root ? (0, import_node_url.fileURLToPath)(astroConfig.root) : process.cwd();
291
+ const files = await resolveWarmupFiles(warmupPatterns, projectRoot);
292
+ warmupCode = generateWarmupCode(files);
293
+ if (files.length > 0) {
294
+ try {
295
+ state.warmupChunkRef = this.emitFile({
296
+ type: "chunk",
297
+ id: VIRTUAL_MODULE_ID,
298
+ name: "warmup"
299
+ });
300
+ } catch {
301
+ }
302
+ logger.info(`warmup: ${files.length} files`);
303
+ }
304
+ }
296
305
  },
297
306
  generateBundle(_, bundle) {
298
307
  const state = getState(this);
@@ -307,18 +316,26 @@ function boot(options = {}) {
307
316
  logger.warn("entry.mjs not found - boot injection skipped");
308
317
  return;
309
318
  }
310
- if (enableWarmup) {
311
- state.warmupModules = collectWarmupModules(bundle);
312
- }
313
319
  const { host, port } = getServerDefaults(astroConfig);
314
320
  const prependCode = getPrependCode();
315
321
  const prefix = prependCode.length ? `${prependCode.join("\n")}
316
322
  ` : "";
317
- const injection = `${prefix}globalThis.__astroscope_server_url = import.meta.url;
318
- import * as __astroscope_boot from './${bootChunkName}';
319
- import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
320
- await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, warmup: enableWarmup })});
323
+ let warmupStart = "";
324
+ let warmupEnd = "";
325
+ if (state.warmupChunkRef) {
326
+ const warmupChunkName = this.getFileName(state.warmupChunkRef);
327
+ if (warmupChunkName) {
328
+ warmupStart = `const __astroscope_warmup = import('./${warmupChunkName}');
329
+ `;
330
+ warmupEnd = `await __astroscope_warmup;
321
331
  `;
332
+ }
333
+ }
334
+ const setupConfig = JSON.stringify({ host, port });
335
+ const injection = `${prefix}${warmupStart}import * as __astroscope_boot from './${bootChunkName}';
336
+ import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
337
+ await __astroscope_bootSetup(__astroscope_boot, ${setupConfig});
338
+ ${warmupEnd}`;
322
339
  const s = new import_magic_string.default(entryChunk.code);
323
340
  s.prepend(injection);
324
341
  entryChunk.code = s.toString();
@@ -326,13 +343,6 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, w
326
343
  entryChunk.map = s.generateMap({ hires: true });
327
344
  }
328
345
  logger.info(`injected ${bootChunkName} into entry.mjs`);
329
- },
330
- writeBundle(outputOptions) {
331
- const state = getState(this);
332
- if (!state.warmupModules) return;
333
- const outDir = outputOptions.dir;
334
- if (!outDir) return;
335
- writeWarmupManifest(outDir, state.warmupModules, logger);
336
346
  }
337
347
  },
338
348
  // startup plugin: runs after all other configureServer hooks
@@ -371,6 +381,9 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, w
371
381
  }
372
382
  // Annotate the CommonJS export names for ESM import in node:
373
383
  0 && (module.exports = {
384
+ WARMUP_MIDDLEWARE_MODULES,
385
+ WARMUP_MODULES,
386
+ WARMUP_PAGE_MODULES,
374
387
  boot,
375
388
  prepend
376
389
  });
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AstroIntegration } from 'astro';
2
- export { B as BootContext, W as WarmupResult } from './types-CxpusND2.cjs';
2
+ export { B as BootContext } from './types-Ccbd5qoU.cjs';
3
3
  export { BootEventHandler, BootEventName } from './events.cjs';
4
4
  export { prepend } from './prepend.cjs';
5
5
 
@@ -15,12 +15,14 @@ interface BootOptions {
15
15
  */
16
16
  hmr?: boolean | undefined;
17
17
  /**
18
- * Pre-import all page modules and middleware on startup to eliminate cold-start latency
19
- * on first request. When enabled, a warmup manifest is generated at build time and all
20
- * discovered modules are imported before `onStartup` runs.
18
+ * Pre-import all page modules and middleware on startup to eliminate cold-start latency.
19
+ *
20
+ * - `true` warmup using default glob patterns ({@link WARMUP_MODULES})
21
+ * - `string[]` — custom glob patterns (use `{@link WARMUP_MODULES}` to include defaults)
22
+ *
21
23
  * @default false
22
24
  */
23
- warmup?: boolean | undefined;
25
+ warmup?: boolean | string[] | undefined;
24
26
  }
25
27
  /**
26
28
  * Astro integration for application lifecycle hooks.
@@ -30,4 +32,14 @@ interface BootOptions {
30
32
  */
31
33
  declare function boot(options?: BootOptions): AstroIntegration;
32
34
 
33
- export { type BootOptions, boot, boot as default };
35
+ /**
36
+ * Default warmup glob patterns covering Astro pages and middleware.
37
+ */
38
+ declare const WARMUP_PAGE_MODULES: string[];
39
+ declare const WARMUP_MIDDLEWARE_MODULES: string[];
40
+ /**
41
+ * All default warmup glob patterns.
42
+ */
43
+ declare const WARMUP_MODULES: string[];
44
+
45
+ export { type BootOptions, WARMUP_MIDDLEWARE_MODULES, WARMUP_MODULES, WARMUP_PAGE_MODULES, boot, boot as default };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AstroIntegration } from 'astro';
2
- export { B as BootContext, W as WarmupResult } from './types-CxpusND2.js';
2
+ export { B as BootContext } from './types-Ccbd5qoU.js';
3
3
  export { BootEventHandler, BootEventName } from './events.js';
4
4
  export { prepend } from './prepend.js';
5
5
 
@@ -15,12 +15,14 @@ interface BootOptions {
15
15
  */
16
16
  hmr?: boolean | undefined;
17
17
  /**
18
- * Pre-import all page modules and middleware on startup to eliminate cold-start latency
19
- * on first request. When enabled, a warmup manifest is generated at build time and all
20
- * discovered modules are imported before `onStartup` runs.
18
+ * Pre-import all page modules and middleware on startup to eliminate cold-start latency.
19
+ *
20
+ * - `true` warmup using default glob patterns ({@link WARMUP_MODULES})
21
+ * - `string[]` — custom glob patterns (use `{@link WARMUP_MODULES}` to include defaults)
22
+ *
21
23
  * @default false
22
24
  */
23
- warmup?: boolean | undefined;
25
+ warmup?: boolean | string[] | undefined;
24
26
  }
25
27
  /**
26
28
  * Astro integration for application lifecycle hooks.
@@ -30,4 +32,14 @@ interface BootOptions {
30
32
  */
31
33
  declare function boot(options?: BootOptions): AstroIntegration;
32
34
 
33
- export { type BootOptions, boot, boot as default };
35
+ /**
36
+ * Default warmup glob patterns covering Astro pages and middleware.
37
+ */
38
+ declare const WARMUP_PAGE_MODULES: string[];
39
+ declare const WARMUP_MIDDLEWARE_MODULES: string[];
40
+ /**
41
+ * All default warmup glob patterns.
42
+ */
43
+ declare const WARMUP_MODULES: string[];
44
+
45
+ export { type BootOptions, WARMUP_MIDDLEWARE_MODULES, WARMUP_MODULES, WARMUP_PAGE_MODULES, boot, boot as default };
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import "./chunk-I62ZQYTP.js";
10
10
 
11
11
  // src/integration.ts
12
12
  import fs2 from "fs";
13
+ import { fileURLToPath } from "url";
13
14
  import MagicString from "magic-string";
14
15
  import { perEnvironmentState } from "vite";
15
16
 
@@ -124,58 +125,33 @@ function setupBootHmr(server, entry, logger, getBootContext2) {
124
125
  });
125
126
  }
126
127
 
127
- // src/warmup-manifest.ts
128
+ // src/warmup.ts
128
129
  import fs from "fs";
129
130
  import path2 from "path";
130
- var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
131
- var MIDDLEWARE_VIRTUAL_ID = "\0virtual:astro:middleware";
132
- function collectWarmupModules(bundle) {
133
- const pageModules = [];
134
- let middlewarePath = null;
135
- for (const [fileName, chunk] of Object.entries(bundle)) {
136
- if (chunk.type === "chunk" && chunk.facadeModuleId === MIDDLEWARE_VIRTUAL_ID) {
137
- middlewarePath = fileName;
138
- break;
131
+ var WARMUP_PAGE_MODULES = ["src/pages/**/*.{astro,ts,tsx,js,jsx,md,mdx}"];
132
+ var WARMUP_MIDDLEWARE_MODULES = ["src/middleware.{ts,js}", "src/middleware/index.{ts,js}"];
133
+ var WARMUP_MODULES = [...WARMUP_PAGE_MODULES, ...WARMUP_MIDDLEWARE_MODULES];
134
+ var VIRTUAL_MODULE_ID = "virtual:@astroscope/boot/warmup";
135
+ var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
136
+ async function resolveWarmupFiles(patterns, projectRoot) {
137
+ const files = [];
138
+ for (const pattern of patterns) {
139
+ for await (const entry of fs.promises.glob(pattern, {
140
+ cwd: projectRoot,
141
+ exclude: (name) => name === "node_modules"
142
+ })) {
143
+ files.push(path2.resolve(projectRoot, entry));
139
144
  }
140
145
  }
141
- const entryChunk = bundle["entry.mjs"];
142
- if (entryChunk?.type !== "chunk") {
143
- return { pageModules, middlewarePath };
144
- }
145
- let serverChunk = null;
146
- for (const imp of entryChunk.imports) {
147
- const chunk = bundle[imp];
148
- if (chunk?.type !== "chunk") continue;
149
- if (!serverChunk || chunk.dynamicImports.length > serverChunk.dynamicImports.length) {
150
- serverChunk = chunk;
151
- }
152
- }
153
- if (serverChunk) {
154
- const skip = /* @__PURE__ */ new Set([middlewarePath]);
155
- for (const dynImport of serverChunk.dynamicImports) {
156
- if (skip.has(dynImport)) continue;
157
- const chunk = bundle[dynImport];
158
- if (chunk?.type !== "chunk") continue;
159
- const facadeId = chunk.facadeModuleId ?? "";
160
- if (facadeId.includes("noop-") || facadeId.includes("virtual:astro:server-island") || facadeId.includes("virtual:astro:session")) {
161
- continue;
162
- }
163
- pageModules.push(dynImport);
164
- }
165
- }
166
- return { pageModules, middlewarePath };
146
+ return [...new Set(files)];
167
147
  }
168
- function writeWarmupManifest(outDir, { pageModules, middlewarePath }, logger) {
169
- const modules = [];
170
- if (middlewarePath) {
171
- modules.push(`./${middlewarePath}`);
172
- }
173
- for (const page of pageModules) {
174
- modules.push(`./${page}`);
175
- }
176
- const manifestPath = path2.join(outDir, "chunks", WARMUP_MANIFEST_FILE);
177
- fs.writeFileSync(manifestPath, JSON.stringify({ modules }));
178
- logger.info(`generated warmup for ${pageModules.length} pages`);
148
+ function generateWarmupCode(files) {
149
+ if (files.length === 0) return "";
150
+ const imports = files.map((f) => ` import(${JSON.stringify(f)})`).join(",\n");
151
+ return `await Promise.allSettled([
152
+ ${imports},
153
+ ]);
154
+ `;
179
155
  }
180
156
 
181
157
  // src/integration.ts
@@ -199,12 +175,20 @@ function getBootContext(server, config) {
199
175
  const { host, port } = getServerDefaults(config);
200
176
  return { dev: true, host, port };
201
177
  }
202
- var getState = perEnvironmentState(() => ({ bootChunkRef: null, warmupModules: null }));
178
+ function resolveWarmupPatterns(warmup) {
179
+ if (!warmup) return null;
180
+ if (Array.isArray(warmup)) {
181
+ return warmup;
182
+ }
183
+ return WARMUP_MODULES;
184
+ }
185
+ var getState = perEnvironmentState(() => ({ bootChunkRef: null, warmupChunkRef: null }));
203
186
  function boot(options = {}) {
204
187
  const entry = resolveEntry(options.entry);
205
188
  const hmr = options.hmr ?? false;
206
- const enableWarmup = options.warmup ?? false;
189
+ const warmupPatterns = resolveWarmupPatterns(options.warmup);
207
190
  let astroConfig = null;
191
+ let warmupCode = null;
208
192
  return {
209
193
  name: "@astroscope/boot",
210
194
  hooks: {
@@ -213,16 +197,38 @@ function boot(options = {}) {
213
197
  updateConfig({
214
198
  vite: {
215
199
  plugins: [
216
- // build plugin: handles entry.mjs injection, warmup manifest
200
+ // build plugin: handles entry.mjs injection and warmup virtual module
217
201
  {
218
202
  name: "@astroscope/boot",
219
- buildStart() {
203
+ resolveId(id) {
204
+ if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;
205
+ },
206
+ load(id) {
207
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) return warmupCode ?? "";
208
+ },
209
+ async buildStart() {
220
210
  if (this.environment.name !== "ssr") return;
221
211
  const state = getState(this);
222
212
  try {
223
213
  state.bootChunkRef = this.emitFile({ type: "chunk", id: entry, name: "boot" });
224
214
  } catch {
225
215
  }
216
+ if (warmupPatterns) {
217
+ const projectRoot = astroConfig?.root ? fileURLToPath(astroConfig.root) : process.cwd();
218
+ const files = await resolveWarmupFiles(warmupPatterns, projectRoot);
219
+ warmupCode = generateWarmupCode(files);
220
+ if (files.length > 0) {
221
+ try {
222
+ state.warmupChunkRef = this.emitFile({
223
+ type: "chunk",
224
+ id: VIRTUAL_MODULE_ID,
225
+ name: "warmup"
226
+ });
227
+ } catch {
228
+ }
229
+ logger.info(`warmup: ${files.length} files`);
230
+ }
231
+ }
226
232
  },
227
233
  generateBundle(_, bundle) {
228
234
  const state = getState(this);
@@ -237,18 +243,26 @@ function boot(options = {}) {
237
243
  logger.warn("entry.mjs not found - boot injection skipped");
238
244
  return;
239
245
  }
240
- if (enableWarmup) {
241
- state.warmupModules = collectWarmupModules(bundle);
242
- }
243
246
  const { host, port } = getServerDefaults(astroConfig);
244
247
  const prependCode = getPrependCode();
245
248
  const prefix = prependCode.length ? `${prependCode.join("\n")}
246
249
  ` : "";
247
- const injection = `${prefix}globalThis.__astroscope_server_url = import.meta.url;
248
- import * as __astroscope_boot from './${bootChunkName}';
249
- import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
250
- await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, warmup: enableWarmup })});
250
+ let warmupStart = "";
251
+ let warmupEnd = "";
252
+ if (state.warmupChunkRef) {
253
+ const warmupChunkName = this.getFileName(state.warmupChunkRef);
254
+ if (warmupChunkName) {
255
+ warmupStart = `const __astroscope_warmup = import('./${warmupChunkName}');
256
+ `;
257
+ warmupEnd = `await __astroscope_warmup;
251
258
  `;
259
+ }
260
+ }
261
+ const setupConfig = JSON.stringify({ host, port });
262
+ const injection = `${prefix}${warmupStart}import * as __astroscope_boot from './${bootChunkName}';
263
+ import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
264
+ await __astroscope_bootSetup(__astroscope_boot, ${setupConfig});
265
+ ${warmupEnd}`;
252
266
  const s = new MagicString(entryChunk.code);
253
267
  s.prepend(injection);
254
268
  entryChunk.code = s.toString();
@@ -256,13 +270,6 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, w
256
270
  entryChunk.map = s.generateMap({ hires: true });
257
271
  }
258
272
  logger.info(`injected ${bootChunkName} into entry.mjs`);
259
- },
260
- writeBundle(outputOptions) {
261
- const state = getState(this);
262
- if (!state.warmupModules) return;
263
- const outDir = outputOptions.dir;
264
- if (!outDir) return;
265
- writeWarmupManifest(outDir, state.warmupModules, logger);
266
273
  }
267
274
  },
268
275
  // startup plugin: runs after all other configureServer hooks
@@ -300,6 +307,9 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, w
300
307
  };
301
308
  }
302
309
  export {
310
+ WARMUP_MIDDLEWARE_MODULES,
311
+ WARMUP_MODULES,
312
+ WARMUP_PAGE_MODULES,
303
313
  boot,
304
314
  boot as default,
305
315
  prepend
@@ -1,4 +1,4 @@
1
- import { B as BootContext } from './types-CxpusND2.cjs';
1
+ import { B as BootContext } from './types-Ccbd5qoU.cjs';
2
2
 
3
3
  interface BootModule {
4
4
  onStartup?: ((context: BootContext) => Promise<void> | void) | undefined;
@@ -1,4 +1,4 @@
1
- import { B as BootContext } from './types-CxpusND2.js';
1
+ import { B as BootContext } from './types-Ccbd5qoU.js';
2
2
 
3
3
  interface BootModule {
4
4
  onStartup?: ((context: BootContext) => Promise<void> | void) | undefined;
package/dist/setup.cjs CHANGED
@@ -57,61 +57,6 @@ async function runShutdown(boot, context) {
57
57
  }
58
58
  }
59
59
 
60
- // src/warmup.ts
61
- var import_node_fs = require("fs");
62
- var import_node_path = require("path");
63
- var import_node_url = require("url");
64
- var import_meta = {};
65
- var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
66
- function isDevMode() {
67
- return Boolean(import_meta.env?.["DEV"]);
68
- }
69
- function loadManifest() {
70
- if (isDevMode()) {
71
- return null;
72
- }
73
- const serverUrl = globalThis.__astroscope_server_url;
74
- if (!serverUrl) {
75
- return null;
76
- }
77
- const serverDir = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(serverUrl));
78
- const manifestPath = (0, import_node_path.join)(serverDir, "chunks", WARMUP_MANIFEST_FILE);
79
- if (!(0, import_node_fs.existsSync)(manifestPath)) {
80
- return null;
81
- }
82
- const manifest = JSON.parse((0, import_node_fs.readFileSync)(manifestPath, "utf-8"));
83
- return {
84
- modules: manifest.modules ?? [],
85
- serverDir
86
- };
87
- }
88
- async function warmup() {
89
- const manifest = loadManifest();
90
- if (!manifest || manifest.modules.length === 0) {
91
- return { success: [], failed: [], duration: 0 };
92
- }
93
- const { modules, serverDir } = manifest;
94
- const start = Date.now();
95
- const resolvedModules = modules.map((mod) => {
96
- const absolutePath = (0, import_node_path.resolve)(serverDir, mod);
97
- return (0, import_node_url.pathToFileURL)(absolutePath).href;
98
- });
99
- const results = await Promise.allSettled(resolvedModules.map((mod) => import(
100
- /* @vite-ignore */
101
- mod
102
- )));
103
- const success = [];
104
- const failed = [];
105
- for (let i = 0; i < results.length; i++) {
106
- if (results[i].status === "fulfilled") {
107
- success.push(modules[i]);
108
- } else {
109
- failed.push(modules[i]);
110
- }
111
- }
112
- return { success, failed, duration: Date.now() - start };
113
- }
114
-
115
60
  // src/setup.ts
116
61
  async function setup(boot, config) {
117
62
  const context = {
@@ -120,16 +65,7 @@ async function setup(boot, config) {
120
65
  port: process.env["PORT"] ? Number(process.env["PORT"]) : config.port
121
66
  };
122
67
  try {
123
- const warmupPromise = config.warmup ? warmup().then((result) => {
124
- if (result.success.length > 0) {
125
- console.log(`[boot] warmed up ${result.success.length} modules in ${result.duration}ms`);
126
- }
127
- if (result.failed.length > 0) {
128
- console.warn(`[boot] failed to warm up ${result.failed.length} modules`);
129
- }
130
- }) : void 0;
131
68
  await runStartup(boot, context);
132
- await warmupPromise;
133
69
  } catch (err) {
134
70
  console.error("[boot] startup failed:", err);
135
71
  try {
package/dist/setup.d.cts CHANGED
@@ -1,10 +1,9 @@
1
1
  import { BootModule } from './lifecycle.cjs';
2
- import './types-CxpusND2.cjs';
2
+ import './types-Ccbd5qoU.cjs';
3
3
 
4
4
  declare function setup(boot: BootModule, config: {
5
5
  host: string;
6
6
  port: number;
7
- warmup?: boolean | undefined;
8
7
  }): Promise<void>;
9
8
 
10
9
  export { setup };
package/dist/setup.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import { BootModule } from './lifecycle.js';
2
- import './types-CxpusND2.js';
2
+ import './types-Ccbd5qoU.js';
3
3
 
4
4
  declare function setup(boot: BootModule, config: {
5
5
  host: string;
6
6
  port: number;
7
- warmup?: boolean | undefined;
8
7
  }): Promise<void>;
9
8
 
10
9
  export { setup };
package/dist/setup.js CHANGED
@@ -3,9 +3,6 @@ import {
3
3
  runStartup
4
4
  } from "./chunk-LUFQ5I47.js";
5
5
  import "./chunk-I62ZQYTP.js";
6
- import {
7
- warmup
8
- } from "./chunk-SXYYPF3C.js";
9
6
 
10
7
  // src/setup.ts
11
8
  async function setup(boot, config) {
@@ -15,16 +12,7 @@ async function setup(boot, config) {
15
12
  port: process.env["PORT"] ? Number(process.env["PORT"]) : config.port
16
13
  };
17
14
  try {
18
- const warmupPromise = config.warmup ? warmup().then((result) => {
19
- if (result.success.length > 0) {
20
- console.log(`[boot] warmed up ${result.success.length} modules in ${result.duration}ms`);
21
- }
22
- if (result.failed.length > 0) {
23
- console.warn(`[boot] failed to warm up ${result.failed.length} modules`);
24
- }
25
- }) : void 0;
26
15
  await runStartup(boot, context);
27
- await warmupPromise;
28
16
  } catch (err) {
29
17
  console.error("[boot] startup failed:", err);
30
18
  try {
@@ -0,0 +1,10 @@
1
+ interface BootContext {
2
+ /** Whether running in development mode (vite dev server) */
3
+ dev: boolean;
4
+ /** Server host from Astro config */
5
+ host: string;
6
+ /** Server port from Astro config */
7
+ port: number;
8
+ }
9
+
10
+ export type { BootContext as B };
@@ -0,0 +1,10 @@
1
+ interface BootContext {
2
+ /** Whether running in development mode (vite dev server) */
3
+ dev: boolean;
4
+ /** Server host from Astro config */
5
+ host: string;
6
+ /** Server port from Astro config */
7
+ port: number;
8
+ }
9
+
10
+ export type { BootContext as B };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/boot",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Startup and graceful shutdown hooks for Astro SSR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,10 +11,6 @@
11
11
  "import": "./dist/index.js",
12
12
  "require": "./dist/index.cjs"
13
13
  },
14
- "./warmup": {
15
- "types": "./dist/warmup.d.ts",
16
- "import": "./dist/warmup.js"
17
- },
18
14
  "./events": {
19
15
  "types": "./dist/events.d.ts",
20
16
  "import": "./dist/events.js"
@@ -65,7 +61,7 @@
65
61
  "magic-string": "^0.30.21"
66
62
  },
67
63
  "scripts": {
68
- "build": "tsup src/index.ts src/warmup.ts src/events.ts src/lifecycle.ts src/setup.ts src/prepend.ts --format esm,cjs --dts",
64
+ "build": "tsup src/index.ts src/events.ts src/lifecycle.ts src/setup.ts src/prepend.ts --format esm,cjs --dts",
69
65
  "typecheck": "tsc --noEmit",
70
66
  "lint": "eslint src",
71
67
  "lint:fix": "eslint src --fix"
@@ -1,57 +0,0 @@
1
- // src/warmup.ts
2
- import { existsSync, readFileSync } from "fs";
3
- import { dirname, join, resolve } from "path";
4
- import { fileURLToPath, pathToFileURL } from "url";
5
- var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
6
- function isDevMode() {
7
- return Boolean(import.meta.env?.["DEV"]);
8
- }
9
- function loadManifest() {
10
- if (isDevMode()) {
11
- return null;
12
- }
13
- const serverUrl = globalThis.__astroscope_server_url;
14
- if (!serverUrl) {
15
- return null;
16
- }
17
- const serverDir = dirname(fileURLToPath(serverUrl));
18
- const manifestPath = join(serverDir, "chunks", WARMUP_MANIFEST_FILE);
19
- if (!existsSync(manifestPath)) {
20
- return null;
21
- }
22
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
23
- return {
24
- modules: manifest.modules ?? [],
25
- serverDir
26
- };
27
- }
28
- async function warmup() {
29
- const manifest = loadManifest();
30
- if (!manifest || manifest.modules.length === 0) {
31
- return { success: [], failed: [], duration: 0 };
32
- }
33
- const { modules, serverDir } = manifest;
34
- const start = Date.now();
35
- const resolvedModules = modules.map((mod) => {
36
- const absolutePath = resolve(serverDir, mod);
37
- return pathToFileURL(absolutePath).href;
38
- });
39
- const results = await Promise.allSettled(resolvedModules.map((mod) => import(
40
- /* @vite-ignore */
41
- mod
42
- )));
43
- const success = [];
44
- const failed = [];
45
- for (let i = 0; i < results.length; i++) {
46
- if (results[i].status === "fulfilled") {
47
- success.push(modules[i]);
48
- } else {
49
- failed.push(modules[i]);
50
- }
51
- }
52
- return { success, failed, duration: Date.now() - start };
53
- }
54
-
55
- export {
56
- warmup
57
- };
@@ -1,18 +0,0 @@
1
- interface BootContext {
2
- /** Whether running in development mode (vite dev server) */
3
- dev: boolean;
4
- /** Server host from Astro config */
5
- host: string;
6
- /** Server port from Astro config */
7
- port: number;
8
- }
9
- interface WarmupResult {
10
- /** Modules that were successfully loaded */
11
- success: string[];
12
- /** Modules that failed to load */
13
- failed: string[];
14
- /** Time taken in milliseconds */
15
- duration: number;
16
- }
17
-
18
- export type { BootContext as B, WarmupResult as W };
@@ -1,18 +0,0 @@
1
- interface BootContext {
2
- /** Whether running in development mode (vite dev server) */
3
- dev: boolean;
4
- /** Server host from Astro config */
5
- host: string;
6
- /** Server port from Astro config */
7
- port: number;
8
- }
9
- interface WarmupResult {
10
- /** Modules that were successfully loaded */
11
- success: string[];
12
- /** Modules that failed to load */
13
- failed: string[];
14
- /** Time taken in milliseconds */
15
- duration: number;
16
- }
17
-
18
- export type { BootContext as B, WarmupResult as W };
package/dist/warmup.cjs DELETED
@@ -1,82 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/warmup.ts
21
- var warmup_exports = {};
22
- __export(warmup_exports, {
23
- warmup: () => warmup
24
- });
25
- module.exports = __toCommonJS(warmup_exports);
26
- var import_node_fs = require("fs");
27
- var import_node_path = require("path");
28
- var import_node_url = require("url");
29
- var import_meta = {};
30
- var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
31
- function isDevMode() {
32
- return Boolean(import_meta.env?.["DEV"]);
33
- }
34
- function loadManifest() {
35
- if (isDevMode()) {
36
- return null;
37
- }
38
- const serverUrl = globalThis.__astroscope_server_url;
39
- if (!serverUrl) {
40
- return null;
41
- }
42
- const serverDir = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(serverUrl));
43
- const manifestPath = (0, import_node_path.join)(serverDir, "chunks", WARMUP_MANIFEST_FILE);
44
- if (!(0, import_node_fs.existsSync)(manifestPath)) {
45
- return null;
46
- }
47
- const manifest = JSON.parse((0, import_node_fs.readFileSync)(manifestPath, "utf-8"));
48
- return {
49
- modules: manifest.modules ?? [],
50
- serverDir
51
- };
52
- }
53
- async function warmup() {
54
- const manifest = loadManifest();
55
- if (!manifest || manifest.modules.length === 0) {
56
- return { success: [], failed: [], duration: 0 };
57
- }
58
- const { modules, serverDir } = manifest;
59
- const start = Date.now();
60
- const resolvedModules = modules.map((mod) => {
61
- const absolutePath = (0, import_node_path.resolve)(serverDir, mod);
62
- return (0, import_node_url.pathToFileURL)(absolutePath).href;
63
- });
64
- const results = await Promise.allSettled(resolvedModules.map((mod) => import(
65
- /* @vite-ignore */
66
- mod
67
- )));
68
- const success = [];
69
- const failed = [];
70
- for (let i = 0; i < results.length; i++) {
71
- if (results[i].status === "fulfilled") {
72
- success.push(modules[i]);
73
- } else {
74
- failed.push(modules[i]);
75
- }
76
- }
77
- return { success, failed, duration: Date.now() - start };
78
- }
79
- // Annotate the CommonJS export names for ESM import in node:
80
- 0 && (module.exports = {
81
- warmup
82
- });
package/dist/warmup.d.cts DELETED
@@ -1,22 +0,0 @@
1
- import { W as WarmupResult } from './types-CxpusND2.cjs';
2
-
3
- declare global {
4
- var __astroscope_server_url: string | undefined;
5
- }
6
- /**
7
- * Warms up V8 by importing all page modules and middleware.
8
- *
9
- * In development mode, this is a no-op that returns empty results.
10
- * In production, reads the warmup manifest and imports all discovered modules.
11
- *
12
- * @example
13
- * ```ts
14
- * import { warmup } from '@astroscope/boot/warmup';
15
- *
16
- * const result = await warmup();
17
- * console.log(`warmed up ${result.success.length} modules`);
18
- * ```
19
- */
20
- declare function warmup(): Promise<WarmupResult>;
21
-
22
- export { WarmupResult, warmup };
package/dist/warmup.d.ts DELETED
@@ -1,22 +0,0 @@
1
- import { W as WarmupResult } from './types-CxpusND2.js';
2
-
3
- declare global {
4
- var __astroscope_server_url: string | undefined;
5
- }
6
- /**
7
- * Warms up V8 by importing all page modules and middleware.
8
- *
9
- * In development mode, this is a no-op that returns empty results.
10
- * In production, reads the warmup manifest and imports all discovered modules.
11
- *
12
- * @example
13
- * ```ts
14
- * import { warmup } from '@astroscope/boot/warmup';
15
- *
16
- * const result = await warmup();
17
- * console.log(`warmed up ${result.success.length} modules`);
18
- * ```
19
- */
20
- declare function warmup(): Promise<WarmupResult>;
21
-
22
- export { WarmupResult, warmup };
package/dist/warmup.js DELETED
@@ -1,6 +0,0 @@
1
- import {
2
- warmup
3
- } from "./chunk-SXYYPF3C.js";
4
- export {
5
- warmup
6
- };