Svelte

Effortless SvelteKit Decorators: 1 New Vite Plugin (2025)

Tired of boilerplate in SvelteKit? Discover the new `vite-plugin-svelte-decorators` and learn how to write cleaner, more declarative code in 2025. Supercharge your DX!

A

Alexei Petrov

Svelte enthusiast and DX advocate focused on building cleaner, more maintainable web applications.

7 min read1 views

Effortless SvelteKit Decorators: 1 New Vite Plugin (2025)

Say goodbye to boilerplate and hello to elegant, declarative code. A new year brings a new tool that will fundamentally change how you write SvelteKit components.

For years, the Svelte community has looked on with a mix of curiosity and envy at how other frameworks like Angular and NestJS leverage decorators. This powerful TypeScript feature allows developers to annotate and modify classes, methods, and properties with clean, reusable syntax. However, due to Svelte's compile-time nature, integrating traditional runtime decorators has always been a square-peg-in-a-round-hole problem. We've relied on higher-order functions and manual composition, which work, but often add verbosity and boilerplate to our otherwise pristine .svelte files.

What if I told you that era is over? As of January 2025, a groundbreaking new Vite plugin has landed, and it's set to redefine developer experience in the SvelteKit ecosystem. It’s called vite-plugin-svelte-decorators, and it cleverly uses Vite's build-time transformation pipeline to give us the decorators we've always wanted, without sacrificing the performance and simplicity that makes Svelte special.

This isn't just a syntactic sugar rush; it's a paradigm shift towards writing more declarative, maintainable, and self-documenting component logic. Let's dive in and explore how this little plugin can make a huge impact on your daily workflow.

The Decorator Dilemma in Svelte

To appreciate the solution, we must first understand the problem. JavaScript decorators are functions that wrap another function or class to alter its behavior. They are stage 3 proposals, widely used via TypeScript. The issue is that they primarily operate at runtime. Svelte, on the other hand, is a compiler. It takes your .svelte files, parses them, and spits out highly optimized, imperative JavaScript code that directly manipulates the DOM.

This fundamental difference means that by the time a traditional decorator would run, Svelte's magic has already happened. There's no component instance in the traditional sense for a decorator to latch onto. This friction has kept a truly ergonomic decorator pattern out of reach for Svelte developers... until now.

Introducing vite-plugin-svelte-decorators

This new plugin takes a brilliantly simple approach: if Svelte is a compiler, let's make the decorators part of the compilation process. It hooks into Vite's transform step, finding and transpiling decorator syntax within your <script lang="ts"> blocks before Svelte even sees the code. It converts the elegant decorator syntax into the plain, Svelte-compatible JavaScript it would have replaced.

The result? You get to write this:


import { Debounce } from 'svelte-decorators';

export class MyComponent {
  @Debounce(300)
  handleInput(event) {
    console.log('Value:', event.target.value);
  }
}
    

And the plugin ensures Svelte receives the equivalent of this:


import { debounce } from './utils'; // or similar helper

export class MyComponent {
  handleInput = debounce((event) => {
    console.log('Value:', event.target.value);
  }, 300);
}
    

You get the best of both worlds: clean, declarative syntax for you, and optimized, vanilla code for the Svelte compiler.

Getting Started: Installation & Setup

Getting up and running is incredibly straightforward. It's a single npm package and one line in your Vite config.

Step 1: Install the plugin


npm install -D vite-plugin-svelte-decorators
    

Step 2: Update your vite.config.ts

Import the plugin and add it to your plugins array. It's crucial to place it before the sveltekit() plugin so it can transform the code first.


import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import svelteDecorators from 'vite-plugin-svelte-decorators'; // 1. Import

export default defineConfig({
  plugins: [
    svelteDecorators(), // 2. Add before sveltekit()
    sveltekit()
  ]
});
    

That's it! You'll also need to enable experimental decorator support in your tsconfig.json:


{
  "compilerOptions": {
    "experimentalDecorators": true,
    "useDefineForClassFields": true
  }
}
    

Now you're ready to supercharge your components.

Practical Magic: Decorators in Action

Let's look at a few real-world examples that demonstrate just how much boilerplate this plugin can eliminate.

Lifecycle Logging with @Log()

Ever wanted to quickly debug when a component mounts and unmounts? The @Log() decorator makes it a one-liner.

Before:


import { onMount, onDestroy } from 'svelte';

console.log('Component script executing');

onMount(() => {
  console.log('Component has mounted');
});

onDestroy(() => {
  console.log('Component will be destroyed');
});
      

After:


import { Log } from 'svelte-decorators';

@Log()
export default class {}
      

By decorating the class itself, the plugin injects the appropriate onMount and onDestroy logging calls automatically. Clean and simple.

Effortless Debouncing with @Debounce()

Debouncing user input is a classic task. The @Debounce(ms) decorator makes the implementation trivial and reusable.

Before:


let timeoutId;

function handleSearchInput(event) {
  clearTimeout(timeoutId);
  timeoutId = setTimeout(() => {
    console.log('Searching for:', event.target.value);
  }, 500);
}
      

After:


import { Debounce } from 'svelte-decorators';

class SearchComponent {
  @Debounce(500)
  handleSearchInput(event) {
    console.log('Searching for:', event.target.value);
  }
}
      

Seamless Store Subscriptions with @Store

Tired of manually subscribing to stores and assigning them to local variables? The @Store decorator connects a class property directly to a Svelte store, handling subscription and unsubscription for you.

Before:


import { userStore } from '$lib/stores';
import { onMount, onDestroy } from 'svelte';

let currentUser;
const unsubscribe = userStore.subscribe(value => {
  currentUser = value;
});

onDestroy(unsubscribe);
      

After:


import { Store } from 'svelte-decorators';
import { userStore } from '$lib/stores';

class ProfileHeader {
  @Store(userStore)
  currentUser;
}
      

The plugin transforms this into the necessary subscription logic, including the onDestroy cleanup. It feels just like Svelte's own auto-subscription ($userStore), but works beautifully with class properties.

Direct Route Param Injection with @RouteParam()

This SvelteKit-specific decorator is a game-changer for page components. It injects a URL parameter directly into a property.

Before (in src/routes/blog/[slug]/+page.svelte):


import { page } from '$app/stores';

let slug;
$: slug = $page.params.slug;
      

After:


import { RouteParam } from 'svelte-decorators/kit';

class BlogPostPage {
  @RouteParam('slug')
  slug: string;
}
      

This not only reduces boilerplate but also makes the component's data dependencies immediately obvious from its property definitions.

How It Works Under the Hood

The magic lies in Vite's universal plugin API, specifically the transform hook. When you run your dev server or build your project, Vite reads your files. When it encounters a .svelte file, vite-plugin-svelte-decorators gets first dibs on the content of the <script> tag.

It uses a fast Abstract Syntax Tree (AST) parser like Babel or SWC to parse the TypeScript code. It walks the tree, finds the decorator nodes, and replaces them and their targets (e.g., a class method) with the corresponding vanilla JavaScript implementation (like a debounced function or a store subscription). This new, decorator-free code is then passed along to the next plugin in the chain, which is typically @sveltejs/kit/vite. Svelte never even knows decorators were there; it just gets the clean, efficient code it's designed to handle.

Performance & Caveats

Performance: The build-time performance impact is negligible in most projects. The transformation is incredibly fast and happens in memory. There is absolutely zero runtime performance cost, as the output is just standard JavaScript. In some cases, the generated code might even be more optimal than a hand-rolled implementation.

Caveats:

  • TypeScript Only: This plugin currently only works in <script lang="ts"> blocks.
  • Limited Scope: Decorators can be applied to classes, class methods, and class properties. They cannot be used on standalone functions or reactive declarations ($: ...) within a .svelte file's script tag. Adopting a class-based structure for your component logic is the recommended way to leverage this plugin.
  • Experimental Syntax: While widely adopted, the decorators proposal is still technically experimental. Future changes to the spec could require updates to the plugin.

Conclusion: A Cleaner Future for SvelteKit

The arrival of vite-plugin-svelte-decorators marks a significant step forward in the evolution of the SvelteKit developer experience. It bridges a long-standing gap, allowing developers to write more expressive, declarative, and robust code without compromising the core principles of Svelte.

By abstracting away common patterns like debouncing, logging, and store subscriptions into clean decorators, we can focus more on what our components *do* rather than the boilerplate required to make them *work*. It encourages better organization through class-based components and makes codebases easier to read and maintain. If you're building with SvelteKit in 2025, this isn't just a nice-to-have—it's an essential tool for writing next-level web applications.