This site looks a lot better with CSS turned on. Note: IE<10 is not supported.

an article is displayed.

How to use the simple-line-numbers NPM package to add line numbers to your code examples

An example of the simple-line-numbers NPM package in use

Introduction

There are some great code highlighting tools available, and some of them come with line-numbering functionality either baked in, or else available as a plug-in. If you're already using one of these, and you're perfectly happy with its features, then you can probably flag this article as TLDR and get on with the rest of your day.

If, on the other hand, you're looking for a tool that will provide you with just that standalone line-numbering, and without the bulk of additional features that you may not need, then the simple-line-numbers NPM package might be just what you're looking for.

It's lightweight, easy to use, and requires minimal modification to your markup, so if and when you do need to upgrade to a fully featured highlighting tool, the switch should be as simple as possible.

For most of the other articles on this site (at the time of writing), I'm using simple-line-numbers in conjunction with the very minimal highlight.js tool, but for our purposes here today, I'll focus on using simple-line-numbers without anything else involved.

Goals

By the end of this article, you should be able to:

  • install the simple-line-numbers package using NPM
  • configure the simple-line-numbers package, and produce line-numbering in a simple HTML document

Prerequisites

  • an existing NodeJs / NPM installation
  • basic knowledge of front-end web development

I've tested simple-line-numbers on the latest versions of the mainstream browsers, but Internet Explorer versions 9 and earlier remain uncharted territory. If enough people are interested in the package, and they really want me to support those older browsers, then of course I'm happy to tweak it

Using the built-in test harness

I've included a handy-dandy test harness with the simple-line-numbers package, and we can use this to get up and running very quickly. The test harness is set up to run everything directly in the browser, so there's no need for any http server or complicated bundling config.

First of all, let's create a mini test project that we can use to try things out.

Create a new directory, and name it simple-line-numbers_test

Open a terminal, navigate to the new simple-line-numbers_test directory, and run the following command to create a new node project:

npm init

You'll be presented with a number of questions regarding the setup of the new project, but you can leave everything as default for our purposes here. Just keep pressing enter until the process is complete.

Ensure that a file named package.json has been created within the simple-line-numbers_test directory

Run the following command to install the simple-line-numbers package:

npm i simple-line-numbers --save

You should see that a node_modules directory has been created within the simple-line-numbers_test directory.

Navigate to simple-line-numbers_test / node_modules / simple-line-numbers / test-harness

You should see the following files:

  • bundle.js: contains the code that will actually be run in the browser. This is the file that will be linked directly from our markup (see index.html), and has been created by transpiling the simple-line-numbers package using Browserify. I'll talk more about this below.

  • client.js: contains the instantiation of simple-line-numbers. We can pass this into Browserify in order to rebuild bundle.js using additional configuration options if necessary.

  • index.html: is our markup file that also contains a reference to bundle.js

Seeing the package in action

Open simple-line-numbers_test / node_modules / simple-line-numbers / test-harness / index.html in the latest version of a mainstream web browser

You should see a screen with code similar to the following:

An example of the test harness in use

We have two lines of code, along with corresponding line-numbers.

Let's have a look at what's going on in the code.

The magic attribute

Open simple-line-numbers_test / node_modules / simple-line-numbers / test-harness / index.html in your favourite text editor.

<!DOCTYPE html>
<html lang="en">
    <head>
    </head>
    <body>
<pre><code line-numbers="1">var xx = () => 'blah';
var yy = () => 'blah some more';
</code></pre>
        <script src="bundle.js"></script>
    </body>
</html>

Have a look in the markup, and you'll see a line-numbers attribute:

<pre><code line-numbers="1">var xx = () => 'blah';

You'll probably notice that the formatting of the code within the pre tags looks a bit mashed. The pre tags instruct the browser to format the code exactly as it appears within the markup, including any indentation or carriage returns that we might use to make our markup look pretty. Unless we specifically want those things, it's best to leave them out.

simple-line-numbers searches for pre tags that contain code tags, which in turn have the line-numbers attribute, and then applies the line numbers accordingly. The value of 1 that I've provided here isn't strictly necessary, as simple-line-numbers uses 1 by default, but I've included it for completeness.

In simple-line-numbers_test / node_modules / simple-line-numbers / test-harness / index.html, try changing the value of line-numbers to a different integer. When you refresh your browser, you should notice that the line numbers now begin from this new integer instead:

Code displaying line-numbers that begin at 7

A change of style

As mentioned previously, the client.js file deals with the instantiation of the simple-line-numbers package.

Open simple-line-numbers_test / node_modules / simple-line-numbers / test-harness / client.js in your favourite text editor, and take a look at the code.

var SimpleLineNumbers = require('../index');
new SimpleLineNumbers ({});

NOTE: as we're using a test harness here, we're using require to pull the code directly from simple-line-numbers_test / node_modules / simple-line-numbers / index.js. If this were a regular project, we'd simply pull the code in from NPM, using something more like: var SimpleLineNumbers = require('simple-line-numbers');

If you read the Styling Options section of the documentation, you'll see that we can use those parentheses to pass configuration options to the constructor.

In simple-line-numbers_test / node_modules / simple-line-numbers / test-harness / client.js, edit the code as follows:

var SimpleLineNumbers = require('../index');
new SimpleLineNumbers ({
    lineNumbersStyles: {
        borderRight:'#ff0000 11px double'
    }
});

If you refresh your browser then you'll notice that absolutely nothing has happened yet. To produce changes, we must first rebuild the bundle.

Rebuilding the bundle

In the client.js code above, notice that we're using the require method.

Also, if you look at the source code for simple-line-numbers (in simple-line-numbers_test / node_modules / simple-line-numbers / index.js), you'll see that at the bottom of the file there's this line of code:

module.exports = SimpleLineNumbers;

Both require and module are designed to be run directly on NodeJs, but as simple-line-numbers is a client-side package, we'll need to transpile the code into plain Javascript so that it can run in the browser.

We can perform this transpilation using Browserify.

Run the following command to install the browserify package globally:

npm i browserify -g

Now perform the transpilation by running the following command in the test-harness directory, which should overwrite the existing native Javascript file named bundle.js:

browserify client.js -o bundle.js

If you open the file node_modules / simple-line-numbers / test-harness / bundle.js, you should see that the contents of node_modules / simple-line-numbers / index.js has been wrapped up in some pretty funky looking Javascript, along with the instantiation and configuration that we provided in node_modules / simple-line-numbers / test-harness / client.js.

Depending on which version of simple-line-numbers you're using (3.0.0 at the time of writing), you should see something like this:

(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
function SimpleLineNumbers (options) {
    var targetAttribute = options && options.targetAttribute || 'line-numbers';
    var lineNumbersWrapperClass = options && options.lineNumbersWrapperClass || 'line-numbers-wrapper';
    var lineNumbersStyles = options && options.lineNumbersStyles || { paddingRight: '1rem', borderRight: '#000 1px dashed' };
    var codeGapConfig = options && options.codeGapConfig || { value: 20, unit: 'px' };
    var defaultStartNumber = 1;
    var stylesheetOvr = options && options.stylesheetOverrides || {};

    var codeEls = document.getElementsByTagName('code');

    for (var item in codeEls) {
        if (!codeEls[item].hasAttribute || !codeEls[item].hasAttribute(targetAttribute)) continue;

        var preEl = codeEls[item].parentNode;
        if (!preEl || preEl.tagName.toLowerCase() !== 'pre') continue;

        var intStartValue = parseInt(codeEls[item].getAttribute(targetAttribute)) || defaultStartNumber;
        var nodes = codeEls[item].childNodes;
        if (nodes.length === 0) continue;

        var wrapperEl = document.createElement('span');

        wrapperEl.setAttribute('class', lineNumbersWrapperClass);
        preEl.appendChild(wrapperEl);

        for (var n = 0; n < nodes.length; n++) {
            var lineCount = (nodes[n].nodeValue.match(/\n/g) || []).length;
            if (lineCount === 0 && nodes[n].nodeValue !== '') lineCount = 1;

            if (!stylesheetOvr.lineNumbersWrapper) {
                wrapperEl.style.position = 'absolute';
                wrapperEl.style.top = 0;
                Object.assign(wrapperEl.style, lineNumbersStyles);
            }

            for (var l = 0; l < lineCount; l++) {
                var el = document.createElement('span');
                el.innerText = intStartValue + l;
                if (!stylesheetOvr.lineNumberSpans) el.style.display = 'block';
                wrapperEl.appendChild(el);
            }
        }

        
        if (!stylesheetOvr.preElement) preEl.style.position = 'relative';

        if (!stylesheetOvr.codeElement) {
            codeEls[item].style.display = 'block';
            codeEls[item].style.paddingLeft = '' + (wrapperEl.offsetWidth + codeGapConfig.value) + codeGapConfig.unit;
        }
    }
};

module.exports = SimpleLineNumbers;
},{}],2:[function(require,module,exports){
var SimpleLineNumbers = require('../index');
new SimpleLineNumbers ({
    lineNumbersStyles: {
        borderRight:'#ff0000 11px double'
    }
});
},{"../index":1}]},{},[2]);

In a real world scenario, we'd more than likely squish all that down using a minifier before it went to production, but that's beyond the scope of this article.

Notice the updated config on lines 58 - 62:

new SimpleLineNumbers ({
    lineNumbersStyles: {
        borderRight:'#ff0000 11px double'
    }
});

If you refresh your browser now, you should see the following:

Code displaying line-numbers that have a red, double border

A little bit basic, but it illustrates how we can pass a configuration object into the constructor and then rebuild so that the changes will appear in a web browser.

Summary

So that's the quick and dirty on how you can get started with simple-line-numbers. The exact implementation will differ between projects, based on build tools and production platforms, but you should now be at a stage where you can play around with the config and experiment with a few of the features.

Be sure to read the documentation if you want to get the most out of the package, as I've done my best to make the config as flexible as possible whilst keeping things reasonably simple, and don't forget to check out the source code on Github.

simple-line-numbers is my first attempt at creating an NPM package, and is still pretty new as I'm typing this. I plan to maintain the package and make further improvements so long as there's still interest in the JS community, so please keep an eye out for more updates :)

If you liked that article, then why not try:

Using Kali Linux to scan web applications on your local machine

In this article I'll show you how to use Kali Linux and OWASP's Zed Attack Proxy to test for some basic vulnerabilities in a web application that's being developed on your local machine.