Skip to content

webeach/ecss-parser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

@ecss/parser


@ecss/parser

npm package build npm downloads

πŸ‡ΊπŸ‡Έ English version | πŸ‡·πŸ‡Ί Русская вСрсия

High-performance ECSS parser written in Rust (napi-rs). Accepts source text and returns an AST.


πŸ“– Documentation | πŸ“‹ Specification


πŸ’Ž Features

  • ⚑ Written in Rust β€” native N-API addon, minimal overhead
  • 🌐 WASM support β€” works in Node.js without a native build (@ecss/parser/wasm) and in the browser (@ecss/parser/wasm/browser)
  • πŸ“¦ Dual CJS/ESM β€” require and import out of the box
  • πŸ–₯️ Cross-platform β€” macOS, Linux, Windows, plus a WASM target
  • πŸ“ TypeScript β€” types included, auto-generated from Rust code

πŸ“¦ Installation

npm install @ecss/parser

or

pnpm add @ecss/parser

or

yarn add @ecss/parser

πŸš€ Quick start

import { parseEcss } from '@ecss/parser';

const ast = parseEcss(`
  @state-variant Theme {
    values: light, dark;
  }

  @state-def Button(--theme Theme: "light", --disabled boolean: false) {
    border-radius: 6px;

    @if (--disabled) {
      opacity: 0.4;
      cursor: not-allowed;
    }

    @if (--theme == "light") {
      background: #fff;
      color: #111;
    }
    @else {
      background: #1e1e1e;
      color: #f0f0f0;
    }
  }
`);

console.log(ast.rules);

πŸ›  API

parseEcss(source: string): EcssStylesheet

The only exported function. Accepts an ECSS source string and returns the AST.

On parse failure throws a JavaScript Error with the source location: [line:column] description.

import { parseEcss } from '@ecss/parser';

try {
  const ast = parseEcss(source);
  // ast: EcssStylesheet
} catch (err) {
  // e.g. "[3:5] Unknown at-rule: @unknown"
  console.error(err.message);
}

πŸ“ AST types

EcssStylesheet

The root node of the tree.

interface EcssStylesheet {
  rules: EcssRule[];
}

EcssRule

A top-level rule. The kind discriminant determines which field is populated.

interface EcssRule {
  kind: 'state-variant' | 'state-def' | 'qualified-rule' | 'at-rule';
  stateVariant?: StateVariant;
  stateDef?: StateDef;
  qualifiedRule?: CssQualifiedRule;
  atRule?: CssRawAtRule;
}

StateVariant

An @state-variant node.

interface StateVariant {
  name: string; // enumeration name, e.g. "Theme"
  values: string[]; // ["light", "dark"]
  span: Span;
}

StateDef

An @state-def node.

interface StateDef {
  name: string;
  params: StateParam[];
  body: StateDefItem[];
  span: Span;
}

interface StateParam {
  name: string; // "--theme"
  paramType: 'boolean' | string; // "boolean" or a @state-variant name
  variantName?: string; // @state-variant name for variant params
  defaultValue?: string; // "light", "true", "false", etc.
}

StateDefItem

An item inside a @state-def or @if block body.

interface StateDefItem {
  kind: 'declaration' | 'qualified-rule' | 'if-chain' | 'at-rule';
  declaration?: CssDeclaration;
  qualifiedRule?: CssQualifiedRule;
  ifChain?: IfChain;
  atRule?: CssRawAtRule;
}

IfChain

An @if / @elseif / @else node.

interface IfChain {
  ifClause: IfClause;
  elseIfClauses: IfClause[];
  elseBody?: StateDefItem[];
  span: Span;
}

interface IfClause {
  condition: unknown; // JSON-serialized ConditionExpr
  body: StateDefItem[];
  span: Span;
}

The condition field contains a ConditionExpr serialized to JSON. Shape:

// { kind: "var",        var: "--name" }
// { kind: "comparison", left: "--name", op: "==" | "!=", right: { kind, value } }
// { kind: "and",        left: ConditionExpr, right: ConditionExpr }
// { kind: "or",         left: ConditionExpr, right: ConditionExpr }

CssDeclaration

A CSS declaration (property: value).

interface CssDeclaration {
  property: string;
  value: string;
  important: boolean;
  span: Span;
}

CssQualifiedRule

A CSS rule with a selector (including CSS Nesting inside @state-def).

interface CssQualifiedRule {
  selector: string; // "&:hover", ".class > div", etc.
  body: StateDefItem[];
  span: Span;
}

CssRawAtRule

An arbitrary CSS at-rule that is not an ECSS construct.

interface CssRawAtRule {
  name: string;
  prelude: string;
  block?: string;
  span: Span;
}

Span

Source position of a node.

interface Span {
  line: number;
  column: number;
  endLine: number;
  endColumn: number;
}

🌐 WASM

If the native build is unavailable (e.g. containers without N-API support, or the browser), use the WASM variants.

Node.js / WASI:

import { parseEcss } from '@ecss/parser/wasm';

Browser:

import { parseEcss } from '@ecss/parser/wasm/browser';

The native addon is preferred automatically; the WASM binding acts as a fallback. To force WASM, set the environment variable NAPI_RS_FORCE_WASI=1.


πŸ–₯️ Supported platforms

Platform Architecture Target
macOS x64 x86_64-apple-darwin
macOS ARM64 aarch64-apple-darwin
Linux (glibc) x64 x86_64-unknown-linux-gnu
Windows x64 x86_64-pc-windows-msvc
WASM β€” wasm32-wasip1-threads

πŸ”§ Development

Build native addon:

pnpm build          # release
pnpm build:debug    # debug

Build WASM:

pnpm build:wasm         # release
pnpm build:wasm:debug   # debug

Tests:

pnpm test

Type check:

pnpm typecheck

Lint and format (JS/TS):

pnpm lint         # oxlint
pnpm lint:fix     # oxlint --fix
pnpm fmt          # oxfmt
pnpm fmt:check    # oxfmt --check

Lint and format (Rust):

pnpm lint:rs      # cargo clippy -D warnings
pnpm lint:rs:fix  # cargo clippy --fix
pnpm fmt:rs       # cargo fmt
pnpm fmt:rs:check # cargo fmt --check

πŸ‘¨β€πŸ’» Author

Developed and maintained by Ruslan Martynov.

Found a bug or have a suggestion? Open an issue or submit a pull request.


πŸ“„ License

Distributed under the MIT License.

About

High-performance ECSS parser written in Rust (napi-rs). Parses ECSS source into an AST.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors