@ox-content/vite-plugin-svelte#
Svelte integration for Ox Content - embed Svelte 5 components in Markdown.
Installation#
vp install @ox-content/vite-plugin-svelte svelte @sveltejs/vite-plugin-svelte
Usage#
// vite.config.ts
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { oxContentSvelte } from "@ox-content/vite-plugin-svelte";
export default defineConfig({
plugins: [
svelte(),
oxContentSvelte({
srcDir: "docs",
// Auto-discover components with glob pattern
components: "./src/components/*.svelte",
}),
],
});
Options#
components#
- Type:
string | string[] | Record<string, string>
Components to register for use in Markdown. Supports:
Glob Pattern (Recommended)#
// Single pattern
components: "./src/components/*.svelte";
// Multiple patterns
components: ["./src/components/*.svelte", "./src/ui/*.svelte"];
Component names are derived from file names in PascalCase:
counter.svelte→Countermy-button.svelte→MyButton
Explicit Map#
components: {
Counter: './src/components/Counter.svelte',
Alert: './src/components/Alert.svelte',
}
runes#
- Type:
boolean - Default:
true
Enable Svelte 5 Runes mode.
compiler#
- Type:
SvelteCompileFunction | { compile: SvelteCompileFunction } - Default:
compilefromsvelte/compiler
Select the compiler used for Markdown and MDX modules generated by
oxContentSvelte(). The configured Vite Svelte plugin still compiles ordinary
.svelte files; this option keeps Ox Content-generated document modules on the
same compiler path when you opt into an alternative compiler.
import { svelte } from "@rsvelte/vite-plugin-svelte";
import { compile } from "@rsvelte/vite-plugin-svelte-native";
import { oxContentSvelte } from "@ox-content/vite-plugin-svelte";
export default {
plugins: [
svelte(),
oxContentSvelte({
srcDir: "docs",
compiler: compile,
}),
],
};
Compiler warnings are forwarded to Vite, and generated JavaScript, sourcemaps, and diagnostics are normalized for both client and server document builds.
Using Components in Markdown#
Register shared components in the global components map, or import a
component from the document that uses it.
# My Page
Here's an interactive counter:
<Counter initial={5} />
And an alert:
<Alert type="warning">
This is a warning message!
</Alert>
On .mdx, a relative import is local to that file and overrides the global
map when the names match:
import GtvChart from './gtv-chart/GtvChart.svelte'
<GtvChart title="ok" />
Optional renderIsland(name, props, filePath, slotHtml) can replace island
inner HTML at transform time. Server-rendered islands are marked and adopted
with Svelte 5 hydrate() on the client, while islands without SSR markup keep
using mount(). The hook belongs on the Svelte adapter; @ox-content/vite-plugin
does not import svelte/server.
Example Component (Svelte 5 Runes)#
<!-- src/components/Counter.svelte -->
<script lang="ts">
interface Props {
initial?: number;
}
let { initial = 0 }: Props = $props();
let count = $state(initial);
</script>
<div class="counter">
<button onclick={() => count--}>-</button>
<span>{count}</span>
<button onclick={() => count++}>+</button>
</div>
<style>
.counter {
display: inline-flex;
gap: 8px;
align-items: center;
}
</style>
HTML-string custom hosts#
Custom hosts that call renderMarkdown() and place the resulting HTML inside
their own Svelte page shell can use the Svelte adapter without importing each
Markdown document as a Vite module. createSvelteHtmlHostRenderer() resolves
document-local MDX imports, loads server modules, renders islands to HTML, and
records browser module metadata for the island client. Svelte SSR head output,
including <svelte:head> markup and injected scoped CSS, is returned as
headHtml so the host can place it in the document <head> before client
entries.
The lower-level framework-neutral contract is exported from
@ox-content/vite-plugin/html-host, and the browser loader contract is exported
from @ox-content/islands/html-host. The Svelte helpers keep the existing
Svelte-specific names and virtual module id while delegating the shared
discovery, metadata, lazy-loading, error, completion, and disposal behavior to
that contract.
import { renderMarkdown, type MdxImport } from "@ox-content/vite-plugin";
import { createSvelteHtmlHostRenderer } from "@ox-content/vite-plugin-svelte";
const renderIslands = createSvelteHtmlHostRenderer({
root: "/repo",
srcDir: "content",
loadModule: (moduleId) => viteDevServer.ssrLoadModule(moduleId),
});
const markdown = await renderMarkdown(source, "/repo/content/post.mdx", {
srcDir: "content",
});
const rendered = await renderIslands(markdown.html, {
documentPath: "/repo/content/post.mdx",
imports: markdown.imports as MdxImport[],
components: { Badge: "./src/components/Badge.svelte" },
});
The resolved client identity is written to each island as data-ox-module,
with data-ox-export preserving named imports. Use the same module ids in the
browser loader map so post-local components can share names across documents.
Pass diagnostics: "collect" when a host wants structured diagnostics instead
of thrown SvelteHtmlHostRenderError failures.
The default server renderer owns its Svelte SSR runtime import so package-level
ssr.noExternal keeps the renderer and compiled component aligned in Vite SSR.
Hosts that pass renderComponent own runtime selection themselves and may load
svelte/server through their host loader when that better matches their module
graph.
Inside ctx.markdown.render({ renderHtml }), renderHtmlHostMarkdown() from
@ox-content/vite-plugin/html-host can combine the Svelte island renderer with
the Markdown context and return metadata.documentAssets for the final document
shell:
const page = await ctx.markdown.render({
source,
documentPath: "content/post.mdx",
renderHtml(markdown) {
return renderHtmlHostMarkdown({
context: markdown,
renderIslands,
documentAssets: { clientEntries: ["src/client.ts"] },
});
},
});
The browser client lives on a separate subpath:
@ox-content/vite-plugin-svelte/html-host/client. It can hydrate existing SSR
markup or mount fresh Svelte islands, and it keeps the runtime import lazy so
custom hosts do not bundle Svelte DOM code into server-only paths.
import { initIslands } from "@ox-content/islands";
import {
initSvelteHtmlHost,
loadSvelteHtmlHostDomRuntime,
} from "@ox-content/vite-plugin-svelte/html-host/client";
import modules from "virtual:ox-content-svelte/html-host/modules";
initSvelteHtmlHost({
initIslands,
modules,
loadRuntime: loadSvelteHtmlHostDomRuntime,
mount: { mode: "hydrate" },
options: { selector: ".ox-content [data-ox-island]" },
onError(error) {
console.error(error.message);
},
});
For production custom hosts, generate the module map from the same selected
document policy used by the site. createSvelteHtmlHostIslandRegistry() can
read configured Ox Content collections, preserve published-only filtering, and
serve virtual:ox-content-svelte/html-host/modules in both build and dev.
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { oxContentCustomHost } from "@ox-content/vite-plugin";
import { createSvelteHtmlHostIslandRegistry } from "@ox-content/vite-plugin-svelte";
const oxContent = {
srcDir: "content",
collections: {
blog: "blog/**/*.mdx",
},
};
const svelteIslands = createSvelteHtmlHostIslandRegistry({
oxContent,
collectionDocuments: {
select(document, context) {
return context.command === "serve" || document.frontmatter.published === true;
},
},
});
export default defineConfig({
appType: "custom",
plugins: [
svelteIslands.plugin,
oxContentCustomHost({ host: "./src/site-host.ts", oxContent }),
svelte(),
],
});
SSR stylesheets for custom hosts#
Svelte page and layout modules can be listed in
oxContentCustomHost({ ssrStylesheets }) so the host can emit blocking styles
for server-rendered output.
const ssr = ctx.assets.ssrStylesheets({
modules: ["/src/Page.svelte"],
});
const content = await ctx.assets.stylesheetContent({
stylesheets: ssr.stylesheets,
});
const assets = ctx.assets.document({
islandStyles: ssr.stylesheets,
inlineStyles: content.stylesheets.map((stylesheet) => ({
key: `critical:${stylesheet.href}`,
content: stylesheet.content,
})),
});
Production builds use the configured Vite/Svelte pipeline and emit hashed CSS assets. Development traverses Svelte SSR roots and local Svelte children, deduplicates discovered style blocks per route, and returns dependency paths so custom-host responses are invalidated after component edits.
Virtual Modules#
virtual:ox-content-svelte/runtime- Svelte-specific runtimevirtual:ox-content-svelte/components- Registered componentsvirtual:ox-content-svelte/html-host/modules- Custom-host island module map
import { mount, unmount } from "virtual:ox-content-svelte/runtime";
import components from "virtual:ox-content-svelte/components";
HMR#
Components are hot-reloaded when modified. Svelte's built-in HMR is supported.
Svelte 5 Features#
This plugin is designed for Svelte 5 and supports:
- Runes:
$state,$derived,$effect,$props - Snippets:
{#snippet}and{@render} - New event syntax:
onclickinstead ofon:click