Preloading Node module

Examples of useful functionality that can be used in Node without code changes.

If you have a working Nodejs project, its features are limited to whatever the code implements, and whatever its dependencies deliver. But Node allows you to change the runtime behavior in a limited way by preloading a specific module before running the program. Just load the additional functionality using -r option and enjoy additional behavior without any code changes. This blog post shows several useful examples of such runtime extension.

Extending built-in methods

Nodejs console object has a pair of very useful methods for measuring duration. Just start the timer for a specific label and then end it - the duration in milliseconds will be printed to the console.

index.js
1
2
3
4
console.time('foo')
foo()
console.timeEnd('foo')
// foo: 100.230ms

But what if you call foo multiple times? It would be very useful to get average / min / max and standard deviation of multiple measurements. It would be very simple to implement this change to the standard console.time method behavior; but what if you already have a working code and do not want to modify the existing code? Module preload to the rescue.

Take the existing application and preload time-stats. The console.time method will be extended but no other modifications will be necessary.

1
2
3
4
npm i time-stats
node -r time-stats ./index.js
foo: ∼ 103.988ms, min 103.988ms max 103.988ms median 103.988ms mean 103.988ms n=1
foo: ▼ 103.716ms, min 103.716ms max 103.988ms median 103.988ms mean 103.852ms n=2

This particular implementation keeps all measurements for each label and displays min, max, median and mean values. It even displays an up or down arrow to show if the latest duration is smaller or larger than the previous mean value.

Of course, if you like this behavior, you can easily include it in the "standard" source code to avoid using -r time-stats every time one starts the program. Just load the module first before any other code!

index.js
1
2
require('time-stats')
// the rest of the code

Securing the Node module cache

NodeJS is a very flexible environment. You can change methods on existing objects, or set properties for methods that do not exist yet, and then intercept them, etc. The loaded modules cache is an important part of the NodeJS runtime and an Achilles' heel of its security. Any module can modify an already loaded module, set traps for modules to be loaded or crawl through all modules to discover sensitive methods like "login" and intercept them. How can we secure the NodeJS module cache?

A preloader that "freezes" and controls the access to the module cache to the rescue! A 3rd party module you can preload controls the module.cache and makes it "read-only" for example, would disallow any "after-load" crawling and tampering. It is simple to implement using ES6 Proxies, but there are ways to implement the same features using ES5. See Playing havoc with Node module system for more details. NodeSource even offers a commercial product for controlling what modules can execute the "dangerous" calls, see Blacklisting Node.js Core Modules.

Monitoring running system

We do not have to modify the system's behavior; we can just observe it. For example, by preloading a module that instruments every module loaded later on we can turn an ordinary server into a live code coverage producer.

In the animation below I am running a Nodejs application with a preloaded module that makes a real time WebSocket server and instruments the desired source files. Then any client can connect and observe in real time executed statements.

liverage

No modifications to the existing source code were necessary; this was purely -r runtime option. See blog post Turning code coverage into live stream for more details.

Module preloading limitations

Unfortunately, the external module preloading has the same limitation as the regular require call - is is synchronous. Whatever the preloaded module does, it has to be synchronous. This eliminates an entire class of useful features, like starting a server or making a remote call.