Split Highlight.js highlighted HTML into
individually-sanitized lines — each line is a self-contained HTML fragment
with all <span> tags properly opened and closed.
Getting per-line highlighted HTML from Highlight.js is surprisingly tricky:
Option 1 — highlight first, then split. Highlight.js wraps tokens in
<span> tags that can start on one line and end on another (e.g. a multi-line
string or block comment). Splitting the raw .value output on newlines leaves
those spans unclosed on some lines and unopened on others, producing invalid
HTML that browsers will render incorrectly.
Option 2 — split first, then highlight each line. Highlight.js is a stateful parser: it carries context (open block comments, string continuations, indentation-sensitive modes) from one line into the next. Highlighting each line in isolation breaks that context, so tokens that span multiple lines are misclassified and the result is wrong highlighting.
This package takes the only correct approach: highlight the whole input at once
and then post-process the resulting HTML, re-balancing <span> tags at every
line boundary so that each line can be rendered independently.
npm install highlightjs-split-linesconst hljs = require("highlight.js");
const hljsSplitLines = require("highlightjs-split-lines");
const code = `function greet(name) {
console.log("Hello, " + name);
}`;
const highlighted = hljs.highlight(code, { language: "javascript" }).value;
const lines = hljsSplitLines.default(highlighted);
lines.forEach((line, i) => {
console.log(`<div class="line">${line}</div>`);
});import hljs from "highlight.js";
import hljsSplitLines, { hljsSanitizeLines } from "highlightjs-split-lines";
const highlighted = hljs.highlight(code, { language: "javascript" }).value;
// Split and sanitize in one step:
const lines = hljsSplitLines(highlighted);
// Or sanitize a pre-split array:
const rawLines = highlighted.split("\n");
const sanitizedLines = hljsSanitizeLines(rawLines);The default export. Splits highlightedCode (the .value string returned by
hljs.highlight() or hljs.highlightAuto()) on any line-ending sequence
(\n, \r\n, \r, or \n\r) and returns an array of HTML strings where
every <span> tag is balanced within its line.
const lines = hljsSplitLines(hljs.highlight(code, { language: "js" }).value);
// lines[0] → '<span class="hljs-keyword">function</span> <span class="hljs-title function_">greet</span>...'
// lines[1] → ' <span class="hljs-variable language_">console</span>...'A named export for when you've already split the code yourself. Takes an
array of raw HTML strings (which may have unbalanced <span> tags) and
returns a new array where every element is a balanced HTML fragment.
Each line is prefixed with the opening tags still active from preceding lines and suffixed with the matching closing tags.
const rawLines = highlighted.split("\n");
const sanitized = hljsSanitizeLines(rawLines);Given this JavaScript:
function greet(name) {
return `Hello, ${name}!`;
}hljs.highlight() may produce output like:
<span class="hljs-keyword">function</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-string">`Hello, <span class="hljs-subst">${name}</span>!`</span>;
}Notice the hljs-string span opens on line 2 and the inner hljs-subst
span is also nested inside it. Splitting on \n would leave these spans
unclosed on some lines. hljsSplitLines ensures every line can stand alone:
<!-- line 1 -->
<span class="hljs-keyword">function</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>) {
<!-- line 2 -->
<span class="hljs-keyword">return</span> <span class="hljs-string">`Hello, <span class="hljs-subst">${name}</span>!`</span>;
<!-- line 3 -->
}This package processes the HTML string output of Highlight.js and has no
runtime dependency on it. Any version of Highlight.js that produces
<span>-based HTML output is supported.
| Node.js | highlightjs-split-lines |
|---|---|
| ≥ 18 | ✓ |
MIT © ABDK Consulting