@devtea2026/quis-recusandae-natus-distinctio v5.8.114
css-parser
CSS parser and minifier for node and the browser
Installation
$ npm install @devtea2026/quis-recusandae-natus-distinctio
Features
- no dependency
- fault-tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
- fast and efficient minification without unsafe transforms, see benchmark
- minify colors.
- support css color level 4 & 5: color(), lab(), lch(), oklab(), oklch(), color-mix() and relative color
- generate nested css rules
- convert nested css rules to legacy syntax
- generate sourcemap
- compute css shorthands. see supported properties list below
- evaluate calc()
- inline css variables
- remove duplicate properties
- flatten @import rules
Playground
Try it online
Exports
There are several ways to import the library into your application.
Node exports
import as a module
import {transform} from '@devtea2026/quis-recusandae-natus-distinctio';
// ...
Deno exports
import as a module
import {transform} from 'npm:@devtea2026/quis-recusandae-natus-distinctio';
// ...
import as a CommonJS module
const {transform} = require('@devtea2026/quis-recusandae-natus-distinctio/cjs');
// ...
Web export
Programmatic import
import {transform} from '@devtea2026/quis-recusandae-natus-distinctio/web';
// ...
Javascript module from cdn
<script type="module">
import {transform} from 'https://esm.sh/@devtea2026/quis-recusandae-natus-distinctio@0.4.0/web';
const css = `
.s {
background: color-mix(in hsl, color(display-p3 0 1 0) 80%, yellow);
}
`;
console.debug(await transform(css).then(r => r.code));
</script>
Javascript module
<script src="dist/web/index.js" type="module"></script>
Single Javascript file
<script src="dist/index-umd-web.js"></script>
Transform
Parse and render css in a single pass.
Usage
transform(css, transformOptions: TransformOptions = {}): TransformResult
Example
import {transform} from '@devtea2026/quis-recusandae-natus-distinctio';
const {ast, code, map, errors, stats} = await transform(css, {minify: true, resolveImport: true, cwd: 'files/css'});
TransformOptions
Include ParseOptions and RenderOptions
ParseOptions
Minify Options
- minify: boolean, optional. default to true. optimize ast.
- nestingRules: boolean, optional. automatically generated nested rules.
- expandNestingRules: boolean, optional. convert nesting rules into separate rules. will automatically set nestingRules to false.
- removeDuplicateDeclarations: boolean, optional. remove duplicate declarations.
- computeShorthand: boolean, optional. compute shorthand properties.
- computeCalcExpression: boolean, optional. evaluate calc() expression
- inlineCssVariables: boolean, optional. replace some css variables with their actual value. they must be declared once in the :root {} or html {} rule.
- removeEmpty: boolean, optional. remove empty rule lists from the ast.
Sourcemap Options
- src: string, optional. original css file location to be used with sourcemap.
- sourcemap: boolean, optional. preserve node location data.
Misc Options
- resolveUrls: boolean, optional. resolve css 'url()' according to the parameters 'src' and 'cwd'
- resolveImport: boolean, optional. replace @import rule by the content of its referenced stylesheet.
- removeCharset: boolean, optional. remove @charset.
- cwd: string, optional. the current working directory. when specified url() are resolved using this value
- visitor: VisitorNodeMap, optional. node visitor used to transform the ast.
- signal: AbortSignal, optional. abort parsing.
RenderOptions
Minify Options
- minify: boolean, optional. default to true. minify css output.
- withParents: boolean, optional. render this node and its parents.
- expandNestingRules: boolean, optional. expand nesting rules.
- preserveLicense: boolean, force preserving comments starting with '/*!' when minify is enabled.
- removeComments: boolean, remove comments in generated css.
- convertColor: boolean, convert colors to hex.
Sourcemap Options
- sourcemap: boolean, optional. generate sourcemap
Misc Options
- indent: string, optional. css indention string. uses space character by default.
- newLine: string, optional. new line character.
- output: string, optional. file where to store css. url() are resolved according to the specified value. no file is created though.
- cwd: string, optional. value used as current working directory. when output is not provided, urls are resolved according to this value.
Parsing
Usage
parse(css, parseOptions = {})
Example
const {ast, errors, stats} = await parse(css);
Rendering
Usage
render(ast, RenderOptions = {});
Examples
Rendering ast
import {parse, render} from '@devtea2026/quis-recusandae-natus-distinctio';
const css = `
@media screen and (min-width: 40em) {
.featurette-heading {
font-size: 50px;
}
.a {
color: red;
width: 3px;
}
}
`;
const result = await parse(css, options);
// print declaration without parents
console.error(render(result.ast.chi[0].chi[1].chi[1], {withParents: false}));
// -> width:3px
// print declaration with parents
console.debug(render(result.ast.chi[0].chi[1].chi[1], {withParents: true}));
// -> @media screen and (min-width:40em){.a{width:3px}}
Merge similar rules
CSS
.clear {
width: 0;
height: 0;
color: transparent;
}
.clearfix:before {
height: 0;
width: 0;
}
import {transform} from '@devtea2026/quis-recusandae-natus-distinctio';
const result = await transform(css);
Result
.clear,.clearfix:before{height:0;width:0}.clear{color:#0000}
Automatic CSS Nesting
CSS
const {parse, render} = require("@devtea2026/quis-recusandae-natus-distinctio/cjs");
const css = `
table.colortable td {
text-align:center;
}
table.colortable td.c {
text-transform:uppercase;
}
table.colortable td:first-child, table.colortable td:first-child+td {
border:1px solid black;
}
table.colortable th {
text-align:center;
background:black;
color:white;
}
`;
const result = await parse(css, {nestingRules:true}).then(result => render(result.ast, {minify:false}).code);
Result
table.colortable {
& td {
text-align: center;
&.c {
text-transform: uppercase
}
&:first-child,&:first-child+td {
border: 1px solid #000
}
}
& th {
text-align: center;
background: #000;
color: #fff
}
}
Nested CSS Expansion
CSS
table.colortable {
& td {
text-align: center;
&.c {
text-transform: uppercase
}
&:first-child,&:first-child+td {
border: 1px solid #000
}
}
& th {
text-align: center;
background: #000;
color: #fff
}
}
Javascript
import {parse, render} from '@devtea2026/quis-recusandae-natus-distinctio';
const options = {minify: true};
const {code} = await parse(css, options).then(result => render(result.ast, {minify: false, expandNestingRules: true}));
//
console.debug(code);
Result
table.colortable td {
text-align:center;
}
table.colortable td.c {
text-transform:uppercase;
}
table.colortable td:first-child, table.colortable td:first-child+td {
border:1px solid black;
}
table.colortable th {
text-align:center;
background:black;
color:white;
}
Calc() resolution
import {parse, render} from '@devtea2026/quis-recusandae-natus-distinctio';
const css = `
.foo-bar {
width: calc(100px * 2);
height: calc(((75.37% - 63.5px) - 900px) + (2 * 100px));
max-width: calc(3.5rem + calc(var(--bs-border-width) * 2));
}
`;
const prettyPrint = await parse(css).then(result => render(result.ast, {minify: false}).code);
result
.foo-bar {
width: 200px;
height: calc(75.37% - 763.5px);
max-width: calc(3.5rem + var(--bs-border-width)*2)
}
CSS variable inlining
import {parse, render} from '@devtea2026/quis-recusandae-natus-distinctio';
const css = `
:root {
--preferred-width: 20px;
}
.foo-bar {
width: calc(calc(var(--preferred-width) + 1px) / 3 + 5px);
height: calc(100% / 4);}
`
const prettyPrint = await parse(css, {inlineCssVariables: true}).then(result => render(result.ast, {minify: false}).code);
result
.foo-bar {
width: 12px;
height: 25%
}
CSS variable inlining and relative color
import {parse, render} from '@devtea2026/quis-recusandae-natus-distinctio';
const css = `
:root {
--color: green;
}
._19_u :focus {
color: hsl(from var(--color) calc(h * 2) s l);
}
`
const prettyPrint = await parse(css, {inlineCssVariables: true}).then(result => render(result.ast, {minify: false}).code);
result
._19_u :focus {
color: navy
}
CSS variable inlining and relative color
import {parse, render} from '@devtea2026/quis-recusandae-natus-distinctio';
const css = `
html { --bluegreen: oklab(54.3% -22.5% -5%); }
.overlay {
background: oklab(from var(--bluegreen) calc(1.0 - l) calc(a * 0.8) b);
}
`
const prettyPrint = await parse(css, {inlineCssVariables: true}).then(result => render(result.ast, {minify: false}).code);
result
.overlay {
background: #0c6464
}
Node Walker
import {walk} from '@devtea2026/quis-recusandae-natus-distinctio';
for (const {node, parent, root} of walk(ast)) {
// do something
}
AST
Comment
- typ: string 'Comment'
- val: string, the comment
Declaration
- typ: string 'Declaration'
- nam: string, declaration name
- val: array of tokens
Rule
- typ: string 'Rule'
- sel: string, css selector
- chi: array of children
AtRule
- typ: string 'AtRule'
- nam: string. AtRule name
- val: rule prelude
AtRuleStyleSheet
- typ: string 'Stylesheet'
- chi: array of children
Sourcemap
- sourcemap generation
Minification
- reduce calc()
- inline css variables
- merge identical rules
- merge adjacent rules
- minify colors
- minify numbers and Dimensions tokens
- compute shorthand: see the list below
- remove redundant declarations
- conditionally unwrap :is()
- automatic css nesting
- automatically wrap selectors using :is()
- avoid reparsing (declarations, selectors, at-rule)
- node and browser versions
- decode and replace utf-8 escape sequence
Computed shorthands properties
- ~all~
- animation
- background
- border
- border-block-end
- border-block-start
- border-bottom
- border-color
- border-image
- border-inline-end
- border-inline-start
- border-left
- border-radius
- border-right
- border-style
- border-top
- border-width
- column-rule
- columns
- container
- contain-intrinsic-size
- flex
- flex-flow
- font
- font-synthesis
- font-variant
- gap
- grid
- grid-area
- grid-column
- grid-row
- grid-template
- inset
- list-style
- margin
- mask
- offset
- outline
- overflow
- padding
- place-content
- place-items
- place-self
- scroll-margin
- scroll-padding
- scroll-timeline
- text-decoration
- text-emphasis
- transition
Performance
- flatten @import
Node Transformation
Ast can be transformed using node visitors
Exemple 1: Declaration
the visitor is called for any declaration encountered
import {AstDeclaration, ParserOptions} from "../src/@types";
const options: ParserOptions = {
visitor: {
Declaration: (node: AstDeclaration) => {
if (node.nam == '-webkit-transform') {
node.nam = 'transform'
}
}
}
}
const css = `
.foo {
-webkit-transform: scale(calc(100 * 2/ 15));
}
`;
console.debug(await transform(css, options));
// .foo{transform:scale(calc(40/3))}
Exemple 2: Declaration
the visitor is called only on 'height' declarations
import {AstDeclaration, LengthToken, ParserOptions} from "../src/@types";
import {EnumToken, EnumToken} from "../src/lib";
import {transform} from "../src/node";
const options: ParserOptions = {
visitor: {
Declaration: {
// called only for height declaration
height: (node: AstDeclaration): AstDeclaration[] => {
return [
node,
{
typ: EnumToken.DeclarationNodeType,
nam: 'width',
val: [
<LengthToken>{
typ: EnumToken.Length,
val: '3',
unit: 'px'
}
]
}
];
}
}
}
};
const css = `
.foo {
height: calc(100px * 2/ 15);
}
.selector {
color: lch(from peru calc(l * 0.8) calc(c * 0.7) calc(h + 180))
}
`;
console.debug(await transform(css, options));
// .foo{height:calc(40px/3);width:3px}.selector{color:#0880b0}
Exemple 3: At-Rule
the visitor is called on any at-rule
import {AstAtRule, ParserOptions} from "../src/@types";
import {transform} from "../src/node";
const options: ParserOptions = {
visitor: {
AtRule: (node: AstAtRule): AstAtRule => {
if (node.nam == 'media') {
return {...node, val: 'all'}
}
}
}
};
const css = `
@media screen {
.foo {
height: calc(100px * 2/ 15);
}
}
`;
console.debug(await transform(css, options));
// .foo{height:calc(40px/3)}
Exemple 4: At-Rule
the visitor is called only for at-rule media
import {AstAtRule, ParserOptions} from "../src/@types";
import {transform} from "../src/node";
const options: ParserOptions = {
visitor: {
AtRule: {
media: (node: AstAtRule): AstAtRule => {
return {...node, val: 'all'}
}
}
}
};
const css = `
@media screen {
.foo {
height: calc(100px * 2/ 15);
}
}
`;
console.debug(await transform(css, options));
// .foo{height:calc(40px/3)}
Exemple 5: Rule
the visitor is called on any Rule
import {AstAtRule, ParserOptions} from "../src/@types";
import {transform} from "../src/node";
const options: ParserOptions = {
visitor: {
Rule (node: AstRule): AstRule {
return {...node, sel: '.foo,.bar,.fubar'};
}
}
};
const css = `
.foo {
height: calc(100px * 2/ 15);
}
`;
console.debug(await transform(css, options));
// .foo,.bar,.fubar{height:calc(40px/3)}
Exemple 6: Rule
Adding declarations to any rule
import {transform} from "../src/node";
import {AstRule, ParserOptions} from "../src/@types";
import {parseDeclarations} from "../src/lib";
const options: ParserOptions = {
removeEmpty: false,
visitor: {
Rule: async (node: AstRule): Promise<AstRule | null> => {
if (node.sel == '.foo') {
node.chi.push(...await parseDeclarations('width: 3px'));
return node;
}
return null;
}
}
};
const css = `
.foo {
}
`;
console.debug(await transform(css, options));
// .foo{width:3px}
Thanks to Jetbrains for sponsoring this project with a free license
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
11 months ago
1 year ago
11 months ago
11 months ago
1 year ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
1 year ago
11 months ago
1 year ago
11 months ago
12 months ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago