How to use the simple-line-numbers NPM package to add line numbers to your code examples
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
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.
npm init
npm i simple-line-numbers --save
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
You should see a screen with code similar to the following:
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
<!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';
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.
A change of style
As mentioned previously, the client.js file deals with the instantiation of the simple-line-numbers package.
var SimpleLineNumbers = require('../index');
new SimpleLineNumbers ({});
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.
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.
npm i browserify -g
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]);
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:
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.