API
ogygia/vite
The Vite plugin and its configuration options.
On this page
- 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
- BuildContext.root
- BuildContext.dev
- BuildContext.routeConfig
- BuildContext.foreignHydrate
- BuildContext.macro
- BuildContext.markup
- BuildContext.events
- BuildContext.measure
- BuildContext.files
- BuildContext.pageModules
- DebarrelOptions
- DebarrelOptions.packages
- DebarrelOptions.force
- DebarrelOptions.keep
- DebarrelOptions.importers
- DebarrelOptions.followPackages
- DebarrelOptions.report
- DebarrelOptions.debug
- MacroContext
- MacroContext.id
- MacroContext.rel
- MacroContext.root
- MacroContext.markupExts
- MarkupRegions
- MarkupRegions.transform
- MarkupSource
- MarkupSource.extensions
- MarkupSource.scan
- MarkupSource.styles
- OgygiaOptions
- OgygiaOptions.visible
- OgygiaOptions.presets
- OgygiaOptions.ttl
- OgygiaOptions.maxPageAge
- OgygiaOptions.router
- OgygiaOptions.importKeys
- OgygiaOptions.rateLimit
- OgygiaOptions.sessionCookie
- OgygiaOptions.barrels
- OgygiaOptions.standalone
- OgygiaPlugin
- OgygiaPlugin.[PLUGIN]
- OgygiaPreset
- OgygiaPreset.render
- OgygiaPreset.wake
- OgygiaPreset.fetch
- OgygiaPreset.margin
- OgygiaPreset.maxAge
- OgygiaPreset.onExpire
- OgygiaPreset.prefetch
- OgygiaPreset.revalidate
- OgygiaPreset.keep
- OgygiaRateLimit
- OgygiaRateLimit.max
- OgygiaRateLimit.windowMs
- PluginDefinition
- PluginDefinition.name
- PluginDefinition.server
- PluginDefinition.client
- PluginDefinition.options
- PluginDefinition.enforce
- PluginDefinition.build
- ImportKeys
- MacroHandler
- Matcher
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. 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_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 }).
renderattribute → the MODE:static(inline HTML) |deferred(a hole, fetched) |live(a hole that revalidates). Defaults tostatic.fetchattribute → adeferredhole's TIMING, when its HTML arrives (defaultload):with { render: 'deferred', fetch: 'visible' }.wakeattribute → 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 anx-ogygia-knownheader) 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 (outsidenode_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);