如果你想要把你写的 Node 脚本发给别人使用,你可能需要指导对方下载安装 Node.js 运行环境,毕竟大多数电脑上都不会安装有 Node.js。把 Node 脚本打包为 exe 后,可以直接在没有安装 Node.js 的电脑或服务器上运行,不需要再安装 Node.js。

我这里的 Node 打包 exe 是不带 GUI 界面的命令行程序,如果需要使用界面操作只能启动一个 HTTP 服务,使用浏览器里的 HTML 和 JS 来操作。

如果你想要使用 JavaScript 开发带 GUI 界面的程序,可以使用 Electron。

Node 脚本需要有 Node.js 运行环境才能运行,把 Node 项目打包为 exe 就是把 Node.js 运行环境打包到项目中。因为程序中包含了 Node 运行环境,所以一个没有 node_modules 模块的 console.log 单文件,打包为 exe 后,文件体积也在 30M 以上。

安装

我这里使用的打包工具是 pkg,,它可以把 Node.js 项目打包为 Windows、Linux、Mac 的可执行程序。

pkg 打包的时候需要到 Node.js 的服务器(nodejs.org)下载对应版本的 Node.js,为了能顺利打包,你可能需要有一个稳定的国际互联网连接。

下面使用 npm 全局安装 pkg:

npm install pkg -g

安装完成后可以输入:

pkg -h

或输入:

pkg -v

如果能显示使用说明和版本信息就说明安装成功。

打包

创建一个 index.js 文件,随便写几行代码:

console.log('我的博客 https://www.misterma.com');

setTimeout(() => {
  console.log('完成');
}, 5000);

如果你在资源管理器里直接运行打包的 exe 文件的话,代码执行完成后命令行会直接关闭,也就是说你只能看到命令行闪一下。如果要让程序不自动关闭可以使用定时器或 readline 接收输入。

我使用的是全局安装,打包前需要先进入项目目录,输入 pkg 文件名.js ,例如:

pkg index.js

打包完成后默认会在项目目录生成三个文件,其中 win.exe 结尾的就是 Windows 的程序,其它两个是 Linux 和 Mac 的。

如果你的程序中包含第三方的 Node 模块,使用 require 引入后 pkg 也能在 node_modules 查找打包,下面是一个包含 Express 的项目:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send(`<h1>Hello</h1>`);
});

app.listen('7777', () => {
  console.log('浏览器访问 http://localhost:7777');
});

打包后运行程序,在浏览器中也能访问 HTTP 服务。

配置文件

pkg 支持很多自定义打包配置,package.json 可以作为 pkg 的配置文件,下面是一些配置说明:

入口文件和输出位置

你可以直接在 package.json 中设置 pkg 的入口文件和输出位置:

{
  "name": "express-test",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "pkg": {
    "outputPath": "dist"
  },
  "bin": "index.js"
}

上面主要看 pkgbinbin 设置的 index.js 就是 pkg 的入口文件。pkg 就是 pkg 相关的配置,outputPath 就是设置 pkg 打包完成后的存放位置。

使用了配置文件后,打包也需要使用:

pkg .

静态文件

pkg 默认只会打包 require 引入的模块,如果你的项目中还包含 HTML、CSS、图片之类的静态文件的话,pkg 是不会打包的。

下面配置 pkg 打包 public 目录里的文件:

{
  "pkg": {
    "assets": ["public/**/*"],
    "outputPath": "dist"
  },
  "bin": "index.js"
}

assets 就是静态文件打包配置,数组里的 public/**/* 就是打包 public 目录的所有文件,静态文件可以配置多个目录。

pkg 会把所有的静态文件都打包到一个 exe 程序里,运行的时候才会释放文件,程序关闭后释放的文件也会被销毁。

目标平台配置

目标平台配置可以配置集成的 Node 版本、操作系统、架构,下面是一组简单的配置:

{
  "pkg": {
    "assets": ["public/**/*"],
    "outputPath": "dist",
    "targets": ["node14-win-x64", "node14-win-arm64"] 
  },
  "bin": "index.js"
}

上面的 pkg 里的 targets 是目标平台和版本配置,targets 的配置项包含三个参数,参数之间用 - 连接,下面是用到的参数说明:

  • node14:Node.js 的版本,支持 node8、10、12、14、16、latest (最新版本)
  • win:操作系统平台,支持alpinelinuxwinmacoslinuxstaticfreebsd
  • x64:架构,支持 x64arm64armv6armv7

文件路径处理

pkg 打包的程序就像 Electron 一样,执行的时候 JS 脚本才会被释放。pkg 的 Windows 程序执行的时候会释放到 C:\snapshot\项目目录 ,程序关闭后,释放的 JS 也会被销毁。

对于打包到 exe 程序里的静态文件来说,你可以使用 __dirname 当前脚本目录来查找文件,但是如果你要查找 exe 程序的所在位置就不能使用 __dirname

要获取当前的 exe 程序位置可以使用 process.cwd() ,要获取 exe 程序目录下的其它文件可以使用 path.join(process.cwd(), '文件名')

压缩

在打包的时候加入 --compress Brotli--compress GZip 选项可以减小文件体积,使用方式如下:

pkg . --compress

或者:

pkg index.js --compress

使用压缩后,程序的启动时间可能会稍微增加。