🇺🇸 English version | 🇷🇺 Русская версия
TypeScript Language Service Plugin for ECSS — provides per-file types for .ecss imports.
Requires TypeScript ≥ 5.0.
- 🎯 Per-file types — generates accurate TypeScript declarations for each
.ecssfile in real time - 🔄 Auto-refresh — re-generates types on file save, no manual step required
- 📦 Dual CJS/ESM —
requireandimportout of the box - ⚙️ Config — reads
ecss.config.jsonfrom the project root; supports inline plugin options intsconfig.json - 🧩 Framework-agnostic — supports React (
className), Vue / Svelte / Solid (class) and both at once
npm install @ecss/typescript-pluginor
pnpm add @ecss/typescript-pluginor
yarn add @ecss/typescript-pluginAdd the plugin to tsconfig.json:
{
"compilerOptions": {
"plugins": [
{
"name": "@ecss/typescript-plugin"
}
]
}
}After that every import styles from './component.ecss' will get accurate types in the IDE — state functions with overloads, parameter types, result shapes and merge.
Options can be passed inline in tsconfig.json under the plugin entry:
{
"compilerOptions": {
"plugins": [
{
"name": "@ecss/typescript-plugin",
"classAttribute": "class",
"classTemplate": "[name]-[hash:8]"
}
]
}
}| Option | Type | Default | Description |
|---|---|---|---|
classAttribute |
'className' | 'class' | 'both' |
'className' |
Which field(s) to include in the state function result |
classTemplate |
string |
'[name]-[hash:6]' |
Class name template; supports [name] and [hash:N] tokens |
These options are merged with ecss.config.json — explicit values take precedence.
The plugin hooks into TypeScript's Language Service:
getExternalFiles— registers all.ecssfiles in the project upfront so tsserver createsscriptInfoentries before module resolutionresolveModuleNameLiterals— resolves./Foo.ecssimports to the actual.ecssfiles on disk, marking them with a.d.tsextensiongetScriptSnapshot— parses the.ecsssource via@ecss/parser, transforms the AST to a.d.tsstring via@ecss/transformer, and serves it as the script contentgetScriptVersion— returns the file'smtimeso TypeScript re-checks when the file changes
Results are cached by mtime — unchanged files are served instantly without re-parsing.
For a file like:
@state-variant Theme {
values: light, dark;
}
@state-def Button(--theme Theme: "light", --disabled boolean: false) {
border-radius: 6px;
}The plugin generates:
type Theme = 'light' | 'dark';
interface ButtonResult {
className: string;
'data-e-a1b2c3-theme': string;
'data-e-a1b2c3-disabled'?: '';
}
interface ButtonParams {
theme?: Theme;
disabled?: boolean;
}
interface EcssStyles {
Button: {
(theme?: Theme, disabled?: boolean): ButtonResult;
(params: ButtonParams): ButtonResult;
};
merge: (
...results: Record<string, string | undefined>[]
) => Record<string, string | undefined>;
}
declare const styles: EcssStyles;
export default styles;Build:
pnpm build # production
pnpm dev # watch modeType 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.