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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@tbela99/css-parser

v0.7.1

Published

CSS parser for node and the browser

Downloads

40

Readme

npm npm cov NPM Downloads

css-parser

CSS parser and minifier for node and the browser

Installation

From npm

$ npm install @tbela99/css-parser

from jsr

$ deno add @tbela99/css-parser

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(), light-dark(), system colors 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
  • partial css validation: only css selector is validated

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

// ...

Deno exports

import as a module


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

// ...

import as a CommonJS module


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

// ...

Web export

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

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.

Minify Options

  • validation: boolean, optional. enable strict css validation using (mdn data)[https://github.com/mdn/data]. only the selector is validated at this time.

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.

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. destination directory used to resolve url().
  • 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. 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}}

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 = `

.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 '@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] reduce calc()
  • [x] inline css variables
  • [x] merge identical rules
  • [x] merge adjacent rules
  • [x] minify colors
  • [x] minify numbers and Dimensions tokens
  • [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

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

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