API
ogygia/vite
The Vite plugin and its config surface.
On this page
- collectIslandDepModulepreloads
- derive_css_scope_owners
- dev_hmr_client_source
- import_keys_hint
- island_vpaths_affected_by_file
- islandChunkFileName
- islandDepsHandoffPath
- islandId
- islandPublicUrl
- islandVirtualId
- mpaSpeculationRules
- needs_csr_false_full_reload
- needs_island_entry_full_reload
- normalize_import_keys
- ogygia
- regionId
- regionIdentity
- rewrite_island_sourcemap_sources
- rewrite_lake_import_to_placeholder
- same_module_path
- strategyKey
- wrapperVirtualId
- CLIENT_BINDING_STUB
- DEFAULT_IMPORT_KEYS
- ContentPreset
- ContentPreset.markdown
- OgygiaOptions
- OgygiaOptions.regions
- OgygiaOptions.router
- OgygiaOptions.content
- OgygiaOptions.importKeys
- OgygiaOptions.rateLimit
- OgygiaOptions.sessionCookie
- OgygiaOptions.regionTtl
- OgygiaOptions.standalone
- OgygiaPreset
- OgygiaPreset.render
- OgygiaPreset.wake
- OgygiaPreset.margin
- OgygiaPreset.maxAge
- OgygiaPreset.onExpire
- OgygiaPreset.revalidate
- OgygiaPreset.keep
- OgygiaRateLimit
- OgygiaRateLimit.max
- OgygiaRateLimit.windowMs
- RegionsOptions
- RegionsOptions.visible
- RegionsOptions.presets
- ImportKeys
Auto-generated from the package’s own .d.ts on every build — signatures, JSDoc, and examples straight from the source.
import { … } from 'ogygia/vite';Exports collectIslandDepModulepreloads · derive_css_scope_owners · dev_hmr_client_source · import_keys_hint · island_vpaths_affected_by_file · islandChunkFileName · islandDepsHandoffPath · islandId · islandPublicUrl · islandVirtualId · mpaSpeculationRules · needs_csr_false_full_reload · needs_island_entry_full_reload · normalize_import_keys · ogygia · regionId · regionIdentity · rewrite_island_sourcemap_sources · rewrite_lake_import_to_placeholder · same_module_path · strategyKey · wrapperVirtualId · CLIENT_BINDING_STUB · DEFAULT_IMPORT_KEYS · ContentPreset · OgygiaOptions · OgygiaPreset · OgygiaRateLimit · RegionsOptions · ImportKeys
collectIslandDepModulepreloads
function
From a client generateBundle output, collect transitive static imports for each og-region.<id>.js facade. Keys/values are public URLs (/_app/immutable/…).
Used so SSR can modulepreload hashed dependency chunks for hydrate: 'load' islands
(Vite’s auto graph does not apply to @vite-ignore import(entry)).
function collectIslandDepModulepreloads(bundle: Record<string, {
type: string;
fileName?: string;
imports?: string[];
dynamicImports?: string[];
viteMetadata?: {
importedCss?: Set<string> | string[];
};
}>): {
js: Record<string, string[]>;
css: Record<string, string[]>;
};derive_css_scope_owners
function
Which top-level route scopes can reach abs_file — walked UP the dev module graph until route
files are hit. A scope is a route file's first path segment under src/routes ('(docs)', 'playground', '' for a root-level route file). [] = no route owner found; the client treats
that as shared and joins anywhere (the safe default for exotic graphs).
function derive_css_scope_owners(abs_file: string, root: string, graphs: Array<{
getModulesByFile?: (f: string) => Set<DevGraphModule> | undefined;
} | undefined | null>): string[];dev_hmr_client_source
function
Client bridge source for virtual:ogygia/dev-hmr (vite serve only): a LAZY map of app CSS under /src, joined per-file when the plugin broadcasts a scoped ogygia:css event — plus full-reload
on vite:error.
LAZY is load-bearing. The bridge used to join every /src stylesheet EAGERLY, which painted the
whole app's CSS onto every page — invisible while one app owned one look, but the moment two
sub-apps share one project (the (docs) / playground split), each page wore the other's skin
in dev while prod (Kit's per-route CSS) stayed clean. Now a page boots with exactly its SSR CSS,
and joins a stylesheet only when it changes AND this page's route scope owns it (the ogygia-dev-scope meta the handle stamps vs. the owners the plugin derives from the module
graph). Once joined, later edits soft-apply through Vite's normal CSS HMR.
Do not strip Kit’s <style data-sveltekit> FOUC bag. Under csr = false that bag is
how page + component CSS is delivered (no client module graph for route shells). Removing
it blanks the page; a MutationObserver would also delete FOUC styles the SPA router merges
in on navigation.
function dev_hmr_client_source(): string;import_keys_hint
function
function import_keys_hint(import_keys: ImportKeys): RegExp;island_vpaths_affected_by_file
function
Virtual island ids whose generated source must be dropped when file changes or is deleted.
Island ids are hash(componentPath\\0strategyKey) — renaming a host route keeps the same id, so
Vite's moduleGraph must be invalidated or it keeps serving the old import.
function island_vpaths_affected_by_file(file: string, entries: Iterable<[string, {
hostPath?: string | null;
componentPath?: string | null;
}]>): string[];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): string;islandDepsHandoffPath
function
Stable handoff path: client generateBundle writes; SSR reads at render (Kit is SSR-first).
function islandDepsHandoffPath(root: 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
Public URL for islandChunkFileName (leading slash, same shape as runtime-url).
function islandPublicUrl(iid: string): string;islandVirtualId
function
Virtual island ENTRY module id — JS re-export of the real component (not a thin .svelte).
const islandVirtualId: (iid: string) => stringmpaSpeculationRules
function
Static Speculation Rules for MPA mode (router: false) — the server handle injects them into
every page head. Document rules covering same-origin links, with a per-link/subtree opt-out
(data-ogygia-speculate="off", re-enable with "on"); the region endpoint is never speculated.
BOTH lists ship: a prerender-capable browser prerenders (prefetch is its first stage), a
prefetch-only browser prefetches, an unsupporting one ignores the JSON entirely — graceful by
construction, no JS fallback. In SPA mode no rules exist at all: speculation caches serve real
navigations only, which a body-swap router can never read.
function mpaSpeculationRules(): string;needs_csr_false_full_reload
function
Host route shells (+page / +layout / …) never join the browser module graph under csr=false, so Vite's fine-grained HMR for them has no client importer. Force a full reload.
Standalone CSS is soft-updated via virtual:ogygia/dev-hmr (Vite inject after Kit FOUC).
If that soft path fails, the client bridge listens for vite:error and reloads the document.
function needs_csr_false_full_reload(file: string): boolean;needs_island_entry_full_reload
function
Island entry .svelte files (the import X from '…' with { hydrate }) sit behind a virtual
wrapper; Svelte soft HMR through that edge is unreliable. Shared .ts deps still soft-update.
function needs_island_entry_full_reload(file: string, entries: Iterable<{
componentPath?: string | null;
}>): boolean;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. SeeOgygiaOptions.
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_island_sourcemap_sources
function
Rewrite vite-plugin-svelte island sourcemap sources so Vite treats them as virtual.
Svelte emits the basename of virtual:ogygia/island/<id>.svelte (just <id>.svelte).
That string does not match Vite's virtualSourceRE, so injectSourcesContent tries a
disk read and warns "points to missing source files". Pointing sources back at the
full virtual module id silences the warning (and keeps maps coherent).
function rewrite_island_sourcemap_sources(moduleId: string, sources: (string | null)[] | undefined): 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;same_module_path
function
Absolute path equality with querystrings stripped (host vs component vs Vite watch paths).
function same_module_path(a: string | null | undefined, b: string | null | undefined): boolean;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 }).
renderattribute → the MODE:static(inline HTML) |deferred(a hole, fetched) |live(a hole that revalidates). Defaults tostatic.wakeattribute → when the region comes alive: forstaticthat is HYDRATION; fordeferred/livethat is the FETCH schedule.
const DEFAULT_IMPORT_KEYS: {
readonly wake: "wake";
readonly render: "render";
readonly preset: "preset";
readonly region: "region";
}ContentPreset
interface
A named CONTENT preset — a partial content-config referenced by a literal preset: 'name' on a
loader macro (import.meta.og.loader.folder('../docs', { preset: 'name' })). The referencing
collection's files compile as their own module VARIANTS (?og_preset=name), so the same file
used by another collection under another preset renders independently — never a conflict.
interface ContentPreset {/*…*/}ContentPreset.markdown
Markdown options merged over content.markdown (per setting key — depth-2 replace).
markdown?: MarkdownOptions;OgygiaOptions
interface
Options for the ogygia Vite plugin — one top-level key per subsystem, each subsystem defaults + its own presets.
interface OgygiaOptions {/*…*/}import { ogygia } from 'ogygia/vite';
export default defineConfig({
plugins: [
ogygia({
regions: {
visible: { margin: '200px' },
presets: { chart: { wake: 'visible', margin: '200px' } }
},
regionTtl: 3600
}),
sveltekit()
]
});OgygiaOptions.regions
The regions subsystem: island defaults (visible.margin) + named island presets.
regions?: RegionsOptions;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).
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;
};OgygiaOptions.content
The content subsystem. markdown configures the mdsvex preprocessor (themes, remark plugins,
heading ids…) — the defaults for every markdown file. presets are named variants a loader
macro opts a whole collection into ({ preset: 'name' }); requires markdown to be set (the
defaults are the base every preset merges over). The rest are dev HMR options.
content?: ContentPluginOptions & {
markdown?: MarkdownOptions;
presets?: Record<string, ContentPreset>;
};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 (RegionsOptions.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.regionTtl
Capability URL lifetime in seconds (default 3600). Clamped to [60, 86400].
Keep short for harvested-URL risk; raise only if long-lived tabs must keep deferred holes valid.
regionTtl?: number;OgygiaOptions.standalone
standalone?: boolean;OgygiaPreset
interface
Named strategy bundle referenced from source via with { preset: 'name' } (or the renamed importKeys.preset key).
A preset speaks the SAME two-dial grammar as an inline import: render (the delivery mode) + wake (the schedule), 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) | 'live' (a hole that revalidates).
render?: 'static' | 'deferred' | 'live';OgygiaPreset.wake
Schedule (the wake import attribute): hydration when render is 'static', the FETCH
schedule when 'deferred'/'live'. 'load' | 'idle' | 'visible' | 'interaction' |
a CSS media query | 'none' (a frozen lake).
wake?: string;OgygiaPreset.margin
IntersectionObserver rootMargin when the schedule is 'visible'.
margin?: string;OgygiaPreset.maxAge
render: 'deferred' — 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.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;RegionsOptions
interface
The regions subsystem — island defaults + the named preset dictionary. One grammar shared by
every subsystem: defaults at the root, variance in presets, referenced from a use site by a
LITERAL preset: 'name' (here: import X from '…' with { preset: 'name' }).
interface RegionsOptions {/*…*/}RegionsOptions.visible
Global defaults for islands that use wake: 'visible' / render+wake: 'visible' without their own margin (via a preset).
visible?: {
margin?: string;
};RegionsOptions.presets
Named strategy bundles. Reference one from an import: import Chart from '$lib/Chart.svelte' with { preset: 'chart' };
presets?: Record<string, OgygiaPreset>;ImportKeys
type
Import-attribute key names claimed by the transform
(with { wake | render | preset | region }). Override via ogygia({ importKeys }) when
another tool already uses the default names.
type ImportKeys = {
wake: string;
render: string;
preset: string;
region: string;
};