@astroscope/boot 0.3.4 → 0.5.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Simon Bobrov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,57 @@
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
+ };
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ module.exports = __toCommonJS(index_exports);
39
39
  // src/integration.ts
40
40
  var import_node_fs2 = __toESM(require("fs"), 1);
41
41
  var import_magic_string = __toESM(require("magic-string"), 1);
42
+ var import_vite2 = require("vite");
42
43
 
43
44
  // src/hmr.ts
44
45
  var import_node_path = __toESM(require("path"), 1);
@@ -127,6 +128,16 @@ function serializeError(error) {
127
128
  return JSON.stringify(error);
128
129
  }
129
130
 
131
+ // src/vite-env.ts
132
+ var import_vite = require("vite");
133
+ async function ssrImport(server, moduleId) {
134
+ const ssr = server.environments["ssr"];
135
+ if (!(0, import_vite.isRunnableDevEnvironment)(ssr)) {
136
+ throw new Error("SSR environment is not runnable");
137
+ }
138
+ return ssr.runner.import(moduleId);
139
+ }
140
+
130
141
  // src/hmr.ts
131
142
  function setupBootHmr(server, entry, logger, getBootContext2) {
132
143
  const bootModuleId = `/${entry}`;
@@ -158,14 +169,14 @@ function setupBootHmr(server, entry, logger, getBootContext2) {
158
169
  logger.info(`boot dependency changed: ${changedPath}, rerunning hooks...`);
159
170
  const bootContext = getBootContext2();
160
171
  try {
161
- const oldModule = await server.ssrLoadModule(bootModuleId);
172
+ const oldModule = await ssrImport(server, bootModuleId);
162
173
  await runShutdown(oldModule, bootContext);
163
174
  } catch (error) {
164
175
  logger.error(`Error during boot HMR shutdown: ${serializeError(error)}`);
165
176
  }
166
177
  server.moduleGraph.invalidateAll();
167
178
  try {
168
- const newModule = await server.ssrLoadModule(bootModuleId);
179
+ const newModule = await ssrImport(server, bootModuleId);
169
180
  await runStartup(newModule, bootContext);
170
181
  } catch (error) {
171
182
  logger.error(`Error during boot HMR startup: ${serializeError(error)}`);
@@ -187,16 +198,39 @@ function getPrependCode() {
187
198
  var import_node_fs = __toESM(require("fs"), 1);
188
199
  var import_node_path2 = __toESM(require("path"), 1);
189
200
  var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
201
+ var MIDDLEWARE_VIRTUAL_ID = "\0virtual:astro:middleware";
190
202
  function collectWarmupModules(bundle) {
191
203
  const pageModules = [];
192
204
  let middlewarePath = null;
193
205
  for (const [fileName, chunk] of Object.entries(bundle)) {
194
- if (chunk.type !== "chunk") continue;
195
- if (fileName.startsWith("pages/") && fileName.endsWith(".mjs")) {
196
- pageModules.push(fileName);
197
- }
198
- if (fileName.includes("_astro-internal_middleware") || fileName.includes("_noop-middleware")) {
206
+ if (chunk.type === "chunk" && chunk.facadeModuleId === MIDDLEWARE_VIRTUAL_ID) {
199
207
  middlewarePath = fileName;
208
+ break;
209
+ }
210
+ }
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);
200
234
  }
201
235
  }
202
236
  return { pageModules, middlewarePath };
@@ -235,13 +269,12 @@ function getBootContext(server, config) {
235
269
  const { host, port } = getServerDefaults(config);
236
270
  return { dev: true, host, port };
237
271
  }
272
+ var getState = (0, import_vite2.perEnvironmentState)(() => ({ bootChunkRef: null, warmupModules: null }));
238
273
  function boot(options = {}) {
239
274
  const entry = resolveEntry(options.entry);
240
275
  const hmr = options.hmr ?? false;
241
- let isSSR = false;
242
- let bootChunkRef = null;
276
+ const enableWarmup = options.warmup ?? false;
243
277
  let astroConfig = null;
244
- let warmupModules = null;
245
278
  return {
246
279
  name: "@astroscope/boot",
247
280
  hooks: {
@@ -253,19 +286,18 @@ function boot(options = {}) {
253
286
  // build plugin: handles entry.mjs injection, warmup manifest
254
287
  {
255
288
  name: "@astroscope/boot",
256
- configResolved(config2) {
257
- isSSR = !!config2.build?.ssr;
258
- },
259
289
  buildStart() {
260
- if (!isSSR) return;
290
+ if (this.environment.name !== "ssr") return;
291
+ const state = getState(this);
261
292
  try {
262
- bootChunkRef = this.emitFile({ type: "chunk", id: entry, name: "boot" });
293
+ state.bootChunkRef = this.emitFile({ type: "chunk", id: entry, name: "boot" });
263
294
  } catch {
264
295
  }
265
296
  },
266
297
  generateBundle(_, bundle) {
267
- if (!isSSR || !bootChunkRef) return;
268
- const bootChunkName = this.getFileName(bootChunkRef);
298
+ const state = getState(this);
299
+ if (!state.bootChunkRef) return;
300
+ const bootChunkName = this.getFileName(state.bootChunkRef);
269
301
  if (!bootChunkName) {
270
302
  logger.warn("boot chunk not found");
271
303
  return;
@@ -275,7 +307,9 @@ function boot(options = {}) {
275
307
  logger.warn("entry.mjs not found - boot injection skipped");
276
308
  return;
277
309
  }
278
- warmupModules = collectWarmupModules(bundle);
310
+ if (enableWarmup) {
311
+ state.warmupModules = collectWarmupModules(bundle);
312
+ }
279
313
  const { host, port } = getServerDefaults(astroConfig);
280
314
  const prependCode = getPrependCode();
281
315
  const prefix = prependCode.length ? `${prependCode.join("\n")}
@@ -283,7 +317,7 @@ function boot(options = {}) {
283
317
  const injection = `${prefix}globalThis.__astroscope_server_url = import.meta.url;
284
318
  import * as __astroscope_boot from './${bootChunkName}';
285
319
  import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
286
- await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })});
320
+ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, warmup: enableWarmup })});
287
321
  `;
288
322
  const s = new import_magic_string.default(entryChunk.code);
289
323
  s.prepend(injection);
@@ -294,10 +328,11 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })
294
328
  logger.info(`injected ${bootChunkName} into entry.mjs`);
295
329
  },
296
330
  writeBundle(outputOptions) {
297
- if (!isSSR || !warmupModules) return;
331
+ const state = getState(this);
332
+ if (!state.warmupModules) return;
298
333
  const outDir = outputOptions.dir;
299
334
  if (!outDir) return;
300
- writeWarmupManifest(outDir, warmupModules, logger);
335
+ writeWarmupManifest(outDir, state.warmupModules, logger);
301
336
  }
302
337
  },
303
338
  // startup plugin: runs after all other configureServer hooks
@@ -308,7 +343,7 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })
308
343
  if (command !== "dev") return;
309
344
  try {
310
345
  const bootContext = getBootContext(server, astroConfig);
311
- const module2 = await server.ssrLoadModule(`/${entry}`);
346
+ const module2 = await ssrImport(server, `/${entry}`);
312
347
  await runStartup(module2, bootContext);
313
348
  } catch (error) {
314
349
  logger.error(`Error running startup script: ${serializeError(error)}`);
@@ -316,7 +351,7 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })
316
351
  server.httpServer?.once("close", async () => {
317
352
  try {
318
353
  const bootContext = getBootContext(server, astroConfig);
319
- const module2 = await server.ssrLoadModule(`/${entry}`);
354
+ const module2 = await ssrImport(server, `/${entry}`);
320
355
  await runShutdown(module2, bootContext);
321
356
  } catch (error) {
322
357
  logger.error(`Error running shutdown script: ${serializeError(error)}`);
package/dist/index.d.cts CHANGED
@@ -14,6 +14,13 @@ interface BootOptions {
14
14
  * @default false
15
15
  */
16
16
  hmr?: boolean | undefined;
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.
21
+ * @default false
22
+ */
23
+ warmup?: boolean | undefined;
17
24
  }
18
25
  /**
19
26
  * Astro integration for application lifecycle hooks.
package/dist/index.d.ts CHANGED
@@ -14,6 +14,13 @@ interface BootOptions {
14
14
  * @default false
15
15
  */
16
16
  hmr?: boolean | undefined;
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.
21
+ * @default false
22
+ */
23
+ warmup?: boolean | undefined;
17
24
  }
18
25
  /**
19
26
  * Astro integration for application lifecycle hooks.
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import "./chunk-I62ZQYTP.js";
11
11
  // src/integration.ts
12
12
  import fs2 from "fs";
13
13
  import MagicString from "magic-string";
14
+ import { perEnvironmentState } from "vite";
14
15
 
15
16
  // src/hmr.ts
16
17
  import path from "path";
@@ -66,6 +67,16 @@ function serializeError(error) {
66
67
  return JSON.stringify(error);
67
68
  }
68
69
 
70
+ // src/vite-env.ts
71
+ import { isRunnableDevEnvironment } from "vite";
72
+ async function ssrImport(server, moduleId) {
73
+ const ssr = server.environments["ssr"];
74
+ if (!isRunnableDevEnvironment(ssr)) {
75
+ throw new Error("SSR environment is not runnable");
76
+ }
77
+ return ssr.runner.import(moduleId);
78
+ }
79
+
69
80
  // src/hmr.ts
70
81
  function setupBootHmr(server, entry, logger, getBootContext2) {
71
82
  const bootModuleId = `/${entry}`;
@@ -97,14 +108,14 @@ function setupBootHmr(server, entry, logger, getBootContext2) {
97
108
  logger.info(`boot dependency changed: ${changedPath}, rerunning hooks...`);
98
109
  const bootContext = getBootContext2();
99
110
  try {
100
- const oldModule = await server.ssrLoadModule(bootModuleId);
111
+ const oldModule = await ssrImport(server, bootModuleId);
101
112
  await runShutdown(oldModule, bootContext);
102
113
  } catch (error) {
103
114
  logger.error(`Error during boot HMR shutdown: ${serializeError(error)}`);
104
115
  }
105
116
  server.moduleGraph.invalidateAll();
106
117
  try {
107
- const newModule = await server.ssrLoadModule(bootModuleId);
118
+ const newModule = await ssrImport(server, bootModuleId);
108
119
  await runStartup(newModule, bootContext);
109
120
  } catch (error) {
110
121
  logger.error(`Error during boot HMR startup: ${serializeError(error)}`);
@@ -117,16 +128,39 @@ function setupBootHmr(server, entry, logger, getBootContext2) {
117
128
  import fs from "fs";
118
129
  import path2 from "path";
119
130
  var WARMUP_MANIFEST_FILE = "warmup-manifest.json";
131
+ var MIDDLEWARE_VIRTUAL_ID = "\0virtual:astro:middleware";
120
132
  function collectWarmupModules(bundle) {
121
133
  const pageModules = [];
122
134
  let middlewarePath = null;
123
135
  for (const [fileName, chunk] of Object.entries(bundle)) {
124
- if (chunk.type !== "chunk") continue;
125
- if (fileName.startsWith("pages/") && fileName.endsWith(".mjs")) {
126
- pageModules.push(fileName);
127
- }
128
- if (fileName.includes("_astro-internal_middleware") || fileName.includes("_noop-middleware")) {
136
+ if (chunk.type === "chunk" && chunk.facadeModuleId === MIDDLEWARE_VIRTUAL_ID) {
129
137
  middlewarePath = fileName;
138
+ break;
139
+ }
140
+ }
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);
130
164
  }
131
165
  }
132
166
  return { pageModules, middlewarePath };
@@ -165,13 +199,12 @@ function getBootContext(server, config) {
165
199
  const { host, port } = getServerDefaults(config);
166
200
  return { dev: true, host, port };
167
201
  }
202
+ var getState = perEnvironmentState(() => ({ bootChunkRef: null, warmupModules: null }));
168
203
  function boot(options = {}) {
169
204
  const entry = resolveEntry(options.entry);
170
205
  const hmr = options.hmr ?? false;
171
- let isSSR = false;
172
- let bootChunkRef = null;
206
+ const enableWarmup = options.warmup ?? false;
173
207
  let astroConfig = null;
174
- let warmupModules = null;
175
208
  return {
176
209
  name: "@astroscope/boot",
177
210
  hooks: {
@@ -183,19 +216,18 @@ function boot(options = {}) {
183
216
  // build plugin: handles entry.mjs injection, warmup manifest
184
217
  {
185
218
  name: "@astroscope/boot",
186
- configResolved(config2) {
187
- isSSR = !!config2.build?.ssr;
188
- },
189
219
  buildStart() {
190
- if (!isSSR) return;
220
+ if (this.environment.name !== "ssr") return;
221
+ const state = getState(this);
191
222
  try {
192
- bootChunkRef = this.emitFile({ type: "chunk", id: entry, name: "boot" });
223
+ state.bootChunkRef = this.emitFile({ type: "chunk", id: entry, name: "boot" });
193
224
  } catch {
194
225
  }
195
226
  },
196
227
  generateBundle(_, bundle) {
197
- if (!isSSR || !bootChunkRef) return;
198
- const bootChunkName = this.getFileName(bootChunkRef);
228
+ const state = getState(this);
229
+ if (!state.bootChunkRef) return;
230
+ const bootChunkName = this.getFileName(state.bootChunkRef);
199
231
  if (!bootChunkName) {
200
232
  logger.warn("boot chunk not found");
201
233
  return;
@@ -205,7 +237,9 @@ function boot(options = {}) {
205
237
  logger.warn("entry.mjs not found - boot injection skipped");
206
238
  return;
207
239
  }
208
- warmupModules = collectWarmupModules(bundle);
240
+ if (enableWarmup) {
241
+ state.warmupModules = collectWarmupModules(bundle);
242
+ }
209
243
  const { host, port } = getServerDefaults(astroConfig);
210
244
  const prependCode = getPrependCode();
211
245
  const prefix = prependCode.length ? `${prependCode.join("\n")}
@@ -213,7 +247,7 @@ function boot(options = {}) {
213
247
  const injection = `${prefix}globalThis.__astroscope_server_url = import.meta.url;
214
248
  import * as __astroscope_boot from './${bootChunkName}';
215
249
  import { setup as __astroscope_bootSetup } from '@astroscope/boot/setup';
216
- await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })});
250
+ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port, warmup: enableWarmup })});
217
251
  `;
218
252
  const s = new MagicString(entryChunk.code);
219
253
  s.prepend(injection);
@@ -224,10 +258,11 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })
224
258
  logger.info(`injected ${bootChunkName} into entry.mjs`);
225
259
  },
226
260
  writeBundle(outputOptions) {
227
- if (!isSSR || !warmupModules) return;
261
+ const state = getState(this);
262
+ if (!state.warmupModules) return;
228
263
  const outDir = outputOptions.dir;
229
264
  if (!outDir) return;
230
- writeWarmupManifest(outDir, warmupModules, logger);
265
+ writeWarmupManifest(outDir, state.warmupModules, logger);
231
266
  }
232
267
  },
233
268
  // startup plugin: runs after all other configureServer hooks
@@ -238,7 +273,7 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })
238
273
  if (command !== "dev") return;
239
274
  try {
240
275
  const bootContext = getBootContext(server, astroConfig);
241
- const module = await server.ssrLoadModule(`/${entry}`);
276
+ const module = await ssrImport(server, `/${entry}`);
242
277
  await runStartup(module, bootContext);
243
278
  } catch (error) {
244
279
  logger.error(`Error running startup script: ${serializeError(error)}`);
@@ -246,7 +281,7 @@ await __astroscope_bootSetup(__astroscope_boot, ${JSON.stringify({ host, port })
246
281
  server.httpServer?.once("close", async () => {
247
282
  try {
248
283
  const bootContext = getBootContext(server, astroConfig);
249
- const module = await server.ssrLoadModule(`/${entry}`);
284
+ const module = await ssrImport(server, `/${entry}`);
250
285
  await runShutdown(module, bootContext);
251
286
  } catch (error) {
252
287
  logger.error(`Error running shutdown script: ${serializeError(error)}`);
package/dist/setup.cjs CHANGED
@@ -57,6 +57,61 @@ 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
+
60
115
  // src/setup.ts
61
116
  async function setup(boot, config) {
62
117
  const context = {
@@ -65,7 +120,16 @@ async function setup(boot, config) {
65
120
  port: process.env["PORT"] ? Number(process.env["PORT"]) : config.port
66
121
  };
67
122
  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;
68
131
  await runStartup(boot, context);
132
+ await warmupPromise;
69
133
  } catch (err) {
70
134
  console.error("[boot] startup failed:", err);
71
135
  try {
package/dist/setup.d.cts CHANGED
@@ -4,6 +4,7 @@ import './types-CxpusND2.cjs';
4
4
  declare function setup(boot: BootModule, config: {
5
5
  host: string;
6
6
  port: number;
7
+ warmup?: boolean | undefined;
7
8
  }): Promise<void>;
8
9
 
9
10
  export { setup };
package/dist/setup.d.ts CHANGED
@@ -4,6 +4,7 @@ import './types-CxpusND2.js';
4
4
  declare function setup(boot: BootModule, config: {
5
5
  host: string;
6
6
  port: number;
7
+ warmup?: boolean | undefined;
7
8
  }): Promise<void>;
8
9
 
9
10
  export { setup };
package/dist/setup.js CHANGED
@@ -3,6 +3,9 @@ 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";
6
9
 
7
10
  // src/setup.ts
8
11
  async function setup(boot, config) {
@@ -12,7 +15,16 @@ async function setup(boot, config) {
12
15
  port: process.env["PORT"] ? Number(process.env["PORT"]) : config.port
13
16
  };
14
17
  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;
15
26
  await runStartup(boot, context);
27
+ await warmupPromise;
16
28
  } catch (err) {
17
29
  console.error("[boot] startup failed:", err);
18
30
  try {
package/dist/warmup.js CHANGED
@@ -1,56 +1,6 @@
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
- }
1
+ import {
2
+ warmup
3
+ } from "./chunk-SXYYPF3C.js";
54
4
  export {
55
5
  warmup
56
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/boot",
3
- "version": "0.3.4",
3
+ "version": "0.5.0",
4
4
  "description": "Startup and graceful shutdown hooks for Astro SSR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,22 +52,22 @@
52
52
  "url": "https://github.com/smnbbrv/astroscope/issues"
53
53
  },
54
54
  "homepage": "https://github.com/smnbbrv/astroscope/tree/main/packages/boot#readme",
55
- "scripts": {
56
- "build": "tsup src/index.ts src/warmup.ts src/events.ts src/lifecycle.ts src/setup.ts src/prepend.ts --format esm,cjs --dts",
57
- "typecheck": "tsc --noEmit",
58
- "lint": "eslint src",
59
- "lint:fix": "eslint src --fix"
60
- },
61
55
  "devDependencies": {
62
- "astro": "^5.17.1",
56
+ "astro": "^6.0.7",
63
57
  "tsup": "^8.5.1",
64
- "typescript": "^5.9.3",
65
- "vite": "^6.4.1"
58
+ "typescript": "^5.9.3"
66
59
  },
67
60
  "peerDependencies": {
68
- "astro": "^5.0.0"
61
+ "astro": "^6.0.0",
62
+ "vite": "^7.0.0"
69
63
  },
70
64
  "dependencies": {
71
65
  "magic-string": "^0.30.21"
66
+ },
67
+ "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",
69
+ "typecheck": "tsc --noEmit",
70
+ "lint": "eslint src",
71
+ "lint:fix": "eslint src --fix"
72
72
  }
73
- }
73
+ }