boxednode compiles Node.js from source to ship one JS file as a binary
📦 boxednode – Ship a JS file with Node.js in a box
At a glance
- What is it?
- boxednode takes a single JavaScript file and a Node.js runtime and produces one executable, by compiling Node.js from source rather than by bundling a prebuilt runtime. It supports native addons, produces binaries that can be signed and notarized on macOS, and refuses to handle more than one JavaScript file.
- Who is it for?
- Adopt boxednode when you need to ship one JavaScript program as a self contained executable and you need native addons or a macOS binary that can be signed and notarized, which the project lists as its differentiators. Skip it if your program has more than one source file, since multiple JavaScript files are explicitly not supported, or if a long compile of Node.js from source is unacceptable in your pipeline.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 106 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two inputs and one output
The pitch is stated as a recipe. Take a JavaScript file and Node.js, and pack them up as a single binary. The readme shows the whole cycle in four commands:
$ cat example.js
console.log('Hello, world!');
$ boxednode -s example.js -t example
$ ./example
Hello, world!The result is an executable that no longer needs a Node.js installation on the target machine, which is the point for a CLI tool distributed to users who do not have a runtime, for a container image that wants one less layer, or for a machine where you cannot install anything.
There is one important restriction stated in the readme under a heading that says it plainly: multiple JavaScript files are not supported. The unit of input is a single .js file, and the generated binary loads it as its entry point.
That single file restriction shapes everything else. It means the program has to be self contained before you box it, which usually means bundling the dependencies into that file first, and it means the compiled binary is as large as a Node.js runtime no matter how small the program is.
The CLI surface is small and mostly about the build
The options list is short, and most of it is about controlling the Node.js compilation rather than the JavaScript itself:
Options:
--version Show version number [boolean]
-c, --clean Clean up temporary directory after success [boolean]
-s, --source Source .js file [string] [required]
-t, --target Target executable file [string] [required]
-n, --node-version Node.js version or semver version range
[string] [default: "*"]
-C, --configure-args Extra ./configure or vcbuild arguments, comma-separated
[string]
-M, --make-args Extra make or vcbuild arguments, comma-separated[string]
--tmpdir Temporary directory for compiling Node.js source[string]
--help Show help [boolean]The two required flags are the source file and the target executable name. The node-version option accepts a specific version, a semver range or any of the aliases supported by the nv tool, and it defaults to a wildcard, which means the build picks whatever version resolves at build time. For a reproducible artifact you want to pin that, and the aliases make it easy to say something like the latest long term release without hardcoding a number.
The configure-args and make-args flags are the escape hatch for platform specific builds, and the temporary directory flag lets you keep the compiled Node.js source tree around for inspection.
Compiling Node.js from source is the expensive part
The prerequisites section says it directly: this package compiles Node.js from source, and it points to the Node.js BUILDING.md file for a complete list of tools that may be necessary.
That sentence is the practical centre of the project. You are not downloading a runtime, you are running the Node.js build on your machine, which means a C++ toolchain, the build system Node expects, and enough time to compile Node. The configure and make arguments exist because that build has knobs that occasionally need turning, and vcbuild is mentioned in the same option help because the Windows path differs.
The consequence for a release pipeline is a slow, machine dependent build step. The clean option removes the temporary directory afterwards, and tmpdir lets you point the build somewhere with room, which hints at how large the intermediate tree is.
The readme also explains why this route was chosen. Among the reasons given are that it works for Node.js v20.19.5 and above without being tied to specific versions, that it uses only officially supported, stable Node.js APIs, that the binaries are not bloated with extra features, that they can be signed and notarized on macOS, and that native addons can be linked in. The package metadata requires Node 20.19.5 or later on the build machine as well.
The programmatic API exposes the same knobs as typed options
The library exports a single function, compileJSFileAsBinary, taking an options object of type CompilationOptions. The type definition is the clearest specification of what the tool does:
type CompilationOptions = {
// Single Node.js version, semver range or shorthand alias to pick from
nodeVersionRange: string;
// Optional temporary directory for storing and compiling Node.js source
tmpdir?: string;
// A single .js file that serves as the entry point for the generated binary
sourceFile: string;
// The file path to the target binary
targetFile: string;Further down the type there are options for extra arguments passed to ./configure or vcbuild and to make or vcbuild, an env field whose default is to inherit the environment variables, a clean flag that removes the temporary directory when the build is done, a preCompileHook that runs just before the compile step, and a namespace option that controls the entry point target name. The namespace default is the basename of the source file, so a file at /path/bar.js is loadable as require("bar/bar") in the generated binary.
The preCompileHook is the interesting one for a build pipeline, since it is a deliberate seam where a caller can act on the Node.js source tree before compilation starts.
Native addons need a path and a matching regexp
Supporting native addons is one of the reasons the project gives for its approach, and the type definitions show the shape of that support.
An AddonConfig has a path, described as the root directory of the target addon, meaning the directory containing a binding.gyp file, and a requireRegexp. The regexp is matched against require() calls from the main file, and a matching call returns the linked binding. The readme is careful about the distinction: this is not the same as require(path), which is usually a JavaScript wrapper around the binding.
There is a related option, enableBindingsPatch, for addons that use the bindings npm package, which would otherwise not work in a single binary model. It is enabled by default when addons are specified and disabled otherwise, and the comment notes that enabling it makes fs.accessSync('/node_modules') not throw.
The trade-off is that native addons are the part of this approach that most couples your build to the target platform. A binding compiled on one operating system and architecture is not portable, and the resulting binary is no longer a single artefact you can ship everywhere.
Environment variables cover the CI case
Two environment variables exist for builds that cannot pass options directly. The BOXEDNODE_CONFIGURE_ARGS variable is read as a comma-separated list of strings and added to configureArgs, and BOXEDNODE_MAKE_ARGS does the same for makeArgs.
That is a small feature with a clear purpose. Build arguments have to come from somewhere on a continuous integration machine, and reading them from the environment means a CI configuration can inject a platform specific configure flag without a wrapper script that rewrites the command line.
The remaining options in the type cover Windows executable metadata, which is currently only used on Windows. The name and description set the Windows internal name, product name and file description, the version sets the file version and product version, the manufacturer sets the company name, the copyright sets the legal copyright, and the icon field provides the path to an .ico file for the executable. Those fields exist because a signed, branded Windows binary is a requirement for distribution, and the same motivation explains the macOS signing and notarisation claim in the readme.
Releasing and running the project
The readme documents the release procedure as a single command to run in main:
npm version [patch|minor|major] && npm it & npm publish && git push origin main --tagsThat is a compact release script that bumps the version, publishes to npm and pushes the tags. It also shows a practical detail, that the publish step depends on a local install having happened.
The package is called boxednode, the current version in package.json is 3.0.1, the main entry is lib/index.js, and the executable is exposed through the bin field as boxednode. The exports map offers a CommonJS require path and an ESM import path through a generated wrapper, and the build script creates that wrapper with gen-esm-wrapper after compiling the TypeScript.
The repository layout matches a project with real test infrastructure. There is a test/ directory, mocha with ts-node for the tests, nyc for coverage, and an Evergreen configuration in .evergreen.yml for continuous integration, plus an .eslintrc and a tsconfig.json. The test script runs the linter, then the build, then the CI test run, which is a stricter chain than most small tools use.
Where boxednode is the wrong choice
The clearest wrong choice is a multi file program. The readme lists multiple JavaScript files as not supported, so a project with a directory of modules has to bundle first, which moves the problem rather than solving it, and a bundle step is exactly the kind of thing the project was built to avoid.
The second wrong choice is a build pipeline that cannot tolerate compiling Node.js from source. Every build becomes a full Node.js compilation, on every platform you ship to, and the project points at Node's own BUILDING.md for the toolchain list. If your pipeline builds in a minimal container without a compiler toolchain, this will not work as written.
The third is a runtime version constraint. The package requires Node 20.19.5 or later, and the readme claims support for that version and above, so an application pinned to an older runtime line is out of scope.
The readme lists pkg and nexe as similar projects without describing them further, and the difference it claims for its own approach is the list above: compiling from source with supported stable APIs, small binaries that can be signed and notarized, and native addon support. Those claims come with the cost of the compile step, and that is the trade to weigh.
Editorial conclusion
Adopt boxednode when you need to ship one JavaScript program as a self contained executable and you need native addons or a macOS binary that can be signed and notarized, which the project lists as its differentiators. Skip it if your program has more than one source file, since multiple JavaScript files are explicitly not supported, or if a long compile of Node.js from source is unacceptable in your pipeline. Before you start, read the Node.js BUILDING.md list of required tools, confirm your build machine has them, and pin the Node.js version with the node-version option rather than letting it resolve to the newest release.
Frequently asked questions
What does boxednode do?
It takes a single JavaScript file and a Node.js runtime and packs them into one binary executable. The readme describes it as a way to create shippable binaries from a source file, and it compiles Node.js from source as part of that process.
How do I build a binary with boxednode?
Run boxednode -s example.js -t example, where the source flag takes the .js file and the target flag names the executable. The result runs on its own, without a Node.js installation on the target machine.
Can boxednode bundle multiple JavaScript files?
No. The readme lists multiple JavaScript files under a heading for what is not supported, so the entry point has to be a single .js file and any other modules have to be bundled into it before compiling.
How do I pick the Node.js version for a boxednode build?
Use the node-version option with a specific version, a semver range, or an alias supported by the nv tool, and the default is a wildcard. Extra arguments for the Node.js build itself go through the configure-args and make-args options, or the BOXEDNODE_CONFIGURE_ARGS and BOXEDNODE_MAKE_ARGS environment variables.
Does boxednode support native addons?
Yes. The AddonConfig type takes the path of an addon root containing a binding.gyp file and a regular expression matched against require() calls, and the enableBindingsPatch option covers addons that use the bindings npm package.
What are the prerequisites for building with boxednode?
The package compiles Node.js from source, and the readme points to the Node.js BUILDING.md file for the full list of tools that may be needed. The package metadata also requires Node 20.19.5 or later on the machine doing the build.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mongodb-js-boxednode)Was this useful?