Skip to content

Latest commit

 

History

399 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bowdy
bowdy • A fast CSS Preprocessor
Typed • VM & JIT Compiler • Written in Nim language

nimble install bowdy / clue install bowdy --build

API Reference | Documentation | Download binaries
Github Actions Github Actions

Overview

bowdy transpiles BASS files to standard CSS. It is written in Nim and designed for fast compilation, a typed system that catches errors early, and syntax that stays close to CSS while adding variables, nesting, mixins, control flow, and modules.

BASS files use the .bass extension and compile to .css.

Features

  • Fast stack-based VM & JIT Compiler
  • Typed system for CSS values (color, length, number, etc.) with compile-time checks
  • Familiar CSS syntax with indentation or brace blocks
  • Variables (var, const) with optional type annotations and export (*)
  • Nesting with parent selector &, combinators, and comma-separated selectors
  • Reusable mixins with typed parameters and named arguments
  • Control flow (if / elif / else, for, while, case / of) and functions (fn / func)
  • Module imports (import "./vars.bass") and package imports (pkg/)
  • Modern CSS passthrough: custom properties, var(), calc(), color-mix(), gradients, and at-rules
  • Source maps, bundling, and pretty-printed output

Quick Start

Installation

Requires Nim >= 2.0.0 (https://nim-lang.org/install.html).

nimble install bowdy

Or use clue, an alternative package manager for Nim development:

clue install bowdy --build

Compile

bowdy c style.bass -o style.css        # compile to CSS (minified by default)
bowdy c style.bass --pretty -o style.css  # pretty-printed output
bowdy c style.bass --watch             # recompile on change
bowdy -h                               # all options

Source maps are supported with --sourceMap.

By default bowdy c is lenient: only VM types apply (variables, function signatures, stdlib constructors), so a mistyped width: $colorVar still compiles. Pass --strict to enable the static CSS type system: property values are checked against their CSS syntax, invalid colors are hard errors, and undeclared var(--x) names warn. Output is identical either way; only diagnostics change.

Syntax Showcase

All examples are minified by default. Add --pretty for formatted output.

1. Variables

var $primary = #0d6efd
var $radius = 4px

.card
  color: $primary
  border-radius: $radius
.card{color:#0d6efd;border-radius:4px}

Variables use var / const, support interpolation ($primary), and are checked against CSS property types — the compiler rejects mismatches such as width: red. CSS custom properties take part in the same system: var(--x) parses as a real call returning a typed cssvar value, so custom-property names stay atomic (var(--color-gray-100) is never re-split) and use sites check structurally. Every --x declaration (entry file and imports, order-independent) registers its inferred type, so under --strict color: var(--fs-medium) is a hard error when --fs-medium holds a size. Undeclared names only warn (they may come from plain-CSS imports or JS), var(--x, fallback) validates the fallback too, and env() is left alone. The CLI collects warnings during a compile and prints them with displayWarning once the CSS is out.

2. Nesting

.card
  color: gray
  &:hover
    color: black
  .title
    font-weight: bold
.card{color:gray}.card:hover{color:black}.card .title{font-weight:bold}

Supports & for pseudo-classes, combinators (& > .item, & + .item), and comma-separated parents. Brace syntax works as well: .card { &:hover { color: black } }.

3. Mixins

mixin btn(color: color) {
  color: $color
  border-radius: 4px
}

.a
  @btn(red)
.a{color:red;border-radius:4px}

Mixins accept typed parameters, support named arguments (@box($h = 5px, $w = 10px)), and can contain nested selectors.

4. Control Flow and Code Generation

for $i in range(1, 3):
  .p-${$i}
    z-index: $i
.p-1{z-index:1}.p-2{z-index:2}.p-3{z-index:3}

Other constructs:

var debug = true
.a
  if $debug:
    outline: 1px
  else:
    outline: none

for also iterates over arrays of objects (for $s in [{k:0,v:0}, {k:1,v:0.25rem}]), while, and case / of are available.

5. Imports

// _vars.bass
var $accent* = #0d6efd
var $radius* = 4px

// main.bass
import "./_vars.bass"
.a
  color: $accent
  border-radius: $radius
.a{color:#0d6efd;border-radius:4px}

Export with *, import relative files or packages.

6. Functions

fn dbl($n: int): int
  return $n * 2

var $p = dbl(21)
.a { z-index: $p }
.a{z-index:42}

func is an alias for fn. Functions support overloading and forward declarations.

7. Embed bowdy in your Nim app

import bowdy gives two high-level calls that never quit the process. Both return a BroCompileResult (ok, css, warnings, error):

import bowdy

let r = compileStylesheet("var $primary = #0d6efd\n.card\n  color: $primary")
if r.ok:
  echo r.css
  for w in r.warnings: echo w
else:
  echo "build failed: " & r.error

let f = compileStylesheetFile("styles/main.bass", pretty = true)

compileStylesheet compiles a source string (relative imports resolve against the working directory); compileStylesheetFile compiles a file on disk and resolves sibling imports next to it. Both take an optional strict = false parameter mirroring bowdy c --strict. Parse, type, and codegen failures come back as ok == false with error set, so a host app stays in control.

Benchmarks

benchmarks/bench.sh times four CLI commands head-to-head with hyperfine: sassc, bowdy c, bowdy c --strict, and dart-sass when the vendored benchmarks/dart-sass/sass binary (gitignored) is present:

benchmarks/bench.sh
benchmarks/bench.sh --warmup=2 --runs=5
benchmarks/bench.sh --big-count=500   # smaller Suite C

Suites (reports land in bin/bench-a.md, bin/bench-b.md, bin/bench-c.md):

  • A (throughput): the same bin/bootstrap.css (280KB) through every compiler, since plain CSS is valid input for all of them.
  • B (features): the equivalent pair benchmarks/vs_sassc/features.scss / features.bass (variables, nesting, parent refs, mixins with args, loops, conditionals, color functions, media queries).
  • C (scale): a generated pair, big.scss / big.bass (~300KB at the default 2000 rules), produced by benchmarks/vs_sassc/gen_big.py.

Re-run on your own machine before quoting numbers: absolute times depend on hardware and build flags (release).

Known fixture constraints: $vars inside opaque raw CSS calls (linear-gradient(to right, $c, ...)) stay verbatim, runtime ${$var}px interpolation needs a loop or literal base, and construct ordering in features.bass is load-bearing in spots. # is not a comment in BASS (it starts an ID selector); the .bass fixture uses // comments. Typed var() approximations: the registry is file-global (selector and media scoping ignored, last declaration wins), shorthand syntaxes stay narrow (border: var(--w) with a length errors, same as border: $w today), and var() text smuggled through $var strings (rather than a real var() call) is unchecked. Uppercase VAR() stays opaque and unvalidated. Static color calls keep their source spelling: fully-static rgb() / rgba() / hsl()-family calls render verbatim (rgb(13 110 253) stays space-separated, rgba(0,0,0,.3) keeps , and .3), while dynamic forms (any $var / var() / nested call) evaluate to typed colors and render canonically. Static calls nested in var() fallbacks stringify compactly (translate3d(0.25em,0,0)).

Documentation

Contributing

License

bowdy is released under the LGPL-3.0-or-later license. Made by Humans from OpenPeeps.
Copyright © 2026 OpenPeeps & Contributors — All rights reserved.

About

Bowdy - A fast CSS preprocessor! Alternative to SassC, DartSass SassJS and Less

Topics

Resources

Stars

23 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

Generated from openpeeps/pistachio