Skip to content

API

ogygia/vite

The Vite plugin and its configuration options.

On this page

This page is generated from the package’s own .d.ts files on every build, so the signatures, descriptions and examples below always match the source.

import { … } from 'ogygia/vite';

Exports debarrel · definePlugin · import_keys_hint · islandChunkFileName · islandId · islandPublicUrl · normalize_import_keys · ogygia · regionId · regionIdentity · rewrite_lake_import_to_placeholder · strategyKey · wrapperVirtualId · CLIENT_BINDING_STUB · DEFAULT_IMPORT_KEYS · BuildContext · DebarrelOptions · MacroContext · MarkupRegions · MarkupSource · OgygiaOptions · OgygiaPlugin · OgygiaPreset · OgygiaRateLimit · PluginDefinition · ImportKeys · MacroHandler · Matcher

debarrel

function

function debarrel(options?: DebarrelOptions | true, internal?: DebarrelInternal): Plugin;

definePlugin

function

A plugin, as the Vite plugin the app lists beside ogygia().

function definePlugin<Options = undefined>(definition: PluginDefinition<Options>): OgygiaPlugin<Options>;

import_keys_hint

function

function import_keys_hint(import_keys: ImportKeys): RegExp;

islandChunkFileName

function

Deterministic client chunk path for a hydrate island (mirrors og-runtime.<hash>.js). SSR bakes this into <ogygia-region entry> so the sticky runtime can import(entry) with no app-wide regions map — Kit builds server before client, so content-hashed Vite names can't hand off.

function islandChunkFileName(iid: string, appDir?: string): string;

islandId

function

Deterministic short id for a region (stable across dev + build). When salt is set (production OGYGIA_SECRET), ids are not offline-computable (P1-ID). Paths are always posix so SSR/client builds agree across OS path separators.

function islandId(relHostPath: string, index: string | number, salt?: string): string;

islandPublicUrl

function

App-internal URL for islandChunkFileName: /<appDir>/immutable/… (leading slash, appDir, NO base). It is what SSR bakes onto <ogygia-region entry>; Region.svelte runs it through Kit's asset() at render time, which is the sole authority on base / the assets CDN / relative paths — so a base is never baked here (that would double-apply it). NOT the on-disk output filename.

function islandPublicUrl(iid: string, appDir?: string): string;

normalize_import_keys

function

function normalize_import_keys(overrides?: Partial<ImportKeys> | null): ImportKeys;

ogygia

function

Vite plugin: transforms with { hydrate | defer | preset } imports into islands, serves virtual island modules, and wires signed region endpoints for deferred HTML.

Place before sveltekit() in vite.config.

function ogygia(options?: OgygiaOptions): Plugin[];
  • options — Plugin configuration. See OgygiaOptions.

Returns Vite plugins (ogygia pre + island sourcemap fix post). Vite flattens the array.

regionId

function

Hash an identity string (optional production salt) → 12-char region id.

function regionId(identityKey: string, salt?: string): string;

regionIdentity

function

Cross-host stable identity: posix component path + strategyKey. Drives region ids so multiple hosts / import sites / <A /> usages share one module.

function regionIdentity(componentRelPath: string, mark: {
  strategy: string;
  options?: Record<string, unknown> | null;
}): string;

rewrite_lake_import_to_placeholder

function

Rewrite a lake binding's import to the render-nothing placeholder (client island modules only). Default imports are repointed; named imports drop that specifier (and keep siblings) then add a default import of the placeholder under the same local name.

function rewrite_lake_import_to_placeholder(src: string, local: string, placeholder: string): string;

strategyKey

function

Fingerprint of a region mark for dedupe. Same component path + same key → one wrapper/entry.

function strategyKey(mark: {
  strategy: string;
  options?: Record<string, unknown> | null;
}): string;

wrapperVirtualId

function

Portable wrapper module id — SSR / csr=true host binding (Island/ServerIsland/Lake shell).

function wrapperVirtualId(iid: string): string;

CLIENT_BINDING_STUB

const

csr=false CLIENT host binding target. Kit still emits those page nodes; pointing marked imports here (instead of wrappers) keeps N island wrappers/entries out of the page graph so emitFile owns hydrate modules and Rolldown does not thin-facade them. Hydration uses import(entry) only — this stub is never loaded for island JS.

Beside the stub the transform emits import 'virtual:ogygia/fouc-css/<entry>' so Kit still links stylesheets (FOUC) via CSS-only side effects. Importing the full .svelte JS module dual-owns it with the emitFile island entry and Rolldown thin-facades og-region.*. Omitting CSS entirely orphans rules (scoped class hashes, no stylesheet).

const CLIENT_BINDING_STUB = "virtual:ogygia/client-binding-stub"

DEFAULT_IMPORT_KEYS

const

Default import-attribute keys — the two-dial grammar. Internal role names stay hydrate/defer (the wire format + runtime keep those anchors); the user-facing ATTRIBUTE names are the values. Override via ogygia({ importKeys }).

  • render attribute → the MODE: static (inline HTML) | deferred (a hole, fetched) | live (a hole that revalidates). Defaults to static.
  • fetch attribute → a deferred hole's TIMING, when its HTML arrives (default load): with { render: 'deferred', fetch: 'visible' }.
  • wake attribute → when an island's JS runs (none: a lake). Never a hole's timing.
const DEFAULT_IMPORT_KEYS: {
  readonly wake: "wake";
  readonly fetch: "fetch";
  readonly render: "render";
  readonly preset: "preset";
  readonly region: "region";
}

BuildContext

interface

The build, as one plugin sees it (handed to build).

interface BuildContext {/*…*/}

BuildContext.root

The app's root directory.

readonly root: string;

BuildContext.dev

vite dev (true) or vite build (false).

readonly dev: boolean;

BuildContext.routeConfig

A key this plugin reads inside Kit's own config page option (export const config = { <key>: false } in a +page / +layout module, .server included). Read at build time, merged the way Kit merges config (layouts, then the page; later wins), and answered on the server by ctx.routeConfig(key). Literal values only (a boolean, number, string or null); nothing is stripped, because Kit accepts any key inside config.

routeConfig(key: string): void;

BuildContext.foreignHydrate

Keep the foreign-hydrate exports on this app's island entries: another app's page may host them and wake them through its own runtime (fragment federation). Off by default — they cost every island ~185 B gz and a preload.

foreignHydrate(): void;

BuildContext.macro

An import.meta.og.<name>(…) construct this plugin compiles ('code', 'loader'). Plugin macros run after core's own (wire, $, source, store) and before bake, in plugin order.

macro(name: string, handler: MacroHandler): void;

BuildContext.markup

A source format that becomes Svelte through a preprocessor (markdown): register it, get the island transform to call from that preprocessor.

markup(source: MarkupSource): MarkupRegions;

BuildContext.events

Compile core's EVENT emits in, browser and server (ogygia/events: island wakes, server renders, hub mints, navigations) — off, every emit folds away. lazy: measure only once a viewer opens a tool (the page carries a launcher; a preview build).

events(options?: {
    lazy?: boolean;
  }): void;

BuildContext.measure

Record the build's MEASUREMENT data: per-island detail, chunk contents, server source maps, the module map, the embedded maps, the request stats' detail — what a profiler reads.

measure(): void;

BuildContext.files

Compile a package's files as app source (their islands, router-css roots): dirs / files absolute, under root (the package's directory). For a plugin's own pages (a UI it serves).

files(scan: {
    root: string;
    dirs?: string[];
    files?: string[];
  }): void;

BuildContext.pageModules

Modules that render pages outside Kit's routes (a programmatic router): every component they import gets its whole-tree CSS linked when it renders (componentCssHead() from ogygia/plugin/css). importers: any compiled module importing one of these specifiers is a page module; files: absolute paths that are page modules themselves (compiled ones: app source, or a files() / ogygia.files surface).

pageModules(modules: {
    importers?: string[];
    files?: string[];
  }): void;

DebarrelOptions

interface

interface DebarrelOptions {/*…*/}

DebarrelOptions.packages

DEPENDENCIES whose barrels may be bypassed too. The project's own modules need no listing: every file outside node_modules is a candidate, and purity decides (a barrel is PURE when every top-level statement is an import, a re-export, a type, or export {}). Packages are opt-in because Vite pre-bundles bare imports in dev: a leaf import that skips the optimizer while another importer gets the pre-bundled copy is two instances of one module. List a package here when you know it is safe (an ESM component library, excluded from optimizeDeps). Names, globs or regexes: ['@acme/ui', '@acme/*', /\/components\/index\.js$/]. A plain string matches the specifier prefix ('@acme/ui' covers @acme/ui/components).

packages?: Matcher[];

DebarrelOptions.force

Modules that are IMPURE barrels (own top-level code next to the re-exports) but should still be rewritten: the re-exported names go to their leaves, the barrel itself stays imported for its side effects (import 'barrel'). Without this, an impure barrel is left alone.

force?: Matcher[];

DebarrelOptions.keep

Never rewrite imports of these modules, whatever else says (a barrel whose own module-level code the app depends on — a store singleton created in the barrel itself).

keep?: Matcher[];

DebarrelOptions.importers

Which IMPORTERS are rewritten. Default: project files (not node_modules) with a JS / TS / Svelte extension.

importers?: {
    include?: Matcher[];
    exclude?: Matcher[];
  };

DebarrelOptions.followPackages

Follow export * from into node_modules packages when building a barrel's export map (default true). A star that reaches something unparsable (CJS, JSON) makes the barrel OPAQUE for the names it cannot see: those stay on the barrel import.

followPackages?: boolean;

DebarrelOptions.report

A report at the end of each build leg — files rewritten, imports and names moved, barrels bypassed, time spent in the pass, then the barrels by names moved. Default true: the eight biggest and a count of the rest; 'all': every barrel; false: nothing.

report?: boolean | "all";

DebarrelOptions.debug

Log every rewrite as it happens (dev + build).

debug?: boolean;

MacroContext

interface

What a macro knows about the module it rewrites.

interface MacroContext {/*…*/}

MacroContext.id

The module id, query stripped.

readonly id: string;

MacroContext.rel

The id relative to the app root, POSIX (a stable identity for stamps).

readonly rel: string;

MacroContext.root

readonly root: string;

MacroContext.markupExts

Markup extensions whose <script> blocks hold the macro calls (.svelte).

readonly markupExts: readonly string[];

MarkupRegions

interface

What the build hands back for a markup source: run the region transform on preprocessed Svelte.

interface MarkupRegions {/*…*/}

MarkupRegions.transform

Rewrite the marked imports in already-preprocessed Svelte and register its regions. The code, or null when nothing changed.

transform(source: string, filename: string): string | null;

MarkupSource

interface

A source format that becomes Svelte only through a preprocessor (markdown → Svelte): how its islands and its modules' own scoped CSS reach the build.

interface MarkupSource {/*…*/}

MarkupSource.extensions

The format's file extensions (['.svx', '.md']).

extensions: readonly string[];

MarkupSource.scan

Discover the format's islands up front (build start, dev included): read each file, turn it into Svelte, and hand that to transform so its islands are registered before chunks emit.

scan?(ctx: {
    root: string;
    readFile(abs: string): string | null;
  }): void | Promise<void>;

MarkupSource.styles

Modules that carry their OWN scoped <style>, absolute path → their post-preprocess Svelte source: the build compiles each one's CSS and ships it as a client asset (the module itself only ever renders on the server). The plugin keeps filling this map; the build reads it late.

styles?: ReadonlyMap<string, string>;

OgygiaOptions

interface

Options for the {@link ogygia } Vite plugin. The root is the region system itself — its defaults (visible), its named presets (referenced from a use site by a LITERAL preset: 'name': import X from '…' with { preset: 'name' }), its holes' capability lifetimes — beside the client router. Feature packages are Vite plugins listed beside it (plugins: [ogygia(), flags(), sveltekit()]).

interface OgygiaOptions {/*…*/}
import { ogygia } from 'ogygia/vite';
export default defineConfig({
  plugins: [
    ogygia({
      visible: { margin: '200px' },
      presets: { chart: { wake: 'visible', margin: '200px' } },
      ttl: 3600
    }),
    sveltekit()
  ]
});

OgygiaOptions.visible

Defaults for regions that wake 'visible' (wake: 'visible' / render + wake: 'visible') without their own margin (via a preset).

visible?: {
    margin?: string;
  };

OgygiaOptions.presets

Named strategy bundles. Reference one from an import: import Chart from '$lib/Chart.svelte' with { preset: 'chart' };

presets?: Record<string, OgygiaPreset>;

OgygiaOptions.ttl

How long a deferred/live hole's signed capability URL stays valid, in seconds (default 3600). Clamped to [60, 86400]. Keep short for harvested-URL risk. Holes outlive this automatically where they must: on prerendered and postrendered pages, and on a page a load marks shared-cacheable (setHeaders({ 'cache-control': 'public, s-maxage=…' })), where an anonymous hole is signed for the page's cache life; a hole on an older cached copy renews itself when it wakes.

ttl?: number;

OgygiaOptions.maxPageAge

The oldest a cached copy of a page can be and still get its holes, in seconds (default 691200, 8 days). An anonymous hole whose capability expired while a cache kept its page renews itself for up to this long after that expiry; past it, the hole keeps its fallback. Raise it to the longest your CDN may serve a page when the page does not say so in its cache-control. 0 turns renewal off. Unlike ttl it never lengthens a hole URL's life (a renewed URL lives ttl), and a hole bound to a session never renews. A renewable URL replays its props: keep hole props to routing keys (a locale, a page), and read anything sensitive at render.

maxPageAge?: number;

OgygiaOptions.router

Client-side SPA router — app-wide, on by default. It intercepts same-origin links, swaps <body>, merges <head>, and keeps data-ogygia-keep chrome across navigations. No component to place: the server handle injects the runtime + the ogygia-router meta into every page.

  • true (default) — router on, View Transitions on, form continuity on.
  • false — router off (the whole feature is tree-shaken out; same-origin links do full MPA loads, and form continuity — which rides SPA navigation — goes with it).
  • { viewTransitions: false } — router on, but no View Transitions API on navigation.
  • { forms: false } — router on, but an island's half-filled form fields are NOT carried across SPA navigation (continuity off).
  • { serverDelta: true } — opt IN to server-delta nav: an SPA nav tells the server (via an x-ogygia-known header) which islands it already has live, so the server SKIPS re-rendering the unchanged ones and the client keeps them running. OFF by default (it is a new client↔server protocol); when off the client never sends the header and the server always full-renders.

Per-page escape hatch (no second config): a page opts itself out of View Transitions by emitting <svelte:head><meta name="ogygia-router" content="plain" /></svelte:head> — the handle injects the app default but a page that sets its own meta wins.

router?: boolean | {
    viewTransitions?: boolean;
    forms?: boolean;
    serverDelta?: boolean;
  };

OgygiaOptions.importKeys

Rename the import-attribute keys claimed by the transform. Defaults stay hydrate / defer / preset. Escape hatch if another tool already uses those names on the same imports.

Preset definitions (OgygiaOptions.presets) still use canonical hydrate / defer / margin / remount — only the with { … } spellings in source change.

importKeys?: Partial<ImportKeys>;

OgygiaOptions.rateLimit

Per-IP budget for the signed island endpoint served by ogygiaHandle(). Default { max: 60, windowMs: 60_000 }. Pass false to disable.

rateLimit?: false | OgygiaRateLimit;

OgygiaOptions.sessionCookie

Cookie name to seal into the region MAC (opt-in). Empty/prerender stays unbound. Harvested capability URLs then fail verification without that cookie. Default false (unbound).

sessionCookie?: false | string;

OgygiaOptions.barrels

Barrel imports become leaf imports at transform time — import { Button } from '$lib' (an index.ts that re-exports forty files) becomes import Button from '…/Button.svelte', so the module graph (dev) and the chunk graph (build) hold only the leaves a file uses.

  • true — every PURE barrel in the project (outside node_modules) is bypassed.
  • { packages, force, keep, … } — opt dependencies in, force impure barrels, keep some.

Independent of islands: an import that carries region marks (with { wake } …) is never rewritten — a barrel binding is marked through asRegion or a mark in the barrel, as before. The same plugin is debarrel() from 'ogygia/vite' for any Vite app.

barrels?: boolean | DebarrelOptions;

OgygiaOptions.standalone

standalone?: boolean;

OgygiaPlugin

interface

The value a plugin's factory returns: a Vite plugin carrying its definition.

interface OgygiaPlugin<Options = unknown> extends Plugin {/*…*/}

OgygiaPlugin.[PLUGIN]

readonly [PLUGIN]: PluginDefinition<Options>;

OgygiaPreset

interface

Named strategy bundle referenced from source via with { preset: 'name' } (or the renamed importKeys.preset key).

A preset speaks the SAME grammar as an inline import: a render mode with its timing (fetch for a deferred hole, wake for an island), plus the tuning options that aren't allowed inline (margin, maxAge, …).

interface OgygiaPreset {/*…*/}

OgygiaPreset.render

Delivery mode (the render import attribute): 'static' (default — an island that hydrates) | 'deferred' (a hole whose HTML is fetched, on its fetch timing) | 'live' (a hole that revalidates).

render?: "static" | "deferred" | "live";

OgygiaPreset.wake

When an island's JS runs (the wake import attribute): 'load' | 'idle' | 'visible' | 'interaction' | a CSS media query | 'none' (a frozen lake). With render: 'live', the revalidate schedule. Never a deferred hole's timing — that is fetch.

wake?: string;

OgygiaPreset.fetch

A render: 'deferred' hole's timing — when its HTML arrives (the fetch import attribute): 'load' (default) | 'idle' | 'visible' | 'interaction' | a CSS media query.

fetch?: string;

OgygiaPreset.margin

IntersectionObserver rootMargin when the schedule is 'visible'.

margin?: string;

OgygiaPreset.maxAge

A hole (fetch) — response cache max-age for the hole's HTML: seconds (number) or a duration string ('30s' | '5m' | '1h'). Absent or 0 → no-store: the hole is dynamic, re-rendered on every request. A positive value opts into a private, max-age browser cache. Signed into the hole's endpoint so a harvested URL can't be re-pointed at a longer cache.

With render: 'live' this is instead the client revalidate staleness — use a duration string to stay unit-explicit across both.

maxAge?: number | string;

OgygiaPreset.onExpire

render: 'live' — past maxAge, whether to clear the hole ('empty') or refetch ('fetch').

onExpire?: "empty" | "fetch";

OgygiaPreset.prefetch

A hole (fetch) — warm the hole's HTML into the frame store on this schedule ('load' | 'idle' | 'visible' | a media query) while fetch still decides when it swaps in. An on-demand hole (fetch: 'interaction') then has its bytes before the first hover: the gesture joins the warm frame instead of paying the origin round trip. Never 'interaction' — that is the swap schedule, fetch's job.

prefetch?: string;

OgygiaPreset.revalidate

render: 'live' — the revalidate schedule (false disables). Defaults to wake.

revalidate?: false | string;

OgygiaPreset.keep

Continuity name (the keep import attribute): the live island relocates across SPA navigation instead of remounting when the next page carries the same name.

keep?: string;

OgygiaRateLimit

interface

Per-IP budget for the signed deferred-region / lake-remount endpoint.

interface OgygiaRateLimit {/*…*/}

OgygiaRateLimit.max

Max requests per window (default 60). 0 disables allowing any.

max?: number;

OgygiaRateLimit.windowMs

Sliding window length in milliseconds (default 60_000).

windowMs?: number;

PluginDefinition

interface

What a plugin is: the argument to definePlugin.

interface PluginDefinition<Options = unknown> {/*…*/}

PluginDefinition.name

The plugin's package name ('@ogygia/postrender'): its identity.

name: string;

PluginDefinition.server

The SERVER realm: a module whose default export is defineServer({…}) (ogygia/plugin), as a file: URL or an absolute path — import.meta.resolve('./server.ts').

server?: string;

PluginDefinition.client

The BROWSER realm: a module whose default export is defineClient({…}) (ogygia/plugin/client), booted on every page — same form as server.

client?: string;

PluginDefinition.options

Settings for every realm (JSON: no functions, no clients — live objects are configured at runtime, in hooks.server.ts).

options?: Options;

PluginDefinition.enforce

Run before ('pre') or after ('post') the other plugins, in every realm.

enforce?: "pre" | "post";

PluginDefinition.build

Build-time contributions, with the build context.

build?(b: BuildContext): void;

ImportKeys

type

Import-attribute key names claimed by the transform (with { wake | fetch | render | preset | region }). Override via ogygia({ importKeys }) when another tool already uses the default names.

type ImportKeys = {
  wake: string;
  fetch: string;
  render: string;
  preset: string;
  region: string;
};

MacroHandler

type

A import.meta.og.<name> macro: rewrites a module that contains the marker (it is called only then), before the island transform or region minting see it. Returns the new code (the same string when nothing changed).

type MacroHandler = (code: string, m: MacroContext) => string | Promise<string>;

Matcher

type

ogygia({ barrels }) — what counts as a barrel, which importers get rewritten, what to keep.

A BARREL is a module whose exports are re-exports of other modules: index.ts files, a package's components.ts, export * from fan-outs. Importing one name from it drags the whole graph in: in dev every module behind it is transformed and served, in build the chunk graph follows the import graph before tree-shaking, and a package that is not sideEffects: false keeps all of it. The debarrel pass replaces import { A } from 'barrel' with import A from '<leaf of A>' at transform time, so the graph is the leaves the file actually uses.

This pass is independent of islands: it does not read or write region marks. An import that carries ogygia attributes (with { wake } …) is left exactly as written — a barrel binding is marked through asRegion / a mark in the barrel, as before.

type Matcher = string | RegExp | ((id: string) => boolean);