CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-highlight-js

Syntax highlighting with language autodetection for over 190 programming languages and markup formats.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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

docs

configuration-modes.md

core-highlighting.md

dom-integration.md

index.md

language-management.md

modes-utilities.md

plugin-system.md

vue-integration.md

tile.json