DEV Community

Cover image for 📦 Bundle Node.js code into single executable binary
Muhammad Bin Zafar
Muhammad Bin Zafar

Posted on • Edited on

📦 Bundle Node.js code into single executable binary

Node.js 🐢, the asynchronous event-driven JavaScript runtime, has unparalleled support for file-system access, among other things - opening up the door to endless possiblities! However, Node.js often loses out to other runtimes/languages in cases where being able to package a single, executable application simplifies distribution and management of what needs to be delivered.

While there are components/approaches for doing this, they need to be better documented and evangelized so that this is not seen as a barrier for using Node.js in these situations. This is important to support the expansion of where/when Node.js is used in building solutions.

This article addresses 2 major concerns in the Node.js ecosystem: bundling and packaging. Let's talk about them briefly.

Bundling is the concept of merging the code, and all its dependencies into a single file. This is commonly seen for frontend development.

However, using the ESM packaging format has one advantage than CJS: tree-shaking. Tree-shaking is the concept of removing unused code from a dependency. Tools: esbuild, parcel, webpack, rollup, terser.

Packaging in Node.js is concept of creating a single executable binary, which includes the source code and the Node.js runtime. This way, Node.js will not be needed to be installed on end-user's machine.

During the process, the tool parses the source code, detects calls to require(), traverses the dependencies, and includes them into executable. Usually the source code is compiled into bytecode using the V8 engine. Tools: pkg, ncc, nexe.

esbuild to bundle

  • An extremely fast JavaScript and CSS bundler and minifier
  • Most convenient
  • Fastest in comparison
  • Support for TypeScript syntax, ESM, and CJS
  • Supports tree-shaking for ESM
  • Supports minification and source maps
# Output CommonJS bundle
$ npx esbuild index.js  --bundle --outfile=build.cjs \
--format=cjs --platform=node
Enter fullscreen mode Exit fullscreen mode
# Output ESM bundle
# Note that, you may not need the --banner flag.
# But, in some cases, require() and __dirname are needed.
$ npx esbuild index.js  --bundle --outfile=build.mjs \
--format=esm --platform=node --banner:js="
import {createRequire} from 'module';
const require = createRequire(import.meta.url);
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));"
Enter fullscreen mode Exit fullscreen mode

pkg to package

  • Package your Node.js project into an executable
  • Instantly make executables for Windows, Mac, Linux, etc
  • No need to install Node.js, or hundreds of dependencies
# Packaging tools work best with CJS. 
# These tools don't go well with ESM.

# To package into executable, just take the file outputted
# by `esbuild`, and pass it to `pkg`, and we're done!
$ npx pkg build.cjs
Enter fullscreen mode Exit fullscreen mode

This command will output 3 binary exectuable files build-linux, build-macos, and build-win.exe. You might want to run the executable file for your platform. Now you can simply distribute these files to your end-users or deploy in production - without installing Node.js or any dependencies or anything - just this one file!


Thanks for reading! Found it interesting? Give it a ❤️ or 🦄! Any topic you'd want to me cover? Let me know in the comments.

Have a great day!

Top comments (13)

Collapse
 
raguay profile image
Richard Guay

I’ve ended up using caxa. Pkg didn’t work for my setup.

Collapse
 
pandi1331 profile image
Pandiyan

Is this works for macos?

Collapse
 
raguay profile image
Richard Guay

Yes. I use it on a MacBook Pro M1 system

Collapse
 
mhm13dev profile image
Mubashir Hassan

Interesting. Thanks for sharing

Collapse
 
midnqp profile image
Muhammad Bin Zafar

Thanks @mhm13dev! I'm happy to know!

Collapse
 
bias profile image
Tobias Nickel

hmm, I always did not like that pkg only understand require statements, I never thought of using a compiler such as esbuild first.

Collapse
 
midnqp profile image
Muhammad Bin Zafar • Edited

@bias Another option would be to use TypeScript, and output CJS.

Collapse
 
othimar profile image
Pélé Oussoumanou

I tried it with a simple example. It works. Really good. Thanks!👍

Collapse
 
midnqp profile image
Muhammad Bin Zafar

That's fabulous 🎉 @othimar ! Wish you the best on your journey!

Collapse
 
tobecci profile image
Tochukwu Ojinaka

did not know this, thanks so much

Collapse
 
midnqp profile image
Muhammad Bin Zafar

🚀 @tobecci You're always welcome!

Collapse
 
chupzzz profile image
Ruslan ZH

Great idea to use Esbuild before Pkg! Because when I used Pkg straight to my project JS-file I always had issues with path and includes. Now it works from the first try! Thanks for suggestion.

Collapse
 
darkterminal profile image
Imam Ali Mustofa

I love you dude!!!! This is really help me...