Write tiny Svelte components straight inside your JavaScript / TypeScript tests using tagged‑template literals.
The plugin lets you write Svelte components directly in your .ts
or .js
test files. It finds every template literal whose tag matches a list you choose (defaults to html
and svelte
), compiles the markup with the Svelte compiler, and replaces it with an import
of a virtual module that exports the compiled component. No extra files.
pnpm add -D @hvniel/vite-plugin-svelte-inline-component
# or
npm i -D @hvniel/vite-plugin-svelte-inline-component
# or
yarn add -D @hvniel/vite-plugin-svelte-inline-component
import { defineConfig } from "vite";
import { sveltekit } from "@sveltejs/kit/vite";
import inlineSveltePlugin from "@hvniel/vite-plugin-svelte-inline-component/plugin";
export default defineConfig(({ mode }) => ({
plugins: [mode === "test" && inlineSveltePlugin(), sveltekit()],
// ⬑ only enable during vitest runs in this example – remove the condition to run always
}));
Why conditionally enable? In a typical SvelteKit project you already compile
.svelte
files. Turning the plugin on just for unit tests keeps production builds untouched while giving Vitest access to inline components.
To share code across multiple inline components in the same file, wrap your ES imports, variables, and "global" component definitions in a special comment block. The plugin will make everything inside this fence available to each inline component's script block.
/* svelte:definitions
import { tick } from "svelte";
// Available to all inline components in this file
const sharedClass = "text-red-500";
// This component is also globally available
const GlobalButton = html`<button>Click Me!</button>`;
*/
const Thing1 = html`
<script>
let n = $state(0);
</script>
<button class="{sharedClass}" onclick={() => n++}>Button: {n}</button>
<GlobalButton />
`;
Just like regular Svelte files, you can use <script context="module">
(or <script module>
) to export values from an inline component. This is especially useful for Svelte 5 snippets.
The plugin makes any named exports available as properties on the component itself.
import { html, type InlineSnippet } from "@hvniel/vite-plugin-svelte-inline-component";
const ComponentWithSnippets = html`
<script lang="ts" module>
// These snippets will be attached to the component export
export { header, footer };
</script>
{#snippet header(text: string)}
<header>
<h1>{text}</h1>
</header>
{/snippet} {#snippet footer()}
<footer>
<p>© 2025</p>
</footer>
{/snippet}
`;
// Now you can render the component and pass snippets to it
const { header, footer } = ComponentWithSnippets as unknown as {
header: InlineSnippet;
footer: InlineSnippet;
};
const renderer = render(anchor => {
header(anchor, () => "Welcome!");
});
To make TypeScript aware of your named exports, you'll need to use a type assertion.
import { html, type InlineSnippet } from "@hvniel/vite-plugin-svelte-inline-component";
const defaultExport = html`
<script module>
export { element };
</script>
{#snippet element(content)}
<strong>{content}</strong>
{/snippet}
`;
// Use `as` to tell TypeScript about the named export
const { element } = defaultExport as unknown as {
element: InlineSnippet<string>;
};
// `element` is now fully typed!
The plugin is perfect for writing component tests without creating separate .svelte
files. It works great with Vitest and testing libraries like @testing-library/svelte or vitest-browser-svelte.
Here’s a sample test that proves reactivity with Svelte 5 runes works out of the box:
import { render } from "vitest-browser-svelte";
import { html, type InlineSnippet } from "@hvniel/vite-plugin-svelte-inline-component";
it("supports reactive components", async () => {
const ReactiveComponent = html`
<script>
let count = $state(0);
function increment() {
count++;
}
</script>
<button onclick="{increment}">Count: {count}</button>
`;
const { getByRole } = render(ReactiveComponent);
const button = getByRole("button");
expect(button).toHaveTextContent("Count: 0");
await user.click(button);
expect(button).toHaveTextContent("Count: 1");
});
inlineSveltePlugin(options?: InlineSvelteOptions): Plugin;
InlineSvelteOptions
option | type | default | description |
---|---|---|---|
tags |
string[] |
["html", "svelte"] |
Tag names to be treated as inline Svelte markup. |
fenceStart |
string |
/* svelte:definitions |
The comment that starts a standard import fence. |
fenceEnd |
string |
*/ |
The comment that ends a standard import fence. |
The plugin uses a multi-stage process to transform your code:
/* svelte:globals */
and /* svelte:imports */
fences to identify shared components and imports.globals
fence.html\
...`` literals in your code with variables that point to the newly created virtual components.The result behaves just like a normal Svelte component import.
node_modules
.MIT © 2025 Haniel Ubogu