A lightweight CSS preprocessor that provides CSS parsing, AST manipulation, vendor prefixing, and serialization capabilities.
—
Pluggable transformation pipeline that allows custom CSS processing, vendor prefixing, namespacing, and other AST manipulations during serialization.
Combines multiple middleware functions into a single processing pipeline that executes in sequence.
/**
* Combine multiple middleware functions into processing pipeline
* @param collection - Array of middleware functions to execute in sequence
* @returns Combined middleware function
*/
function middleware(collection: function[]): function;Usage Examples:
import { compile, serialize, middleware, prefixer, stringify } from 'stylis';
// Single middleware
const withPrefixing = serialize(
compile('div { display: flex; }'),
middleware([prefixer, stringify])
);
// Multiple middleware in sequence
const processed = serialize(
compile('.component { user-select: none; }'),
middleware([
customMiddleware,
prefixer,
namespace,
stringify
])
);
// Custom middleware example
const loggerMiddleware = (element, index, children, callback) => {
console.log(`Processing ${element.type}: ${element.value}`);
return stringify(element, index, children, callback);
};Built-in middleware that adds vendor prefixes to CSS properties and values for cross-browser compatibility.
/**
* Vendor prefixing middleware for cross-browser compatibility
* @param element - AST node to process
* @param index - Index in children array
* @param children - Sibling nodes
* @param callback - Recursive callback function
* @returns void (modifies element.return property)
*/
function prefixer(element: object, index: number, children: object[], callback: function): void;Supported Properties:
display: flex, flex-direction, justify-content, align-items, etc.display: grid, grid-template-columns, grid-gap, etc.transform, transform-originanimation, transition, @keyframesuser-select, appearance, tab-sizeposition: sticky, writing-modemask, clip-path, filter, backdrop-filterUsage Examples:
// Flexbox prefixing
serialize(compile('div { display: flex; }'), middleware([prefixer, stringify]));
// Output includes: -webkit-box, -ms-flexbox, flex
// Animation prefixing
serialize(compile('@keyframes slide { from { opacity: 0; } }'), middleware([prefixer, stringify]));
// Output includes: @-webkit-keyframes
// Pseudo-selector prefixing
serialize(compile('input::placeholder { color: gray; }'), middleware([prefixer, stringify]));
// Output includes: ::-webkit-input-placeholder, ::-moz-placeholder, etc.Scopes CSS selectors to prevent style conflicts by adding namespace prefixes or transforming selectors.
/**
* CSS selector namespacing middleware for style isolation
* @param element - AST node to process (modifies element.props for rulesets)
* @returns void (modifies element in place)
*/
function namespace(element: object): void;Features:
:global() selectors to bypass scopingUsage Examples:
// Basic namespacing (requires setup with namespace identifier)
serialize(compile('.button { color: blue; }'), middleware([namespace, stringify]));
// Global selector escaping
serialize(compile(':global(.external) { margin: 0; }'), middleware([namespace, stringify]));Creates middleware for processing complete CSS rules after they are fully processed.
/**
* Create middleware for processing complete CSS rules
* @param callback - Function to call with complete rule strings
* @returns Middleware function
*/
function rulesheet(callback: function): function;Usage Examples:
// Collect all generated CSS rules
const rules = [];
const collector = rulesheet((rule) => {
rules.push(rule);
});
serialize(compile('h1 { color: red; } p { color: blue; }'), middleware([collector, stringify]));
console.log(rules); // ['h1{color:red;}', 'p{color:blue;}']
// Rule validation middleware
const validator = rulesheet((rule) => {
if (rule.includes('!important')) {
console.warn('Important declaration found:', rule);
}
});All middleware functions follow this interface:
/**
* Middleware function interface
* @param element - Current AST node being processed
* @param index - Index of element in parent's children array
* @param children - Array of sibling elements
* @param callback - Recursive callback to process child elements
* @returns CSS string output or void (for element modification)
*/
interface MiddlewareFunction {
(element: object, index: number, children: object[], callback: function): string | void;
}Middleware can modify elements in several ways:
// Modify element properties
const propertyMiddleware = (element) => {
if (element.type === 'decl' && element.props === 'color') {
element.children = 'blue'; // Change all colors to blue
}
};
// Add to element.return for output
const outputMiddleware = (element, index, children, callback) => {
if (element.type === 'rule') {
element.return = `/* Generated rule */\n${stringify(element, index, children, callback)}`;
}
};
// Generate additional rules
const duplicatorMiddleware = (element, index, children, callback) => {
if (element.type === 'rule') {
const original = stringify(element, index, children, callback);
const duplicate = original.replace(element.props[0], element.props[0] + '-copy');
return original + duplicate;
}
return stringify(element, index, children, callback);
};const unitConverter = (element) => {
if (element.type === 'decl') {
// Convert px to rem
element.children = element.children.replace(/(\d+)px/g, (match, num) => {
return (parseInt(num) / 16) + 'rem';
});
}
};const selectorPrefix = (element) => {
if (element.type === 'rule') {
element.props = element.props.map(selector => `.namespace ${selector}`);
}
};const responsiveMiddleware = (element, index, children, callback) => {
if (element.type === 'rule' && element.props.some(prop => prop.includes('.mobile'))) {
// Wrap mobile styles in media query
return `@media (max-width: 768px) { ${stringify(element, index, children, callback)} }`;
}
return stringify(element, index, children, callback);
};Middleware functions execute in the order they appear in the collection array:
stringify)Best Practices:
stringifystringifyrulesheet for final rule collection and validationMiddleware should handle errors gracefully to avoid breaking the entire processing pipeline:
const safeMiddleware = (element, index, children, callback) => {
try {
// Middleware logic here
return customProcessing(element, index, children, callback);
} catch (error) {
console.warn('Middleware error:', error);
// Fallback to default processing
return stringify(element, index, children, callback);
}
};Install with Tessl CLI
npx tessl i tessl/npm-stylis