vite-plugin-svelte-inline-component Svelte Themes

Vite Plugin Svelte Inline Component

Test leaner with inline svelte components!

@hvniel/vite-plugin-svelte-inline-component

Write tiny Svelte components straight inside your JavaScript / TypeScript tests using tagged‑template literals.


📖 Table of Contents


✨ What it does

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.

🔧 Installation

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

🚀 Usage

vite.config.ts / vite.config.js

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.


🧱 Definitions Fence

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 />
`;

🧩 Named Exports & Snippets

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>&copy; 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!");
});

Typing Named Exports

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!

🧪 Testing Inline & Reactive Components

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");
});

🛠️ API

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.

🧐 How it works (nutshell)

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.
  2. Process Globals: It compiles any components found inside the globals fence.
  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 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.


⚠️ Caveats

  • 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.

📝 License

MIT © 2025 Haniel Ubogu

Top categories

Loading Svelte Themes