ncc: Node.js Compiler Collection

Today we are thrilled to introduce ncc, a simple Node.js CLI program and API for compiling Node.js projects into a single JavaScript file.
Inspired by native compiler toolchains, such as go, that export a static ELF binary file, ncc will output a self-contained script that bundles all its dependencies by leveraging webpack.

A Walkthrough

npm i -g @zeit/ncc

It will download the ncc compiler, which has been compiled with ncc itself.

Let's create a project and add a dependency:

cd myapp
npm i chalk

And populate your main file index.js:

const chalk = require("chalk");
console.log("Hello world!"));

We compile and run in one go

Or we can ncc build it for production:

We output the final version to the dist directory

ncc implements a go-style API and design philosophy. Compiling a program should be intuitive, single-command, zero-configuration.

For more usage information, run ncc help or check out the api docs.


Serverless Deployment

We wrote ncc by extracting the best practices out of our @now/node builders.

Deploying a very large dependency tree in just 950kb

The entire bundle of code actually used by our entrypoint amounts to 950kb, which considers apollo-server-express, express, graphql and all their sub-dependencies.

In contrast, node_modules alone amounts to 35 times the size of our bundle:

It is said that node_modules is one of the heaviest objects in the universe. ncc is therefore anti-gravity.

Thus, ncc makes deployment faster and more secure, by only shipping the necessary code to production.

Faster Distribution

In the early days of ncc development, it used to bundle webpack as a regular npm dependency. It eventually became good enough to self-host.

We can now compare the installation duration of the non-ncc version:

In most of our tests, ncc would take between 15 and 30 seconds to install

In stark contrast to installing the ncc version (usually 5-10x faster):

The installation performance of ncc is solely bound by your download speed

Faster Bootup

To demonstrate this, we simply time how long it takes to require("ncc") (which requires webpack, a very large amount of JavaScript).

This is how long it takes to require src/index.js, where every subsequent require invokes file-system lookups.

Just importing code can be remarkably slow in Node.js, compared to native programs

When run through ncc and minify we consistently see a 50% improvement in boot-up performance, which will only get faster.

Faster, but there is still a lot of room for improvement

Future Work

These represent active areas of work following our initial release. Contributions are welcome.


Generally speaking, ncc is already quite fast, even though no optimizations have been made.

By leveraging a global cache, we intend for re-runs and re-builds to get incrementally faster with no tradeoffs in safety.

Built-In Minification

Minification is a remarkably safe operation that has significant boot-up time and package size benefits.

It will be default off for ncc run and default on for ncc build. Developers might want to opt out of this feature when programs or dependencies.

Furthermore, ncc will favor safety over maximally optimal minification.


Stack traces that are produced by ncc run should be accurate by default. Developers should be able to opt-into emitting source maps for ncc build production outputs.

Automatic Dependency Installation

Following go's example, we intend for ncc to transparently ensure dependencies have been installed, by executing yarn install or npm install automatically.

This will help develop more confidently. For example, the following is error-prone:

git pull
node my-program

This is because after git pull the dependencies (package.json, yarn.lock, package-lock.json) might have changed.

However, the following will always be predictable and safe:

git pull
ncc run my-program
We also look forward to evaluting ncc cooperating with next-generation installation approaches like tink and yarn pnp.

Typescript Support

TypeScript is now being used by up to 50% of Node.js developers according to recent surveys.

It addresses important gaps in the Node.js ecosystem (such as static typing and JSX support). Crucially, we can add it onobtrusively by introducing support for the .ts[x] extensions:

ncc build my-app.ts

V8 Compiler Cache

In addition to minification, it's possible to get further boot-up benefits by leveraging the V8 API that emits an image of the parse and compile step.

Native Addons Support

The contract of ncc is that it yields an output directory containing one or more files. Typically it's one, but we also perform static analysis on synchronous filesystem reads.

For example, pdkfit and its dependencies read a lot of files that are bundled with their packages upon initialization:

ncc is capable of emitting dependencies, such as statically analyzable file-system lookups

We plan to use the same technique to support .node files, making ncc a complete solution that supports the vast majority of npm modules in the ecosystem out of the box.

Frequently Asked Questions

Whyncc run file.jsInstead ofnode file.js

Using ncc run is optional, but it has the following current and future benefits:

  • Fewer surprises upon ncc build. While ncc gets remarkably close to the native node semantics (with many integration tests) from time to time some modules with unusually-dynamic dependencies or filesystem reads could give it trouble.
  • TypeScript support (planned)
  • Transparent dependency installtion (planned)

How Is This Different from Webpack, Rollup, Parcel?

We wanted to focus on a development experience that maximizes productivity, followed the semantics of the Node.js platform out of the box and mirrored the conventions of other well-designed and battle-tested languages.

This means that it should do the right thing out of the box, with no extra configuration.

How Is This Different frompkg?

pkg is complimentary to ncc, by taking any JavaScript source(s) and bundling them into an executable that bundles the Node.js runtime inside. While ncc outputs scripts (text), pkg outputs executables (binary).

In some cases you will want to use them together, and in others, independently.