NAME
Litavis - CSS preprocessor and compiler implemented in C via XS
SYNOPSIS
use Litavis;
# Basic usage
my $css = Litavis->new->parse($input)->compile;
# With options
my $l = Litavis->new(
pretty => 1,
indent => "\t",
dedupe => 1, # 0=off, 1=conservative, 2=aggressive
shorthand_hex => 1, # #aabbcc -> #abc
sort_props => 0, # alphabetise properties
);
# Multiple inputs accumulate
$l->parse($base_css);
$l->parse($theme_css);
my $output = $l->compile;
# File and directory input
$l->parse_file('styles.css');
$l->parse_dir('css/'); # sorted, .css only, non-recursive
# Write directly to file
$l->compile_file('output.css');
# Reset between independent compilations
$l->reset;
$l->parse($other_css);
my $fresh = $l->compile;
DESCRIPTION
Litavis is a CSS preprocessor and compiler with its entire engine implemented in C via reusable header files, exposed to Perl through XS. It succeeds Crayon with a focus on correctness and performance.
Features
Nested selectors with flattening (
.a { .b { } }becomes.a .b { })Parent references (
&:hover,&.active)Preprocessor variables (
$color: red; .a { color: $color; })Mixins (
%box: ( padding: 8px; ); .a { %box; })Map variables (
%sizes: ( sm: 8px; ); .a { padding: $sizes{sm}; })Colour functions via Colouring::In::XS (
lighten,darken,mix,saturate,desaturate,fade,tint,shade,greyscale)Cascade-aware deduplication that only merges selectors when provably safe
Order preservation using C arrays (no Perl hash randomisation)
CSS custom properties passthrough (
var(--x),calc(),clamp())@import/@charset hoisting to the top of output
Hex shorthand optimisation (
#ffffffbecomes#fff)Comment stripping (block
/* */and line//)
Compilation Pipeline
Each call to compile processes the full accumulated AST through these stages, all in C:
1. Flatten nested selectors
2. Resolve preprocessor variables, mixins, and map variables
3. Evaluate colour functions (lighten, darken, etc.)
4. Merge rules with the same selector (later properties win)
5. Deduplicate rules with identical properties (cascade-aware)
6. Emit CSS string (minified or pretty-printed)
compile is non-destructive; calling it multiple times returns the same result. Use reset to clear state between independent compilations.
METHODS
new
my $l = Litavis->new(%options);
Create a new Litavis instance. All options are optional.
- pretty => 0 | 1
-
Output mode.
0(default) produces minified CSS with no whitespace.1produces human-readable output with indentation and newlines. - dedupe => 0 | 1 | 2
-
Deduplication strategy.
0disables deduplication entirely.1(default) uses conservative mode which only merges rules when no intervening rule defines a conflicting property.2uses aggressive mode which merges all rules with identical properties regardless of cascade position. - indent => $string
-
Indent string for pretty mode. Default is two spaces
" ". Common alternative is"\t". - shorthand_hex => 0 | 1
-
Hex colour shorthand.
1(default) converts#aabbccto#abcwhen possible.0preserves the original form. - sort_props => 0 | 1
-
Property sorting.
0(default) preserves source order.1alphabetises properties within each rule.
parse
$l->parse($css_string);
Parse a CSS string and accumulate the rules into the internal AST. Supports nested selectors, preprocessor variables ($var: value;), mixins (%name: (...);), map variables (%name: ( key: value; );), @media, @keyframes, @import, and other at-rules.
Returns self for chaining.
parse_file
$l->parse_file($filename);
Read and parse a CSS file. Dies if the file cannot be opened.
Returns self for chaining.
parse_dir
$l->parse_dir($directory);
Parse all .css files in a directory in alphabetical order. Non-recursive (subdirectories are ignored). Non-CSS files are skipped.
Variables defined in earlier files (by sort order) are available to later files, enabling patterns like 01-vars.css, 02-base.css, 03-theme.css.
Returns self for chaining.
compile
my $css = $l->compile;
Compile the accumulated AST to a CSS string. Runs the full pipeline (flatten, resolve variables, resolve colours, merge, deduplicate, emit).
Non-destructive: can be called multiple times with the same result.
compile_file
$l->compile_file($filename);
Compile and write the output directly to a file. Dies if the file cannot be opened for writing.
reset
$l->reset;
Clear all accumulated state (AST, variables, mixins, maps). Configuration options (pretty, dedupe, etc.) are preserved.
pretty
my $val = $l->pretty; # get
$l->pretty(1); # set
Get or set the pretty-print mode.
dedupe
my $val = $l->dedupe; # get
$l->dedupe(2); # set
Get or set the deduplication strategy.
include_dir
my $dir = Litavis->include_dir;
Returns the path to the installed C header files. Intended for downstream XS modules that want to #include the Litavis C engine directly:
# In downstream Makefile.PL
my $inc = Litavis->include_dir;
# Then use -I$inc in CCFLAGS
COLOUR FUNCTIONS
Litavis evaluates colour functions at compile time using the Colouring::In::XS C headers. Colour arguments can be hex (#rgb, #rrggbb), rgb(), rgba(), hsl(), or hsla(). Functions that are not recognised as colour functions (e.g. calc(), var(), linear-gradient()) are passed through unchanged.
lighten
.a { color: lighten(#000, 50%); } /* #7f7f7f */
.a { color: lighten(#3498db, 20%); } /* lighter blue */
Increases lightness by the given percentage. Converts to HSL internally, adds the amount to the lightness component, and converts back to hex.
darken
.a { color: darken(#fff, 50%); } /* #7f7f7f */
.a { color: darken(#3498db, 20%); } /* darker blue */
Decreases lightness by the given percentage.
saturate
.a { color: saturate(#7f7f7f, 50%); }
Increases the saturation of a colour by the given percentage.
desaturate
.a { color: desaturate(#3498db, 50%); }
Decreases the saturation of a colour by the given percentage.
greyscale
.a { color: greyscale(#3498db); }
Removes all saturation, converting the colour to its greyscale equivalent. Equivalent to desaturate($colour, 100%).
fade
.a { color: fade(#3498db, 50%); } /* rgba(52,152,219,0.5) */
Sets the absolute opacity of a colour. The result is emitted as rgba() when alpha is less than 1.
fadein
.a { color: fadein(rgba(0,0,0,0.5), 25%); }
Increases opacity by the given amount.
fadeout
.a { color: fadeout(#3498db, 25%); } /* rgba(52,152,219,0.75) */
Decreases opacity by the given amount.
mix
.a { color: mix(#fff, #000, 50); } /* #7f7f7f */
.a { color: mix(#f00, #00f, 75); } /* 75% red, 25% blue */
Mixes two colours. The third argument is the weight (0-100) given to the first colour. Default is 50 (equal mix). Uses alpha-aware blending.
tint
.a { color: tint(#3498db, 50); }
Mixes the colour with white. The argument is the weight (0-100) given to the original colour.
shade
.a { color: shade(#3498db, 50); }
Mixes the colour with black. The argument is the weight (0-100) given to the original colour.
EXAMPLES
Basic Compilation
use Litavis;
my $css = Litavis->new->parse('
.card {
color: red;
font-size: 16px;
}
')->compile;
# .card{color:red;font-size:16px;}
Nested Selectors and Parent References
my $css = Litavis->new->parse('
.nav {
background: #333;
.item {
color: white;
&:hover {
color: yellow;
}
&.active {
font-weight: bold;
}
}
}
')->compile;
# .nav{background:#333;}.nav .item{color:white;}.nav .item:hover{color:yellow;}.nav .item.active{font-weight:bold;}
Variables and Mixins
my $css = Litavis->new->parse('
$brand: #3498db;
$pad: 16px;
%button-base: (
padding: 8px $pad;
border: none;
border-radius: 4px;
);
.btn-primary {
%button-base;
background: $brand;
color: white;
}
.btn-secondary {
%button-base;
background: #ecf0f1;
color: #2c3e50;
}
')->compile;
Map Variables
my $css = Litavis->new->parse('
%breakpoints: (
sm: 576px;
md: 768px;
lg: 992px;
);
.container { max-width: $breakpoints{lg}; }
')->compile;
# .container{max-width:992px;}
Colour Functions with Variables
my $css = Litavis->new->parse('
$primary: #3498db;
.btn {
background: $primary;
color: white;
}
.btn:hover {
background: darken($primary, 15%);
}
.btn:disabled {
background: desaturate($primary, 40%);
color: fade(#000, 50%);
}
')->compile;
Cascade-Aware Deduplication
# Conservative mode (default) — safe merging only
my $css = Litavis->new->parse('
.reset { color: black; margin: 0; }
.theme { color: red; }
.footer { color: black; margin: 0; }
')->compile;
# .reset and .footer are NOT merged because .theme
# defines "color" which conflicts — merging would
# reorder the cascade.
# Aggressive mode — merge all identical, ignore cascade
my $css = Litavis->new(dedupe => 2)->parse('
.a { padding: 8px; }
.b { color: red; }
.c { padding: 8px; }
')->compile;
# .a,.c{padding:8px;}.b{color:red;}
Pretty-Printed Output
my $css = Litavis->new(pretty => 1, indent => " ")->parse('
.card {
color: red;
background: blue;
}
')->compile;
# .card {
# color: red;
# background: blue;
# }
@media Queries
my $css = Litavis->new(pretty => 1)->parse('
.container { max-width: 1200px; }
@media (max-width: 768px) {
.container { max-width: 100%; padding: 0 16px; }
}
')->compile;
Multi-File Project with Directory Parsing
# css/
# 01-variables.css -> $brand: #3498db; $text: #333;
# 02-base.css -> body { color: $text; }
# 03-components.css -> .btn { background: $brand; }
my $l = Litavis->new;
$l->parse_dir('css/');
my $css = $l->compile;
# Variables from 01 are available in 02 and 03
CSS Custom Properties (Passthrough)
my $css = Litavis->new->parse('
$brand: #3498db;
:root {
--primary: $brand;
--spacing: 8px;
}
.card {
color: var(--primary);
padding: var(--spacing);
width: calc(100% - 32px);
}
')->compile;
# Preprocessor $brand is resolved; var(), calc() pass through unchanged
C HEADER FILES
The entire engine is implemented in standalone C header files that can be reused by other XS modules:
litavis.h Master include (context struct, lifecycle)
litavis_ast.h Ordered AST with hash index
litavis_tokeniser.h Single-pass CSS tokeniser
litavis_parser.h Recursive descent parser with selector flattening
litavis_cascade.h Cascade-aware deduplication
litavis_vars.h Variable, mixin, and map resolution
litavis_colour.h Colour function evaluation (uses colouring.h)
litavis_emitter.h CSS output (minified and pretty-printed)
DEPENDENCIES
Colouring::In::XS - C headers for colour manipulation
SEE ALSO
Crayon - the pure-Perl predecessor that Litavis replaces
AUTHOR
LNATION <email@lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 401:
Non-ASCII character seen before =encoding in '—'. Assuming UTF-8