🇺🇸 English version | 🇷🇺 Русская версия
Vite plugin for ECSS — transforms .ecss files into CSS + JS with HMR support.
- ⚡ Native Vite integration — uses Vite Plugin API directly
- 🔥 HMR — hot module replacement for
.ecssfiles without full page reload - 🎨 Virtual CSS — generated CSS is injected via Vite's native CSS pipeline
- 🏃 Runtime — automatically resolves
virtual:ecss/runtimeto the real helpers from@ecss/transformer - 🧩 Framework-agnostic — supports React (
className), Vue / Svelte / Solid (class) and both at once - 📝 TypeScript — typed API, generic types for
.ecssimports via./client - ⚙️ Config — reads
ecss.config.jsonfrom the project root; explicit options take precedence
npm install @ecss/vite-pluginor
pnpm add @ecss/vite-pluginor
yarn add @ecss/vite-plugin// vite.config.ts
import { defineConfig } from 'vite';
import ecss from '@ecss/vite-plugin';
export default defineConfig({
plugins: [
ecss({
classAttribute: 'className', // 'className' | 'class' | 'both'
}),
],
});import styles from './button.ecss';
// Positional call
const props = styles.Button('dark', true);
// → { className: 'Button-a1b2c3', 'data-e-a1b2c3-theme': 'dark', 'data-e-a1b2c3-disabled': '' }
// Named-object call
const props2 = styles.Button({ theme: 'dark' });
// Merge multiple styles
const merged = styles.merge(styles.Button('dark'), styles.Icon({ size: 'sm' }));Creates a Vite plugin instance. Import as default export:
import ecss from '@ecss/vite-plugin';
const plugin = ecss({ classAttribute: 'class' });interface EcssPluginOptions {
classAttribute?: 'className' | 'class' | 'both'; // default: 'className'
classTemplate?: string; // default: '[name]-[hash:6]'
extensions?: string[]; // default: ['.ecss']
generateDeclarations?: boolean; // default: false
}| Option | Description |
|---|---|
classAttribute |
Which field(s) (class, className or both) to include in the state function result |
classTemplate |
Class name template; supports [name] and [hash:N] tokens |
extensions |
File extensions the plugin should process |
generateDeclarations |
When true, writes .ecss.d.ts next to each .ecss file at build start |
Options can also be set in ecss.config.json in the project root — explicit plugin options take precedence:
{
"classAttribute": "class",
"classTemplate": "[name]-[hash:8]",
"generateDeclarations": true
}The classTemplate string supports two tokens:
| Token | Description |
|---|---|
[name] |
The @state-def identifier (e.g. Button) |
[hash] |
First 6 characters of the SHA-256 digest of filePath + name |
[hash:N] |
First N characters of the hash |
Example: "[name]-[hash:8]" for Button produces something like Button-a1b2c3d4.
The plugin hooks into Vite's build pipeline:
transform— when Vite encounters a.ecssfile, the plugin parses it via@ecss/parser, transforms the AST into CSS + JS via@ecss/transformer, and returns the JS with a virtual CSS importresolveId+load— the virtual CSS import (?ecss&lang.css) is resolved and served from an in-memory cache; Vite treats it as native CSS thanks to thelang.csssuffixhandleHotUpdate— when a.ecssfile changes, the plugin re-processes it and invalidates the virtual CSS module so Vite can apply HMR
The virtual:ecss/runtime module is resolved to the real @ecss/transformer/runtime package so the generated JS can call _h() and merge() at runtime.
For accurate per-file types, add @ecss/typescript-plugin to tsconfig.json — it generates types directly in the IDE without extra files.
If the language service plugin is unavailable, add a reference to @ecss/vite-plugin/client in tsconfig.json:
{
"compilerOptions": {
"types": ["@ecss/vite-plugin/client"]
}
}This provides a generic type for all .ecss imports:
declare module '*.ecss' {
const styles: Record<
string,
(...args: any[]) => Record<string, string | undefined>
> & {
merge: (
...objects: Record<string, string | undefined>[]
) => Record<string, string | undefined>;
};
export default styles;
}With generateDeclarations: true the plugin writes an accurate .ecss.d.ts sidecar next to each .ecss source. Suitable for Svelte and other tools that do not load tsserver plugins.
Build:
pnpm build # production
pnpm dev # watch modeTests:
pnpm test
pnpm test:watchType check:
pnpm typecheckLint and format:
pnpm lint # oxlint
pnpm lint:fix # oxlint --fix
pnpm fmt # oxfmt
pnpm fmt:check # oxfmt --checkDeveloped and maintained by Ruslan Martynov.
Found a bug or have a suggestion? Open an issue or submit a pull request.
Distributed under the MIT License.