DEV Community

Stefan Bauer
Stefan Bauer

Posted on

Use PrismJS for syntax highlighting in console.log in NodeJS

With Markshell I created a small tool that allows you to output Markdown files directly to the console. Why that? Right now, when you write a CLI or any console application, you like to provide some help for it. On the other hand, you also want to have proper documentation set up on Github pages or only in the Github Repo.

Markshell is precisely for that and helps and provides this opportunity and helps you to avoid writing multiple documentations.

The first version released focused more on the overall output of a Markdown file written to the console. The new version 0.0.5 now also supports source code highlighting inside the documentation. What could be a better match than to use something lightweight like the amazing PrismJS?

PrimsJS for web sites now for NodeJS

Prismjs is an excellent syntax highlighter for the web but is not explicitly made to output highlighted source code on the console. Nevertheless, the algorithm of how this tool highlights source code on a website can get used for console.log too.
To perform this transformation, the first thing to do is to install the PrismJS package.

npm install prismjs --save

To format the source code, then only needs two things.

// require prismjs
const prismjs = require('prismjs');

const language = 'javascript';

// highlight source code
const prismCode = prismjs.highlight("console.log('Hello world')", Prism.languages[language], language);

The highlight function takes three arguments. The first is the source code to highlight, the second argument is the grammar, and finally, the language.

To highlight, for example console.log('Hello world'), this is the first argument as a string. The second and third are defined by the language to use for highlighting. In the previous example, it is 'javascript'.

The result returned by the PrismJS is HTML that normally would get rendered on a web site. Since we cannot output the HTML directly to the console, the HTML needs a transformation.
The returned HTML contains only -Elements and class names that describe things like keywords, strings, comments, ... and so on.

Transform theme CSS to console color using chalks

Prismjs use for the highlighting CSS. For console.log we need a tool named Chalk to color the output.

A theme for PrismJS contains the following definitions.

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
    color: slategray;
}

.token.punctuation {
    color: #999;
}

.token.namespace {
    opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
    color: #905;
}

So a span always contains two classes—a 'token' class followed by a descriptive more class. The second defines the output colour. This CSS can get transferred in a somewhat stylesheet for JavaScipt.

const chalk = require("chalk");

var theme = {};
theme.token = {};

theme.background = chalk.bgHex("#f5dfd0");

theme.token["comment"] = theme.background.keyword('slategray');
theme.token["prolog"] = theme.background.keyword('slategray');
theme.token["doctype"] = theme.background.keyword('slategray');
theme.token["cdata"] = theme.background.keyword('slategray');

theme.token["punctuation"] = theme.background.hex("#999");

theme.token["namespace"];

theme.token["property"] = theme.background.hex('#905');
theme.token["tag"] = theme.background.hex('#905');
theme.token["constant"] = theme.background.hex('#905');
theme.token["symbol"] = theme.background.hex('#905');
theme.token["deleted"] = theme.background.hex('#905');

With this style definition gives use all needed for the console output.

Convert HTML to console.log output

Now that we have the output styles ready and the HTML output. The only thing left is to run over the HTML. NodeJS does not contain any classes to manipulate the DOM structures because the primary use is backend code.

The solution is to add another npm package that provides the HTML Document Object Model, and it is named JSDom.

const jsdom = require("jsdom");
const {
    JSDOM
} = jsdom;

First, it needs to be required by the script after that it could be used.

 // Parse source code and return HTML from PrismJS output
 const prismCode = prismjs.highlight(source, Prism.languages[language], language);

 // load HTML fragment
 const dom = JSDOM.fragment(prismCode);

var highlightedSource = parseFormatedContent(dom.childNodes, 0);

So the prismCode gets converted in a new HTML document fragment. This minimal document structure recursively parsed and replaced into Chalk wrap text junks does the following code.

const parseFormatedContent = (domElement, recLevel) = > {

    let highlightedSource = ""

    domElement.forEach((element, index) => {

            if (element.hasChildNodes()) {

                let hlCode = getHighlightToken(element.classList);
                highlightedSource += hlCode(parseFormatedContent(element.childNodes, recLevel + 1));

            } else {

                highlightedSource += element.textContent;

            }

        }

    );

    return highlightedSource;

}

SPAN that contains no child nodes gets rendered directly out; all other gets processed again no further child elements get found.
To replace and wrap the content with the correct styles the class list gets passed to another function named 'getHighlightToken'.

const getHighlightToken = (tokens) => {

    let tokenFound = null;

    for (let i = 0; i < tokens.length; i++) {

        if (themeTokenKeys.indexOf(tokens[i]) !== -1) {

            tokenFound = theme.token[tokens[i]];
            break;
        }

    }

    if (tokenFound !== null) {

        return tokenFound;

    } else {

        return (content) => {
            return content
        };

    }

}

Once a matching style definition found, it returns the chalk function that needs to wrap the inner text and controls the output.

The last thing to do is to take the overall result of the function 'parseFormatedContent' need to be printed to the console using 'console.log'.

The Result on the console

The following example shows the console outputs based on different themes.

[caption id="attachment_52901" align="aligncenter" width="900"] Console output using Okaido theme[/caption]

[caption id="attachment_52902" align="aligncenter" width="900"] Console output using Funky theme[/caption]

[caption id="attachment_52903" align="aligncenter" width="900"] Console output using Tomorrow theme[/caption]

Verdict

I love PrismJS not only on the web but also in the here presented way to output source code formatted on the console. With not much effort it allows me to highlight 253 different programming languages.

Special kudos to Lea Verou to bring this simple, lightweight tool to life.
It is more than useful on the web and in NodeJS too.

The complete code for this can be found on Github in my Markshell project

Top comments (0)