CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-diff

A JavaScript text diff implementation based on the Myers algorithm for comparing text at different granularities.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

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

Install with Tessl CLI

npx tessl i tessl/npm-diff

docs

array-diffing.md

character-diffing.md

css-diffing.md

custom-diffing.md

format-conversion.md

index.md

json-diffing.md

line-diffing.md

patch-application.md

patch-creation.md

patch-utilities.md

sentence-diffing.md

word-diffing.md

tile.json