@tbela99/css-parser
TypeScript icon, indicating that this package has built-in type declarations

0.4.1 • Public • Published

npm cov NPM Downloads

css-parser

CSS parser and minifier for node and the browser

Installation

$ npm install @tbela99/css-parser

Features

  • no dependency
  • fault-tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
  • 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

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/css-parser@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 '@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.
  • inlineCssVariables: boolean, optional. replace css variables with their current value.
  • 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.
  • expandNestingRules: boolean, optional. expand nesting rules.
  • preserveLicense: boolean, force preserving comments starting with '/*!' when minify is enabled.
  • removeComments: boolean, remove comments in generated css.
  • colorConvert: 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 {render} from '@tbela99/css-parser';

// minified
const {code, stats} = render(ast, {minify: true});

console.log(code);

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

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
}

Node Walker

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

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

  • [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

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

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

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

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

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

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

Package Sidebar

Install

npm i @tbela99/css-parser

Weekly Downloads

8

Version

0.4.1

License

MIT OR LGPL-3.0

Unpacked Size

1.05 MB

Total Files

59

Last publish

Collaborators

  • tbela99