An NPM-based task runner
Builder takes your
npm tasks and makes them composable, controllable from
a single point, and flexible.
npm is fantastic for controlling dependencies, tasks (via
general project workflows. But a project-specific
package.json simply doesn't
scale when you're managing many (say 5-50) very similar repositories.
Enter Builder. Builder is "almost"
npm, but provides for off-the-shelf
"archetypes" to provide central sets of
devDependencies. The rest of this page will dive into
the details and machinations of the tool, but first here are a few of the
rough goals and motivations behind the project.
npmworkflow. So much so, that we include a section in this guide on how to abandon the use of Builder in a project and revert everything from archetypes back to vanilla
At a high level
builder is a tool for consuming
commands, providing sensible / flexible defaults, and support various scenarios
("archetypes") for your common use cases across multiple projects.
Builder is not opinionated, although archetypes are and typically dictate file structure, standard configurations, and dev workflows. Builder supports this in an agnostic way, providing essentially the following:
PATHenhancements to run, build, import from archetypes so dependencies and configurations don't have to be installed directly in a root project.
run) or multiple concurrent tasks (
... and that's about it!
To start using builder, install and save
builder and any archetypes you
intend to use. We'll use the builder-react-component archetype as an
Note: Most archetypes have an
ARCHTEYPE package and parallel
ARCHETYPE-dev NPM package. The
ARCHETYPE package contains almost
everything needed for the archtype (prod dependencies, scripts, etc.) except
devDependencies which the latter
ARCHETYPE-dev package is solely
responsible for bringing in.
For ease of use, one option is to globally install
builder and locally install
$ npm install -g builder$ npm install --save builder-react-component$ npm install --save-dev builder-react-component-dev
Like a global install of any Node.js meta / task runner tool (e.g.,
grunt) doing a global install is painful because:
... so instead, we strongly recommend a local install described in the next section!
To avoid tying yourself to a single, global version of
builder, the option
that we endorse is locally installing both
builder and archetypes:
$ npm install --save builder$ npm install --save builder-react-component$ npm install --save-dev builder-react-component-dev
However, to call
builder from the command line you will either need to
PATH variable with a shell configuration (Mac/Linux) like:
or call the longer
./node_modules/.bin/builder instead of
builder from the
builder is available, you can edit
to bind archetypes.
... and from here you are set for
builder-controlled meta goodness!
$ builder help
Run a single
$ builder run foo-task
$ builder concurrent foo-task bar-task baz-task
The underlying concept here is that
script commands simply are
script commands. Pretty much anything that you
can execute with
npm run FOO can be executed with
builder run FOO.
Builder can run 1+ tasks based out of
scripts. For a basic
"scripts":"foo": "echo FOO""bar": "echo BAR"
Builder can run these tasks individually:
$ builder run foo$ builder run bar
&& shell helpers:
$ builder run foo && builder run bar
Concurrently via the Builder built-in
$ builder concurrent foo bar
concurrent, all tasks continue running until they all complete or
any task exits with a non-zero exit code, in which case all still alive tasks
are killed and the Builder process exits with the error code.
Archetypes deal with common scenarios for your projects. Like:
Archetypes typically provide:
In most cases, you won't need to override anything. But, if you do, pick the
scripts command in the archetype you need to override and
define just that in your project's
script section. Copy
any configuration files that you need to tweak and re-define the command.
The easiest bet is to just have one archetype per project. But, multiple are
supported. In terms of
scripts tasks, we end up with the following example:
Say we have a
---archetypes:- ARCHETYPE_ONE- ARCHETYPE_TWO
The resolution order for a
script task (say,
foo) present in all three
package.json's would be the following:
ROOT/package.jsonthen the configured archetypes in reverse order:
ARCHETYPE_ONE/package.jsonfor a matching task
foo, check if it is a "passthrough" task, which means it delegates to a later instance -- basically
"foo": "builder run foo". If so, then look to next instance of task found in order above.
Archetypes use conventional
scripts task names, except for the following
These tasks are specifically actionable during the
npm lifecycle, and
consequently, the archetype mostly ignores those for installation by default,
offering them up for actual use in your project.
As an additional restriction, non-
npm:FOO-prefixed tasks with the same
FOO) may call then
npm:-prefixed task, but not the other
way around. So
// Good / OK"npm:test": "builder run test-frontend""test": "builder run npm:test"// Bad"npm:test": "builder run test""test": "builder run test-frontend"
Builder uses some magic to enhance
NODE_PATH to look in the root of your
project (normal) and in the installed modules of builder archetypes. This
latter path enhancement sometimes throws tools / libraries for a loop. We
require.resolve("LIBRARY_OR_REQUIRE_PATH") to get the
appropriate installed file path to a dependency.
This comes up in situations including:
The other thing that comes up in our Archetype configuration file is the
general requirement that builder is running from the project root, not
relative to an archetype. However, some libraries / tools will interpret
"./" as relative to the configuration file which may be in an archetype.
So, for these instances and instances where you typically use
an archetype may need to use
process.cwd() and be constrained to only
ever running from the project root. Some scenarios where the
path base is necessary include:
The execution of tasks generally must originate from Builder, because of all
of the environment enhancements it adds. So, for things that themselves exec
or spawn processes, like
concurrently, this can be a problem. Typically, you
will need to have the actual command line processes invoked by Builder.
exec under the hood with piped
typically interpret the piped environment as "doesn't support color" and
disable color. Consequently, you typically need to set a "force color"
option on your executables in
scripts commands if they exist.
exec and not
spawn or something similar that has a lot more process
control and flexibility? The answer lies in the fact that most of what Builder
consumes is shell strings to execute, like
script --foo --bar "Hi there".
Parsing these arguments into something easily consumable by
spawn and always
correct is quite challenging.
exec works easily with straight strings, and
since that is the target of
scripts commands, that is what we use for Builder.
Builder is designed to be as close to vanilla npm as possible. So, if for
example you were using the
builder-react-component archetype with a project
"scripts":"postinstall": "builder run npm:postinstall""preversion": "builder run npm:preversion""version": "builder run npm:version""test": "builder run npm:test"/* other deps */"dependencies":"builder": "v2.0.0""builder-react-component": "v0.0.5"/* other deps */"devDependencies":"builder-react-component-dev": "v0.0.5"/* other deps */
and decided to no longer use Builder, here is a rough set of steps to unpack the archetype into your project and remove all Builder dependencies:
builder-react-component). You do not need to copy over
PROJECT/package.json:scriptsthat do not begin with the
builder:prefix. You may have to manually resolve
scriptstasks of the same name.
ARCHETYPEinto the root project. For example, for
builder-react-componentyou would need to copy the
PROJECT/config(or a renamed directory).
builder run <TASK>with
npm run <TASK>
builder concurrent <TASK1> <TASK2>tasks, first install the
concurrentlypackage and then rewrite to:
concurrent 'npm run <TASK1>' 'npm run <TASK2>'
... and (with assuredly a few minor hiccups) that's about it! You are
Builder-free and back to a normal
builder project effectively starts at
v2.x.x. Prior to that Builder was
a small DOM utility that fell into disuse, so we repurposed it for a new
wonderful destiny! But, because we follow semver, that means everything starts
v2 and as a helpful tip / warning:
We'll try hard to keep it tight, but at our current velocity there are likely
to be some bumps and API changes that won't adhere strictly to semver until
things settle down in