Pre-compiled CDN assets for highlight.js syntax highlighting with language autodetection and theme support.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Extensible plugin architecture for customizing highlighting behavior, adding functionality, and hooking into the highlighting lifecycle.
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);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');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);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);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
};
}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);
}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);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 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);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.