or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

array-diffing.mdcharacter-diffing.mdcss-diffing.mdcustom-diffing.mdformat-conversion.mdindex.mdjson-diffing.mdline-diffing.mdpatch-application.mdpatch-creation.mdpatch-utilities.mdsentence-diffing.mdword-diffing.md
tile.json

css-diffing.mddocs/

CSS Diffing

Specialized diffing for CSS content with CSS-aware tokenization. Optimizes diff results for CSS syntax by treating CSS structural elements (selectors, properties, values, and punctuation) as meaningful tokens.

Capabilities

diffCss Function

Performs CSS-aware diff between two CSS strings with specialized tokenization.

/**
 * Compare two CSS strings with CSS-aware tokenization
 * @param oldStr - Original CSS content
 * @param newStr - New CSS content to compare against
 * @param options - Configuration options
 * @returns Array of change objects representing the diff
 */
function diffCss(oldStr, newStr, options);

Usage Examples:

import { diffCss } from "diff";

// Basic CSS comparison
const result = diffCss(
  ".class { color: red; }",
  ".class { color: blue; }"
);
console.log(result);
// Shows structured diff with CSS tokens

// Complex CSS comparison
const complexResult = diffCss(`
  .header {
    background-color: #ffffff;
    padding: 20px;
    margin: 0;
  }
  
  .footer {
    background-color: #000000;
  }
`, `
  .header {
    background-color: #f0f0f0;
    padding: 20px;
    margin: 10px;
  }
  
  .sidebar {
    width: 300px;
  }
  
  .footer {
    background-color: #000000;
  }
`);

cssDiff Instance

Pre-configured Diff instance for CSS comparisons with CSS-specific tokenization.

/**
 * Pre-configured CSS diff instance
 * Uses CSS-aware tokenization for structural elements
 */
const cssDiff: Diff;

CSS Tokenization

The CSS diff tokenizes content based on CSS syntax elements:

  • Selectors: .class, #id, element, [attribute]
  • Braces: { and }
  • Properties and values: color, red, 20px
  • Punctuation: :, ;, ,
  • Whitespace: Preserved for formatting context
import { cssDiff } from "diff";

// Understanding tokenization
const tokens = cssDiff.tokenize(".class { color: red; }");
console.log("CSS tokens:", tokens);
// [".class", " ", "{", " ", "color", ":", " ", "red", ";", " ", "}"]

Advanced Usage

Style Rule Comparison

import { diffCss } from "diff";

function compareStyleRules(oldCss, newCss) {
  const changes = diffCss(oldCss, newCss);
  
  const analysis = {
    selectorsChanged: 0,
    propertiesChanged: 0,
    valuesChanged: 0,
    rulesAdded: 0,
    rulesRemoved: 0
  };
  
  changes.forEach(change => {
    if (change.added || change.removed) {
      const content = change.value;
      if (content.includes('{')) analysis.rulesAdded += change.added ? 1 : 0;
      if (content.includes('{')) analysis.rulesRemoved += change.removed ? 1 : 0;
      if (content.includes(':')) analysis.propertiesChanged++;
    }
  });
  
  return analysis;
}

// Usage
const oldStyles = `
.button { 
  background: blue; 
  padding: 10px; 
}`;

const newStyles = `
.button { 
  background: green; 
  padding: 15px; 
  border: 1px solid; 
}`;

const styleAnalysis = compareStyleRules(oldStyles, newStyles);

CSS Refactoring Detection

import { diffCss } from "diff";

function detectCssChanges(originalCss, refactoredCss) {
  const diff = diffCss(originalCss, refactoredCss);
  
  const changeTypes = {
    colorChanges: [],
    sizingChanges: [],
    layoutChanges: [],
    newRules: [],
    removedRules: []
  };
  
  diff.forEach(change => {
    if (change.added || change.removed) {
      const value = change.value.toLowerCase();
      
      if (value.includes('color') || value.includes('#') || 
          value.includes('rgb') || value.includes('rgba')) {
        changeTypes.colorChanges.push(change);
      }
      
      if (value.includes('px') || value.includes('em') || 
          value.includes('rem') || value.includes('%')) {
        changeTypes.sizingChanges.push(change);
      }
      
      if (value.includes('position') || value.includes('display') || 
          value.includes('flex') || value.includes('grid')) {
        changeTypes.layoutChanges.push(change);
      }
    }
  });
  
  return changeTypes;
}

Stylesheet Migration

import { diffCss } from "diff";

function generateMigrationReport(oldStylesheet, newStylesheet) {
  const changes = diffCss(oldStylesheet, newStylesheet);
  
  const report = {
    timestamp: new Date().toISOString(),
    totalChanges: changes.filter(c => c.added || c.removed).length,
    additions: changes.filter(c => c.added).map(c => c.value.trim()),
    removals: changes.filter(c => c.removed).map(c => c.value.trim()),
    affectedSelectors: new Set()
  };
  
  // Extract selectors from changes
  changes.forEach(change => {
    if (change.added || change.removed) {
      const selectorMatch = change.value.match(/([.#]?[\w-]+)\s*{/);
      if (selectorMatch) {
        report.affectedSelectors.add(selectorMatch[1]);
      }
    }
  });
  
  report.affectedSelectors = Array.from(report.affectedSelectors);
  return report;
}

CSS Validation Integration

import { diffCss } from "diff";

function validateCssChanges(oldCss, newCss) {
  const changes = diffCss(oldCss, newCss);
  const warnings = [];
  
  changes.forEach(change => {
    if (change.added) {
      const value = change.value;
      
      // Check for potential issues
      if (value.includes('!important')) {
        warnings.push({
          type: 'important_usage',
          content: value.trim(),
          message: 'New !important declaration added'
        });
      }
      
      if (value.match(/color:\s*#[\da-f]{3,6}/i)) {
        const colorMatch = value.match(/#[\da-f]{3,6}/i);
        if (colorMatch) {
          warnings.push({
            type: 'color_change',
            content: colorMatch[0],
            message: 'New color value introduced'
          });
        }
      }
    }
    
    if (change.removed) {
      // Check for potentially breaking removals
      if (change.value.includes('display') || change.value.includes('position')) {
        warnings.push({
          type: 'layout_removal',
          content: change.value.trim(),
          message: 'Layout-affecting property removed'
        });
      }
    }
  });
  
  return warnings;
}

Performance for Large Stylesheets

import { diffCss } from "diff";

function diffLargeStylesheets(css1, css2, callback) {
  // Process large CSS files asynchronously
  diffCss(css1, css2, {
    callback: callback,
    maxEditLength: 20000,  // Suitable for large CSS files
    timeout: 30000         // 30 second timeout
  });
}

// Usage
diffLargeStylesheets(largeStylesheet1, largeStylesheet2, (result) => {
  if (result) {
    const significantChanges = result.filter(change => 
      (change.added || change.removed) && 
      change.value.trim().length > 0
    );
    console.log(`Found ${significantChanges.length} significant CSS changes`);
  } else {
    console.log("Stylesheets too different to compute diff efficiently");
  }
});

Direct Instance Usage

import { cssDiff } from "diff";

// Using the pre-configured instance
const directResult = cssDiff.diff(
  ".old { color: red; }",
  ".new { color: blue; }"
);

// Access tokenization directly
const tokens = cssDiff.tokenize("body { margin: 0; padding: 0; }");
console.log("CSS structure:", tokens);

// Custom processing with the instance
const customResult = cssDiff.diff(oldCss, newCss);
const onlyMeaningfulChanges = customResult.filter(change => {
  // Filter out pure whitespace changes
  return !(change.value.trim() === '' || /^\s+$/.test(change.value));
});

Use Cases

Theme Migration

import { diffCss } from "diff";

function migrateTheme(lightTheme, darkTheme) {
  const themeDiff = diffCss(lightTheme, darkTheme);
  
  return themeDiff
    .filter(change => change.added)
    .map(change => change.value)
    .join('')
    .replace(/\s+/g, ' ')
    .trim();
}

Build Process Integration

import { diffCss } from "diff";

function cssChangeDetection(beforeBuild, afterBuild) {
  const changes = diffCss(beforeBuild, afterBuild);
  const hasSignificantChanges = changes.some(change => 
    (change.added || change.removed) && 
    !change.value.match(/^\s*$/)  // Not just whitespace
  );
  
  return {
    hasChanges: hasSignificantChanges,
    changeCount: changes.filter(c => c.added || c.removed).length,
    diff: changes
  };
}