这个命令行界面可以将你的Node.js项目打包成可执行文件,即使在没有安装Node.js的设备上也可以运行。
英文文档
使用场景:
这些功能使得pkg成为将Node.js应用程序打包成可执行文件的有用工具,适用于各种使用场景。
npm install -g pkg
安装完成后,运行pkg --help命令(无参数)以查看选项列表:
pkg [options]
选项:
-h, --help 输出使用信息
-v, --version 输出pkg版本
-t, --targets 逗号分隔的目标列表(参见示例)
-c, --config package.json或任何包含顶级配置的json文件
--options 将v8选项嵌入可执行文件中以在其中运行
-o, --output 输出文件名或多个文件的模板
--out-path 保存输出的路径,可以是一个或多个可执行文件
-d, --debug 在打包过程中显示更多信息 [关闭]
-b, --build 不下载预构建的基本二进制文件,而是构建它们
--public 加快速度并公开顶级项目的源代码
--public-packages 强制指定的包被视为公开
--no-bytecode 跳过字节码生成,将源文件作为普通js文件包含
--no-native-build 跳过本机插件的构建
--no-signature 在macos上跳过最终可执行文件的签名
--no-dict 逗号分隔的要忽略字典的包名称列表。使用--no-dict *来禁用所有字典
-C, --compress [默认=None] 压缩算法 = Brotli或GZip
示例:
- 为Linux、macOS和Windows生成可执行文件
$ pkg index.js
- 使用当前工作目录中的package.json并遵循'bin'条目
$ pkg .
- 为特定目标机器生成可执行文件
$ pkg -t node16-win-arm64 index.js
- 为您选择的目标机器生成可执行文件
$ pkg -t node16-linux,node18-linux,node16-win index.js
- 将'--expose-gc'和'--max-heap-size=34'嵌入可执行文件中
$ pkg --options "expose-gc,max-heap-size=34" index.js
- 将packageA和packageB视为公开
$ pkg --public-packages "packageA,packageB" index.js
- 将所有包视为公开
$ pkg --public-packages "*" index.js
- 将'--expose-gc'嵌入可执行文件中
$ pkg --options expose-gc index.js
- 使用GZip减小可执行文件中打包的数据的大小
$ pkg --compress GZip index.js
你的项目的入口点是一个必需的命令行参数。它可以是:
pkg
可以同时为多个目标机器生成可执行文件。你可以通过--targets
选项指定一个逗号分隔的目标列表。一个规范的目标由3个元素组成,用破折号分隔,例如node18-macos-x64
或node14-linux-arm64
:
nodeRange
(node8
)、node10
、node12
、node14
、node16
或latest
platform
:alpine
、linux
、linuxstatic
、win
、macos
、freebsd
arch
:x64
、arm64
、armv6
、armv7
你可以省略任何元素(例如只指定node14
)。省略的元素将从当前平台或系统范围的Node.js安装中获取(其版本和架构)。还有一个别名host
,表示所有3个元素都来自当前平台/Node.js。默认情况下,目标是当前Node.js版本和架构的linux
、macos
、win
。
如果你想为不同的架构生成可执行文件,请注意,默认情况下,pkg
必须运行目标架构的可执行文件来生成字节码:
或者,使用--no-bytecode --public-packages "*" --public
禁用字节码生成。
macos-arm64
是实验性的。请注意强制的代码签名要求。最终的可执行文件必须使用macOS的codesign
工具(或Linux上的ldid
工具)进行签名(ad-hoc签名就足够了)。否则,可执行文件将被内核终止,最终用户无法允许其运行。pkg
会尝试对最终的可执行文件进行ad-hoc签名。如果需要,你可以用你自己的可信任的Apple Developer ID替换这个签名。
为了能够为所有支持的架构和平台生成可执行文件,请在配置了binfmt(QEMU仿真)和安装了ldid
的Linux主机上运行pkg
。
在打包过程中,pkg
会解析你的源代码,检测到require
调用,遍历你的项目的依赖项,并将它们包含到可执行文件中。在大多数情况下,你不需要手动指定任何内容。
然而,你的代码可能会有require(variable)
调用(即所谓的非字面量参数传递给require
),或者使用非JavaScript文件(例如视图、CSS、图像等)。
require('./build/' + cmd + '.js');
path.join(__dirname, 'views/' + viewName);
这些情况下,pkg
无法处理。因此,你必须在package.json
文件的pkg
属性中手动指定文件 - 脚本和资源。
"pkg": {
"scripts": "build/**/*.js",
"assets": "views/**/*",
"targets": [ "node14-linux-arm64" ],
"outputPath": "dist"
}
上面的示例将包含assets/
目录中的所有内容,以及build/
目录中的所有.js
文件,仅为node14-linux-arm64
构建,并将可执行文件放置在dist/
目录中。
你还可以指定多个glob模式的数组:
"assets": [ "assets/**/*", "images/**/*" ]
只需确保调用pkg package.json
或pkg .
以使用package.json
的配置。
scripts
是一个glob模式或glob模式列表。指定为脚本的文件将使用v8::ScriptCompiler
进行编译,并将其作为没有源代码的内容放入可执行文件中。它们必须符合你所目标的Node.js版本的JS标准(参见Targets),即已经进行了转译。
assets
是一个glob模式或glob模式列表。指定为资源的文件将作为原始内容打包到可执行文件中,不进行修改。JavaScript文件也可以指定为资源。它们的源代码不会被剥离,这样可以提高文件的执行性能并简化调试过程。
另请参阅在源代码中检测资源和快照文件系统。
Node.js应用程序可以使用运行时选项(属于Node.js或V8)进行调用。要列出这些选项,请输入node --help
或node --v8-options
。
你可以将这些运行时选项"烘焙"到打包的应用程序中。应用程序将始终以打开这些选项的方式运行。只需删除选项名称前面的--
即可。
你可以通过将它们连接在一个字符串中,用逗号(,
)分隔,来指定多个选项:
pkg app.js --options expose-gc
pkg app.js --options max_old_space_size=4096
pkg app.js --options max-old-space-size=1024,tls-min-v1.0,expose-gc
如果你只创建一个可执行文件,可以指定--output
选项,如果要为多个目标生成可执行文件,则可以指定--out-path
选项来指定输出路径。
通过在pkg
命令后添加--debug
参数,可以获取打包过程的日志。如果你在某个特定文件上遇到问题(似乎没有被打包到可执行文件中),查看日志可能会有帮助。
默认情况下,你的源代码在写入输出文件之前会被预编译为v8字节码。要禁用此功能,请在pkg
命令中添加--no-bytecode
参数。
为什么要这样做?
如果你需要一个可复现的构建过程,使得你的可执行文件的哈希值(例如md5、sha1、sha256等)在构建之间保持相同的值。因为编译字节码不是确定性的(参见这里或这里),它会导致具有不同哈希值的可执行文件。禁用字节码编译可以确保给定的输入始终具有相同的输出。
为什么不要这样做?
尽管编译为字节码不能使你的源代码100%安全,但它确实为你的源代码增加了一层小的安全性/隐私性/混淆性。关闭字节码编译会导致原始源代码直接写入可执行文件。如果你在*nix机器上,并且想要一个示例,请使用--no-bytecode
标志运行pkg
,然后使用GNU strings工具对输出进行处理。然后,你应该能够使用grep命令搜索你的源代码。
其他注意事项
指定--no-bytecode
选项将在项目中存在任何未在其package.json
中的许可证中明确标记为公共的软件包时失败。默认情况下,pkg
将检查每个软件包的许可证,并确保不面向公众的内容只作为字节码包含在内。
如果你确实需要为其他架构构建pkg
二进制文件,并且依赖于package.json
中具有损坏许可证的软件包,你可以通过使用--public-packages "packageA,packageB"
显式地将软件包列入公共白名单,或者使用--public-packages "*"
将所有软件包设置为公共来覆盖此行为。
pkg
有所谓的“基本二进制文件” - 实际上它们是相同的Node.js可执行文件,但应用了一些补丁。它们被用作pkg
创建的每个可执行文件的基础。在打包应用程序之前,pkg
会下载预编译的基本二进制文件。如果你希望从源代码编译基本二进制文件而不是下载它们,可以在pkg
命令中添加--build
选项。首先确保你的计算机符合编译原始Node.js的要求:BUILDING.md
有关更多信息,请参阅pkg-fetch
。
通过在pkg
命令中添加--compress Brotli
或--compress GZip
参数,可以进一步压缩嵌入在可执行文件中的文件内容。
此选项可以将嵌入式文件系统的大小减小多达60%。
应用程序的启动时间可能会略微缩短。
-C
可以用作--compress
的快捷方式。
变量 | 描述 |
---|---|
PKG_CACHE_PATH | 用于指定自定义路径以存储Node.js二进制文件的缓存文件夹。默认值为~/.pkg-cache |
PKG_IGNORE_TAG | 允许忽略与pkg-fetch 版本匹配的PKG_CACHE_PATH 中创建的附加文件夹 |
MAKE_JOB_COUNT | 允许配置用于编译的进程数 |
示例:
# 1 - 使用export
export PKG_CACHE_PATH=/my/cache
pkg app.js
# 2 - 在脚本之前传递
PKG_CACHE_PATH=/my/cache pkg app.js
通过命令行调用打包的应用程序./app a b
等同于node app.js a b
。
在打包过程中,pkg
会收集项目文件并将它们放入可执行文件中。这被称为快照。在运行时,打包的应用程序可以访问快照文件系统,其中包含所有这些文件。
打包的文件在其路径中具有/snapshot/
前缀(在Windows中为C:\snapshot\
)。如果你使用了pkg /path/app.js
命令行,那么在运行时,__filename
的值可能是/snapshot/path/app.js
。__dirname
也将是/snapshot/path
。下面是路径相关值的比较表:
值 | 在Node中的值 | 打包后的值 | 注释 |
---|---|---|---|
__filename |
/project/app.js |
/snapshot/project/app.js |
|
__dirname |
/project |
/snapshot/project |
|
process.cwd() |
/project |
/deploy |
假设应用程序被调用在/deploy 目录下 |
process.execPath |
/usr/bin/nodejs |
/deploy/app-x64 |
在/deploy 目录下运行的app-x64 |
process.argv[0] |
/usr/bin/nodejs |
/deploy/app-x64 |
|
process.argv[1] |
/project/app.js |
/snapshot/project/app.js |
|
process.pkg.entrypoint |
undefined |
/snapshot/project/app.js |
|
process.pkg.defaultEntrypoint |
undefined |
/snapshot/project/app.js |
|
require.main.filename |
/project/app.js |
/snapshot/project/app.js |
因此,为了使用在打包时收集的文件(require一个JavaScript文件或提供一个资源),你应该将__filename
、__dirname
、process.pkg.defaultEntrypoint
或require.main.filename
作为路径计算的基础。对于JavaScript文件,你可以直接使用require
或require.resolve
,因为它们默认使用当前的__dirname
。对于资源,请使用path.join(__dirname, '../path/to/asset')
。了解更多关于path.join
的信息,请参阅在源代码中检测资源。
另一方面,为了在运行时访问真实的文件系统(获取用户的外部JavaScript插件、JSON配置甚至获取用户目录列表),你应该使用process.cwd()
或path.dirname(process.execPath)
。
当pkg
遇到path.join(__dirname, '../path/to/asset')
时,它会自动将指定的文件打包为资源。请注意,path.join
必须有两个参数,最后一个参数必须是字符串字面量。
这样,你甚至可以避免为你的项目创建pkg
配置。
原生插件(.node文件)的使用是支持的。当pkg
在require
调用中遇到一个.node
文件时,它会将其打包为资源。在某些情况下(比如使用bindings
包),模块路径是动态生成的,pkg
无法检测到它。在这种情况下,你应该直接将.node
文件添加到package.json
中的assets
字段中。
Node.js加载原生插件的方式与经典的JS文件不同。它需要在磁盘上有一个文件来加载它,但是pkg
只生成一个文件。为了解决这个问题,pkg
会在磁盘上创建一个临时文件。这些文件会在进程退出后保留在磁盘上,并在下次进程启动时再次使用。
当安装包含原生模块的包时,原生模块会根据当前系统范围的Node.js版本进行编译。然后,当你使用pkg
编译你的项目时,注意使用--target
选项。你应该指定与系统范围的Node.js版本相同的Node.js版本,以使编译后的可执行文件与.node
文件兼容。
请注意,完全静态的Node二进制文件无法加载原生绑定,因此你不能在linuxstatic
上使用Node绑定。
const { exec } = require('pkg')
exec(args)
exec
函数接受一个命令行参数的数组,并返回一个Promise。它可以用来从JavaScript文件构建可执行文件。例如:
await exec(['app.js', '--target', 'host', '--output', 'app.exe']);
// 对app.exe进行操作,运行、测试、上传、部署等等
错误:ENOENT: no such file or directory, uv_chdir:当应用程序运行时删除了运行目录,或者通常情况下删除了process.cwd()
目录时会出现此错误。请确保在运行应用程序之前目录存在。
错误:ERR_INSPECTOR_NOT_AVAILABLE:当使用NODE_OPTIONS
变量强制启用调试模式运行Node.js时会出现此错误。由于pkg可执行文件通常用于生产环境,因此不允许使用调试选项。如果确实需要使用调试器,可以自己构建一个可调试的Node.js。
错误:require(…).internalModuleStat is not a function:当使用NODE_OPTIONS
变量与一些引导或节点选项导致与pkg冲突时,会出现此错误。一些IDE(如VS Code)可能会自动添加此环境变量。
你可以在Unix系统(Linux/macOS)的bash中进行检查:
$ printenv | grep NODE
在构建可执行文件时使用--debug
标志时,pkg会在应用程序启动时提供显示虚拟文件系统和符号链接表内容的能力,前提是设置了环境变量DEBUG_PKG
。这个功能可以用来检查符号链接是否被正确处理,并检查所有应用程序所需的文件是否正确地合并到最终的可执行文件中。
$ pkg --debug app.js -o output
$ DEBUG_PKG=1 output
或者在Windows系统的命令提示符中:
C:\> pkg --debug app.js -o output.exe
C:\> set DEBUG_PKG=1
C:\> output.exe
注意:在生产环境中不要使用--debug
标志。