@ogygia/core 0.11.5 → 0.12.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 (53) hide show
  1. package/dist/Region.svelte +28 -21
  2. package/dist/Region.svelte.d.ts +0 -2
  3. package/dist/compiler/bom.js +1 -1
  4. package/dist/compiler/browser.d.ts +1 -1
  5. package/dist/compiler/browser.js +1 -1
  6. package/dist/compiler/ctx.d.ts +1 -1
  7. package/dist/compiler/ctx.js +3 -3
  8. package/dist/compiler/{macros/dedent.d.ts → dedent.d.ts} +12 -1
  9. package/dist/compiler/dedent.js +43 -0
  10. package/dist/compiler/driver.d.ts +9 -6
  11. package/dist/compiler/driver.js +16 -24
  12. package/dist/compiler/index.d.ts +2 -2
  13. package/dist/compiler/index.js +2 -2
  14. package/dist/compiler/link/caps.js +59 -9
  15. package/dist/compiler/link/island-deps.js +225 -22
  16. package/dist/compiler/link/location-map.d.ts +10 -0
  17. package/dist/compiler/link/location-map.js +53 -0
  18. package/dist/compiler/link/manifest.js +5 -1
  19. package/dist/compiler/link/server-manifest.js +43 -7
  20. package/dist/compiler/link/transport.js +39 -7
  21. package/dist/compiler/macros/source.js +2 -2
  22. package/dist/compiler/region/transform.d.ts +13 -4
  23. package/dist/compiler/region/transform.js +25 -9
  24. package/dist/hooks.d.ts +16 -0
  25. package/dist/hooks.js +4 -2
  26. package/dist/insights/page-insights.js +2 -2
  27. package/dist/runtime/beacon.js +1 -1
  28. package/dist/runtime/boot-link.js +4 -1
  29. package/dist/runtime/core.js +3 -2
  30. package/dist/runtime/entry-locations.d.ts +10 -3
  31. package/dist/runtime/entry-locations.js +132 -21
  32. package/dist/runtime/morph.d.ts +7 -0
  33. package/dist/runtime/morph.js +21 -15
  34. package/dist/runtime/ownership.d.ts +10 -1
  35. package/dist/runtime/ownership.js +19 -1
  36. package/dist/runtime/reconcile.d.ts +5 -2
  37. package/dist/runtime/reconcile.js +52 -16
  38. package/dist/runtime/region-endpoint-url.js +1 -0
  39. package/dist/runtime/router-nav.js +15 -0
  40. package/dist/runtime/router.js +3 -0
  41. package/dist/runtime/slots.d.ts +8 -2
  42. package/dist/server/document-tail.d.ts +3 -2
  43. package/dist/server/document-tail.js +4 -3
  44. package/dist/server/entry-location.js +11 -8
  45. package/dist/server/props-wire.js +2 -2
  46. package/dist/server/signing-key.d.ts +10 -2
  47. package/dist/server/signing-key.js +49 -15
  48. package/dist/vite/index.js +5 -4
  49. package/dist/vite/kit-dirs.js +1 -1
  50. package/package.json +1 -1
  51. package/dist/compiler/link/entry-shim.d.ts +0 -4
  52. package/dist/compiler/link/entry-shim.js +0 -29
  53. package/dist/compiler/macros/dedent.js +0 -36
@@ -389,6 +389,18 @@ function preflight(doc, dest) {
389
389
  stamp_region_keys(doc.body);
390
390
  page_seed_of(doc);
391
391
  }
392
+ /**
393
+ * Which client build a document belongs to: its runtime script's file and its location map's
394
+ * (server/document-tail.ts `runtime_bootstrap_tags`) — both content-hashed, so a deploy that changed
395
+ * any island, Svelte or the runtime names another. By file name: a `src` written relative to its
396
+ * page reads the same from any address. Null: the document names no runtime (no island on it).
397
+ */
398
+ function build_of(d) {
399
+ const s = d.querySelector("script[data-ogygia-runtime]");
400
+ if (!s) return null;
401
+ const file = (v) => v ? v.slice(v.lastIndexOf("/") + 1) : "";
402
+ return file(s.getAttribute("src")) + " " + file(s.getAttribute("data-og-locations"));
403
+ }
392
404
  async function navigate(r, url, from, { push = true, pop_scroll = null, type = "link", replace = false } = {}) {
393
405
  if (!r.run_before(from, url, type)) {
394
406
  if (r.nav_target === url.href) r.nav_target = null;
@@ -460,6 +472,9 @@ async function navigate(r, url, from, { push = true, pop_scroll = null, type = "
460
472
  if (KitBoot.document_has(doc)) return hand_off();
461
473
  const marker = doc.querySelector("meta[name=\"ogygia-router\"]");
462
474
  if (!marker) return hand_off();
475
+ const next_build = build_of(doc);
476
+ const this_build = next_build === null ? null : build_of(document);
477
+ if (this_build !== null && next_build !== this_build) return hand_off();
463
478
  if (doc.querySelector("meta[name=\"ogygia-first-load\"]")) return hand_off();
464
479
  globalThis.__og_restore?.(doc);
465
480
  if (dest !== url) replace_state({
@@ -2,6 +2,7 @@ import { kit_hydrates_page } from "./kit-boot.js";
2
2
  import { slots } from "./slots.js";
3
3
  import { publish_nav } from "./nav-handle.js";
4
4
  import { nearest_attr } from "./nearest-attr.js";
5
+ import { record_server_body } from "./ownership.js";
5
6
  // src/runtime/router.ts
6
7
  /**
7
8
  * Minimal SPA router for ogygia (Astro's ClientRouter equivalent).
@@ -300,6 +301,8 @@ var SpaRouter = class {
300
301
  this.#started = true;
301
302
  this.doc_key = document_key(new URL(location.href));
302
303
  this.current_url = new URL(location.href);
304
+ const body = document.body;
305
+ if (body) record_server_body(body, body, Array.from(body.childNodes));
303
306
  document.addEventListener("click", (event) => {
304
307
  const anchor = anchor_of(event);
305
308
  const url = this.#should_intercept(event, anchor);
@@ -9,7 +9,7 @@ import { regions_in_shadow } from "./connected.js";
9
9
  import { yield_task } from "./schedule.js";
10
10
  import { beacon_nav } from "./beacon.js";
11
11
  import { load_slot } from "./load-scheduler.js";
12
- import { is_self_owned, walk_enters } from "./ownership.js";
12
+ import { is_self_owned, record_server_body, server_body_attributes, server_body_child, walk_enters } from "./ownership.js";
13
13
  import { document_key, jump_to_hash, push_state, replace_state } from "./router.js";
14
14
  import { resolve_address } from "./interaction.js";
15
15
  import { sync_attributes } from "./morph.js";
@@ -53,9 +53,11 @@ export type FormOps = {
53
53
  /** Wake a cold island when interaction lands inside it; returns a disarm fn. */
54
54
  export type ArmFn = (el: HTMLElement, fire: () => void) => void | (() => void);
55
55
  /** `target: 'walk'` = the hydration repair (the server sequence Svelte's walk binds); default = a live
56
- * morph toward a new answer. Same ownership rules either way (runtime/ownership.ts). */
56
+ * morph toward a new answer. Same ownership rules either way (runtime/ownership.ts). `stays`: a
57
+ * child of `parent` the morph steps over (the page's own, beside the render's). */
57
58
  export type MorphFn = (parent: Element, nodes: ArrayLike<Node>, options?: {
58
59
  target?: "live" | "walk";
60
+ stays?: (node: Node) => boolean;
59
61
  }) => void;
60
62
  export type WireOps = {
61
63
  /** THE hub key (`OgygiaRef`) — every transportable kind crosses under it. */
@@ -149,6 +151,10 @@ export type BootLink = {
149
151
  /** DOM ownership (./ownership.ts): which children Svelte's walk enters, and self-owned elements */
150
152
  walk_enters: typeof walk_enters;
151
153
  is_self_owned: typeof is_self_owned;
154
+ /** `<body>`'s two owners (./ownership.ts): the router's record of what the server sent it */
155
+ record_server_body: typeof record_server_body;
156
+ server_body_attributes: typeof server_body_attributes;
157
+ server_body_child: typeof server_body_child;
152
158
  };
153
159
  export type RouterLink = {
154
160
  document_key: typeof document_key;
@@ -41,9 +41,10 @@ export declare function modulepreload_tag(href: string): string;
41
41
  * module's imports after downloading and parsing it; hinted here, they download ALONGSIDE the
42
42
  * runtime instead of one round trip after it. Normal priority — the runtime cannot run without
43
43
  * them. `data-ogygia-runtime-dep` keeps them attached to the script when the handle moves it
44
- * (head-presence.ts `runtime_first`).
44
+ * (head-presence.ts `runtime_first`). `locations`: the build's location map, which the runtime reads
45
+ * for a load that arrives with an identity alone (runtime/entry-locations.ts).
45
46
  */
46
- export declare function runtime_bootstrap_tags(src: string, deps: readonly string[]): string;
47
+ export declare function runtime_bootstrap_tags(src: string, deps: readonly string[], locations?: string | null): string;
47
48
  /**
48
49
  * The island graph (island-graph.ts) as one inert JSON script: entry → the chunks its code needs.
49
50
  * The runtime turns an entry's list into modulepreload links when that island wakes. Empty map →
@@ -33,10 +33,11 @@ function modulepreload_tag(href) {
33
33
  * module's imports after downloading and parsing it; hinted here, they download ALONGSIDE the
34
34
  * runtime instead of one round trip after it. Normal priority — the runtime cannot run without
35
35
  * them. `data-ogygia-runtime-dep` keeps them attached to the script when the handle moves it
36
- * (head-presence.ts `runtime_first`).
36
+ * (head-presence.ts `runtime_first`). `locations`: the build's location map, which the runtime reads
37
+ * for a load that arrives with an identity alone (runtime/entry-locations.ts).
37
38
  */
38
- function runtime_bootstrap_tags(src, deps) {
39
- let html = "<script type=\"module\" data-ogygia-runtime src=\"" + src + "\"><\/script>";
39
+ function runtime_bootstrap_tags(src, deps, locations = null) {
40
+ let html = "<script type=\"module\" data-ogygia-runtime" + (locations ? " data-og-locations=\"" + locations + "\"" : "") + " src=\"" + src + "\"><\/script>";
40
41
  for (const dep of deps) html += "<link rel=\"modulepreload\" href=\"" + dep + "\" data-ogygia-runtime-dep>";
41
42
  return html;
42
43
  }
@@ -1,17 +1,19 @@
1
1
  import { runtime_bootstrap_tags } from "./document-tail.js";
2
2
  import runtime_url from "virtual:ogygia/runtime-url";
3
- import { entryLocation, islandDeps } from "virtual:ogygia/island-deps";
3
+ import { entryLocation, islandDeps, locationMap } from "virtual:ogygia/island-deps";
4
4
  // src/server/entry-location.ts
5
5
  /**
6
6
  * ENTRY IDENTITY → LOCATION, on the server. A hydrate island's entry and the runtime each have an
7
- * IDENTITY — the stable URL the compiler bakes (`/<appDir>/immutable/og-region.<iid>.js`, the
7
+ * IDENTITY — the stable URL the compiler bakes (`/<appDir>/ogygia/og-region.<iid>.js`, the
8
8
  * runtime's stable name) and every map keys by — and a LOCATION: the content-hashed file the build
9
- * emitted, the one a browser loads. A stable name served `immutable` would be kept for a year after
10
- * its content changed; a hashed name cannot go stale. The server bundle is built before the client
11
- * bundle, so the location is looked up at render time through the build handoff (`entryLocation`).
9
+ * emitted under `immutable/`, the one a browser loads. A hashed name cannot go stale; the stable name
10
+ * lives outside `immutable/` (compiler/region/transform.ts `STABLE_NAME_DIR`), so a host revalidates
11
+ * it. The server bundle is built before the client bundle, so the location is looked up at render
12
+ * time through the build handoff (`entryLocation`).
12
13
  *
13
- * No location (the dev server, a handoff from before hashing, an island from another app): the
14
- * identity itself is served — in a build, the stable-name shim that re-exports the current file.
14
+ * In dev the identity is the location (the dev server serves the module at it). No location in a
15
+ * build (a handoff from before hashing, an island from another app): a region writes no `src`, and the
16
+ * browser finds the current file through the build's location maps (compiler/link/location-map.ts).
15
17
  */
16
18
  /** The file an entry is loaded from: its content-hashed location, else the identity itself. */
17
19
  function entry_src(identity) {
@@ -24,6 +26,7 @@ function entry_src(identity) {
24
26
  */
25
27
  function runtime_bootstrap(resolve = (url) => url) {
26
28
  if (!runtime_url) return "";
27
- return runtime_bootstrap_tags(resolve(entry_src(runtime_url)), islandDeps(runtime_url).map(resolve));
29
+ const map = locationMap();
30
+ return runtime_bootstrap_tags(resolve(entry_src(runtime_url)), islandDeps(runtime_url).map(resolve), map ? resolve(map) : null);
28
31
  }
29
32
  export { entry_src, runtime_bootstrap };
@@ -153,8 +153,8 @@ function plan_props_wire(value, entry) {
153
153
  culprit: () => culprit === void 0 ? culprit = json_culprit(value) : culprit
154
154
  };
155
155
  }
156
- const LIVE_ENTRY_MARK = "/immutable/og-region.";
157
- const LIVE_ENTRY_RE = /^\/[^"\s]+\/immutable\/og-region\.[0-9a-f]+\.js$/;
156
+ const LIVE_ENTRY_MARK = "/ogygia/og-region.";
157
+ const LIVE_ENTRY_RE = /^\/[^"\s]+\/ogygia\/og-region\.[0-9a-f]+\.js$/;
158
158
  function live_entries_in(text) {
159
159
  const out = [];
160
160
  let at = text.indexOf(LIVE_ENTRY_MARK);
@@ -1,11 +1,19 @@
1
1
  export interface SigningKey {
2
2
  secret: string;
3
- /** env-provided: survives redeploys and is shared by every deployment that sets the same value */
3
+ /** the app's or the env's: survives redeploys and is shared by every deployment that sets the same value */
4
4
  stable: boolean;
5
5
  /** 8-char key fingerprint (`&k=` on capabilities, `x-ogygia-key` on refusals) */
6
6
  fp: string;
7
+ /** where the secret came from: `handle({ secret })`, `OGYGIA_SECRET`, or this build's random key */
8
+ source: "option" | "env" | "build";
7
9
  }
8
10
  export declare function signing_key(): SigningKey;
11
+ /**
12
+ * Record the app's `handle({ secret })`. A blank one counts as none (an app can pass a value that may
13
+ * be unset). Once a key is decided it never changes in the process: capabilities minted under the
14
+ * first would stop verifying, so a different secret arriving later is an error (thrown in dev).
15
+ */
16
+ export declare function configure_signing_secret(secret: string | undefined): void;
9
17
  export declare function key_fingerprint(secret: string): string;
10
- /** Tests: forget the decided key. */
18
+ /** Tests: forget the decided key and the app's secret. */
11
19
  export declare function reset_signing_key(): void;
@@ -5,7 +5,12 @@ import { building } from "$app/env";
5
5
  /**
6
6
  * THE region signing key, decided once at first use (the first mint or verify), not at module eval:
7
7
  * a host that fills `process.env` after the server's modules load (an adapter loading a `.env` late)
8
- * still counts. The runtime's `OGYGIA_SECRET` wins; else this build's random key.
8
+ * still counts. The app's `handle({ secret })` wins, then the runtime's `OGYGIA_SECRET`, else this
9
+ * build's random key.
10
+ *
11
+ * Process-wide on `globalThis`, not module state (like the islands endpoint, server/endpoint.ts):
12
+ * one server can load this file as two copies (pnpm gives a package one folder per set of peer
13
+ * versions), and a copy that never saw the handle's secret would mint with the build's key.
9
14
  *
10
15
  * A capability carries its minter's key FINGERPRINT (`&k=`): an HMAC of a constant under the key —
11
16
  * the same on every instance sharing the key, and it reveals nothing of the key. A deployment that
@@ -13,18 +18,47 @@ import { building } from "$app/env";
13
18
  * instead of an opaque 403: the page and its islands endpoint came from different deployments (a CDN
14
19
  * routing them to two backends, HTML cached across a key change).
15
20
  */
16
- let key;
21
+ const SLOT = Symbol.for("ogygia.signing-key");
22
+ const SOURCE_NAME = {
23
+ option: "handle({ secret })",
24
+ env: "OGYGIA_SECRET",
25
+ build: "this build's random key"
26
+ };
27
+ function slot() {
28
+ const g = globalThis;
29
+ return g[SLOT] ??= {};
30
+ }
17
31
  function signing_key() {
18
- if (key) return key;
32
+ const s = slot();
33
+ if (s.key) return s.key;
19
34
  const from_env = typeof process !== "undefined" && process.env?.OGYGIA_SECRET || "";
20
- const secret = from_env || buildKey;
21
- key = {
35
+ const [secret, source] = s.option ? [s.option, "option"] : from_env ? [from_env, "env"] : [buildKey, "build"];
36
+ s.key = {
22
37
  secret,
23
- stable: from_env !== "",
24
- fp: key_fingerprint(secret)
38
+ stable: source !== "build",
39
+ fp: key_fingerprint(secret),
40
+ source
25
41
  };
26
- if (!key.stable && !import.meta.env.DEV) warn_build_key();
27
- return key;
42
+ if (!s.key.stable && !import.meta.env.DEV) warn_build_key();
43
+ return s.key;
44
+ }
45
+ /**
46
+ * Record the app's `handle({ secret })`. A blank one counts as none (an app can pass a value that may
47
+ * be unset). Once a key is decided it never changes in the process: capabilities minted under the
48
+ * first would stop verifying, so a different secret arriving later is an error (thrown in dev).
49
+ */
50
+ function configure_signing_secret(secret) {
51
+ if (!secret?.trim()) return;
52
+ const s = slot();
53
+ const decided = s.key?.secret ?? s.option;
54
+ if (decided === void 0 || decided === secret) {
55
+ s.option = secret;
56
+ return;
57
+ }
58
+ const from = s.key ? `${SOURCE_NAME[s.key.source]}, decided by an earlier mint or verify` : "another handle({ secret })";
59
+ const message = `[ogygia] handle({ secret }) names key ${key_fingerprint(secret)}, but this server already signs with key ${key_fingerprint(decided)} (from ${from}). The first key stays: pass one secret, before anything renders.`;
60
+ if (import.meta.env.DEV) throw new Error(message);
61
+ console.error(message);
28
62
  }
29
63
  function key_fingerprint(secret) {
30
64
  return sign(secret, "ogygia:key-fp").slice(0, 8);
@@ -36,12 +70,12 @@ function warn_build_key() {
36
70
  const K = Symbol.for("ogygia.build-key-warned");
37
71
  if (g[K]) return;
38
72
  g[K] = true;
39
- if (building) console.warn("[ogygia] prerendered page mints region capabilities signed with a per-build random key — they stop verifying after your next deploy while the static HTML lives on. Set a stable OGYGIA_SECRET env var so prerendered holes survive redeploys.");
40
- else if (buildHadSecret) console.error("[ogygia] OGYGIA_SECRET was set when this app was built but is not set where it runs: region capabilities are signed with this build's random key, so every hole minted by another deployment (another backend behind the same CDN, the previous deploy) is refused (403, x-ogygia-deny: foreign-key). Set OGYGIA_SECRET in the server's runtime environment.");
41
- else console.warn("[ogygia] OGYGIA_SECRET is not set: region capabilities are signed with this build's random key, so a page cached across a deploy, or served by another deployment than its islands endpoint, gets its holes refused (403, x-ogygia-deny: foreign-key). Set a stable OGYGIA_SECRET.");
73
+ if (building) console.warn("[ogygia] prerendered page mints region capabilities signed with a per-build random key — they stop verifying after your next deploy while the static HTML lives on. Set a stable OGYGIA_SECRET env var (or pass it to handle({ secret })) so prerendered holes survive redeploys.");
74
+ else if (buildHadSecret) console.error("[ogygia] OGYGIA_SECRET was set when this app was built but is not set where it runs: region capabilities are signed with this build's random key, so every hole minted by another deployment (another backend behind the same CDN, the previous deploy) is refused (403, x-ogygia-deny: foreign-key). Set OGYGIA_SECRET in the server's runtime environment, or pass it to handle({ secret }) (e.g. from $env/static/private).");
75
+ else console.warn("[ogygia] OGYGIA_SECRET is not set: region capabilities are signed with this build's random key, so a page cached across a deploy, or served by another deployment than its islands endpoint, gets its holes refused (403, x-ogygia-deny: foreign-key). Set a stable OGYGIA_SECRET, or pass one to handle({ secret }).");
42
76
  }
43
- /** Tests: forget the decided key. */
77
+ /** Tests: forget the decided key and the app's secret. */
44
78
  function reset_signing_key() {
45
- key = void 0;
79
+ delete globalThis[SLOT];
46
80
  }
47
- export { key_fingerprint, reset_signing_key, signing_key };
81
+ export { configure_signing_secret, key_fingerprint, reset_signing_key, signing_key };
@@ -701,10 +701,10 @@ function ogygia(options = {}) {
701
701
  },
702
702
  generateBundle() {
703
703
  if (!is_build || server_leg_of(this)) return;
704
- for (const shim of compiler.entry_shims((ref) => this.getFileName(ref))) this.emitFile({
704
+ for (const file of compiler.location_maps((ref) => this.getFileName(ref))) this.emitFile({
705
705
  type: "asset",
706
- fileName: shim.fileName,
707
- source: shim.source
706
+ fileName: file.fileName,
707
+ source: file.source
708
708
  });
709
709
  },
710
710
  closeBundle: {
@@ -805,7 +805,8 @@ function ogygia(options = {}) {
805
805
  content_css,
806
806
  css_inline,
807
807
  fn_manifest: Object.fromEntries(compiler.dollar_hoists),
808
- entries: entries.locations
808
+ entries: entries.locations,
809
+ locations: compiler.location_map_url
809
810
  });
810
811
  emit_island_deps_handoff(root, json, kit_dirs(root).out_dir);
811
812
  }
@@ -8,7 +8,7 @@ import { pathToFileURL } from "node:url";
8
8
  * `src/routes`) and `outDir` (default `.svelte-kit`). ogygia scans the routes tree (csr worlds, the
9
9
  * client-build keepalive, route options) and writes/reads its island-deps handoff under Kit's output
10
10
  * dir — every one of those must follow the app's configuration, not the defaults (an app building
11
- * two route trees from one source, `PES_MODE=v2 → src/routes-v2` + `.svelte-kit-v2`, had its
11
+ * two route trees from one source, `CATALOG_MODE=v2 → src/routes-v2` + `.svelte-kit-v2`, had its
12
12
  * all-csr=false tree read as "no routes": Kit skipped the client build, the runtime chunk was never
13
13
  * emitted, every island 404'd).
14
14
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogygia/core",
3
- "version": "0.11.5",
3
+ "version": "0.12.1",
4
4
  "description": "The core of ogygia (SSR islands for SvelteKit): apps install `ogygia`, plugins build on this",
5
5
  "homepage": "https://ogygiajs.dev",
6
6
  "bugs": {
@@ -1,4 +0,0 @@
1
- /** An island entry's shim: every export of its hashed file (a component entry has a default). */
2
- export declare function island_shim_source(shim_file: string, entry_file: string): string;
3
- /** The runtime's shim: the hashed runtime, run (it has no exports; loading it boots). */
4
- export declare function runtime_shim_source(shim_file: string, runtime_file: string): string;
@@ -1,29 +0,0 @@
1
- import { path } from "../host.js";
2
- // src/compiler/link/entry-shim.ts
3
- /**
4
- * THE STABLE-NAME SHIMS. An island entry and the runtime are content-hashed files; each one's old
5
- * stable name is ALSO written, as a shim of the hashed file, so that a page cached before content
6
- * hashing (whose islands import their stable names), and a load whose location failed (the runtime
7
- * re-fetches the stable name fresh), still reach the CURRENT build.
8
- *
9
- * A shim is written as a plain ASSET at the bundle's end, once every hashed name is final — never as
10
- * a bundled chunk. Its name never changes, so the bundler has nothing to hash, and a chunk would make
11
- * the island's module the target of two entries: the bundler would split its code behind two facades
12
- * and every island load would cost two requests. As an asset, the island has one entry, its location
13
- * IS its code, and the shim is a static re-export by relative path.
14
- */
15
- /** The specifier from the shim's file to the hashed file (both output-relative paths). */
16
- function relative_specifier(from_file, to_file) {
17
- const rel = path.posix.relative(path.posix.dirname(from_file), to_file);
18
- return rel.startsWith(".") ? rel : "./" + rel;
19
- }
20
- /** An island entry's shim: every export of its hashed file (a component entry has a default). */
21
- function island_shim_source(shim_file, entry_file) {
22
- const spec = JSON.stringify(relative_specifier(shim_file, entry_file));
23
- return `export * from ${spec};\nexport { default } from ${spec};\n`;
24
- }
25
- /** The runtime's shim: the hashed runtime, run (it has no exports; loading it boots). */
26
- function runtime_shim_source(shim_file, runtime_file) {
27
- return `import ${JSON.stringify(relative_specifier(shim_file, runtime_file))};\n`;
28
- }
29
- export { island_shim_source, runtime_shim_source };
@@ -1,36 +0,0 @@
1
- // src/compiler/macros/dedent.ts
2
- const CRLF_G = /\r\n?/g;
3
- const LEADING_INDENT_RE = /^[\t ]*/;
4
- /**
5
- * Strip the common leading indentation from a block of source, and trim leading/trailing blank
6
- * lines. Source embedded in a call (`import.meta.og.code(\`…\`, …)`) is indented to match where the
7
- * call sits; that indentation is an artifact of the surrounding code, not part of the snippet. This
8
- * normalizes it so the rendered `<pre>` shows the snippet's OWN logical indentation, flush-left.
9
- *
10
- * Blank lines (whitespace-only) don't count toward the common indent; tabs and spaces are treated by
11
- * their literal column contribution to the prefix (the common prefix is computed on raw characters,
12
- * so mixed tabs/spaces still de-indent correctly as long as the leading run is consistent — which it
13
- * is for machine-indented source).
14
- */
15
- function dedent(text) {
16
- const lines = text.replace(CRLF_G, "\n").split("\n");
17
- while (lines.length && lines[0].trim() === "") lines.shift();
18
- while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
19
- if (!lines.length) return "";
20
- let common = null;
21
- for (const line of lines) {
22
- if (line.trim() === "") continue;
23
- const lead = LEADING_INDENT_RE.exec(line)[0];
24
- if (common === null) common = lead;
25
- else {
26
- let i = 0;
27
- const max = Math.min(common.length, lead.length);
28
- while (i < max && common[i] === lead[i]) i++;
29
- common = common.slice(0, i);
30
- }
31
- if (common === "") break;
32
- }
33
- const n = common?.length ?? 0;
34
- return lines.map((line) => line.trim() === "" ? "" : line.slice(n)).join("\n");
35
- }
36
- export { dedent };