Skip to content

Vite Plugin

Enhance your Vite development experience with HMR and style validation

Vite Plugin

The optional @typestyles/vite plugin enhances your development experience with Hot Module Replacement (HMR) and helpful warnings.

Installation

bash
npm install -D @typestyles/vite
# or
pnpm add -D @typestyles/vite
# or
yarn add -D @typestyles/vite

Basic setup

Add the plugin to your vite.config.ts:

ts
import { defineConfig } from 'vite';
import typestyles from '@typestyles/vite';

export default defineConfig({
  plugins: [typestyles()],
});

For zero-runtime production builds while keeping runtime + HMR in development, add a convention style entry (e.g. src/typestyles-entry.ts) or set extract.modules (see Zero-runtime extraction). When at least one extraction module resolves, the plugin defaults to mode: 'build': vite dev still injects styles at runtime, and vite build emits a static CSS file and strips client injection.

For loading self-hosted fonts with Vite’s asset pipeline, see Fonts.

Features

Hot Module Replacement (HMR)

Without the plugin, editing a style file causes a full page reload. With the plugin:

  • Style changes apply instantly
  • Component state is preserved
  • No flicker or re-render cascade

The plugin works by:

  1. Detecting when a module uses typestyles
  2. Injecting HMR accept handlers
  3. Invalidating affected style registrations on file change
  4. Triggering a targeted update instead of a full reload

Duplicate namespace warnings

The plugin warns you when multiple files use the same namespace:

Style namespace "button" is also used in /path/to/other/file.ts.
Duplicate namespaces cause class name collisions.

This helps catch issues early, since duplicate namespaces can cause unexpected style overwrites.

Configuration

This is the usual setup for Vite: development uses the typestyles runtime so edits hot-reload without running a separate extraction step; production emits a real .css asset the browser can cache and parse in parallel with JS.

Add a convention entry file that imports every registration side effect (see Zero-runtime extraction), or set extract.modules explicitly. You do not need to pass mode — when modules resolve (discovered or explicit), it defaults to build, and the plugin only disables the runtime during vite build, not during vite dev.

Using discovery (minimal vite.config):

ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import typestyles from '@typestyles/vite';

export default defineConfig({
  plugins: [
    react(),
    typestyles(), // discovers e.g. src/typestyles-entry.ts when present
  ],
});

Explicit extract (multiple entries or custom paths):

ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import typestyles from '@typestyles/vite';

export default defineConfig({
  plugins: [
    react(),
    typestyles({
      extract: {
        modules: ['src/typestyles-entry.ts'],
        fileName: 'typestyles.css',
      },
    }),
  ],
});

Link the generated file from index.html (for example <link rel="stylesheet" href="/typestyles.css" />). In dev the file is not emitted yet; the link may 404 harmlessly while the runtime supplies the same rules. Production builds include the asset.

To force runtime-only everywhere (no extraction), set mode: 'runtime' even when extract is present. For dynamic values that cannot be extracted, see mode: 'hybrid' in Zero-runtime extraction.

Disable duplicate warnings

If you have a legitimate use case for duplicate namespaces (uncommon), you can disable the warning:

ts
import { defineConfig } from 'vite';
import typestyles from '@typestyles/vite';

export default defineConfig({
  plugins: [
    typestyles({
      warnDuplicates: false,
    }),
  ],
});

How HMR works

When you save a file that imports from typestyles, the plugin:

  1. Extracts namespaces: Parses your code to find all styles.component(), tokens.create(), createTheme(), and keyframes.create() calls

  2. Injects HMR code: Adds Vite's import.meta.hot handlers to the module

  3. On file change:

    • The HMR handler calls invalidateKeys() from typestyles/hmr
    • Affected styles are removed from the internal registry
    • The module re-executes with fresh style definitions
    • New CSS is injected, replacing the old rules
  4. State preserved: React/Vue/Svelte components keep their state since only the module containing styles changed

Example: seeing HMR in action

Create a simple Vite app with typestyles:

ts
// src/tokens.ts
import { tokens } from 'typestyles';

export const color = tokens.create('color', {
  primary: '#0066ff',
});

// src/styles.ts
import { styles } from 'typestyles';
import { color } from './tokens';

export const button = styles.component('button', {
  base: {
    backgroundColor: color.primary,
    padding: '8px 16px',
  },
});

// src/main.ts
import { button } from './styles';

document.getElementById('app').innerHTML = `
  <button class="${button()}">Click me</button>
`;

Run the dev server:

bash
npm run dev

Now edit src/tokens.ts and change the primary color. The button updates instantly without a page reload!

Framework integration

The plugin works with any Vite-based framework:

React

bash
npm create vite@latest my-app -- --template react-ts
npm install typestyles @typestyles/vite
ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import typestyles from '@typestyles/vite';

export default defineConfig({
  plugins: [react(), typestyles()],
});

Vue

bash
npm create vite@latest my-app -- --template vue-ts
npm install typestyles @typestyles/vite
ts
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import typestyles from '@typestyles/vite';

export default defineConfig({
  plugins: [vue(), typestyles()],
});

Svelte

bash
npm create vite@latest my-app -- --template svelte-ts
npm install typestyles @typestyles/vite
ts
// vite.config.ts
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import typestyles from '@typestyles/vite';

export default defineConfig({
  plugins: [svelte(), typestyles()],
});

SSR with Vite

HMR hooks are dev-only (import.meta.hot). For build-time extraction, production client bundles get __TYPESTYLES_RUNTIME_DISABLED__ so the sheet does not inject <style> tags; pair that with the emitted CSS file or SSR helpers. For SSR without extraction, use collectStyles() from typestyles/server as described in the SSR guide.

Troubleshooting

HMR not working

  1. Check that the plugin is listed in vite.config.ts
  2. Ensure your files import from 'typestyles' (not a relative path to the package)
  3. Verify import.meta.hot is available (dev mode, not production)

Duplicate namespace warnings appearing incorrectly

The detection is regex-based and may have false positives if you:

  • Have strings that look like typestyles calls in comments
  • Use variable names like styles for other purposes

These are rare and don't affect functionality—just ignore the warning or disable it.

Full reloads still happening

Some changes require a full reload:

  • Adding/removing imports that affect the module graph
  • Changes to non-typestyles code in the same file
  • Type-only changes (TypeScript types don't affect runtime)

Plugin performance

The plugin adds minimal overhead:

  • Only transforms files that import from 'typestyles'
  • Transformation is a simple regex match, not a full AST parse
  • HMR helpers are stripped from production bundles; extraction runs only during vite build when using build or hybrid mode

Is the plugin required?

No. TypeStyles works perfectly without it. The plugin improves DX for:

  • Faster iteration with HMR
  • Early warning about duplicate namespaces
  • Optional production CSS extraction via extract (see Zero-runtime extraction)

If you prefer full page reloads or use a different bundler, you don't need this plugin.

Future features

Planned enhancements (not yet implemented):

  • Dead style detection: Warn when styles are defined but never used
  • Source maps: Map generated CSS back to your style definitions

Stay tuned to the GitHub repository for updates.