Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static html generation with prismjs - how to enable line-numbers?

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.

like image 589
tig Avatar asked Mar 04 '23 00:03

tig


2 Answers

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>
like image 54
Christos Lytras Avatar answered Mar 05 '23 15:03

Christos Lytras


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>
    );
};
like image 27
Michael Klishevich Avatar answered Mar 05 '23 16:03

Michael Klishevich