Node.js v21.7.1 支持将 JavaScript 代码 “打包” 为单个可执行文件了,那么在此之前怎么把自己应用程序打包发布给别人呢?

我:你先去 https://nodejs.org/en 下载安装一个 nodejs,然后下载我这 app.js,最后打开你的终端或 CMD 执行 node app.js

那当然是直接 Electron 一把嗦啦,实在不行先拷贝一个 nodejs 二进制文件,然后写一个 bash 或者 bat 启动脚本又不是不能用对吧,好了博客到此结束( 🐶。

还是不扯了,让我们一起来看看 Node.js v21.7.1 实验性特性打包为单个可执行文件的流程吧。

首先得通过 sea-config 将 app.js(代码文件)和所需要的 assets 静态资源编译为单个 blob 二进制文件,sea-config 的配置目前也比较简单,在项目根目录创建一个 sea-config.json 文件,配置选项可以参考官方文档

{
  "main": "dist/index.js",
  "output": "dist/out.blob",
  "disableExperimentalSEAWarning": true, // Default: false
  "useSnapshot": false,  // Default: false
  "useCodeCache": true, // Default: false
  "assets": {  // Optional
    "a.dat": "/path/to/a.dat",
    "b.txt": "/path/to/b.txt"
  }
}

配置好之后,执行 node --experimental-sea-config sea-config.json 即可生成 dist/out.blob 文件。

打包好 blob 文件后,执行 cp $(command -v node) app 拷贝一份 node 可执行文件到输出目录,是的你没看错就是拷贝一份 node 可执行文件。

最后将 dist/out.blob 文件注入到 app 可执行文件中就可以了,这里我的运行环境为 macos,如果是 linux 或者 windows 的话可以参考官方文档

npx postject app NODE_SEA_BLOB dist/out.blob \
    --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
    --macho-segment-name NODE_SEA 

Node.js 编译为单个可执行文件-天真的小窝

如此,app 就打包成功了,但是这 helloworld 打包后的文件大小 93M 我不太好评价,不过想想也是蛮合理的比较其实就是 nodejs + 代码和静态文件大小,整个打包过程就是将代码文件注入到 nodejs 的可执行文件的过程,期待后面能使用其他 JS 运行时的集成,未来可期。

我分享一下自己的 package.json 打包配置,也许有小伙伴配置打包时可以参考一下

{
  "name": "app",
  "version": "1.0.0",
  "description": "Single executable applications",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "npm run build:ncc && npm run build:sea && npm run build:copy && npm run build:injection",
    "build:ncc": "ncc build index.ts -m -o dist",
    "build:sea": "node --experimental-sea-config sea-config.json",
    "build:copy": "node -e \"require('fs').copyFileSync(process.execPath, require('./package.json').name)\"",
    "build:injection": "npx postject $(node -e \"console.log(require('./package.json').name)\") NODE_SEA_BLOB ./dist/index.blob ^ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 ^ --macho-segment-name NODE_SEA",
    "build:remove-signature": "codesign --remove-signature $(node -e \"console.log(require('./package.json').name)\")"
  },
  "author": "PBK-B",
  "license": "ISC",
  "pkg": {
    "targets": [
      "macos"
    ]
  },
  "devDependencies": {
    "@types/node": "^20.11.27",
    "@vercel/ncc": "^0.38.1",
    "postject": "^1.0.0-alpha.6",
    "typescript": "^5.4.2"
  }
}