@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 +21 -0
- package/dist/chunk-SXYYPF3C.js +57 -0
- package/dist/index.cjs +58 -23
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +58 -23
- package/dist/setup.cjs +64 -0
- package/dist/setup.d.cts +1 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +12 -0
- package/dist/warmup.js +3 -53
- package/package.json +12 -12
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
package/dist/setup.d.ts
CHANGED
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
"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": "^
|
|
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": "^
|
|
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
|
+
}
|