Creating a Windows Executable File (.exe) from a Node.js app
6 February, 2022
TL;DR: @angablue/exe
Languages such as C++, C# and Python have long been used to create .exe
applications for Windows. However, this isn't commonplace for Node applications written in JavaScript. Here's how and why I combined existing tools to create .exe files for Node.js using @angablue/exe
.
What are .exe applications?
Files with the extension .exe
(i.e. ending with .exe
) are executable applications for the Windows platform. They contain a set of low-level CPU instructions thatare sequentially executed once a user opens the file by double-clicking it.
The problem with distributing JavaScript apps
I originally ran into this problem when trying to distribute my Node.js Gameflip bots to customers. I used to transpile my TypeScript code to JavaScript, then instruct customers to install Node.js and Python (remember some packages need node-gyp
to build when installing). I would then send a zipped folder, containing the app.
This caused many headaches. Sometimes Node.js wouldn't be added to the user's PATH
, meaning node index.js
would throw an error:
'node' is not recognized as an internal or external command, operable program or batch file.
Other times, install scripts would fail, they would have 2 versions of Python etc... All in all, what should be a double click to start, could take nearly an hour of setup to get going.
The solution: compiled binaries
Compiled binaries have a few advantages over zipped bundles of JavaScript.
- It's a standalone file. No need to zip anything or make sure files are in the right folder; once downloaded, you're good to go. Users don't care about how the app works, they just want your app to work. This cuts out all the associated problems with a fragmented app.
- Source code obfuscation. While JavaScript can be easily read, modified and reverse-engineered by anyone with the requisite knowledge, compiled binaries are far harder to read and edit. After being transformed to bytecode, the inner workings of your app become unobtainable knowledge to everyone bar the few modders and reverse engineers.
- System agnostic. Aside from some basic requirements such as a recent-ish install of Windows (7, 10 or 11), compiled apps will work regardless of where they are given they don't rely on external dependencies. They also have added bonus of working on any hardware, just like JavaScript running on Node.
- Smaller build sizes. Yes, that's right, smaller build sizes. Despite the fact that Node.js is bundled into the executable, the final build size for a moderately sized app ends up around 30MB - 40MB. Simply the
node_modules
folder for this project is substantially larger, sitting at 200MB, and that's not even including the size of Node.js.
This was the solution I needed. Ever since implementing the extra step to build to .exe
in my build script, I haven't once thought of going back.
Compiling to .exe
To easily compile to an .exe
, I've built an npm package, @angablue/exe
:
npm i @angablue/exe
Then create a build script, build.js
:
1// build.js
2const exe = require('@angablue/exe');
3
4const build = exe({
5 entry: './index.js',
6 out: './build/My Cool App.exe',
7 target: 'latest-win-x64'
8});
9
10build.then(() => console.log('Build completed!'));
You can customise the appearance of the final output with a few extra fields.
1// build.js
2const exe = require('@angablue/exe');
3
4const build = exe({
5 entry: './index.js',
6 out: './build/Gameflip Rocket League Items Bot.exe',
7 pkg: ['-C', 'GZip'], // Specify extra pkg arguments
8 productVersion: '2.4.2',
9 fileVersion: '2.4.2',
10 target: 'latest-win-x64',
11 icon: './assets/icon.ico', // Application icons must be in .ico format
12 properties: {
13 FileDescription: 'Gameflip Rocket League Items Bot',
14 ProductName: 'Gameflip Rocket League Items Bot',
15 LegalCopyright: 'AngaBlue https://anga.blue',
16 OriginalFilename: 'Gameflip Rocket League Items Bot.exe'
17 }
18});
19
20build.then(() => console.log('Build completed!'));
I also recommend adding this script to your package.json
:
1"scripts": {
2 "package": "node ./build.js"
3}
...or for TypeScript users:
1"scripts": {
2 "build": "tsc",
3 "package": "npm run build && node ./build.js"
4}
You can tweak these values how you like to personalise the output .exe
.
Under the hood
@angablue/exe
is simply an abstraction, combining the power of two packages; Vercel's pkg
and resedit
.
pkg
performs the bulk of the work, allowing us to compile JavaScript to bytecode and bundle assets and Node.js into one package. pkg
has many useful features and applications such as:
- Compiling cross-platform apps, with options to target many versions of Node.js as well as operating systems such as Windows, macOS and Linux.
- Bundling assets into a single package, whether they be code, images or data.
- Centralizing dependencies, reducing reliance and the need for the end-user to install programs and libraries such as Node.js and the associated modules from npm.
Using pkg
, we can actually create fully functional executables right away. However, currently pkg
has no support for changing the appearance or properties of the output file. This is where resedit
comes in.
resedit
is a tool that allows us to directly edit certain properties of any executable file. In this case, we're modifying the output of our pkg
build, adding additional info such as an author, copyright notice, version number and an icon. Using resedit
we can make our app not only function, but also look the part too.