or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcore-highlighting.mdindex.mdlanguage-management.mdmodes-patterns.mdplugin-system.mdutilities.md
tile.json

plugin-system.mddocs/

Plugin System

Plugin architecture for extending highlight.js functionality with custom processing hooks and integrations. The plugin system allows you to intercept and modify highlighting behavior at various stages of the process.

Capabilities

Add Plugin

Registers a plugin with the highlighting system. Plugins can hook into various events during the highlighting process.

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

interface HLJSPlugin {
  /** Called after code highlighting is complete */
  'after:highlight'?: (result: HighlightResult) => void;
  /** Called before code highlighting begins */
  'before:highlight'?: (context: BeforeHighlightContext) => void;
  /** Called after highlighting an HTML element */
  'after:highlightElement'?: (data: HighlightElementData) => void;
  /** Called before highlighting an HTML element */
  'before:highlightElement'?: (data: BeforeHighlightElementData) => void;
  /** Called after highlighting a block (deprecated) */
  'after:highlightBlock'?: (data: HighlightBlockData) => void;
  /** Called before highlighting a block (deprecated) */
  'before:highlightBlock'?: (data: BeforeHighlightBlockData) => void;
}

interface BeforeHighlightContext {
  /** Code string to be highlighted */
  code: string;
  /** Language name for highlighting */
  language: string;
  /** Optional existing result */
  result?: HighlightResult;
}

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

interface BeforeHighlightElementData {
  /** HTML element to be highlighted */
  el: Element;
  /** Language name for highlighting */
  language: string;
}

interface HighlightBlockData {
  /** HTML block element that was highlighted */
  block: Element;
  /** Highlighting result */
  result: HighlightResult;
  /** Original text content */
  text: string;
}

interface BeforeHighlightBlockData {
  /** HTML block element to be highlighted */
  block: Element;
  /** Language name for highlighting */
  language: string;
}

Usage Examples:

import hljs from "highlight.js";

// Basic plugin for logging
const loggingPlugin = {
  'before:highlight': (context) => {
    console.log(`Highlighting ${context.language}:`, context.code.slice(0, 50));
  },
  'after:highlight': (result) => {
    console.log(`Highlighted ${result.language} with relevance ${result.relevance}`);
  }
};

hljs.addPlugin(loggingPlugin);

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

hljs.addPlugin(performancePlugin);

Vue.js Plugin

Provides Vue.js integration through a dedicated plugin system.

/**
 * Returns Vue.js plugin for integration
 * @returns VuePlugin object with install method
 */
function vuePlugin(): VuePlugin;

interface VuePlugin {
  /** Vue.js plugin install function */
  install: (vue: any) => void;
}

Usage Examples:

import hljs from "highlight.js";
import Vue from "vue";

// Register with Vue 2
Vue.use(hljs.vuePlugin());

// Use in template
/*
<template>
  <div>
    <!-- Bind to data property -->
    <highlightjs autodetect :code="codeString" />
    
    <!-- Literal code -->
    <highlightjs language='javascript' code="console.log('Hello');" />
    
    <!-- With custom CSS classes -->
    <highlightjs 
      language='python' 
      :code="pythonCode"
      class="my-code-block" 
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      codeString: 'def hello():\n    print("Hello")',
      pythonCode: 'import os\nprint(os.getcwd())'
    };
  }
};
</script>
*/

// Vue 3 usage
import { createApp } from 'vue';
const app = createApp({});
app.use(hljs.vuePlugin());

Plugin Development

Event Lifecycle

The plugin system provides hooks at various stages of the highlighting process:

  1. before:highlight - Before any highlighting begins
  2. after:highlight - After highlighting is complete
  3. before:highlightElement - Before processing a DOM element
  4. after:highlightElement - After processing a DOM element

Custom Plugin Examples

Code Transformation Plugin

const codeTransformPlugin = {
  'before:highlight': (context) => {
    // Transform code before highlighting
    if (context.language === 'javascript') {
      // Convert arrow functions to regular functions for older browsers
      context.code = context.code.replace(
        /(\w+)\s*=>\s*/g, 
        'function($1) '
      );
    }
  }
};

hljs.addPlugin(codeTransformPlugin);

Line Numbers Plugin

const lineNumbersPlugin = {
  'after:highlightElement': (data) => {
    const lines = data.text.split('\n');
    const lineNumbers = lines.map((_, i) => 
      `<span class="line-number">${i + 1}</span>`
    ).join('\n');
    
    // Add line numbers to the element
    const lineNumbersDiv = document.createElement('div');
    lineNumbersDiv.className = 'line-numbers';
    lineNumbersDiv.innerHTML = lineNumbers;
    
    data.el.parentNode.insertBefore(lineNumbersDiv, data.el);
  }
};

hljs.addPlugin(lineNumbersPlugin);

Copy Button Plugin

const copyButtonPlugin = {
  'after:highlightElement': (data) => {
    // Add copy button to code blocks
    const button = document.createElement('button');
    button.textContent = 'Copy';
    button.className = 'copy-button';
    button.onclick = () => {
      navigator.clipboard.writeText(data.text).then(() => {
        button.textContent = 'Copied!';
        setTimeout(() => {
          button.textContent = 'Copy';
        }, 2000);
      });
    };
    
    // Add button to the parent container
    const container = data.el.parentNode;
    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': (data) => {
    if (data.result.language) {
      const badge = document.createElement('span');
      badge.textContent = data.result.language;
      badge.className = 'language-badge';
      
      const container = data.el.parentNode;
      if (container.tagName === 'PRE') {
        container.insertBefore(badge, data.el);
      }
    }
  }
};

hljs.addPlugin(languageBadgePlugin);

Statistics Plugin

class HighlightStatsPlugin {
  constructor() {
    this.stats = {
      totalHighlights: 0,
      languageCount: {},
      totalTime: 0
    };
  }

  getPlugin() {
    return {
      'before:highlight': (context) => {
        context.pluginStartTime = performance.now();
      },
      
      'after:highlight': (result) => {
        // Update statistics
        this.stats.totalHighlights++;
        
        if (result.language) {
          this.stats.languageCount[result.language] = 
            (this.stats.languageCount[result.language] || 0) + 1;
        }
        
        if (result.context && result.context.pluginStartTime) {
          this.stats.totalTime += 
            performance.now() - result.context.pluginStartTime;
        }
      }
    };
  }

  getStats() {
    return {
      ...this.stats,
      averageTime: this.stats.totalTime / this.stats.totalHighlights
    };
  }

  reset() {
    this.stats = {
      totalHighlights: 0,
      languageCount: {},
      totalTime: 0
    };
  }
}

// Usage
const statsPlugin = new HighlightStatsPlugin();
hljs.addPlugin(statsPlugin.getPlugin());

// Later, check statistics
console.log(statsPlugin.getStats());

Error Handling Plugin

const errorHandlingPlugin = {
  'before:highlight': (context) => {
    // Validate input
    if (!context.code || typeof context.code !== 'string') {
      console.warn('Invalid code input:', context.code);
      context.code = ''; // Provide fallback
    }
  },
  
  'after:highlight': (result) => {
    // Check for errors
    if (result.errorRaised) {
      console.error('Highlighting error:', result.errorRaised);
      
      // Could implement fallback highlighting
      result.value = `<pre class="hljs-error">${result.code || ''}</pre>`;
    }
    
    // Warn about low relevance
    if (result.relevance < 3) {
      console.warn(`Low relevance (${result.relevance}) for language: ${result.language}`);
    }
  }
};

hljs.addPlugin(errorHandlingPlugin);

Content Security Policy (CSP) Plugin

const cspSafePlugin = {
  'after:highlight': (result) => {
    // Remove any inline styles that might violate CSP
    result.value = result.value.replace(/style="[^"]*"/g, '');
    
    // Log CSP violations
    if (result.value.includes('javascript:') || result.value.includes('data:')) {
      console.warn('Potential CSP violation in highlighted content');
    }
  }
};

hljs.addPlugin(cspSafePlugin);

Integration Patterns

React Integration

// React component with plugin system
import React, { useEffect, useRef } from 'react';
import hljs from 'highlight.js';

const CodeBlock = ({ code, language, plugins = [] }) => {
  const codeRef = useRef(null);

  useEffect(() => {
    // Register plugins
    plugins.forEach(plugin => hljs.addPlugin(plugin));

    // Highlight code
    if (codeRef.current) {
      hljs.highlightElement(codeRef.current);
    }

    // Cleanup plugins if needed
    return () => {
      // Plugin cleanup logic
    };
  }, [code, language, plugins]);

  return (
    <pre>
      <code ref={codeRef} className={`language-${language}`}>
        {code}
      </code>
    </pre>
  );
};

// Usage with custom plugins
const copyPlugin = {
  'after:highlightElement': (data) => {
    // Add copy functionality
  }
};

<CodeBlock 
  code="console.log('Hello');" 
  language="javascript" 
  plugins={[copyPlugin]} 
/>

Framework-Agnostic Plugin Manager

class PluginManager {
  constructor() {
    this.plugins = new Map();
    this.enabled = new Set();
  }

  register(name, plugin, options = {}) {
    if (this.plugins.has(name)) {
      console.warn(`Plugin ${name} already registered, overwriting`);
    }
    
    const wrappedPlugin = this.wrapPlugin(plugin, name, options);
    this.plugins.set(name, { original: plugin, wrapped: wrappedPlugin, options });
    hljs.addPlugin(wrappedPlugin);
    this.enabled.add(name);
    return this;
  }

  wrapPlugin(plugin, name, options) {
    const wrapped = {};
    
    // Wrap each hook with enable/disable logic
    Object.keys(plugin).forEach(hook => {
      wrapped[hook] = (...args) => {
        if (this.enabled.has(name)) {
          try {
            return plugin[hook](...args);
          } catch (error) {
            console.error(`Plugin ${name} error in ${hook}:`, error);
            if (options.failSilently !== true) {
              throw error;
            }
          }
        }
      };
    });
    
    return wrapped;
  }

  disable(name) {
    this.enabled.delete(name);
    return this;
  }

  enable(name) {
    if (this.plugins.has(name)) {
      this.enabled.add(name);
    }
    return this;
  }

  isEnabled(name) {
    return this.enabled.has(name);
  }

  list() {
    return Array.from(this.plugins.keys());
  }

  get(name) {
    return this.plugins.get(name)?.original;
  }

  getStats() {
    return {
      total: this.plugins.size,
      enabled: this.enabled.size,
      disabled: this.plugins.size - this.enabled.size,
      plugins: this.list().map(name => ({
        name,
        enabled: this.enabled.has(name),
        options: this.plugins.get(name).options
      }))
    };
  }
}

// Usage
const pluginManager = new PluginManager();
pluginManager
  .register('copy-button', copyButtonPlugin)
  .register('line-numbers', lineNumbersPlugin, { failSilently: true })
  .register('language-badge', languageBadgePlugin);

// Temporarily disable a plugin
pluginManager.disable('line-numbers');

// Check status
console.log(pluginManager.getStats());

Advanced Plugin Patterns

Conditional Plugin Execution

const conditionalPlugin = {
  'before:highlight': (context) => {
    // Only process JavaScript files
    if (context.language !== 'javascript') {
      return;
    }
    
    // Only process in development mode
    if (process.env.NODE_ENV === 'production') {
      return;
    }
    
    console.log('Processing JavaScript code in development mode');
  }
};

Plugin with Configuration

function createConfigurablePlugin(config = {}) {
  const settings = {
    prefix: 'hljs-enhanced-',
    logLevel: 'warn',
    enableMetrics: false,
    ...config
  };

  return {
    'after:highlight': (result) => {
      if (settings.enableMetrics) {
        console.log(`${settings.prefix}metrics: relevance=${result.relevance}`);
      }
      
      // Add custom CSS class
      if (result.value) {
        result.value = result.value.replace(
          /<span class="hljs-/g, 
          `<span class="${settings.prefix}hljs-`
        );
      }
    }
  };
}

// Usage
const customPlugin = createConfigurablePlugin({
  prefix: 'my-app-',
  enableMetrics: true
});

hljs.addPlugin(customPlugin);

Plugin Chain Pattern

class PluginChain {
  constructor() {
    this.beforeHighlightChain = [];
    this.afterHighlightChain = [];
  }

  addBefore(handler) {
    this.beforeHighlightChain.push(handler);
    return this;
  }

  addAfter(handler) {
    this.afterHighlightChain.push(handler);
    return this;
  }

  getPlugin() {
    return {
      'before:highlight': (context) => {
        this.beforeHighlightChain.forEach(handler => {
          try {
            handler(context);
          } catch (error) {
            console.error('Chain handler error:', error);
          }
        });
      },
      
      'after:highlight': (result) => {
        this.afterHighlightChain.forEach(handler => {
          try {
            handler(result);
          } catch (error) {
            console.error('Chain handler error:', error);
          }
        });
      }
    };
  }
}

// Usage
const chain = new PluginChain()
  .addBefore(context => console.log('Step 1: Preprocessing'))
  .addBefore(context => context.preprocessed = true)
  .addAfter(result => console.log('Step 1: Postprocessing'))
  .addAfter(result => result.postprocessed = true);

hljs.addPlugin(chain.getPlugin());

Built-in Plugins

Highlight.js includes several built-in plugins that are automatically registered and cannot be disabled:

BR Plugin (Deprecated)

Converts newlines to

<br>
tags when
useBR
configuration option is enabled. This plugin is deprecated and slated for removal in v11.

Functionality:

  • Automatically replaces
    \n
    characters with
    <br>
    tags in highlighted output
  • Only active when
    hljs.configure({useBR: true})
    is set
  • Primarily used for inline HTML contexts where newlines need to be preserved

Note: This plugin is deprecated. Modern applications should handle line breaks through CSS styling instead.

Merge HTML Plugin

Optimizes HTML output by merging adjacent identical elements to reduce DOM complexity and improve rendering performance.

Functionality:

  • Merges consecutive spans with identical CSS classes
  • Reduces the total number of DOM elements in highlighted code
  • Automatically active for all highlighting operations
  • Improves browser rendering performance for large code blocks

Example:

<!-- Before merging -->
<span class="hljs-keyword">function</span><span class="hljs-keyword"> </span><span class="hljs-title">test</span>

<!-- After merging -->
<span class="hljs-keyword">function </span><span class="hljs-title">test</span>

Tab Replace Plugin

Handles tab character replacement based on the

tabReplace
configuration option.

Functionality:

  • Replaces tab characters (
    \t
    ) with configured replacement string
  • Only active when
    hljs.configure({tabReplace: 'replacement'})
    is set
  • Commonly used to convert tabs to spaces for consistent display
  • Automatically active for all highlighting operations when configured

Usage Example:

// Configure tab replacement
hljs.configure({tabReplace: '    '}); // Replace tabs with 4 spaces

// All highlighting operations will now replace tabs
const result = hljs.highlight('function\ttest()', {language: 'javascript'});
// Tabs in the code will be replaced with 4 spaces

These built-in plugins are automatically active and don't require manual registration. They operate at the core level and cannot be disabled through the public API.