Skip to content

better-lyrics/rics

Repository files navigation

rics

A fast, lightweight SCSS-like preprocessor that runs anywhere JavaScript runs.
Built for real-time compilation in browsers, editors, and build tools.

npm version minzipped size downloads license

Table of Contents

Benchmarks

Auto-generated. Run pnpm bench in the benchmarks folder to regenerate.

Performance (ops/sec, higher is better)

Preprocessor ops/sec Comparison
rics 897 fastest
stylus 334 2.7x slower
sass 279 3.2x slower
less 221 4.1x slower

Package Size (minzipped, smaller is better)

Package Size Dependencies Comparison
rics 12.7 KB 0 smallest
less 49.0 KB 0 4x larger
stylus 82.8 KB 5 7x larger
sass 686.0 KB 1 54x larger

Why rics?

Drop-in compatible — All valid CSS is valid rics. Use your existing stylesheets and progressively adopt preprocessor features.

rics vs Stylis — Stylis handles nesting and autoprefixing (~3KB), but that's it. rics adds full SCSS features — variables, mixins, functions, loops, conditionals, color manipulation — while staying small (~10KB minzipped) and fast.

rics vs Sass/Less/Stylus — These need Node.js or WASM. rics runs anywhere JavaScript runs, compiles in milliseconds, and has zero dependencies.

Feature rics Stylis Sass Less
Variables Yes No Yes Yes
Nesting Yes Yes Yes Yes
Mixins Yes No Yes Yes
Functions Yes No Yes Yes
Loops Yes No Yes Yes
Color functions Yes No Yes Yes
Browser-native Yes Yes No No
Zero dependencies Yes Yes No No

Packages

Package Description Size
rics Core compiler ~40KB
rics-cli Command-line interface ~2KB
vite-plugin-rics Vite plugin ~1KB
webpack-loader-rics Webpack loader ~0.5KB
codemirror-lang-rics CodeMirror 6 support ~4KB
prettier-plugin-rics Prettier formatter ~1KB
eslint-plugin-rics ESLint plugin ~2KB
rics-vscode VS Code extension -

Installation

# Core compiler
npm install rics

# CLI (global)
npm install -g rics-cli

# Build tools
npm install vite-plugin-rics      # Vite
npm install webpack-loader-rics   # Webpack

# Editor support
npm install codemirror-lang-rics  # CodeMirror 6

# Code quality
npm install prettier-plugin-rics  # Prettier
npm install eslint-plugin-rics    # ESLint

Quick Start

Node.js / Browser

import { compile } from "rics";

const css = compile(`
$primary: #f43f5e;
$radius: 8px;

.button {
  background: $primary;
  border-radius: $radius;
  padding: 12px 24px;

  &:hover {
    background: darken($primary, 10%);
  }
}
`);

CLI

# Compile to stdout
rics styles.rics

# Compile to file
rics styles.rics -o styles.css

# Watch mode
rics styles.rics -o styles.css --watch

# Minify output
rics styles.rics -o styles.css --minify

Vite

// vite.config.ts
import { defineConfig } from "vite";
import { ricsPlugin } from "vite-plugin-rics";

export default defineConfig({
  plugins: [ricsPlugin()],
});
// main.ts
import "./styles.rics"; // Auto-compiled and injected

Webpack

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.rics$/,
        use: ["style-loader", "css-loader", "webpack-loader-rics"],
      },
    ],
  },
};

CodeMirror 6

import { EditorView, basicSetup } from "codemirror";
import { ricsLanguage, ricsLinter } from "codemirror-lang-rics";

const editor = new EditorView({
  extensions: [basicSetup, ricsLanguage(), ricsLinter({ delay: 300 })],
  parent: document.getElementById("editor"),
});

Prettier

// .prettierrc
{
  "plugins": ["prettier-plugin-rics"]
}

ESLint

// eslint.config.js
import ricsPlugin from "eslint-plugin-rics";

export default [
  {
    files: ["**/*.rics"],
    ...ricsPlugin.configs.recommended,
    plugins: { rics: ricsPlugin },
  },
];

Features

Variables & Math

$base: 4px;
$primary: #f43f5e;

.element {
  padding: $base * 4; // 16px
  margin: $base * 2 $base * 3; // 8px 12px
  width: 100% - 20px;
  color: $primary;
}

Nesting

.card {
  padding: 16px;

  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
  &--featured {
    border: 2px solid gold;
  }
  &__title {
    font-size: 24px;
  }

  .icon {
    margin-right: 8px;
    &.left {
      margin-left: 0;
    }
  }
}

Mixins with Parameters

@mixin button($bg, $color: white, $radius: 4px) {
  background: $bg;
  color: $color;
  border-radius: $radius;
  border: none;
  cursor: pointer;

  &:hover {
    background: darken($bg, 10%);
  }
}

.btn-primary {
  @include button(#f43f5e);
}
.btn-secondary {
  @include button(#6366f1, white, 8px);
}

Custom Functions

@function rem($px) {
  @return #{$px / 16}rem;
}

@function spacing($n) {
  @return 4px * $n;
}

.text {
  font-size: #{rem(18)}; // 1.125rem
  padding: spacing(4); // 16px
  margin-bottom: spacing(6); // 24px
}

Loops

// Generate utility classes
@for $i from 1 through 8 {
  .gap-#{$i} {
    gap: #{$i * 4}px;
  }
  .p-#{$i} {
    padding: #{$i * 4}px;
  }
  .m-#{$i} {
    margin: #{$i * 4}px;
  }
}

// Iterate maps
$colors: (
  primary: #f43f5e,
  success: #22c55e,
  warning: #f59e0b,
);

@each $name, $color in $colors {
  .text-#{$name} {
    color: $color;
  }
  .bg-#{$name} {
    background: $color;
    &:hover {
      background: darken($color, 10%);
    }
  }
}

Conditionals

@mixin theme($mode) {
  @if $mode == dark {
    background: #0a0a0a;
    color: #fafafa;
  } @else if $mode == light {
    background: #ffffff;
    color: #0a0a0a;
  } @else {
    background: inherit;
  }
}

.panel {
  @include theme(dark);
}

Color Functions

$brand: #f43f5e;

.palette {
  --light: #{lighten($brand, 20%)};
  --dark: #{darken($brand, 20%)};
  --muted: #{desaturate($brand, 30%)};
  --complement: #{complement($brand)};
  --mix: #{mix($brand, #3b82f6, 50%)};
  --transparent: #{rgba($brand, 0.5)};
}

Available: lighten, darken, saturate, desaturate, adjust-hue, mix, complement, invert, grayscale, rgba, rgb, hsl, hsla, red, green, blue, alpha, hue, saturation, lightness

Interpolation

$prop: margin;
$side: top;
$i: 5;

.item {
  #{$prop}-#{$side}: 10px;
}

.col-#{$i} {
  width: percentage($i / 12);
}

[data-count="#{$i * 10}"] {
  content: "#{$i} items";
}

API

compile(input, config?)

Returns compiled CSS string.

const css = compile(scss);

compileWithDetails(input, config?)

Returns full compilation result with errors, warnings, and stats:

const result = compileWithDetails(scss);

console.log(result.css);
console.log(result.errors); // CompileError[]
console.log(result.warnings); // CompileWarning[]
console.log(result.stats); // { duration, rules, iterations, inputSize, outputSize }

compileAsync(input, config?)

Non-blocking compilation for large inputs:

const result = await compileAsync(largeStylesheet);

Configuration

compile(scss, {
  timeout: 5000, // Max compilation time (ms)
  maxIterations: 10000, // Max loop iterations
  maxNestingDepth: 64, // Max selector nesting
  minify: false, // Minify output
  strictMode: false, // Throw on first error
});

Built-in Functions

Math

round, ceil, floor, abs, min, max, percentage, random

Strings

str-length, str-slice, str-index, str-insert, to-upper-case, to-lower-case, quote, unquote

Lists

length, nth, join, append, index

Maps

map-get, map-keys, map-values, map-has-key

Introspection

type-of, unit, unitless, if, inspect

Native CSS Pass-through

Modern CSS features work unchanged:

  • Custom properties: var(--color), env(safe-area-inset-top)
  • Functions: calc(), min(), max(), clamp()
  • At-rules: @media, @container, @supports, @layer, @scope
  • Selectors: :has(), :is(), :where(), @starting-style

License

MIT


Built by Better Lyrics

About

A fast, lightweight SCSS-like preprocessor that runs anywhere JavaScript runs. Built for real-time compilation in browsers, editors, and build tools.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors