nss-run: A new simple build tool for Node.js

Not so simple run (nss-run) is the logic evolution of complex build tools like grunt and gulp as well as minimalistic build tools like runjs.

What is wrong with with grunt and gulp?

Like me a lot of you most likely started out with grunt as it was a simple to configure build tool with a lot available tasks. But then gulp entered the scene and most people migrated to use gulp. Gulp allowed us to configure custom goals and create more complex workflows with task dependencies. Even so gulp claims to not require special plugins the truth is to get all your favorite tools up and running you are still required to embed small gulp wrappers for each tool.

Looking at runjs

Runjs goes a complete different route. Instead of providing any special build commands it just has a simple object you use to define tasks and a method where you can easy executed any shell command (or write some custom Javascript) to create your build pipeline. The benefits of that are clear. Instead of having to wait until a plugin is written/updated to use the newest build library you can just call the exposed command line tool (or include the Javascript API) and therefor you are always able to use the latest version.

So why not runjs?

Even so runjs already goes into exactly the right direction. But the simplicity of this build tool has some big limitations in terms of using it as a build tool for big projects. For example splitting of your build file into multiple smaller task files is very hard. When I converted one of my projects to runjs I created a custom task runner to solve that:

import requireDir from "require-dir";

const tasks = requireDir(`${__dirname}/tasks`, { recurse: true });

let runTasks = {};
Object.keys(tasks).forEach((file) => {
    // Ignore all files that start with _
    if (file.startsWith("_")) {
        return;
    }

    // Check if default is defined
    let taskDef = tasks[file];
    if (typeof taskDef.default !== "undefined") {
        taskDef = taskDef.default;
    }

    // Check if taskDef is a function
    if (typeof taskDef === "function") {
        taskDef = taskDef(runTasks);
    }

    // Merge tasks into big task object
    runTasks = Object.assign(runTasks, taskDef);
});

export default runTasks;

But this is a very ugly workaround for something that a proper build tool should deliver out of the box. Or at least that a good build tool help you doing in a very simple and convenient way.

Meet nss-run

Thats when I decided to write my own build tool. It is a combination of task definitions alla gulp and simplicity of running commands like runjs. You can download nss-run from npm or github. Another big advantage is you can enable/disable babel for running your tasks thru the run file name (similar to gulp) instead of auto including babel-require every time. You might still use babel to compile your project but are already using a new enough NodeJS version to execute your build tasks without the need of babel. So this will speed up your build task startup quiet a lot.

Take a look at this example run-file:

import { task, taskGroup, runTask, run } from "nss-run";

task("create:component", function (name) {
    console.log("Component with name " + name + " created");
});

task("lint", function () {
    runTask("lint:test");
    runTask("lint:source");
}, { description: "Run eslint on the project" });

taskGroup("lint:test", ["lint:test:unit", "lint:test:integration"]);

task("lint:test:unit", function () {
    run("eslint ./test/unit");
});
task("lint:test:integration", function () {
    run("eslint ./test/integration");
});

task("lint:source", function () {
    run("eslint ./src");
});

As you can see it allows you to simple create tasks, run other tasks as well as execute shell commands. Given this you can also easy create individual files per task.

Just create a run file with the following content

import requireDir from "require-dir";
requireDir(`${__dirname}/tasks`, { recurse: true });java

Thats it. It can be so simple. For a more complex build setup feel free to download the build definition I use for one of my internal projects: nss-runfile.babel.js

Outlook

As you can see this project is still very new. I mainly created it for my own Javascript projects (where I will actively using this) and so far I am very happy. It took me less then one hour to convert a complex project from gulp to nss-run.

One of the main features I am thinking of adding at the moment is support for collecting user input (simple requesting a text input from the user) as well as adding native watch support instead of having to install a separate program to execute tasks in a watch mode (in my opinion watching for changes and executing a task is a need pretty much every good build tool needs).

Let me know what you think about this. I would appreciate if you give nss-run a try and let me know what I can improve. The README on the github page also contains a lot more information about the API that might come in handy for your own tasks.

Florian Krauthan

Technology nerd, security advocate, writer and professional software developer