or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration-modes.mdcore-highlighting.mddom-integration.mdindex.mdlanguage-management.mdmodes-utilities.mdplugin-system.mdvue-integration.md
tile.json

plugin-system.mddocs/

Plugin System

Event-based plugin system for extending highlight.js functionality with custom processing, formatting, and integration capabilities.

Capabilities

Add Plugin

Register a new plugin to extend highlighting behavior.

/**
 * Add a plugin to the highlighter
 * @param plugin - Plugin object with event handlers
 */
function addPlugin(plugin: HLJSPlugin): void;

interface HLJSPlugin {
  /** Called before highlighting starts */
  'before:highlight'?: (context: BeforeHighlightContext) => void;
  
  /** Called after highlighting completes */
  'after:highlight'?: (result: HighlightResult) => void;
  
  /** Called before highlighting a DOM element */
  'before:highlightElement'?: (data: {el: Element, language: string}) => void;
  
  /** Called after highlighting a DOM element */
  'after:highlightElement'?: (data: {el: Element, result: HighlightResult, text: string}) => void;
  
  /** @deprecated Use 'before:highlightElement' - will be removed in v12 */
  'before:highlightBlock'?: (data: {block: Element, language: string}) => void;
  
  /** @deprecated Use 'after:highlightElement' - will be removed in v12 */
  'after:highlightBlock'?: (data: {block: Element, result: HighlightResult, text: string}) => void;
}

Usage Examples:

import hljs from 'highlight.js';

// Simple logging plugin
const loggingPlugin = {
  'before:highlight': (context) => {
    console.log('About to highlight:', context.language, context.code.slice(0, 50));
  },
  'after:highlight': (result) => {
    console.log('Highlighted as:', result.language, 'relevance:', result.relevance);
  }
};

hljs.addPlugin(loggingPlugin);

// Performance monitoring plugin
const performancePlugin = {
  'before:highlight': (context) => {
    context.startTime = performance.now();
  },
  'after:highlight': (result) => {
    const duration = performance.now() - result.startTime;
    console.log(`Highlighting took ${duration.toFixed(2)}ms`);
  }
};

hljs.addPlugin(performancePlugin);

// Code transformation plugin
const transformPlugin = {
  'before:highlight': (context) => {
    // Normalize line endings
    context.code = context.code.replace(/\r\n/g, '\n');
    
    // Remove trailing whitespace
    context.code = context.code.replace(/[ \t]+$/gm, '');
  },
  'after:highlight': (result) => {
    // Add line numbers
    const lines = result.value.split('\n');
    result.value = lines.map((line, index) => 
      `<span class="line-number">${index + 1}</span>${line}`
    ).join('\n');
  }
};

hljs.addPlugin(transformPlugin);

Remove Plugin

Unregister a previously added plugin.

/**
 * Remove a plugin from the highlighter
 * @param plugin - Plugin object to remove (must be same reference)
 */
function removePlugin(plugin: HLJSPlugin): void;

Usage Examples:

// Remove specific plugin
hljs.removePlugin(loggingPlugin);

// Conditional plugin management
let debugPlugin = null;

function enableDebugMode() {
  if (!debugPlugin) {
    debugPlugin = {
      'before:highlight': (context) => {
        console.log('Debug: highlighting', context.language);
      },
      'after:highlight': (result) => {
        if (result.errorRaised) {
          console.error('Debug: error during highlighting:', result.errorRaised);
        }
      }
    };
    hljs.addPlugin(debugPlugin);
  }
}

function disableDebugMode() {
  if (debugPlugin) {
    hljs.removePlugin(debugPlugin);
    debugPlugin = null;
  }
}

// Plugin registry for management
class PluginManager {
  constructor() {
    this.plugins = new Map();
  }
  
  register(name, plugin) {
    if (this.plugins.has(name)) {
      this.unregister(name);
    }
    this.plugins.set(name, plugin);
    hljs.addPlugin(plugin);
  }
  
  unregister(name) {
    const plugin = this.plugins.get(name);
    if (plugin) {
      hljs.removePlugin(plugin);
      this.plugins.delete(name);
    }
  }
  
  clear() {
    for (const [name, plugin] of this.plugins) {
      hljs.removePlugin(plugin);
    }
    this.plugins.clear();
  }
}

Plugin Event Details

Before Highlight Event

Triggered before the highlighting process begins.

interface BeforeHighlightContext {
  /** Code string to be highlighted */
  code: string;
  
  /** Target language for highlighting */
  language: string;
  
  /** Result object (may be undefined initially) */
  result?: HighlightResult;
}

Event Handler Examples:

const preprocessorPlugin = {
  'before:highlight': (context) => {
    // Code preprocessing
    if (context.language === 'javascript') {
      // Remove console.log statements for cleaner examples
      context.code = context.code.replace(/console\.log\([^)]*\);?\s*/g, '');
    }
    
    // Language aliasing
    if (context.language === 'js') {
      context.language = 'javascript';
    }
    
    // Code normalization
    context.code = context.code.trim();
  }
};

const securityPlugin = {
  'before:highlight': (context) => {
    // Security: check for potentially malicious patterns
    const dangerousPatterns = [
      /<script[^>]*>/i,
      /javascript:/i,
      /data:text\/html/i
    ];
    
    for (const pattern of dangerousPatterns) {
      if (pattern.test(context.code)) {
        console.warn('Potentially dangerous code detected');
        context.code = context.code.replace(pattern, '[FILTERED]');
      }
    }
  }
};

After Highlight Event

Triggered after highlighting is complete.

interface HighlightResult {
  /** Highlighted HTML output */
  value: string;
  
  /** Detected or specified language */
  language?: string;
  
  /** Confidence score */
  relevance: number;
  
  /** Whether illegal constructs were found */
  illegal: boolean;
  
  /** Any error that occurred */
  errorRaised?: Error;
  
  /** Second-best detection result (auto-detection only) */
  secondBest?: Omit<HighlightResult, 'secondBest'>;
}

Event Handler Examples:

const postProcessorPlugin = {
  'after:highlight': (result) => {
    // Add custom CSS classes
    result.value = result.value.replace(
      /<span class="hljs-keyword">/g,
      '<span class="hljs-keyword syntax-keyword">'
    );
    
    // Add data attributes
    result.value = `<div data-language="${result.language}" data-relevance="${result.relevance}">${result.value}</div>`;
  }
};

const analyticsPlugin = {
  'after:highlight': (result) => {
    // Track highlighting usage
    if (typeof gtag !== 'undefined') {
      gtag('event', 'code_highlight', {
        event_category: 'syntax_highlighting',
        event_label: result.language,
        value: result.relevance
      });
    }
    
    // Performance monitoring
    if (result.language && result.relevance < 3) {
      console.warn(`Low confidence highlighting for ${result.language}: ${result.relevance}`);
    }
  }
};

const errorHandlingPlugin = {
  'after:highlight': (result) => {
    if (result.errorRaised) {
      // Log errors to monitoring service
      console.error('Highlighting error:', {
        language: result.language,
        error: result.errorRaised.message,
        stack: result.errorRaised.stack
      });
      
      // Provide fallback
      result.value = `<pre class="error">Error highlighting ${result.language} code</pre>`;
    }
  }
};

DOM Element Events

Events for DOM-based highlighting operations.

interface ElementEventData {
  /** DOM element being highlighted */
  el: Element;
  
  /** Detected or specified language */
  language: string;
}

interface ElementResultData {
  /** DOM element that was highlighted */
  el: Element;
  
  /** Highlighting result */
  result: HighlightResult;
  
  /** Original text content */
  text: string;
}

Event Handler Examples:

const domPlugin = {
  'before:highlightElement': (data) => {
    // Add loading indicator
    data.el.classList.add('highlighting');
    
    // Store original content
    data.el.dataset.originalText = data.el.textContent;
    
    // Language detection from data attributes
    if (!data.language && data.el.dataset.language) {
      data.language = data.el.dataset.language;
    }
  },
  
  'after:highlightElement': (data) => {
    // Remove loading indicator
    data.el.classList.remove('highlighting');
    data.el.classList.add('highlighted');
    
    // Add metadata
    data.el.dataset.highlightedLanguage = data.result.language;
    data.el.dataset.relevance = data.result.relevance.toString();
    
    // Add copy button
    const copyButton = document.createElement('button');
    copyButton.textContent = 'Copy';
    copyButton.className = 'copy-code-button';
    copyButton.onclick = () => {
      navigator.clipboard.writeText(data.text);
    };
    
    const container = data.el.closest('pre') || data.el;
    container.style.position = 'relative';
    copyButton.style.position = 'absolute';
    copyButton.style.top = '5px';
    copyButton.style.right = '5px';
    container.appendChild(copyButton);
  }
};

hljs.addPlugin(domPlugin);

Common Plugin Patterns

Theming Plugin

const themingPlugin = {
  'after:highlight': (result) => {
    // Apply theme-specific transformations
    const theme = document.body.dataset.theme || 'light';
    
    if (theme === 'dark') {
      result.value = result.value.replace(
        /hljs-/g,
        'hljs-dark-'
      );
    }
  }
};

Line Numbers Plugin

const lineNumbersPlugin = {
  'after:highlightElement': (data) => {
    const lines = data.result.value.split('\n');
    const numberedLines = lines.map((line, index) => 
      `<span class="line-number" data-line="${index + 1}"></span>${line}`
    ).join('\n');
    
    data.el.innerHTML = numberedLines;
    data.el.classList.add('line-numbers');
  }
};

Language Badge Plugin

const languageBadgePlugin = {
  'after:highlightElement': (data) => {
    if (data.result.language) {
      const badge = document.createElement('span');
      badge.className = 'language-badge';
      badge.textContent = data.result.language;
      
      const container = data.el.closest('pre') || data.el;
      container.style.position = 'relative';
      badge.style.position = 'absolute';
      badge.style.top = '0';
      badge.style.right = '0';
      badge.style.padding = '2px 8px';
      badge.style.backgroundColor = '#333';
      badge.style.color = '#fff';
      badge.style.fontSize = '12px';
      
      container.appendChild(badge);
    }
  }
};

Syntax Error Detection Plugin

const syntaxErrorPlugin = {
  'after:highlight': (result) => {
    if (result.illegal && result.language) {
      console.warn(`Syntax errors detected in ${result.language} code`);
      
      // Add warning indicator
      result.value = `<div class="syntax-warning">⚠️ Syntax errors detected</div>${result.value}`;
    }
  }
};

Plugin Best Practices

  1. Keep plugins lightweight - Avoid heavy processing that slows down highlighting
  2. Handle errors gracefully - Don't let plugin errors break highlighting
  3. Use meaningful names - Choose descriptive event handler names
  4. Clean up resources - Remove event listeners and DOM elements when removing plugins
  5. Test thoroughly - Verify plugins work with different languages and edge cases
  6. Document behavior - Clearly document what your plugin does and any side effects