CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-stylis

A lightweight CSS preprocessor that provides CSS parsing, AST manipulation, vendor prefixing, and serialization capabilities.

Pending
Overview
Eval results
Files

middleware.mddocs/

Middleware System

Pluggable transformation pipeline that allows custom CSS processing, vendor prefixing, namespacing, and other AST manipulations during serialization.

Capabilities

Middleware Function

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

Prefixer Middleware

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:

  • Flexbox: display: flex, flex-direction, justify-content, align-items, etc.
  • Grid: display: grid, grid-template-columns, grid-gap, etc.
  • Transforms: transform, transform-origin
  • Animations: animation, transition, @keyframes
  • User Interface: user-select, appearance, tab-size
  • Layout: position: sticky, writing-mode
  • Visual: mask, clip-path, filter, backdrop-filter

Usage 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.

Namespace Middleware

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:

  • Selector Scoping: Adds parent selector to scope styles
  • Global Escaping: Handles :global() selectors to bypass scoping
  • Combinator Handling: Properly processes child, adjacent, and general sibling combinators

Usage 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]));

Rulesheet Helper

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

Custom Middleware Development

Middleware Interface

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

Element Modification Patterns

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

Common Middleware Patterns

Property Transformation

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';
    });
  }
};

Selector Modification

const selectorPrefix = (element) => {
  if (element.type === 'rule') {
    element.props = element.props.map(selector => `.namespace ${selector}`);
  }
};

Conditional Processing

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 Execution Order

Middleware functions execute in the order they appear in the collection array:

  1. Pre-processing: Transform AST nodes before output generation
  2. Output Generation: Generate CSS strings (typically stringify)
  3. Post-processing: Transform generated CSS strings

Best Practices:

  • Place element modification middleware before stringify
  • Place output transformation middleware after stringify
  • Use rulesheet for final rule collection and validation
  • Keep middleware functions focused on single responsibilities

Error Handling in Middleware

Middleware 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

docs

index.md

middleware.md

parser.md

serialization.md

tokenization.md

utilities.md

tile.json