Skip to content

Instantly share code, notes, and snippets.

@subfuzion
Last active November 7, 2024 07:53
Show Gist options
  • Save subfuzion/3e171092d7e21323ce6b6748395301db to your computer and use it in GitHub Desktop.
Save subfuzion/3e171092d7e21323ce6b6748395301db to your computer and use it in GitHub Desktop.
Node.js 21.1.0 - build single executable application

Sharing my project's build script in case it's useful to others. I'm not testing for Windows, which is why the script reports an error, but just note that emitting Windows executables is actually supported by the Node API (see docs). Feel free to use and modify.

Notes

  • Webpack is used for bundling.
  • The entry point for the webpack config for my example is ./bin/app, which was an executable Node.js script with a shebang line. Update the config to point to whatever your app's entry point is.
  • The SEA config file uses disableExperimentalSEAWarning to suppress the experimental warning displayed when running a generated executable. This didn't work for Node 20.1.0 (see issue), but it has been tested and does work for 21.1.0.
#!/bin/bash
set -eu
OS=$(uname -s)
BUILD="./.build"
DIST="./dist"
# The basename of the target executable
SEA="app"
TARGET="${DIST}/${OS}/${SEA}"
SEA_CONFIG="./sea-config.json"
BLOB="${BUILD}/sea-prep.blob"
# generate_bundler() uses webpack
WEBPACK_CONFIG="./webpack.config.js"
info() {
echo "INFO: " "$@"
}
error() {
echo "ERROR: " "$@"
exit 1
}
setup() {
info "Building ${OS} executable target"
mkdir -p "${BUILD}"
mkdir -p "${DIST}/${OS}"
generate_bundle
generate_blob
generate_executable
}
finish() {
info "Built executable: ${TARGET}"
}
generate_bundle() {
npx webpack --config "${WEBPACK_CONFIG}"
}
generate_blob() {
node --experimental-sea-config "${SEA_CONFIG}"
}
generate_executable() {
case "${OS}" in
Windows*) node -e "require('fs').copyFileSync(process.execPath, '${TARGET}')" ;;
*) cp $(command -v node) "${TARGET}" ;;
esac
}
build_sea_linux() {
setup
npx postject "${TARGET}" NODE_SEA_BLOB "${BLOB}" \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
finish
}
build_sea_darwin() {
setup
codesign --remove-signature "${TARGET}"
npx postject "${TARGET}" NODE_SEA_BLOB "${BLOB}" \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
--macho-segment-name NODE_SEA
codesign --sign - "${TARGET}"
finish
}
build_sea_windows() {
# I don't work with Windows, so this is just an initial stab based on the following
# link. Maybe it would work with WSL?
# https://nodejs.org/api/single-executable-applications.html#single-executable-applications
error "This error is to alert you that this script has not been tested for Windows and probably won't work as is"
TARGET="${TARGET}.exe"
setup
signtool remove /s "{TARGET}"
npx postject "${TARGET}" NODE_SEA_BLOB "${BLOB}" --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
codesign --sign - "${TARGET}"
finish
}
case "${OS}" in
Linux*) build_sea_linux ;;
Darwin*) build_sea_darwin ;;
Windows*) build_sea_windows ;;
*) error "Unsupported OS:" "${OS}" ;;
esac
{
"name": "app",
"scripts": {
"build": "./build",
"clean": "rimraf .build dist",
"rebuild": "npm run clean && npm run build"
},
"devDependencies": {
"rimraf": "^5.0.5",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
}
}
{
"main": ".build/bundle.js",
"output": ".build/sea-prep.blob",
"disableExperimentalSEAWarning": true
}
// webpack.config.js
const path = require('path');
module.exports = {
target: 'node',
entry: './bin/app',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '.build'),
},
mode: "production",
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment