A CSS parser, transformer, and minifier written in Rust with Node.js bindings
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Extensible AST visitor pattern for custom CSS transformations and analysis with type-safe interfaces, enabling JavaScript-based CSS processing and manipulation.
Combine multiple visitor objects into a single visitor for modular transformation pipelines.
/**
* Composes multiple visitor objects into a single one
* @param visitors - Array of visitor objects to combine
* @returns Single composed visitor with all transformations
*/
function composeVisitors<C extends CustomAtRules>(
visitors: Visitor<C>[]
): Visitor<C>;Usage Examples:
import { composeVisitors, transform } from "lightningcss";
// Create individual visitors
const colorVisitor = {
Color(color) {
// Convert all red colors to brand color
if (color.type === 'rgb' && color.r === 255 && color.g === 0 && color.b === 0) {
return { type: 'rgb', r: 42, g: 86, b: 153 }; // Brand blue
}
return color;
}
};
const urlVisitor = {
Url(url) {
// Rewrite relative URLs to absolute
if (!url.url.startsWith('http') && !url.url.startsWith('data:')) {
return { ...url, url: `https://cdn.example.com/${url.url}` };
}
return url;
}
};
// Compose visitors
const combinedVisitor = composeVisitors([colorVisitor, urlVisitor]);
const result = transform({
filename: "styles.css",
code: new TextEncoder().encode(`
.logo { background: url('./logo.png'); color: red; }
.button { background: blue; color: red; }
`),
visitor: combinedVisitor,
minify: true
});Comprehensive visitor interface for intercepting and transforming all CSS AST nodes.
interface Visitor<C extends CustomAtRules> {
/** Visit stylesheet before processing rules */
StyleSheet?(stylesheet: StyleSheet): StyleSheet<ReturnedDeclaration, ReturnedMediaQuery> | void;
/** Visit stylesheet after processing rules */
StyleSheetExit?(stylesheet: StyleSheet): StyleSheet<ReturnedDeclaration, ReturnedMediaQuery> | void;
/** Visit CSS rules (can be function or object with rule-type-specific visitors) */
Rule?: RuleVisitor | RuleVisitors<C>;
/** Visit CSS rules after processing contents */
RuleExit?: RuleVisitor | RuleVisitors<C>;
/** Visit CSS declarations (can be function or object with property-specific visitors) */
Declaration?: DeclarationVisitor | DeclarationVisitors;
/** Visit CSS declarations after processing values */
DeclarationExit?: DeclarationVisitor | DeclarationVisitors;
/** Visit URL values */
Url?(url: Url): Url | void;
/** Visit color values */
Color?(color: CssColor): CssColor | void;
/** Visit image values */
Image?(image: Image): Image | void;
/** Visit image values after processing */
ImageExit?(image: Image): Image | void;
/** Visit length values */
Length?(length: LengthValue): LengthValue | void;
/** Visit angle values */
Angle?(angle: Angle): Angle | void;
/** Visit ratio values */
Ratio?(ratio: Ratio): Ratio | void;
/** Visit resolution values */
Resolution?(resolution: Resolution): Resolution | void;
/** Visit time values */
Time?(time: Time): Time | void;
/** Visit custom identifier values */
CustomIdent?(ident: string): string | void;
/** Visit dashed identifier values */
DashedIdent?(ident: string): string | void;
/** Visit media queries */
MediaQuery?(query: MediaQuery): ReturnedMediaQuery | ReturnedMediaQuery[] | void;
/** Visit media queries after processing */
MediaQueryExit?(query: MediaQuery): ReturnedMediaQuery | ReturnedMediaQuery[] | void;
/** Visit @supports conditions */
SupportsCondition?(condition: SupportsCondition): SupportsCondition;
/** Visit @supports conditions after processing */
SupportsConditionExit?(condition: SupportsCondition): SupportsCondition;
/** Visit selectors */
Selector?(selector: Selector): Selector | Selector[] | void;
/** Visit CSS tokens (can be function or object with token-type-specific visitors) */
Token?: TokenVisitor | TokenVisitors;
/** Visit CSS functions (can be function or object with function-name-specific visitors) */
Function?: FunctionVisitor | { [name: string]: FunctionVisitor };
/** Visit CSS functions after processing arguments */
FunctionExit?: FunctionVisitor | { [name: string]: FunctionVisitor };
/** Visit CSS variables */
Variable?(variable: Variable): TokenReturnValue;
/** Visit CSS variables after processing */
VariableExit?(variable: Variable): TokenReturnValue;
/** Visit environment variables (can be function or object with env-name-specific visitors) */
EnvironmentVariable?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;
/** Visit environment variables after processing */
EnvironmentVariableExit?: EnvironmentVariableVisitor | EnvironmentVariableVisitors;
}Visit specific types of CSS rules with type-safe interfaces.
type RuleVisitors<C extends CustomAtRules> = {
/** Visit @media rules */
media?: RuleVisitor<MediaRule>;
/** Visit @import rules */
import?: RuleVisitor<ImportRule>;
/** Visit style rules (selectors + declarations) */
style?: RuleVisitor<StyleRule>;
/** Visit @keyframes rules */
keyframes?: RuleVisitor<KeyframesRule>;
/** Visit @font-face rules */
'font-face'?: RuleVisitor<FontFaceRule>;
/** Visit @page rules */
page?: RuleVisitor<PageRule>;
/** Visit @supports rules */
supports?: RuleVisitor<SupportsRule>;
/** Visit @counter-style rules */
'counter-style'?: RuleVisitor<CounterStyleRule>;
/** Visit @namespace rules */
namespace?: RuleVisitor<NamespaceRule>;
/** Visit @layer rules */
layer?: RuleVisitor<LayerRule>;
/** Visit @container rules */
container?: RuleVisitor<ContainerRule>;
/** Visit unknown at-rules */
unknown?: UnknownVisitors<UnknownAtRule>;
/** Visit custom at-rules */
custom?: CustomVisitors<C>;
};
type RuleVisitor<R> = (rule: R) => ReturnedRule | ReturnedRule[] | void;Usage Examples:
// Rule-specific transformation
const ruleVisitor = {
Rule: {
// Transform media queries
media(rule) {
// Convert old max-width syntax to modern range syntax
if (rule.value.query.mediaType === 'screen') {
// Transform query...
return rule;
}
return rule;
},
// Transform style rules
style(rule) {
// Add vendor prefixes to flex properties
const hasFlexDisplay = rule.value.declarations.declarations.some(
decl => decl.property === 'display' && decl.value === 'flex'
);
if (hasFlexDisplay) {
// Add -webkit-box, -moz-box, etc.
return {
...rule,
value: {
...rule.value,
declarations: {
...rule.value.declarations,
declarations: [
{ property: 'display', value: '-webkit-box' },
{ property: 'display', value: '-moz-box' },
...rule.value.declarations.declarations
]
}
}
};
}
return rule;
},
// Remove @import rules
import(rule) {
console.log(`Removing import: ${rule.value.url}`);
return void 0; // Remove rule
}
}
};Visit specific CSS properties with type-safe value access.
type DeclarationVisitors = {
/** Visit background properties */
background?: DeclarationVisitor<BackgroundDeclaration>;
/** Visit color properties */
color?: DeclarationVisitor<ColorDeclaration>;
/** Visit display properties */
display?: DeclarationVisitor<DisplayDeclaration>;
/** Visit margin properties */
margin?: DeclarationVisitor<MarginDeclaration>;
/** Visit padding properties */
padding?: DeclarationVisitor<PaddingDeclaration>;
/** Visit transform properties */
transform?: DeclarationVisitor<TransformDeclaration>;
/** Visit custom properties */
custom?: CustomPropertyVisitors | DeclarationVisitor<CustomProperty>;
// ... many more property-specific visitors
};
type DeclarationVisitor<P> = (property: P) => ReturnedDeclaration | ReturnedDeclaration[] | void;Usage Examples:
// Property-specific transformations
const declarationVisitor = {
Declaration: {
// Transform background properties
background(decl) {
// Convert background shorthand to individual properties for IE support
if (decl.property === 'background' && typeof decl.value === 'object') {
const individual = [];
if (decl.value.color) {
individual.push({ property: 'background-color', value: decl.value.color });
}
if (decl.value.image) {
individual.push({ property: 'background-image', value: decl.value.image });
}
return individual;
}
return decl;
},
// Transform custom properties
custom: {
'--primary-color'(decl) {
// Replace CSS variable with computed value
return {
property: 'color',
value: { type: 'rgb', r: 42, g: 86, b: 153 }
};
}
},
// Transform display properties
display(decl) {
// Add fallbacks for CSS Grid
if (decl.value === 'grid') {
return [
{ property: 'display', value: 'block' }, // Fallback
decl // Original
];
}
return decl;
}
}
};Visit individual CSS tokens and values for fine-grained transformations.
type TokenVisitors = {
/** Visit identifier tokens */
ident?: (token: IdentToken) => TokenReturnValue;
/** Visit at-keyword tokens */
'at-keyword'?: (token: AtKeywordToken) => TokenReturnValue;
/** Visit hash tokens */
hash?: (token: HashToken) => TokenReturnValue;
/** Visit string tokens */
string?: (token: StringToken) => TokenReturnValue;
/** Visit number tokens */
number?: (token: NumberToken) => TokenReturnValue;
/** Visit percentage tokens */
percentage?: (token: PercentageToken) => TokenReturnValue;
/** Visit dimension tokens */
dimension?: (token: DimensionToken) => TokenReturnValue;
};
type TokenReturnValue = TokenOrValue | TokenOrValue[] | RawValue | void;
interface RawValue {
/** A raw string value which will be parsed like CSS. */
raw: string;
}Usage Examples:
// Token-level transformations
const tokenVisitor = {
Token: {
// Transform dimension tokens
dimension(token) {
// Convert px to rem
if (token.unit === 'px' && typeof token.value === 'number') {
return {
type: 'dimension',
value: token.value / 16, // Assuming 16px = 1rem
unit: 'rem'
};
}
return token;
},
// Transform string tokens
string(token) {
// Replace font family names
if (token.value === 'Arial') {
return { type: 'string', value: 'system-ui' };
}
return token;
},
// Transform identifier tokens
ident(token) {
// Replace color names
const colorMap = {
'red': { raw: '#ff0000' },
'blue': { raw: '#0000ff' }
};
return colorMap[token.value] || token;
}
},
// Function-specific transformations
Function: {
// Transform calc() functions
calc(fn) {
// Simplify calc expressions
if (fn.arguments.length === 1) {
const arg = fn.arguments[0];
if (arg.type === 'token' && arg.value.type === 'dimension') {
return arg.value; // Remove unnecessary calc()
}
}
return fn;
},
// Transform url() functions
url(fn) {
// Rewrite URLs
if (fn.arguments[0]?.type === 'token' && fn.arguments[0].value.type === 'string') {
const url = fn.arguments[0].value.value;
if (url.startsWith('./')) {
return {
...fn,
arguments: [{
...fn.arguments[0],
value: {
...fn.arguments[0].value,
value: `https://cdn.example.com/${url.slice(2)}`
}
}]
};
}
}
return fn;
}
}
};Real-world example combining multiple visitor types for comprehensive CSS transformation.
import { transform, composeVisitors } from "lightningcss";
// Asset optimization visitor
const assetVisitor = {
Url(url) {
// Convert relative URLs to CDN URLs
if (!url.url.startsWith('http') && !url.url.startsWith('data:')) {
return { ...url, url: `https://cdn.example.com/assets/${url.url}` };
}
return url;
}
};
// Color standardization visitor
const colorVisitor = {
Color(color) {
// Standardize brand colors
const brandColors = {
'#ff0000': { type: 'rgb', r: 42, g: 86, b: 153 }, // Brand blue
'#00ff00': { type: 'rgb', r: 40, g: 167, b: 69 } // Brand green
};
if (color.type === 'rgb') {
const hex = `#${color.r.toString(16).padStart(2, '0')}${color.g.toString(16).padStart(2, '0')}${color.b.toString(16).padStart(2, '0')}`;
return brandColors[hex] || color;
}
return color;
}
};
// Legacy support visitor
const legacyVisitor = {
Declaration: {
display(decl) {
// Add IE fallbacks for flexbox
if (decl.value === 'flex') {
return [
{ property: 'display', value: '-ms-flexbox' },
{ property: 'display', value: '-webkit-flex' },
decl
];
}
return decl;
}
},
Rule: {
style(rule) {
// Add -webkit- prefixes for flexbox properties
const needsPrefixing = rule.value.declarations.declarations.some(
decl => ['align-items', 'justify-content', 'flex-direction'].includes(decl.property)
);
if (needsPrefixing) {
const prefixedDeclarations = rule.value.declarations.declarations.flatMap(decl => {
if (['align-items', 'justify-content', 'flex-direction'].includes(decl.property)) {
return [
{ property: `-webkit-${decl.property}`, value: decl.value },
decl
];
}
return [decl];
});
return {
...rule,
value: {
...rule.value,
declarations: {
...rule.value.declarations,
declarations: prefixedDeclarations
}
}
};
}
return rule;
}
}
};
// Compose all visitors
const fullVisitor = composeVisitors([assetVisitor, colorVisitor, legacyVisitor]);
// Apply comprehensive transformations
const result = transform({
filename: "app.css",
code: new TextEncoder().encode(`
.hero {
display: flex;
align-items: center;
background: url('./hero-bg.jpg');
color: #ff0000;
}
.button {
background: #00ff00;
justify-content: center;
}
`),
visitor: fullVisitor,
targets: { ie: 11 << 16 },
minify: true
});
console.log(new TextDecoder().decode(result.code));
// Output includes CDN URLs, brand colors, and IE-compatible flexbox propertiesVisit CSS environment variables like env(safe-area-inset-top) for custom processing and polyfills.
type EnvironmentVariableVisitor = (env: EnvironmentVariable) => TokenReturnValue;
type EnvironmentVariableVisitors = {
[name: string]: EnvironmentVariableVisitor;
};Usage Examples:
// Environment variable transformations
const envVisitor = {
EnvironmentVariable: {
// Handle safe area insets for iOS
'safe-area-inset-top'(env) {
// Provide fallback value for browsers that don't support env()
return { raw: 'max(env(safe-area-inset-top), 20px)' };
},
'safe-area-inset-bottom'(env) {
return { raw: 'max(env(safe-area-inset-bottom), 20px)' };
},
// Handle custom environment variables
'keyboard-height'(env) {
// Polyfill custom env() variables
return { raw: 'var(--keyboard-height, 0px)' };
}
},
// Generic environment variable handler
EnvironmentVariable(env) {
console.log(`Processing env variable: ${env.name}`);
// Add debug information or logging
return env;
}
};
const result = transform({
filename: "mobile.css",
code: new TextEncoder().encode(`
.safe-area {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
margin-bottom: env(keyboard-height);
}
`),
visitor: envVisitor,
minify: true
});Lightning CSS provides detailed type mappings for rule and declaration visitors that enable precise targeting of specific CSS constructs.
// Rule type mapping for maximum specificity
type MappedRuleVisitors = {
[Name in Exclude<Rule['type'], 'unknown' | 'custom'>]?: RuleVisitor<RequiredValue<FindByType<Rule, Name>>>;
}
// Declaration type mapping for property-specific handling
type MappedDeclarationVisitors = {
[Name in Exclude<Declaration['property'], 'unparsed' | 'custom'>]?: DeclarationVisitor<FindProperty<Declaration, Name> | FindProperty<Declaration, 'unparsed'>>;
}
// Unknown rule visitors for handling non-standard at-rules
type UnknownVisitors<T> = {
[name: string]: RuleVisitor<T>;
}
// Custom rule visitors for user-defined at-rules
type CustomVisitors<T extends CustomAtRules> = {
[Name in keyof T]?: RuleVisitor<CustomAtRule<Name, T[Name]>>;
};Usage Examples:
// Type-safe rule handling with comprehensive coverage
const advancedVisitor = {
Rule: {
// Type-safe media rule handling
media(rule) {
// rule is automatically typed as MediaRule
if (rule.value.query.mediaType === 'print') {
// Remove print-specific rules in web builds
return void 0;
}
return rule;
},
// Type-safe keyframes handling
keyframes(rule) {
// rule is automatically typed as KeyframesRule
const name = rule.value.name;
if (name.startsWith('legacy-')) {
// Rename legacy animations
return {
...rule,
value: {
...rule.value,
name: name.replace('legacy-', 'modern-')
}
};
}
return rule;
},
// Handle unknown at-rules
unknown: {
'custom-layout'(rule) {
// Convert custom at-rule to standard CSS
console.log('Converting custom layout rule');
return { raw: `/* Converted: ${rule.prelude} */` };
}
}
}
};