CSS parser and stringifier that converts CSS strings to Abstract Syntax Trees and back to CSS strings
npx @tessl/cli install tessl/npm-css@3.0.0CSS is a comprehensive CSS parser and stringifier for Node.js applications that enables developers to parse CSS strings into Abstract Syntax Trees (AST) and convert AST objects back into CSS strings. It offers robust parsing capabilities with error handling, source map support for debugging and development tools, and flexible stringification options including compression and custom indentation.
npm install cssconst css = require('css');Individual imports:
const { parse, stringify } = require('css');const css = require('css');
// Parse CSS string into AST
const cssString = `
body {
background: #eee;
color: #888;
}
.header {
font-size: 24px;
margin: 10px 0;
}
`;
const ast = css.parse(cssString, { source: 'styles.css' });
// Convert AST back to CSS string
const formattedCSS = css.stringify(ast);
console.log(formattedCSS);
// Generate compressed CSS
const compressedCSS = css.stringify(ast, { compress: true });
// Generate CSS with source maps
const result = css.stringify(ast, { sourcemap: true });
console.log(result.code); // CSS string
console.log(result.map); // Source map objectThe CSS package is built around several key components:
Parses CSS strings into detailed Abstract Syntax Trees with comprehensive error handling and position tracking.
/**
* Parse CSS string into Abstract Syntax Tree
* @param css - CSS string to parse
* @param options - Parsing options
* @returns AST object representing the parsed CSS
*/
function parse(css, options);
interface ParseOptions {
/** Silently fail on parse errors, collect them in parsingErrors array */
silent?: boolean;
/** Path to file containing CSS for better error messages and source maps */
source?: string;
}
interface StylesheetAST {
type: 'stylesheet';
stylesheet: {
/** Source filename from options.source */
source?: string;
/** Array of rules, comments, and at-rules */
rules: Array<Rule | Comment | AtRule>;
/** Array of parse errors when silent: true */
parsingErrors: Error[];
};
}Converts AST objects back to CSS strings with flexible formatting options and source map support.
/**
* Convert AST object back to CSS string
* @param ast - AST object (as produced by css.parse)
* @param options - Stringification options
* @returns CSS string or object with code and source map
*/
function stringify(ast, options);
interface StringifyOptions {
/** Indentation string, defaults to two spaces */
indent?: string;
/** Omit comments and extraneous whitespace */
compress?: boolean;
/** Generate source map; 'generator' returns SourceMapGenerator object */
sourcemap?: boolean | 'generator';
/** Read input source maps (enabled by default) */
inputSourcemaps?: boolean;
}
interface StringifyResult {
/** Generated CSS string */
code: string;
/** Source map object or SourceMapGenerator */
map: object | SourceMapGenerator;
}Enhanced error objects with detailed position information for debugging and development tools.
interface CSSError extends Error {
/** Full error message with source position */
message: string;
/** Error message without position */
reason: string;
/** Value from options.source or undefined */
filename?: string;
/** Line number (1-based) */
line: number;
/** Column number (1-based) */
column: number;
/** Full source CSS string */
source: string;
}Source location tracking for all AST nodes enabling precise debugging and development tool integration.
interface Position {
/** Starting position */
start: {
line: number;
column: number;
};
/** Ending position */
end: {
line: number;
column: number;
};
/** Source filename */
source?: string;
/** Full source CSS string (shared via prototype) */
content: string;
}All AST nodes share common properties and follow a consistent structure.
interface BaseNode {
/** Node type identifier */
type: string;
/** Source position information */
position?: Position;
/** Parent node reference (non-enumerable property) */
parent?: BaseNode | null;
}Root node returned by css.parse containing all top-level rules and metadata.
interface Stylesheet extends BaseNode {
type: 'stylesheet';
stylesheet: {
/** Source filename */
source?: string;
/** Array of child rules, comments, and at-rules */
rules: Array<Rule | Comment | AtRule>;
/** Parse errors when silent mode is enabled */
parsingErrors: CSSError[];
};
}CSS rule with selectors and declarations (e.g., .header { color: red; }).
interface Rule extends BaseNode {
type: 'rule';
/** Array of selectors split on commas, trimmed */
selectors: string[];
/** Array of declarations and comments */
declarations: Array<Declaration | Comment>;
}CSS property declaration (e.g., color: red).
interface Declaration extends BaseNode {
type: 'declaration';
/** Property name, trimmed, comments removed */
property: string;
/** Property value, trimmed, comments removed */
value: string;
}CSS comment node preserving comment content.
interface Comment extends BaseNode {
type: 'comment';
/** Comment content between /* and */ */
comment: string;
}@media at-rule for responsive styles.
interface Media extends BaseNode {
type: 'media';
/** Media query conditions */
media: string;
/** Nested rules, comments, and at-rules */
rules: Array<Rule | Comment | AtRule>;
}@keyframes at-rule for CSS animations with vendor prefix support.
interface Keyframes extends BaseNode {
type: 'keyframes';
/** Animation name */
name: string;
/** Vendor prefix (webkit, moz, etc.) or undefined */
vendor?: string;
/** Array of keyframe and comment nodes */
keyframes: Array<Keyframe | Comment>;
}
interface Keyframe extends BaseNode {
type: 'keyframe';
/** Keyframe selectors (0%, 100%, from, to, etc.) */
values: string[];
/** Array of declarations and comments */
declarations: Array<Declaration | Comment>;
}@import at-rule for importing external stylesheets.
interface Import extends BaseNode {
type: 'import';
/** Import statement value */
import: string;
}@charset at-rule for character encoding.
interface Charset extends BaseNode {
type: 'charset';
/** Character set value */
charset: string;
}@namespace at-rule for XML namespace declarations.
interface Namespace extends BaseNode {
type: 'namespace';
/** Namespace declaration value */
namespace: string;
}@supports at-rule for feature queries.
interface Supports extends BaseNode {
type: 'supports';
/** Feature query conditions */
supports: string;
/** Nested rules, comments, and at-rules */
rules: Array<Rule | Comment | AtRule>;
}@document at-rule with vendor prefix support.
interface Document extends BaseNode {
type: 'document';
/** Document function value */
document: string;
/** Vendor prefix or undefined */
vendor?: string;
/** Nested rules, comments, and at-rules */
rules: Array<Rule | Comment | AtRule>;
}@page at-rule for print styles.
interface Page extends BaseNode {
type: 'page';
/** Page selectors */
selectors: string[];
/** Array of declarations and comments */
declarations: Array<Declaration | Comment>;
}@font-face at-rule for custom font declarations.
interface FontFace extends BaseNode {
type: 'font-face';
/** Array of declarations and comments */
declarations: Array<Declaration | Comment>;
}@host at-rule for Shadow DOM styling.
interface Host extends BaseNode {
type: 'host';
/** Nested rules, comments, and at-rules */
rules: Array<Rule | Comment | AtRule>;
}@custom-media at-rule for custom media queries.
interface CustomMedia extends BaseNode {
type: 'custom-media';
/** Custom media name (--prefixed) */
name: string;
/** Media query definition */
media: string;
}const css = require('css');
const stylesheet = `
.container {
max-width: 1200px;
margin: 0 auto;
}
@media (max-width: 768px) {
.container {
max-width: 100%;
padding: 0 20px;
}
}
`;
// Parse with source tracking
const ast = css.parse(stylesheet, { source: 'layout.css' });
// Access parsed data
console.log(ast.stylesheet.rules.length); // Number of top-level rules
console.log(ast.stylesheet.rules[0].selectors); // ['.container']
console.log(ast.stylesheet.rules[0].declarations[0].property); // 'max-width'
// Convert back to CSS
const output = css.stringify(ast, { indent: ' ' });
console.log(output);const css = require('css');
const invalidCSS = '.broken { color: #invalid-syntax';
try {
const ast = css.parse(invalidCSS);
} catch (error) {
console.log(error.reason); // Error message
console.log(error.line); // Line number
console.log(error.column); // Column number
console.log(error.source); // Full source CSS string
}
// Silent parsing mode
const ast = css.parse(invalidCSS, { silent: true });
console.log(ast.stylesheet.parsingErrors); // Array of errorsconst css = require('css');
const stylesheet = `
body { background: white; }
.header { color: blue; }
`;
const ast = css.parse(stylesheet, { source: 'input.css' });
// Generate with source map
const result = css.stringify(ast, {
sourcemap: true,
compress: false
});
console.log(result.code); // Generated CSS
console.log(result.map); // Source map object
// Access source map generator directly
const withGenerator = css.stringify(ast, {
sourcemap: 'generator'
});
console.log(withGenerator.map.toString()); // Source map JSONconst css = require('css');
const ast = css.parse(`
/* Main styles */
.container {
max-width: 1200px;
margin: 0 auto;
}
/* Responsive */
@media (max-width: 768px) {
.container { max-width: 100%; }
}
`);
// Generate compressed CSS (removes comments and whitespace)
const compressed = css.stringify(ast, { compress: true });
console.log(compressed);
// Output: .container{max-width:1200px;margin:0 auto}@media (max-width: 768px){.container{max-width:100%}}