@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.
Files changed (40) hide show
  1. package/README.md +32 -1
  2. package/dist/emitters-C20tjKLp.js +43 -0
  3. package/dist/emitters-C20tjKLp.js.map +1 -0
  4. package/dist/image-endpoint.d.ts +11 -0
  5. package/dist/image-endpoint.d.ts.map +1 -0
  6. package/dist/image-endpoint.js +11 -0
  7. package/dist/image-endpoint.js.map +1 -0
  8. package/dist/image-service.d.ts +15 -0
  9. package/dist/image-service.d.ts.map +1 -0
  10. package/dist/image-service.js +28 -0
  11. package/dist/image-service.js.map +1 -0
  12. package/dist/index.d.ts +24 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +115 -7
  15. package/dist/index.js.map +1 -1
  16. package/dist/islands-middleware-entrypoint.d.ts +5 -0
  17. package/dist/islands-middleware-entrypoint.d.ts.map +1 -0
  18. package/dist/islands-middleware-entrypoint.js +58 -0
  19. package/dist/islands-middleware-entrypoint.js.map +1 -0
  20. package/dist/islands-runtime.d.ts +1 -0
  21. package/dist/islands-runtime.js +113 -0
  22. package/dist/islands-runtime.js.map +1 -0
  23. package/dist/islands.d.ts +2 -0
  24. package/dist/islands.js +3 -0
  25. package/dist/log-B69HEBvg.js.map +1 -1
  26. package/dist/native-mount-DjYEnO4X.js.map +1 -1
  27. package/dist/prepare-CXZsyAVk.js.map +1 -1
  28. package/dist/prerendered-CpEAJN_q.js +34 -0
  29. package/dist/prerendered-CpEAJN_q.js.map +1 -0
  30. package/dist/route-store-DdxGePj2.js +27 -0
  31. package/dist/route-store-DdxGePj2.js.map +1 -0
  32. package/dist/route-store-DtY3uvLf.d.ts +69 -0
  33. package/dist/route-store-DtY3uvLf.d.ts.map +1 -0
  34. package/dist/server.js +13 -11
  35. package/dist/server.js.map +1 -1
  36. package/dist/strip-build-paths-C6eb1QdG.js +63 -0
  37. package/dist/strip-build-paths-C6eb1QdG.js.map +1 -0
  38. package/dist/transform-D8dIBGEr.js +191 -0
  39. package/dist/transform-D8dIBGEr.js.map +1 -0
  40. 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
@@ -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":";;;;;;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;;;;;;EAMnB;;;;;;EAOA;;;;;;;;;;iBCtFsB,KAAK,UAAS,cAAmB;;;UChExC;EACf;;;;UCJe;EACf,cAAc,SAAS,gBAAgB;EACvC,eAAe,SAAS,gBAAgB"}
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: { endpoint: {
798
- route: config.image.endpoint.route ?? "_image",
799
- entrypoint: config.image.endpoint.entrypoint ?? (command === "dev" ? "astro/assets/endpoint/dev" : "astro/assets/endpoint/node")
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 ?? 1024 * 1024 * 1024,
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