# @ox-content/vite-plugin-svelte

Svelte integration for Ox Content - embed Svelte 5 components in Markdown.

## Installation

```bash
vp install @ox-content/vite-plugin-svelte svelte @sveltejs/vite-plugin-svelte
```

## Usage

```ts
// 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)

```ts
// 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` → `Counter`
- `my-button.svelte` → `MyButton`

#### Explicit Map

```ts
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: `compile` from `svelte/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.

```ts
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.

```markdown
# 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:

```md
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)

```svelte
<!-- 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.

```ts
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:

```ts
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.

```ts
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.

```ts
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.

```ts
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 runtime
- `virtual:ox-content-svelte/components` - Registered components
- `virtual:ox-content-svelte/html-host/modules` - Custom-host island module map

```ts
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**: `onclick` instead of `on:click`
