or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-highlighting.mdindex.mdlanguage-assets.mdlanguage-management.mdplugins.mdutilities.md
tile.json

plugins.mddocs/

Plugin System

Extensible plugin architecture for customizing highlighting behavior, adding functionality, and hooking into the highlighting lifecycle.

Capabilities

addPlugin()

Adds a plugin to extend highlight.js functionality.

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

interface HLJSPlugin {
  /** Called after highlighting is complete */
  'after:highlight'?: (result: HighlightResult) => void;
  /** Called before highlighting starts */
  'before:highlight'?: (context: BeforeHighlightContext) => void;
  /** Called after highlighting a DOM element */
  'after:highlightElement'?: (data: { 
    el: Element; 
    result: HighlightResult; 
    text: string 
  }) => void;
  /** Called before highlighting a DOM element */
  'before:highlightElement'?: (data: { 
    el: Element; 
    language: string 
  }) => void;
  /** @deprecated Legacy event, use 'after:highlightElement' */
  'after:highlightBlock'?: (data: { 
    block: Element; 
    result: HighlightResult; 
    text: string 
  }) => void;
  /** @deprecated Legacy event, use 'before:highlightElement' */
  'before:highlightBlock'?: (data: { 
    block: Element; 
    language: string 
  }) => void;
}

interface BeforeHighlightContext {
  /** Code string to be highlighted */
  code: string;
  /** Language name for highlighting */
  language: string;
  /** Partial result object (may be modified) */
  result?: HighlightResult;
}

Usage Examples:

import hljs from '@highlightjs/cdn-assets/es/highlight.js';

// Simple logging plugin
const loggingPlugin = {
  'before:highlight': (context) => {
    console.log(`Highlighting ${context.code.length} chars as ${context.language}`);
  },
  'after:highlight': (result) => {
    console.log(`Highlighted with relevance ${result.relevance}`);
  }
};

hljs.addPlugin(loggingPlugin);

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

hljs.addPlugin(lineNumbersPlugin);

removePlugin()

Removes a previously added plugin.

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

Usage Examples:

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

// Plugin management system
class PluginManager {
  constructor() {
    this.plugins = new Map();
  }
  
  addPlugin(name, plugin) {
    this.plugins.set(name, plugin);
    hljs.addPlugin(plugin);
  }
  
  removePlugin(name) {
    const plugin = this.plugins.get(name);
    if (plugin) {
      hljs.removePlugin(plugin);
      this.plugins.delete(name);
    }
  }
  
  hasPlugin(name) {
    return this.plugins.has(name);
  }
}

const manager = new PluginManager();
manager.addPlugin('logger', loggingPlugin);
manager.removePlugin('logger');

Plugin Examples

Copy Button Plugin

const copyButtonPlugin = {
  'after:highlightElement': ({ el }) => {
    // Create copy button
    const button = document.createElement('button');
    button.textContent = 'Copy';
    button.className = 'copy-btn';
    
    // Add click handler
    button.addEventListener('click', () => {
      const code = el.textContent;
      navigator.clipboard.writeText(code).then(() => {
        button.textContent = 'Copied!';
        setTimeout(() => button.textContent = 'Copy', 2000);
      });
    });
    
    // Add button to element container
    const container = el.parentElement;
    if (container.tagName === 'PRE') {
      container.style.position = 'relative';
      button.style.position = 'absolute';
      button.style.top = '5px';
      button.style.right = '5px';
      container.appendChild(button);
    }
  }
};

hljs.addPlugin(copyButtonPlugin);

Language Badge Plugin

const languageBadgePlugin = {
  'after:highlightElement': ({ el, result }) => {
    if (result.language) {
      const badge = document.createElement('span');
      badge.className = 'language-badge';
      badge.textContent = result.language;
      badge.style.cssText = `
        position: absolute;
        top: 0;
        right: 0;
        background: #333;
        color: white;
        padding: 2px 6px;
        font-size: 0.8em;
        border-radius: 0 0 0 4px;
      `;
      
      const container = el.parentElement;
      if (container.tagName === 'PRE') {
        container.style.position = 'relative';
        container.appendChild(badge);
      }
    }
  }
};

hljs.addPlugin(languageBadgePlugin);

Code Statistics Plugin

const statsPlugin = {
  'after:highlight': (result) => {
    // Track statistics
    if (!window.hljsStats) {
      window.hljsStats = {
        totalHighlights: 0,
        languageCount: {},
        averageRelevance: []
      };
    }
    
    const stats = window.hljsStats;
    stats.totalHighlights++;
    stats.averageRelevance.push(result.relevance);
    
    if (result.language) {
      stats.languageCount[result.language] = 
        (stats.languageCount[result.language] || 0) + 1;
    }
  }
};

hljs.addPlugin(statsPlugin);

// View statistics
function getHighlightingStats() {
  const stats = window.hljsStats;
  if (!stats) return null;
  
  return {
    total: stats.totalHighlights,
    languages: stats.languageCount,
    avgRelevance: stats.averageRelevance.reduce((a, b) => a + b, 0) / stats.averageRelevance.length
  };
}

Theme Switcher Plugin

const themeSwitcherPlugin = {
  'after:highlightElement': ({ el }) => {
    // Add theme class for styling
    el.classList.add('hljs-themed');
    
    // Add data attribute for theme switching
    el.dataset.theme = document.body.dataset.theme || 'default';
  }
};

hljs.addPlugin(themeSwitcherPlugin);

// Theme switching function
function switchTheme(themeName) {
  document.body.dataset.theme = themeName;
  
  // Update all highlighted elements
  document.querySelectorAll('.hljs-themed').forEach(el => {
    el.dataset.theme = themeName;
  });
  
  // Load theme CSS
  const existingLink = document.querySelector('link[data-hljs-theme]');
  if (existingLink) {
    existingLink.remove();
  }
  
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = `@highlightjs/cdn-assets/styles/${themeName}.css`;
  link.dataset.hljsTheme = 'true';
  document.head.appendChild(link);
}

Performance Monitoring Plugin

const performancePlugin = {
  'before:highlight': (context) => {
    context.startTime = performance.now();
  },
  'after:highlight': (result) => {
    if (result.startTime) {
      const duration = performance.now() - result.startTime;
      console.log(`Highlighting took ${duration.toFixed(2)}ms`);
      
      // Track slow highlights
      if (duration > 50) {
        console.warn(`Slow highlighting detected: ${duration.toFixed(2)}ms for ${result.language}`);
      }
    }
  }
};

hljs.addPlugin(performancePlugin);

Custom Highlighting Plugin

const customHighlightPlugin = {
  'before:highlight': (context) => {
    // Pre-process code before highlighting
    if (context.language === 'sql') {
      // Normalize SQL keywords to uppercase
      context.code = context.code.replace(/\b(select|from|where|insert|update|delete)\b/gi, 
        (match) => match.toUpperCase()
      );
    }
  },
  'after:highlight': (result) => {
    // Post-process highlighted HTML
    if (result.language === 'javascript') {
      // Add special styling for console.log
      result.value = result.value.replace(
        /(console\.log)/g, 
        '<span class="console-method">$1</span>'
      );
    }
  }
};

hljs.addPlugin(customHighlightPlugin);

Plugin Development Best Practices

// Plugin template with error handling
const robustPlugin = {
  'after:highlightElement': ({ el, result, text }) => {
    try {
      // Plugin logic here
      console.log('Processing element:', el);
    } catch (error) {
      console.error('Plugin error:', error);
      // Don't break highlighting for other plugins
    }
  }
};

// Plugin with configuration options
function createConfigurablePlugin(options = {}) {
  const config = {
    showLineNumbers: true,
    showCopyButton: true,
    ...options
  };
  
  return {
    'after:highlightElement': ({ el, result }) => {
      if (config.showLineNumbers) {
        // Add line numbers
      }
      
      if (config.showCopyButton) {
        // Add copy button
      }
    }
  };
}

// Usage
const myPlugin = createConfigurablePlugin({
  showLineNumbers: false,
  showCopyButton: true
});
hljs.addPlugin(myPlugin);

Plugin Event Order

1. before:highlightElement (for DOM highlighting)
2. before:highlight (for all highlighting)
3. [highlighting process]
4. after:highlight (for all highlighting)
5. after:highlightElement (for DOM highlighting)

Multiple plugins are called in the order they were added.