Compile a single executable from your Node app with Node.js 20 and ESBuild
Tags: node, esbuild, typescript
Published: 2023-05-01 16:46:46

#Introduction

Node.js 20 was released very recently. Along with several other features, you can now compile your Node.js project into a single executable that can be run in environments without Node.js installed. It’s important to note that this is still experimental and may not be suitable for use in production.

Node.js has instructions on how to set up these single executables: https://nodejs.org/api/single-executable-applications.html

Unfortunately, when compiling the executable, you will not compile dependencies into your executable. To solve this problem, we will leverage a JavaScript bundler to bundle our dependencies into one file before compiling it into our single executable.

**Prerequisites:**

- `Node.js 20`

Please note: While TypeScript is used in this article, it is not necessary.

#Putting Together our Project

First, we need a project that we will build into our executable.

We’ll first define our server.

`server.ts`

```typescript
import express from "express";
import https from "https";
import fs from "fs";
import path from "path";

export const app = express();

//Initialize Request Data Type
app.use(express.json({ limit: "10mb" }));

app.get("/", (req, res) => res.send("Hello World!!"));

const port = 3000;
app.listen(port, () => {
    console.log(`Server is live on ${port}`);
});

```

We define our package.json next:

`package.json`

```json
{
  "name": "node-executable-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "esbuild server.ts --bundle --platform=node --outfile=server-out.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "esbuild": "0.17.17",
    "express": "^4.18.2"
  },
  "devDependencies": {
    "@types/express": "^4.17.17",
    "@types/node": "^18.15.7",
    "typescript": "^5.0.2"
  }
}
```

Please note the build script. Esbuild will take our `.ts` file and bundle it with our dependencies into a single `.js` file `server-out.js`. You can actually run this file once it is created using `node server-out.js` to check if the bundling was done correctly.

We then define our tsconfig.json

`tsconfig.json`
```json
{
  "compilerOptions": {
    "target": "es2022",
    "lib": ["es2022"],
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}
```

We now define our sea-config.json file.

This is a configuration file building a blob that can be injected into the single executable application (see Generating single executable preparation blobs for details)

`sea-config.json`

```json
{
  "main": "server-out.js",
  "output": "sea-prep.blob"
}
```

Now that we have everything we need, we can begin putting together our single executable.

#Creating the Single Binary File

Begin by installing all the dependencies we’ll need by running this command:

```bash
npm install
```

Once npm install is completed, we run the command:

```bash
npm run build
```

This will create our `server-out.js` which will be our bundled file we will make into an executable.

**Note:** If you rather, you can follow the instructions from the Node.js guide starting from step 3 as the following steps will be exactly the same, located here: https://nodejs.org/api/single-executable-applications.html

Generate the blob to be injected:

```bash
node --experimental-sea-config sea-config.json 
```

Create a copy of the node executable and name it according to your needs:

```bash
cp $(command -v node) server
```

**Note:** If you are on a Linux Distro (such as Ubuntu), you can skip the next steps and move straight to running the binary.

Remove the signature of the binary:

On macOS:
```zsh
codesign --remove-signature server
```

On Windows (optional):
signtool can be used from the installed Windows SDK. If this step is skipped, ignore any signature-related warning from postject.

```powershell
signtool remove /s server
```

Inject the blob into the copied binary by running postject with the following options:

- `server` - The name of the copy of the node executable created in step 2.
- `NODE_SEA_BLOB` - The name of the resource / note / section in the binary where the contents of the blob will be stored.
sea-prep.blob - The name of the blob created in step 1.
- `--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2` - The fuse used by the Node.js project to detect if a file has been injected.
- `--macho-segment-name NODE_SEA` (only needed on macOS) - The name of the segment in the binary where the contents of the blob will be stored.

To summarize, here is the required command for each platform:

On systems other than macOS:

```shell
npx postject server NODE_SEA_BLOB sea-prep.blob \
    --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 
```

On macOS:

```shell
npx postject server NODE_SEA_BLOB sea-prep.blob \
    --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
    --macho-segment-name NODE_SEA 
```

Sign the binary:

On macOS:

```zsh
codesign --sign - server 
```

On Windows (optional):
A certificate needs to be present for this to work. However, the unsigned binary would still be runnable.

```powershell
signtool sign /fd SHA256 server 
```

Run the binary:

```bash
./server
```
You should now have a running Node server similar if you just ran node server-out.js

If you wanted to see a completed example, go here: https://github.com/chadstewart/ts-node-executable-article-example

* If you found this article interesting, please feel free to heart this article!
* If you’re interested in learning more about Front-End Engineering, follow me here on [Dev.to](www.dev.to/chad_r_stewart) and [Twitter](www.twitter.com/chad_r_stewart)
* If you’re looking for jobs, I’d highly recommend checking out @TechIsHiring on [Twitter](www.twitter.com/techishiring), [LinkedIn](www.linkedin.com/company/techishiring) or TechIsHiring's website https://www.TechIsHiring.com/ for posted jobs and other resources!
* Want to check out a curated list of jobs, job seekers and resources from TechIsHiring? Check out [TechIsHiring's Newsletter](https://newsletter.techishiring.com)