@astroscope/node 1.4.0 → 2.0.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 +32 -1
- package/dist/emitters-C20tjKLp.js +43 -0
- package/dist/emitters-C20tjKLp.js.map +1 -0
- package/dist/image-endpoint.d.ts +11 -0
- package/dist/image-endpoint.d.ts.map +1 -0
- package/dist/image-endpoint.js +11 -0
- package/dist/image-endpoint.js.map +1 -0
- package/dist/image-service.d.ts +15 -0
- package/dist/image-service.d.ts.map +1 -0
- package/dist/image-service.js +28 -0
- package/dist/image-service.js.map +1 -0
- package/dist/index.d.ts +24 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +115 -7
- package/dist/index.js.map +1 -1
- package/dist/islands-middleware-entrypoint.d.ts +5 -0
- package/dist/islands-middleware-entrypoint.d.ts.map +1 -0
- package/dist/islands-middleware-entrypoint.js +58 -0
- package/dist/islands-middleware-entrypoint.js.map +1 -0
- package/dist/islands-runtime.d.ts +1 -0
- package/dist/islands-runtime.js +113 -0
- package/dist/islands-runtime.js.map +1 -0
- package/dist/islands.d.ts +2 -0
- package/dist/islands.js +3 -0
- package/dist/log-B69HEBvg.js.map +1 -1
- package/dist/native-mount-DjYEnO4X.js.map +1 -1
- package/dist/prepare-CXZsyAVk.js.map +1 -1
- package/dist/prerendered-CpEAJN_q.js +34 -0
- package/dist/prerendered-CpEAJN_q.js.map +1 -0
- package/dist/route-store-DdxGePj2.js +27 -0
- package/dist/route-store-DdxGePj2.js.map +1 -0
- package/dist/route-store-DtY3uvLf.d.ts +69 -0
- package/dist/route-store-DtY3uvLf.d.ts.map +1 -0
- package/dist/server.js +13 -11
- package/dist/server.js.map +1 -1
- package/dist/strip-build-paths-C6eb1QdG.js +63 -0
- package/dist/strip-build-paths-C6eb1QdG.js.map +1 -0
- package/dist/transform-D8dIBGEr.js +191 -0
- package/dist/transform-D8dIBGEr.js.map +1 -0
- package/package.json +29 -10
package/README.md
CHANGED
|
@@ -11,9 +11,11 @@ Opinionated, cloud-friendly Node adapter for Astro: boot lifecycle, health probe
|
|
|
11
11
|
- **Telemetry** — OpenTelemetry NodeSDK, undici fetch instrumentation, runtime + host metrics, Prometheus reader
|
|
12
12
|
- **CSRF protection** — origin check for unsafe methods, with path exclusions
|
|
13
13
|
- **Platform entry files** — env loading → `src/config.ts` → `src/instrumentation.ts` → `src/log.ts` → boot, each picked up automatically when the file exists
|
|
14
|
+
- **Island preloading** — each island's JS is preloaded in parallel instead of being discovered module by module, removing the hydration request waterfall ([Island preloading](#island-preloading))
|
|
14
15
|
- **Pre-compressed static serving** — build-time brotli/gzip variants, negotiated per `Accept-Encoding`
|
|
15
16
|
- **Native mounts** — http-native handlers (`oidc-provider`, ACME) mounted on the adapter's server
|
|
16
17
|
- **Build tweaks** — SSR sourcemaps, SSR effect stripping
|
|
18
|
+
- **Image processing off unless configured** — without an explicit `image.service`, any `astro:assets` use fails loudly instead of opening astro's on-demand sharp endpoint ([Image processing](#image-processing))
|
|
17
19
|
- **Dev restart machinery** — changes to the boot file or entry seams restart the dev server behind a holding page
|
|
18
20
|
- **Dev island warmup** — `.astro` sources are scanned for `client:*` components; their deps are pre-optimized and their module graphs warmed at server start, preventing "504 Outdated Optimize Dep" hydration failures from vite's lazy dep discovery
|
|
19
21
|
- **HTTPS for development** — `SERVER_CERT_PATH` / `SERVER_KEY_PATH` serve TLS directly for local runs of the built server ([HTTPS](#https)); in production, terminate TLS at the ingress
|
|
@@ -25,7 +27,7 @@ The adapter assumes a container behind a load balancer / reverse proxy (Kubernet
|
|
|
25
27
|
- **Opens `0.0.0.0:9090` in production** — the health probe server. Meant for the kubelet; do not expose it publicly.
|
|
26
28
|
- **Opens `0.0.0.0:9464` in production** — the Prometheus metrics reader. Same: cluster-internal only.
|
|
27
29
|
- **Trusts any `Host` / `X-Forwarded-Host`** — sets `security.allowedDomains: [{}]` (unless you set it yourself), because the reverse proxy is expected to control these headers. Without one, host header injection is possible.
|
|
28
|
-
- **Overrides Astro security and config defaults** — `security.checkOrigin: false` (the embedded CSRF middleware replaces it; `csrf: false` restores Astro's check), `build.redirects: false` (redirects handled at runtime), `trailingSlash: 'never'` (only when yours is at the default `'ignore'`).
|
|
30
|
+
- **Overrides Astro security and config defaults** — `security.checkOrigin: false` (the embedded CSRF middleware replaces it; `csrf: false` restores Astro's check), `build.redirects: false` (redirects handled at runtime), `trailingSlash: 'never'` (only when yours is at the default `'ignore'`), `astro:assets` disabled when `image.service` is at its default ([Image processing](#image-processing)).
|
|
29
31
|
- **Standalone only.** No middleware mode — the adapter always owns the server.
|
|
30
32
|
- **No session driver.** Astro sessions are unsupported unless you configure `session.driver` yourself.
|
|
31
33
|
- **No health probes in dev.** The health server only exists in production and `astro preview`.
|
|
@@ -238,6 +240,12 @@ export const onRequest = withExcluded(someExternalMiddleware(), RECOMMENDED_EXCL
|
|
|
238
240
|
|
|
239
241
|
For hot paths, compile the set once with `createMatcher` from `@entwico/dash/match` instead of scanning per request (the adapter's own request instrumentation does exactly that).
|
|
240
242
|
|
|
243
|
+
## Island preloading
|
|
244
|
+
|
|
245
|
+
Without it, an island's JS loads as a chain: the browser fetches the component module, parses it, discovers its imports, fetches those, and so on. In production the adapter knows each island's chunks from the build and emits preload tags next to the island, so the browser fetches everything in parallel. Deferred islands (`client:visible`, `client:idle`, `client:media`) are preloaded just ahead of their hydration.
|
|
246
|
+
|
|
247
|
+
Always on in production; `islands: false` disables it.
|
|
248
|
+
|
|
241
249
|
## Pre-compressed static serving
|
|
242
250
|
|
|
243
251
|
At build time, every compressible file in `dist/client` gets max-quality `.br` (brotli 11) and `.gz` (gzip 9) variants written next to it — variants that don't shrink the file are skipped. At request time the static handler negotiates `Accept-Encoding` and serves the best variant with the original's content-type, `content-encoding`, `vary: accept-encoding` and per-variant etags (304s included). Behind a caching proxy with per-encoding cache keys, the origin serves each asset once per encoding.
|
|
@@ -249,6 +257,22 @@ Always on, no configuration:
|
|
|
249
257
|
- **SSR sourcemaps** — the server bundle gets sourcemaps for readable stack traces; client bundles stay unmapped so browsers can't fetch source
|
|
250
258
|
- **SSR effect stripping** — `useEffect`/`useLayoutEffect`/`useInsertionEffect` callbacks are emptied in the SSR bundle (effects never run on the server), letting the bundler drop client-only dynamic imports (maplibre-gl, hls.js, …) from the server build and the docker image
|
|
251
259
|
|
|
260
|
+
## Image processing
|
|
261
|
+
|
|
262
|
+
In SSR, astro's default sharp service makes `/_image` an on-demand decode+encode endpoint whose transform space is attacker-controlled: every distinct query string (`w`, `h`, `q`, `fit`, `position`, `background`) costs a full sharp run and is a distinct cache key, so no fronting cache can absorb it. Most SSR apps serve pre-generated variants from a CMS/CDN anyway, so the adapter keeps processing off unless you ask for it — governed by one option:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
node({
|
|
266
|
+
imageService: 'auto', // 'on' | 'off' | 'auto'
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- `'auto'` (default) — on when the astro config sets `image.service` itself, off otherwise
|
|
271
|
+
- `'on'` — astro's default sharp service, no further config needed
|
|
272
|
+
- `'off'` — off even over an explicit `image.service`
|
|
273
|
+
|
|
274
|
+
Off is loud, not silent: the adapter installs a service whose every method throws with an explanation, so any `astro:assets` use — `<Image>`, `<Picture>`, `getImage()`, markdown or content-collection images — fails at render/build time instead of quietly serving unoptimized bytes. The `/_image` endpoint is replaced with a 404 responder — to clients the endpoint simply doesn't exist. Apps without images never load any of it.
|
|
275
|
+
|
|
252
276
|
## Options
|
|
253
277
|
|
|
254
278
|
```typescript
|
|
@@ -287,6 +311,13 @@ node({
|
|
|
287
311
|
dev: false, // start the SDK in dev too (once per process)
|
|
288
312
|
},
|
|
289
313
|
|
|
314
|
+
// island dependency preloading (production-only); false disables it
|
|
315
|
+
islands: false,
|
|
316
|
+
|
|
317
|
+
// on-demand image processing: 'auto' = on only when image.service is set
|
|
318
|
+
// in the astro config (see the Image processing section)
|
|
319
|
+
imageService: 'auto',
|
|
320
|
+
|
|
290
321
|
bodySizeLimit: 1024 * 1024 * 1024, // request body limit in bytes
|
|
291
322
|
shutdownTimeout: 10_000, // ms to wait for in-flight requests on shutdown
|
|
292
323
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/islands/emitters.ts
|
|
2
|
+
/**
|
|
3
|
+
* Emitters registered by other packages (e.g. `@astroscope/i18n`). Keyed on
|
|
4
|
+
* `globalThis` via `Symbol.for` so the vite-runner and native module instances share
|
|
5
|
+
* one registry — same pattern as the log store.
|
|
6
|
+
*/
|
|
7
|
+
const ISLAND_REGISTRY = Symbol.for("@astroscope/node.islandEmitters");
|
|
8
|
+
const DOCUMENT_REGISTRY = Symbol.for("@astroscope/node.documentEmitters");
|
|
9
|
+
function islandRegistry() {
|
|
10
|
+
const scope = globalThis;
|
|
11
|
+
return scope[ISLAND_REGISTRY] ??= [];
|
|
12
|
+
}
|
|
13
|
+
function documentRegistry() {
|
|
14
|
+
const scope = globalThis;
|
|
15
|
+
return scope[DOCUMENT_REGISTRY] ??= [];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Register an emitter that contributes preload links and/or attributes for every
|
|
19
|
+
* island the islands middleware sees. Registration is process-wide — call it once
|
|
20
|
+
* during boot or module initialization, not per request.
|
|
21
|
+
*/
|
|
22
|
+
function registerIslandEmitter(emitter) {
|
|
23
|
+
islandRegistry().push(emitter);
|
|
24
|
+
}
|
|
25
|
+
function getIslandEmitters() {
|
|
26
|
+
return islandRegistry();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Register an emitter that contributes document-level content (a head bootstrap
|
|
30
|
+
* script, a stream-end script) for every html page response the islands middleware
|
|
31
|
+
* streams. Registration is process-wide — call it once during boot or module
|
|
32
|
+
* initialization, not per request.
|
|
33
|
+
*/
|
|
34
|
+
function registerDocumentEmitter(emitter) {
|
|
35
|
+
documentRegistry().push(emitter);
|
|
36
|
+
}
|
|
37
|
+
function getDocumentEmitters() {
|
|
38
|
+
return documentRegistry();
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { registerIslandEmitter as i, getIslandEmitters as n, registerDocumentEmitter as r, getDocumentEmitters as t };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=emitters-C20tjKLp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitters-C20tjKLp.js","names":[],"sources":["../src/islands/emitters.ts"],"sourcesContent":["import type { DocumentEmitter, IslandEmitter } from './types.js';\n\n/**\n * Emitters registered by other packages (e.g. `@astroscope/i18n`). Keyed on\n * `globalThis` via `Symbol.for` so the vite-runner and native module instances share\n * one registry — same pattern as the log store.\n */\nconst ISLAND_REGISTRY = Symbol.for('@astroscope/node.islandEmitters');\nconst DOCUMENT_REGISTRY = Symbol.for('@astroscope/node.documentEmitters');\n\ntype Scope = { [ISLAND_REGISTRY]?: IslandEmitter[]; [DOCUMENT_REGISTRY]?: DocumentEmitter[] };\n\nfunction islandRegistry(): IslandEmitter[] {\n const scope = globalThis as Scope;\n\n return (scope[ISLAND_REGISTRY] ??= []);\n}\n\nfunction documentRegistry(): DocumentEmitter[] {\n const scope = globalThis as Scope;\n\n return (scope[DOCUMENT_REGISTRY] ??= []);\n}\n\n/**\n * Register an emitter that contributes preload links and/or attributes for every\n * island the islands middleware sees. Registration is process-wide — call it once\n * during boot or module initialization, not per request.\n */\nexport function registerIslandEmitter(emitter: IslandEmitter): void {\n islandRegistry().push(emitter);\n}\n\nexport function getIslandEmitters(): readonly IslandEmitter[] {\n return islandRegistry();\n}\n\n/**\n * Register an emitter that contributes document-level content (a head bootstrap\n * script, a stream-end script) for every html page response the islands middleware\n * streams. Registration is process-wide — call it once during boot or module\n * initialization, not per request.\n */\nexport function registerDocumentEmitter(emitter: DocumentEmitter): void {\n documentRegistry().push(emitter);\n}\n\nexport function getDocumentEmitters(): readonly DocumentEmitter[] {\n return documentRegistry();\n}\n"],"mappings":";;;;;;AAOA,MAAM,kBAAkB,OAAO,IAAI,iCAAiC;AACpE,MAAM,oBAAoB,OAAO,IAAI,mCAAmC;AAIxE,SAAS,iBAAkC;CACzC,MAAM,QAAQ;CAEd,OAAQ,MAAM,qBAAqB,CAAC;AACtC;AAEA,SAAS,mBAAsC;CAC7C,MAAM,QAAQ;CAEd,OAAQ,MAAM,uBAAuB,CAAC;AACxC;;;;;;AAOA,SAAgB,sBAAsB,SAA8B;CAClE,eAAe,CAAC,CAAC,KAAK,OAAO;AAC/B;AAEA,SAAgB,oBAA8C;CAC5D,OAAO,eAAe;AACxB;;;;;;;AAQA,SAAgB,wBAAwB,SAAgC;CACtE,iBAAiB,CAAC,CAAC,KAAK,OAAO;AACjC;AAEA,SAAgB,sBAAkD;CAChE,OAAO,iBAAiB;AAC1B"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { APIRoute } from "astro";
|
|
2
|
+
//#region src/image/endpoint-disabled.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Replaces the `/_image` endpoint when image processing is disabled. As far as
|
|
5
|
+
* clients are concerned the endpoint does not exist, so requests answer 404 —
|
|
6
|
+
* a 400 would advertise a live processing surface behind the route.
|
|
7
|
+
*/
|
|
8
|
+
declare const GET: APIRoute;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { GET };
|
|
11
|
+
//# sourceMappingURL=image-endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-endpoint.d.ts","names":[],"sources":["../src/image/endpoint-disabled.ts"],"mappings":";;;;;;;cAOa,KAAK"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/image/endpoint-disabled.ts
|
|
2
|
+
/**
|
|
3
|
+
* Replaces the `/_image` endpoint when image processing is disabled. As far as
|
|
4
|
+
* clients are concerned the endpoint does not exist, so requests answer 404 —
|
|
5
|
+
* a 400 would advertise a live processing surface behind the route.
|
|
6
|
+
*/
|
|
7
|
+
const GET = () => new Response(null, { status: 404 });
|
|
8
|
+
//#endregion
|
|
9
|
+
export { GET };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=image-endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-endpoint.js","names":[],"sources":["../src/image/endpoint-disabled.ts"],"sourcesContent":["import type { APIRoute } from 'astro';\n\n/**\n * Replaces the `/_image` endpoint when image processing is disabled. As far as\n * clients are concerned the endpoint does not exist, so requests answer 404 —\n * a 400 would advertise a live processing surface behind the route.\n */\nexport const GET: APIRoute = () => new Response(null, { status: 404 });\n"],"mappings":";;;;;;AAOA,MAAa,YAAsB,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LocalImageService } from "astro";
|
|
2
|
+
//#region src/image/no-image-service.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The service injected for `imageService: 'off'` (and `'auto'` without a
|
|
5
|
+
* configured `image.service`). Any `astro:assets` use — `<Image>`,
|
|
6
|
+
* `<Picture>`, `getImage()`, markdown or content-collection images — throws
|
|
7
|
+
* the explanation above at render/build time, so disabled processing fails
|
|
8
|
+
* loudly instead of silently serving unoptimized bytes. `/_image` itself is
|
|
9
|
+
* replaced by the 404 endpoint in `endpoint-disabled.ts`. The module is only
|
|
10
|
+
* ever imported on first `astro:assets` use.
|
|
11
|
+
*/
|
|
12
|
+
declare const service: LocalImageService;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { service as default };
|
|
15
|
+
//# sourceMappingURL=image-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-service.d.ts","names":[],"sources":["../src/image/no-image-service.ts"],"mappings":";;;;;;;;;;;cAqBM,SAAS"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/image/no-image-service.ts
|
|
2
|
+
const MESSAGE = "[@astroscope/node] image processing is disabled: astro's on-demand `/_image` endpoint is a decode+encode amplification surface, so the adapter turns it off unless a service is configured. Serve pre-generated variants with a plain <img>, or opt in via the adapter option `imageService: 'on'` (astro's sharp service) or an explicit `image.service` in the astro config.";
|
|
3
|
+
function disabled() {
|
|
4
|
+
throw new Error(MESSAGE);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The service injected for `imageService: 'off'` (and `'auto'` without a
|
|
8
|
+
* configured `image.service`). Any `astro:assets` use — `<Image>`,
|
|
9
|
+
* `<Picture>`, `getImage()`, markdown or content-collection images — throws
|
|
10
|
+
* the explanation above at render/build time, so disabled processing fails
|
|
11
|
+
* loudly instead of silently serving unoptimized bytes. `/_image` itself is
|
|
12
|
+
* replaced by the 404 endpoint in `endpoint-disabled.ts`. The module is only
|
|
13
|
+
* ever imported on first `astro:assets` use.
|
|
14
|
+
*/
|
|
15
|
+
const service = {
|
|
16
|
+
propertiesToHash: ["src"],
|
|
17
|
+
validateOptions: disabled,
|
|
18
|
+
getHTMLAttributes: disabled,
|
|
19
|
+
getSrcSet: disabled,
|
|
20
|
+
getURL: disabled,
|
|
21
|
+
getRemoteSize: disabled,
|
|
22
|
+
parseURL: () => void 0,
|
|
23
|
+
transform: disabled
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { service as default };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=image-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-service.js","names":[],"sources":["../src/image/no-image-service.ts"],"sourcesContent":["import type { LocalImageService } from 'astro';\n\nconst MESSAGE =\n \"[@astroscope/node] image processing is disabled: astro's on-demand `/_image` endpoint is a decode+encode \" +\n 'amplification surface, so the adapter turns it off unless a service is configured. Serve pre-generated ' +\n \"variants with a plain <img>, or opt in via the adapter option `imageService: 'on'` (astro's sharp service) \" +\n 'or an explicit `image.service` in the astro config.';\n\nfunction disabled(): never {\n throw new Error(MESSAGE);\n}\n\n/**\n * The service injected for `imageService: 'off'` (and `'auto'` without a\n * configured `image.service`). Any `astro:assets` use — `<Image>`,\n * `<Picture>`, `getImage()`, markdown or content-collection images — throws\n * the explanation above at render/build time, so disabled processing fails\n * loudly instead of silently serving unoptimized bytes. `/_image` itself is\n * replaced by the 404 endpoint in `endpoint-disabled.ts`. The module is only\n * ever imported on first `astro:assets` use.\n */\nconst service: LocalImageService = {\n propertiesToHash: ['src'],\n validateOptions: disabled,\n getHTMLAttributes: disabled,\n getSrcSet: disabled,\n getURL: disabled,\n getRemoteSize: disabled,\n parseURL: () => undefined,\n transform: disabled,\n};\n\nexport default service;\n"],"mappings":";AAEA,MAAM,UACJ;AAKF,SAAS,WAAkB;CACzB,MAAM,IAAI,MAAM,OAAO;AACzB;;;;;;;;;;AAWA,MAAM,UAA6B;CACjC,kBAAkB,CAAC,KAAK;CACxB,iBAAiB;CACjB,mBAAmB;CACnB,WAAW;CACX,QAAQ;CACR,eAAe;CACf,gBAAgB,KAAA;CAChB,WAAW;AACb"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { r as ExcludePattern } from "./excludes-BDiE3eyp.js";
|
|
2
2
|
import "./construct-BGlPfWMF.js";
|
|
3
|
+
import { c as IslandEmitter, i as registerIslandEmitter, l as IslandInfo, s as IslandEmission, t as getRequestRouteData } from "./route-store-DtY3uvLf.js";
|
|
3
4
|
import { t as BootContext } from "./types-D0uMBi2M.js";
|
|
4
5
|
import { n as BootEventName, t as BootEventHandler } from "./events-u7J3ezJR.js";
|
|
5
6
|
import { AstroIntegration } from "astro";
|
|
@@ -130,6 +131,28 @@ interface NodeOptions {
|
|
|
130
131
|
csrf?: {
|
|
131
132
|
exclude?: ExcludePattern[] | undefined;
|
|
132
133
|
} | false | undefined;
|
|
134
|
+
/**
|
|
135
|
+
* Island dependency preloading: streams html responses through a rewriter that
|
|
136
|
+
* emits `modulepreload` links (and preload data for deferred islands, fired by a
|
|
137
|
+
* small gate runtime with the directive's own scheduling) for each island's
|
|
138
|
+
* chunk closure, removing the hydration request waterfall. Production-only —
|
|
139
|
+
* dev has no chunk graph. Set to `false` to disable the preloading; the islands
|
|
140
|
+
* middleware itself stays active for packages that registered island or document
|
|
141
|
+
* emitters (e.g. `@astroscope/i18n`, `@astroscope/wormhole`).
|
|
142
|
+
*/
|
|
143
|
+
islands?: false | undefined;
|
|
144
|
+
/**
|
|
145
|
+
* On-demand image processing (`astro:assets` through the `/_image`
|
|
146
|
+
* endpoint). `'auto'`: enabled only when the astro config sets
|
|
147
|
+
* `image.service` itself; otherwise processing is disabled — any
|
|
148
|
+
* `<Image>`/`getImage()` use throws with an explanation and `/_image`
|
|
149
|
+
* answers 404, as if the endpoint did not exist. `'on'` keeps astro's default sharp service; `'off'` disables
|
|
150
|
+
* even over an explicit `image.service`. Off unless configured because the
|
|
151
|
+
* sharp endpoint is an on-demand decode+encode amplification surface most
|
|
152
|
+
* SSR apps (serving pre-generated variants) don't need.
|
|
153
|
+
* @default 'auto'
|
|
154
|
+
*/
|
|
155
|
+
imageService?: 'on' | 'off' | 'auto' | undefined;
|
|
133
156
|
/**
|
|
134
157
|
* Maximum request body size in bytes. `0` or `Infinity` disables the limit.
|
|
135
158
|
* @default 1073741824 (1 GiB)
|
|
@@ -163,5 +186,5 @@ interface BootModule {
|
|
|
163
186
|
onShutdown?: ((context: BootContext) => Promise<void> | void) | undefined;
|
|
164
187
|
}
|
|
165
188
|
//#endregion
|
|
166
|
-
export { type BootContext, type BootEventHandler, type BootEventName, type BootModule, type HealthProbePaths, type InstrumentationContext, type NodeBootOptions, type NodeHealthOptions, type NodeLoggingOptions, type NodeOptions, type NodePrometheusOptions, type NodeTelemetryOptions, node as default };
|
|
189
|
+
export { type BootContext, type BootEventHandler, type BootEventName, type BootModule, type HealthProbePaths, type InstrumentationContext, type IslandEmission, type IslandEmitter, type IslandInfo, type NodeBootOptions, type NodeHealthOptions, type NodeLoggingOptions, type NodeOptions, type NodePrometheusOptions, type NodeTelemetryOptions, node as default, getRequestRouteData, registerIslandEmitter };
|
|
167
190
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integration/integration.ts","../src/platform/prepare.ts","../src/lifecycle/lifecycle.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/integration/integration.ts","../src/platform/prepare.ts","../src/lifecycle/lifecycle.ts"],"mappings":";;;;;;;UAEiB;;;;;EAKf;;;;;;EAOA;;;;;UAMe;EACf;EACA;EACA;EACA;;UAGe;;;;;EAKf;;;;;EAMA;;;;;EAMA,QAAQ;;UAGO;;;;;EAKf,UAAU;;;;;;EAOV;;;;;;EAOA;;UAGe;;;;;EAKf;;;;;EAMA;;UAGe;;;;;EAKf,UAAU;;;;;EAMV,aAAa;;;;;;EAOb;;UAGe;;;;;;;;EAQf,OAAO;;;;;;;EAQP,UAAU;;;;;;;EAQV,YAAY;;;;;;EAOZ,SAAS;;;;;;;EAQT;IAAS,UAAU;;;;;;;;;;;EAWnB;;;;;;;;;;;;EAaA;;;;;EAMA;;;;;;EAOA;;;;;;;;;;iBC5GsB,KAAK,UAAS,cAAmB;;;UClExC;EACf;;;;UCJe;EACf,cAAc,SAAS,gBAAgB;EACvC,eAAe,SAAS,gBAAgB"}
|
package/dist/index.js
CHANGED
|
@@ -3,10 +3,13 @@ import { a as runShutdown, i as createRequestInstrumentation, o as runStartup, t
|
|
|
3
3
|
import { n as dispatchNativeMount, t as clearNativeMounts } from "./native-mount-DjYEnO4X.js";
|
|
4
4
|
import { n as getCurrentGeneration, r as incrementGeneration, t as GEN_HEADER } from "./generation-Bp2IA0jf.js";
|
|
5
5
|
import { r as RECOMMENDED_EXCLUDES } from "./excludes-pE23EbmQ.js";
|
|
6
|
+
import { i as registerIslandEmitter } from "./emitters-C20tjKLp.js";
|
|
7
|
+
import { t as getRequestRouteData } from "./route-store-DdxGePj2.js";
|
|
6
8
|
import fs, { readFileSync } from "node:fs";
|
|
7
9
|
import path from "node:path";
|
|
8
10
|
import { fileURLToPath } from "node:url";
|
|
9
11
|
import { parse } from "@astrojs/compiler-rs";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
10
13
|
import { Parser } from "acorn";
|
|
11
14
|
import MagicString from "magic-string";
|
|
12
15
|
//#region src/dev-mode/island-warmup.ts
|
|
@@ -575,6 +578,81 @@ function createDevMachinery(options) {
|
|
|
575
578
|
function serializeExcludePatterns(patterns) {
|
|
576
579
|
return `[${patterns.map((p) => "pattern" in p ? `{ pattern: ${p.pattern.toString()} }` : JSON.stringify(p)).join(", ")}]`;
|
|
577
580
|
}
|
|
581
|
+
const RESOLVED_ISLANDS_VIRTUAL_MODULE_ID = `\0virtual:@astroscope/node/islands-manifest`;
|
|
582
|
+
const MANIFEST_FILE_NAME = "islands-manifest.json";
|
|
583
|
+
/**
|
|
584
|
+
* Client-build side of island preloading: records the chunk import graph (direct
|
|
585
|
+
* static and dynamic edges per chunk), writes the content-hashed gate runtime into
|
|
586
|
+
* the client assets dir, and drops the manifest next to the server chunks — where
|
|
587
|
+
* the virtual module reads it back at runtime, mirroring the i18n manifest.
|
|
588
|
+
*
|
|
589
|
+
* The client build runs after the server build, so the SSR bundle can only carry
|
|
590
|
+
* code that reads the file lazily; in dev there is no manifest and the middleware
|
|
591
|
+
* stays inert.
|
|
592
|
+
*/
|
|
593
|
+
function createIslandsManifestPlugin(options) {
|
|
594
|
+
let isBuild = false;
|
|
595
|
+
return {
|
|
596
|
+
name: "@astroscope/node/islands-manifest",
|
|
597
|
+
configResolved(config) {
|
|
598
|
+
isBuild = config.command === "build";
|
|
599
|
+
},
|
|
600
|
+
resolveId(id) {
|
|
601
|
+
if (id === "virtual:@astroscope/node/islands-manifest") return RESOLVED_ISLANDS_VIRTUAL_MODULE_ID;
|
|
602
|
+
},
|
|
603
|
+
load(id) {
|
|
604
|
+
if (id !== RESOLVED_ISLANDS_VIRTUAL_MODULE_ID) return;
|
|
605
|
+
if (!isBuild || !options.enabled) return "export const manifest = null;";
|
|
606
|
+
return `
|
|
607
|
+
import { readFileSync } from 'node:fs';
|
|
608
|
+
import { fileURLToPath } from 'node:url';
|
|
609
|
+
import { dirname, join } from 'node:path';
|
|
610
|
+
|
|
611
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
612
|
+
|
|
613
|
+
let manifest = null;
|
|
614
|
+
|
|
615
|
+
for (const candidate of [
|
|
616
|
+
join(dir, '${MANIFEST_FILE_NAME}'),
|
|
617
|
+
join(dir, 'chunks', '${MANIFEST_FILE_NAME}'),
|
|
618
|
+
join(dir, '..', 'chunks', '${MANIFEST_FILE_NAME}'),
|
|
619
|
+
]) {
|
|
620
|
+
try {
|
|
621
|
+
manifest = JSON.parse(readFileSync(candidate, 'utf-8'));
|
|
622
|
+
break;
|
|
623
|
+
} catch {
|
|
624
|
+
manifest = null;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export { manifest };
|
|
629
|
+
`;
|
|
630
|
+
},
|
|
631
|
+
writeBundle(outputOptions, bundle) {
|
|
632
|
+
if (!options.enabled || this.environment.name !== "client" || !outputOptions.dir) return;
|
|
633
|
+
const chunks = {};
|
|
634
|
+
for (const chunk of Object.values(bundle)) {
|
|
635
|
+
if (chunk.type !== "chunk") continue;
|
|
636
|
+
chunks[chunk.fileName] = {
|
|
637
|
+
...chunk.imports.length > 0 && { i: chunk.imports },
|
|
638
|
+
...chunk.dynamicImports.length > 0 && { d: chunk.dynamicImports }
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
const runtimeSource = fs.readFileSync(new URL("./islands-runtime.js", import.meta.url), "utf-8").replace(/^\/\/# sourceMappingURL=.*$/m, "").trimEnd();
|
|
642
|
+
const hash = createHash("sha256").update(runtimeSource).digest("hex").slice(0, 8);
|
|
643
|
+
const runtime = `${options.assetsDir}/islands-runtime.${hash}.js`;
|
|
644
|
+
fs.mkdirSync(path.join(outputOptions.dir, options.assetsDir), { recursive: true });
|
|
645
|
+
fs.writeFileSync(path.join(outputOptions.dir, options.assetsDir, `islands-runtime.${hash}.js`), runtimeSource);
|
|
646
|
+
const manifest = {
|
|
647
|
+
runtime,
|
|
648
|
+
chunks
|
|
649
|
+
};
|
|
650
|
+
const chunksDir = path.resolve(outputOptions.dir, "..", "server", "chunks");
|
|
651
|
+
if (fs.existsSync(chunksDir)) fs.writeFileSync(path.join(chunksDir, MANIFEST_FILE_NAME), JSON.stringify(manifest));
|
|
652
|
+
else options.logger.warn(`server chunks directory not found, cannot write manifest to ${chunksDir}`);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
578
656
|
//#endregion
|
|
579
657
|
//#region src/tweaks/sourcemap.ts
|
|
580
658
|
/**
|
|
@@ -669,7 +747,7 @@ function stripSsrEffectsPlugin() {
|
|
|
669
747
|
for (const { start, end } of replacements) s.overwrite(start, end, EMPTY_FN);
|
|
670
748
|
return {
|
|
671
749
|
code: s.toString(),
|
|
672
|
-
map: s.generateMap({ hires: true })
|
|
750
|
+
map: s.generateMap({ hires: true }).toString()
|
|
673
751
|
};
|
|
674
752
|
}
|
|
675
753
|
};
|
|
@@ -732,6 +810,7 @@ function node(options = {}) {
|
|
|
732
810
|
const bootOptions = options.boot ?? {};
|
|
733
811
|
const healthOptions = options.health ?? {};
|
|
734
812
|
const csrfOptions = options.csrf ?? {};
|
|
813
|
+
const islandsEnabled = options.islands !== false;
|
|
735
814
|
const loggingOptions = options.logging ?? {};
|
|
736
815
|
const telemetryOptions = options.telemetry ?? {};
|
|
737
816
|
const loggingExclude = loggingOptions ? loggingOptions.exclude ?? DEFAULT_REQUEST_EXCLUDES : [];
|
|
@@ -755,6 +834,10 @@ function node(options = {}) {
|
|
|
755
834
|
order: "pre",
|
|
756
835
|
entrypoint: "@astroscope/node/csrf-middleware"
|
|
757
836
|
});
|
|
837
|
+
addMiddleware({
|
|
838
|
+
order: "pre",
|
|
839
|
+
entrypoint: "@astroscope/node/islands-middleware"
|
|
840
|
+
});
|
|
758
841
|
const root = fileURLToPath(config.root);
|
|
759
842
|
const watch = bootOptions === false ? false : bootOptions.watch ?? true;
|
|
760
843
|
bootEntry = bootOptions === false ? void 0 : resolveBootEntry(root, bootOptions.entry);
|
|
@@ -787,6 +870,8 @@ function node(options = {}) {
|
|
|
787
870
|
srcDir: fileURLToPath(config.srcDir),
|
|
788
871
|
logger
|
|
789
872
|
})] : [];
|
|
873
|
+
const imageService = options.imageService ?? "auto";
|
|
874
|
+
const imageOff = imageService === "off" || imageService === "auto" && config.image.service.entrypoint === "astro/assets/services/sharp";
|
|
790
875
|
updateConfig({
|
|
791
876
|
build: { redirects: false },
|
|
792
877
|
...config.trailingSlash === "ignore" && { trailingSlash: "never" },
|
|
@@ -794,13 +879,24 @@ function node(options = {}) {
|
|
|
794
879
|
...!config.security.allowedDomains?.length && { allowedDomains: [{}] },
|
|
795
880
|
...csrfOptions && { checkOrigin: false }
|
|
796
881
|
},
|
|
797
|
-
image: {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
882
|
+
image: {
|
|
883
|
+
...imageOff && { service: {
|
|
884
|
+
entrypoint: "@astroscope/node/image-service",
|
|
885
|
+
config: {}
|
|
886
|
+
} },
|
|
887
|
+
endpoint: {
|
|
888
|
+
route: config.image.endpoint.route ?? "_image",
|
|
889
|
+
entrypoint: imageOff ? "@astroscope/node/image-endpoint" : config.image.endpoint.entrypoint ?? (command === "dev" ? "astro/assets/endpoint/dev" : "astro/assets/endpoint/node")
|
|
890
|
+
}
|
|
891
|
+
},
|
|
801
892
|
vite: { plugins: [
|
|
802
893
|
...devMachinery,
|
|
803
894
|
...islandWarmup,
|
|
895
|
+
createIslandsManifestPlugin({
|
|
896
|
+
assetsDir: config.build.assets,
|
|
897
|
+
logger,
|
|
898
|
+
enabled: islandsEnabled
|
|
899
|
+
}),
|
|
804
900
|
ssrSourcemapPlugin(),
|
|
805
901
|
stripSsrEffectsPlugin(),
|
|
806
902
|
{
|
|
@@ -824,7 +920,7 @@ function node(options = {}) {
|
|
|
824
920
|
port: astroConfig.server.port ?? 4321,
|
|
825
921
|
client: astroConfig.build.client.toString(),
|
|
826
922
|
server: astroConfig.build.server.toString(),
|
|
827
|
-
bodySizeLimit: options.bodySizeLimit ??
|
|
923
|
+
bodySizeLimit: options.bodySizeLimit ?? 1073741824,
|
|
828
924
|
shutdownTimeout: options.shutdownTimeout ?? 1e4,
|
|
829
925
|
health: healthOptions ? {
|
|
830
926
|
...healthOptions.host !== void 0 && { host: healthOptions.host },
|
|
@@ -888,6 +984,18 @@ function node(options = {}) {
|
|
|
888
984
|
},
|
|
889
985
|
"astro:build:done": async ({ logger }) => {
|
|
890
986
|
if (!astroConfig) return;
|
|
987
|
+
if (islandsEnabled) {
|
|
988
|
+
const manifestPath = path.join(fileURLToPath(astroConfig.build.server), "chunks", "islands-manifest.json");
|
|
989
|
+
if (fs.existsSync(manifestPath)) {
|
|
990
|
+
const { transformPrerenderedHtml } = await import("./prerendered-CpEAJN_q.js");
|
|
991
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
992
|
+
const transformed = await transformPrerenderedHtml(fileURLToPath(astroConfig.build.client), manifest);
|
|
993
|
+
if (transformed > 0) logger.info(`island preloading applied to ${transformed} prerendered page(s)`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const { stripBuildPaths } = await import("./strip-build-paths-C6eb1QdG.js");
|
|
997
|
+
const stripped = stripBuildPaths(fileURLToPath(astroConfig.build.server), fileURLToPath(astroConfig.root));
|
|
998
|
+
if (stripped > 0) logger.info(`build machine paths stripped from ${stripped} server file(s)`);
|
|
891
999
|
const { compressClientDir } = await import("./compress-B11aeRHQ.js");
|
|
892
1000
|
await compressClientDir(fileURLToPath(astroConfig.build.client), logger);
|
|
893
1001
|
}
|
|
@@ -895,6 +1003,6 @@ function node(options = {}) {
|
|
|
895
1003
|
};
|
|
896
1004
|
}
|
|
897
1005
|
//#endregion
|
|
898
|
-
export { node as default };
|
|
1006
|
+
export { node as default, getRequestRouteData, registerIslandEmitter };
|
|
899
1007
|
|
|
900
1008
|
//# sourceMappingURL=index.js.map
|