npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tbela99/css-parser

v1.4.2

Published

CSS parser, minifier and validator for node and the browser

Readme

playground npm npm cov Doc NPM Downloads bundle size

css-parser

CSS parser, minifier and validator for node and the browser

Installation

From npm

$ npm install @tbela99/css-parser

from jsr

$ deno add @tbela99/css-parser

Features

  • no dependency
  • CSS validation based upon mdn-data
  • CSS modules support
  • fault-tolerant parser implementing the CSS syntax module 3 recommendations.
  • fast and efficient minification without unsafe transforms, see benchmark
  • colors minification: color(), lab(), lch(), oklab(), oklch(), color-mix(), light-dark(), system colors and relative color
  • color conversion to any supported color format
  • automatic nested css rules generation
  • nested css rules conversion to legacy syntax
  • sourcemap generation
  • css shorthands computation. see the supported properties list below
  • css transform functions minification
  • css math functions evaluation: calc(), clamp(), min(), max(), etc.
  • css variables inlining
  • duplicate properties removal
  • @import rules flattening
  • experimental CSS prefix removal

Online documentation

See the full documentation at the CSS Parser documentation site

Playground

Try it online

Import

There are several ways to import the library into your application.

Node

import as a module


import {transform} from '@tbela99/css-parser';

// ...

Deno

import as a module


import {transform} from '@tbela99/css-parser';

// ...

import as a CommonJS module


const {transform} = require('@tbela99/css-parser/cjs');

// ...

Web

Programmatic import


import {transform} from '@tbela99/css-parser/web';

// ...

Javascript module from cdn


<script type="module">

    import {transform} from 'https://esm.sh/@tbela99/[email protected]/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.js" type="module"></script>

Javascript umd module from cdn


<script src="https://unpkg.com/@tbela99/[email protected]/dist/index-umd-web.js"></script>
<script>

    (async () => {

        const css = `

.table {
    border-collapse: collapse;
    width: 100%;
}

.table td, .table th {
    border: 1px solid #ddd;
    padding: 8px;
}

.table tr:nth-child(even){background-color: #f2f2f2;}

.table tr:hover {background-color: #ddd;}

.table th {
    padding-top: 12px;
    padding-bottom: 12px;
    text-align: left;
    background-color: #4CAF50;
    color: white;
}
    `;

        console.debug(await CSSParser.transform(css, {beautify: true, convertColor: CSSParser.ColorType.OKLCH}).then(r => r.code));
    })();

</script>

Exported functions


/* parse and render css */
transform(css: string | ReadableStream<string>, options?: TransformOptions = {}): Promise<TransformResult>
/* parse css */
parse(css: string | ReadableStream<string>, options?: ParseOptions = {}): Promise<ParseResult>;
/* render ast */
render(ast: AstNode, options?: RenderOptions = {}): RenderResult;
/* parse and render css file or url */
transformFile(filePathOrUrl: string, options?: TransformOptions = {}, asStream?: boolean): Promise<TransformResult>;
/* parse css file or url */
parseFile(filePathOrUrl: string, options?: ParseOptions = {}, asStream?: boolean): Promise<ParseResult>;

Transform

Parse and render css in a single pass.

Example


import {transform} from '@tbela99/css-parser';

const {ast, code, map, errors, stats} = await transform(css, {minify: true, resolveImport: true, cwd: 'files/css'});

Example

Read from stdin with node using readable stream

import {transform} from "../src/node";
import {Readable} from "node:stream";
import type {TransformResult} from '../src/@types/index.d.ts';

const readableStream: ReadableStream<string> = Readable.toWeb(process.stdin) as ReadableStream<string>;
const result: TransformResult = await transform(readableStream, {beautify: true});

console.log(result.code);

TransformOptions

Include ParseOptions and RenderOptions

ParseOptions

Minify Options

  • minify: boolean, optional. default to true. optimize ast.
  • pass: number, optional. minification passes. default to 1
  • 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.
  • computeTransform: boolean, optional. compute css transform functions.
  • 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.

CSS modules Options

  • module: boolean | ModuleCaseTransformEnum | ModuleScopeEnumOptions | ModuleOptions, optional. css modules options.

CSS Prefix Removal Options

  • removePrefix: boolean, optional. remove CSS prefixes.

Validation Options

  • validation: ValidationLevel | boolean, optional. enable validation. permitted values are:
    • ValidationLevel.None: no validation
    • ValidationLevel.Default: validate selectors and at-rules (default)
    • ValidationLevel.All. validate all nodes
    • true: same as ValidationLevel.All.
    • false: same as ValidationLevel.None
  • lenient: boolean, optional. preserve invalid tokens.

Sourcemap Options

  • src: string, optional. original css file location to be used with sourcemap, also used to resolve url().
  • sourcemap: boolean, optional. preserve node location data.

Ast Traversal Options

  • visitor: VisitorNodeMap, optional. node visitor used to transform the ast.

Urls and @import Options

  • resolveImport: boolean, optional. replace @import rule by the content of the referenced stylesheet.
  • resolveUrls: boolean, optional. resolve css 'url()' according to the parameters 'src' and 'cwd'

Misc Options

  • removeCharset: boolean, optional. remove @charset.
  • cwd: string, optional. destination directory used to resolve url().
  • signal: AbortSignal, optional. abort parsing.

RenderOptions

Minify Options

  • beautify: boolean, optional. default to false. beautify css output.
  • minify: boolean, optional. default to true. minify css values.
  • withParents: boolean, optional. render this node and its parents.
  • removeEmpty: boolean, optional. remove empty rule lists from the ast.
  • 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 | ColorType, convert colors to the specified color. default to ColorType.HEX. supported values are:
    • true: same as ColorType.HEX
    • false: no color conversion
    • ColorType.HEX
    • ColorType.RGB or ColorType.RGBA
    • ColorType.HSL
    • ColorType.HWB
    • ColorType.CMYK or ColorType.DEVICE_CMYK
    • ColorType.SRGB
    • ColorType.SRGB_LINEAR
    • ColorType.DISPLAY_P3
    • ColorType.PROPHOTO_RGB
    • ColorType.A98_RGB
    • ColorType.REC2020
    • ColorType.XYZ or ColorType.XYZ_D65
    • ColorType.XYZ_D50
    • ColorType.LAB
    • ColorType.LCH
    • ColorType.OKLAB
    • ColorType.OKLCH

Sourcemap Options

  • sourcemap: boolean | 'inline', 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. destination directory used to resolve url().

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 '@tbela99/css-parser';

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}}

CSS Modules

CSS modules features are fully supported. refer to the CSS modules documentation for more information.

import {transform} from '@tbela99/css-parser';

const css = `
.table {
    border-collapse: collapse;
    width: 100%;
}

.table td, .table th {
    border: 1px solid #ddd;
    padding: 8px;
}

.table tr:nth-child(even){background-color: #f2f2f2;}

.table tr:hover {background-color: #ddd;}

.table th {
    padding-top: 12px;
    padding-bottom: 12px;
    text-align: left;
    background-color: #4CAF50;
    color: white;
}
`;

const result = await transform(css, {module: true});

// css code
console.log(result.code);
// css mapping
console.log(result.mapping);

Convert colors

import {transform, ColorType} from '@tbela99/css-parser';


const css = `
.hsl { color: #b3222280; }
`;
const result: TransformResult = await transform(css, {
    beautify: true,
    convertColor: ColorType.SRGB
});

console.log(result.css);

result

.hsl {
    color: color(srgb .7019607843137254 .13333333333333333 .13333333333333333/50%)
}

Merge similar rules

CSS


.clear {
    width: 0;
    height: 0;
    color: transparent;
}

.clearfix:before {

    height: 0;
    width: 0;
}

import {transform} from '@tbela99/css-parser';

const result = await transform(css);

Result

.clear, .clearfix:before {
    height: 0;
    width: 0
}

.clear {
    color: #0000
}

Automatic CSS Nesting

CSS

const {parse, render} = require("@tbela99/css-parser/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
    }
}

CSS Validation

CSS


#404 {
    --animate-duration: 1s;
}

.s, #404 {
    --animate-duration: 1s;
}

.s [type="text" {
    --animate-duration: 1s;
}

.s [type="text"]]
{
    --animate-duration: 1s;
}

.s [type="text"] {
    --animate-duration: 1s;
}

.s [type="text" i] {
    --animate-duration: 1s;
}

.s [type="text" s]
{
    --animate-duration: 1s
;
}

.s [type="text" b]
{
    --animate-duration: 1s;
}

.s [type="text" b],{
    --animate-duration: 1s
;
}

.s [type="text" b]
+ {
    --animate-duration: 1s;
}

.s [type="text" b]
+ b {
    --animate-duration: 1s;
}

.s [type="text" i] + b {
    --animate-duration: 1s;
}


.s [type="text"())]{
    --animate-duration: 1s;
}
.s(){
    --animate-duration: 1s;
}
.s:focus {
    --animate-duration: 1s;
}

with validation enabled

import {parse, render} from '@tbela99/css-parser';

const options = {minify: true, validate: true};
const {code} = await parse(css, options).then(result => render(result.ast, {minify: false}));
//
console.debug(code);
.s:is([type=text],[type=text i],[type=text s],[type=text i]+b,:focus) {
    --animate-duration: 1s
}

with validation disabled

import {parse, render} from '@tbela99/css-parser';

const options = {minify: true, validate: false};
const {code} = await parse(css, options).then(result => render(result.ast, {minify: false}));
//
console.debug(code);
.s:is([type=text],[type=text i],[type=text s],[type=text b],[type=text b]+b,[type=text i]+b,:focus) {
    --animate-duration: 1s
}

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 '@tbela99/css-parser';

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 '@tbela99/css-parser';

const css = `
a {

width: calc(100px * log(625, 5));
}
.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

a {
    width: 400px;
}

.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 '@tbela99/css-parser';

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 '@tbela99/css-parser';

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 '@tbela99/css-parser';

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 '@tbela99/css-parser';

for (const {node, parent, root} of walk(ast)) {

    // do something
}

AST

Comment

  • typ: number
  • val: string, the comment

Declaration

  • typ: number
  • nam: string, declaration name
  • val: array of tokens

Rule

  • typ: number
  • sel: string, css selector
  • chi: array of children

AtRule

  • typ: number
  • nam: string. AtRule name
  • val: rule prelude

AtRuleStyleSheet

  • typ: number
  • chi: array of children

KeyFrameRule

  • typ: number
  • sel: string, css selector
  • chi: array of children

Sourcemap

  • [x] sourcemap generation

Minification

  • [x] minify keyframes
  • [x] minify transform functions
  • [x] evaluate math functions calc(), clamp(), min(), max(), round(), mod(), rem(), sin(), cos(), tan(), asin(), acos(), atan(), atan2(), pow(), sqrt(), hypot(), log(), exp(), abs(), sign()
  • [x] minify colors
  • [x] minify numbers and Dimensions tokens
  • [x] multi-pass minification
  • [x] inline css variables
  • [x] merge identical rules
  • [x] merge adjacent rules
  • [x] compute shorthand: see the list below
  • [x] remove redundant declarations
  • [x] conditionally unwrap :is()
  • [x] automatic css nesting
  • [x] automatically wrap selectors using :is()
  • [x] avoid reparsing (declarations, selectors, at-rule)
  • [x] node and browser versions
  • [x] decode and replace utf-8 escape sequence
  • [x] experimental CSS prefix removal

Computed shorthands properties

  • [ ] ~all~
  • [x] animation
  • [x] background
  • [x] border
  • [ ] border-block-end
  • [ ] border-block-start
  • [x] border-bottom
  • [x] border-color
  • [ ] border-image
  • [ ] border-inline-end
  • [ ] border-inline-start
  • [x] border-left
  • [x] border-radius
  • [x] border-right
  • [x] border-style
  • [x] border-top
  • [x] border-width
  • [x] column-rule
  • [x] columns
  • [x] container
  • [ ] contain-intrinsic-size
  • [x] flex
  • [x] flex-flow
  • [x] font
  • [ ] font-synthesis
  • [ ] font-variant
  • [x] gap
  • [ ] grid
  • [ ] grid-area
  • [ ] grid-column
  • [ ] grid-row
  • [ ] grid-template
  • [x] inset
  • [x] list-style
  • [x] margin
  • [ ] mask
  • [ ] offset
  • [x] outline
  • [x] overflow
  • [x] padding
  • [ ] place-content
  • [ ] place-items
  • [ ] place-self
  • [ ] scroll-margin
  • [ ] scroll-padding
  • [ ] scroll-timeline
  • [x] text-decoration
  • [x] text-emphasis
  • [x] transition

Performance

  • [x] flatten @import

Node Transformation

Ast can be transformed using node visitors

Example 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));
}
`;

const result = await transform(css, options);
console.debug(result.code);

// .foo{transform:scale(calc(40/3))}

Example 2: Declaration

the visitor is called only on 'height' declarations


import {AstDeclaration, EnumToken, LengthToken, ParserOptions, transform} from '@tbela99/css-parser';

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.LengthTokenType,
                                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)) 
}
`;

const result = await transform(css, options);
console.debug(result.code);

// .foo{height:calc(40px/3);width:3px}.selector{color:#0880b0}

Example 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);    
    } 
}
`;

const result = await transform(css, options);
console.debug(result.code);

// .foo{height:calc(40px/3)}

Example 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 => {

                node.val = 'all';
                return node
            }
        }
    }
};

const css = `

@media screen {
       
    .foo {

            height: calc(100px * 2/ 15);    
    } 
}
`;

const result = await transform(css, options);
console.debug(result.code);

// .foo{height:calc(40px/3)}

Example 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 {

            node.sel = '.foo,.bar,.fubar'
            return node;
        }
    }
};

const css = `

    .foo {

            height: calc(100px * 2/ 15);    
    } 
`;

const result = await transform(css, options);
console.debug(result.code);

// .foo,.bar,.fubar{height:calc(40px/3)}

Example 6: Rule

Adding declarations to any rule


import {AstRule, parseDeclarations, ParserOptions, transform} from '@tbela99/css-parser';

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 {
}
`;

const result = await transform(css, options);
console.debug(result.code);

// .foo{width:3px}