> Write tiny Svelte components straight inside your JavaScript / TypeScript tests using tagged‑template literals.
npm install @hvniel/vite-plugin-svelte-inline-component> Write tiny Svelte components straight inside your JavaScript / TypeScript tests using tagged‑template literals.
---
- ✨ What it does
- 🔧 Installation
- 🚀 Usage
- vite.config.ts / vite.config.js
- 🧱 Definitions Fence
- 🧩 Named Exports & Snippets
- 🧪 Testing Inline & Reactive Components
- 🛠️ API
- InlineSvelteOptions
- 🧐 How it works (nutshell)
- ⚠️ Caveats
- 📝 License
---
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.
``bash`
pnpm add -D @hvniel/vite-plugin-svelte-inline-componentor
npm i -D @hvniel/vite-plugin-svelte-inline-componentor
yarn add -D @hvniel/vite-plugin-svelte-inline-component
---
`ts
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.
``tsx
/* 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;
*/
const Thing1 = html
;
---
Just like regular Svelte files, you can use
{#snippet header(text: string)}
{text}
{/snippet} {#snippet 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.
`ts
import { html, type InlineSnippet } from "@hvniel/vite-plugin-svelte-inline-component";
const defaultExport = html
{#snippet element(content)}
{content}
{/snippet};
// Use as to tell TypeScript about the named export
const { element } = defaultExport as unknown as {
element: InlineSnippet
};
// 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:
`tsx
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
;
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;
| 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:
1. Scan for Fences: The plugin first looks for / svelte:globals / and / svelte:imports / fences to identify shared components and imports.globals
2. Process Globals: It compiles any components found inside the fence.html\
3. Process Locals: It then compiles the remaining "local" components, injecting the standard imports and the compiled global components into each one's script scope.
4. Replace Literals: Finally, it replaces all the original ...\\ literals in your code with variables that point to the newly created virtual components.
The result behaves just like a normal Svelte component import.
---
- The plugin only transforms your application's source code, ignoring anything inside node_modules`.
- The template content must be valid Svelte component markup. Syntax errors will surface during compilation.
- Because each inline component gets a unique hash, HMR will re-render the whole component tree containing it. Keep inline components small for best performance.
---
MIT © 2025 Haniel Ubogu