CSSTree: CSS AST Parser, Walker, Generator, and Lexer
CSSTree is a comprehensive toolkit for processing CSS, providing a fast and detailed parser to transform CSS source into an Abstract Syntax Tree (AST), a walker for efficient AST traversal, a generator to serialize AST back into CSS, and a lexer for syntax validation and matching based on W3C specifications and browser implementations. The current stable version is 3.2.1, with frequent patch and minor releases indicating active development. It stands out for its performance (benchmarked as one of the fastest), spec compliance, detailed AST format with adjustable parsing levels, and inherent error tolerance by wrapping malformed content in `Raw` nodes instead of discarding it. It also leverages `mdn/data` for robust syntax validation, making it suitable for complex CSS analysis and source-to-source transformations.
Common errors
-
TypeError: node.children.map is not a function
cause The `children` property of AST nodes is a `List` instance by default (since v3.2.0's `list: true` default), not a standard JavaScript array.fixEither convert the `List` to an array using `node.children.toArray().map(...)` or set the `list` option to `false` when parsing: `parse(css, { list: false })`. -
Error: CSS syntax error: Expecting a semicolon or a right curly bracket
cause This error typically originates from the `Lexer` during validation or strict parsing, indicating a fundamental structural issue in the CSS input that violates W3C syntax rules.fixReview the CSS input for missing semicolons, unbalanced curly brackets, or other syntax errors. If using `parse()` with `onParseError`, inspect the `error` object for `line` and `column` to pinpoint the exact location of the issue. The parser is tolerant, so this error is more likely from strict validation or subsequent processing. -
Unknown node type: SomeNewAtRule
cause Your AST traversal or transformation code might be encountering new CSS at-rules (e.g., `@container`, `@scope`) that were added in later CSSTree versions or CSS specifications, and your logic doesn't explicitly handle them.fixUpdate your AST processing logic (e.g., `walk` callbacks) to recognize and handle new `AtRule` types. You can use a `default` case in a switch statement or `else` clause to log unknown types or skip them gracefully.
Warnings
- breaking CSSTree v3.0.0 introduced support for new CSS at-rules like `@container`, `@starting-style`, `@scope`, `@position-try`, and `@layer`. While generally backward compatible for existing CSS, these additions expand the AST structure. Code that relies on a fixed set of AST node types or specific traversal patterns might need adjustments to account for these new nodes. Ensure your code handles new `AtRule` types if traversing or transforming the AST.
- breaking Starting with v2.3.0, CSSTree added comprehensive support for CSS Nesting, including `NestingSelector` for `&` and changes in how `@media` inside a `Rule`'s block is parsed. This fundamentally alters the AST structure for nested CSS, particularly within `@media` blocks inside rules, which now parse as `Declaration` first. If your tooling processed nested CSS with workarounds, these may now conflict with CSSTree's native support.
- gotcha The `parse()` method's `list` option (added in v3.2.0) defaults to `true`, producing `List` instances for children nodes (e.g., `SelectorList`, `Block`). If you prefer standard JavaScript arrays for children nodes, you must explicitly set `list: false`. Not doing so might lead to unexpected behavior if your code expects array methods directly on children properties.
- gotcha CSSTree parser is tolerant to errors by design, meaning it doesn't throw away malformed content but wraps it in a special `Raw` node type. While this prevents crashes, it means you might encounter `Raw` nodes in your AST if the input CSS is invalid. Ignoring these `Raw` nodes could lead to incomplete or incorrect transformations.
Install
-
npm install css-tree -
yarn add css-tree -
pnpm add css-tree
Imports
- parse
const parse = require('css-tree').parse;import { parse } from 'css-tree'; - generate
const generate = require('css-tree').generate;import { generate } from 'css-tree'; - walk
const walk = require('css-tree').walk;import { walk } from 'css-tree'; - Lexer
const Lexer = require('css-tree').Lexer;import { Lexer } from 'css-tree';
Quickstart
import { parse, generate, walk } from 'css-tree';
const cssInput = `
.container {
display: flex;
gap: 10px; /* New in CSS, handled by CSSTree */
background: linear-gradient(to right, #f00, #00f);
@container (min-width: 400px) {
.item {
font-size: 1.2em;
}
}
}
@media (max-width: 600px) {
.container { flex-direction: column; }
}
`;
try {
const ast = parse(cssInput, {
positions: true, // Include source location info
onParseError: (error, fallbackNode) => {
console.warn(`Parsing error at ${error.line}:${error.column}: ${error.message}`);
// Optionally, return fallbackNode or throw to stop parsing
}
});
console.log('Original AST (partial):', ast.children.first.type, ast.children.first.loc);
// Example: Walk the AST to find all `Declaration` nodes and log their properties
let declarationCount = 0;
walk(ast, {
visit: 'Declaration',
enter: (node) => {
declarationCount++;
console.log(` Declaration: ${node.property}: ${generate(node.value)}`);
}
});
console.log(`Total declarations found: ${declarationCount}`);
// Modify the AST, e.g., change `gap` to `margin` for demonstration
walk(ast, {
visit: 'Declaration',
enter: (node) => {
if (node.property === 'gap') {
node.property = 'margin'; // Simple AST modification
}
}
});
const modifiedCss = generate(ast);
console.log('\nModified CSS:\n', modifiedCss);
} catch (error) {
console.error('An unexpected error occurred:', error);
}