I'm using node.js to generate static html files from code, formatting them with prismjs. Within my app, I do not have access to an HTML renderer that supports Javascript (I'm using 'htmllite'). So I need to be able to generate HTML that does not require Javascript.
const Prism = require('prismjs');
const loadLanguages = require('prismjs/components/');
loadLanguages(['csharp']);
const code = '<a bunch of C# code>';
const html = Prism.highlight(code, Prism.languages.csharp, 'csharp');
This works great. But I want to use the line-numbers
plugin and don't see how to make it work. My <pre>
has the line-numbers
class, and I get a bigger left margin, but no line numbers.
PrismJS needs DOM for most plugins to work. After looking at the code inside plugins/line-numbers/prism-line-numbers.js#L109, we can see that the line numbers is just a span
element with class="line-numbers-rows"
that it contains an empty span
for each line. We can emulate this behavior without DOM by just using the same regular expression that prism-line-numbers
uses to get the lines number and then compose a string that has the html code of the span.line-numbers-rows
and add an empty string <span></span>
for each line.
Prism.highlight
runs only 2 hooks, before-tokenize
and after-tokenize
. We'll use after-tokenize
to compose a lineNumbersWrapper
string that contains the span.line-numbers-rows
element and the empty span
line elements:
const Prism = require('prismjs');
const loadLanguages = require('prismjs/components/');
loadLanguages(['csharp']);
const code = `Console.WriteLine();
Console.WriteLine("Demo: Prism line-numbers plugin with nodejs");`;
// https://github.com/PrismJS/prism/blob/master/plugins/line-numbers/prism-line-numbers.js#L109
var NEW_LINE_EXP = /\n(?!$)/g;
var lineNumbersWrapper;
Prism.hooks.add('after-tokenize', function (env) {
var match = env.code.match(NEW_LINE_EXP);
var linesNum = match ? match.length + 1 : 1;
var lines = new Array(linesNum + 1).join('<span></span>');
lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
});
const formated = Prism.highlight(code, Prism.languages.csharp, 'csharp');
const html = formated + lineNumbersWrapper;
console.log(html);
This will output:
Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Demo: Generate invalid numbers"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span>
which has span.line-numbers-rows
at the end:
<span aria-hidden="true" class="line-numbers-rows">
<span></span>
<span></span>
</span>
Now if we use that output in a pre.language-csharp.line-numbers code.language-csharp
element, we'll get the proper line numbers result. Check this Codepen that has only themes/prism.css
and plugins/line-numbers/prism-line-numbers.css
and properly displays line numbers with the above outputted code.
Note that each line (except the first one) has to be markup intended for the code to appear properly and that's because we're inside a pre.code
block, but I guess you already know that.
UPDATE
In case you don't rely on CSS and you want just a line number before each line, then you can add one by splitting all the lines and add each index + 1
with a space padding at the start using padStart
:
const Prism = require('prismjs');
const loadLanguages = require('prismjs/components/');
loadLanguages(['csharp']);
const code = `Console.WriteLine();
Console.WriteLine("Demo: Prism line-numbers plugin with nodejs");`;
const formated = Prism.highlight(code, Prism.languages.csharp, 'csharp');
const html = formated
.split('\n')
.map((line, num) => `${(num + 1).toString().padStart(4, ' ')}. ${line}`)
.join('\n');
console.log(html);
Will output:
1. Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
2. Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Demo: Prism line-numbers plugin with nodejs"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
I had a React website with code snippets, and I used prismjs node module like that:
SourceCode.js
import * as Prism from "prismjs";
export default function SourceCode(props) {
return (
<div>
<div style={{ maxWidth: 900 }}>
<pre className="language-javascript" style={{ backgroundColor: "#272822", fontSize: "0.8em" }}>
<code
style={{ fontFamily: "Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace" }}
dangerouslySetInnerHTML={{
__html: Prism.highlight(props.code, Prism.languages.javascript, "javascript"),
}}
/>
</pre>
</div>
</div>
);
};
Then I decided to add line-numbers plugin and I had hard time figuring out how to make it work with Node.js and React. The problem was that line-numbers used DOM, and one does not simply use DOM in Node.js.
What I finally did. I uninstalled prismjs module and did it in an old fashion way :).
index.html
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<title>SciChart Web Demo</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.21.0/themes/prism-okaidia.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.21.0/plugins/line-numbers/prism-line-numbers.min.css" />
<script async type="text/javascript" src="bundle.js"></script>
</head>
<body>
<div id="react-root"></div>
<script>
window.Prism = window.Prism || {};
Prism.manual = true;
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.21.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.21.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
</body>
</html>
SourceCode.js
import * as React from "react";
export default function SourceCode(props) {
React.useEffect(() => {
window.Prism.highlightAll();
}, []);
return (
<div>
<div style={{ maxWidth: 900 }}>
<pre
className="language-javascript line-numbers"
style={{ backgroundColor: "#272822", fontSize: "0.8em" }}
>
<code style={{ fontFamily: "Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace" }}>
{props.code}
</code>
</pre>
</div>
</div>
);
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With