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
# 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));"
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
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)
I’ve ended up using caxa. Pkg didn’t work for my setup.
Is this works for macos?
Yes. I use it on a MacBook Pro M1 system
Interesting. Thanks for sharing
Thanks @mhm13dev! I'm happy to know!
hmm, I always did not like that
pkg
only understand require statements, I never thought of using a compiler such as esbuild first.@bias Another option would be to use TypeScript, and output CJS.
I tried it with a simple example. It works. Really good. Thanks!👍
That's fabulous 🎉 @othimar ! Wish you the best on your journey!
did not know this, thanks so much
🚀 @tobecci You're always welcome!
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.
I love you dude!!!! This is really help me...