Skip to content
This repository has been archived by the owner on Jan 13, 2024. It is now read-only.
/ pkg Public archive

Package your Node.js project into an executable

License

Notifications You must be signed in to change notification settings

vercel/pkg

Repository files navigation

pkg

Important

pkghas been deprecated with5.8.1as the last release. There are a number of successful forked versions ofpkgalready with various feature additions. Further, we’re excited about Node.js 21’s support forsingle executable applications.Thank you for the support and contributions over the years. The repository will remain open and archived.

This command line interface enables you to package your Node.js project into an executable that can be run even on devices without Node.js installed.

Use Cases

  • Make a commercial version of your application without sources
  • Make a demo/evaluation/trial version of your app without sources
  • Instantly make executables for other platforms (cross-compilation)
  • Make some kind of self-extracting archive or installer
  • No need to install Node.js and npm to run the packaged application
  • No need to download hundreds of files vianpm installto deploy your application. Deploy it as a single file
  • Put your assets inside the executable to make it even more portable
  • Test your app against new Node.js version without installing it

Usage

npm install -g pkg

After installing it, runpkg --helpwithout arguments to see list of options:

pkg [options] <input>

Options:

-h, --help output usage information
-v, --version output pkg version
-t, --targets comma-separated list of targets (see examples)
-c, --config package.json or any json file with top-level config
--options bake v8 options into executable to run with them on
-o, --output output file name or template for several files
--out-path path to save output one or more executables
-d, --debug show more information during packaging process [off]
-b, --build don't download prebuilt base binaries, build them
--public speed up and disclose the sources of top-level project
--public-packages force specified packages to be considered public
--no-bytecode skip bytecode generation and include source files as plain js
--no-native-build skip native addons build
--no-signature skip signature of the final executable on macos
--no-dict comma-separated list of packages names to ignore dictionaries. Use --no-dict * to disable all dictionaries
-C, --compress [default=None] compression algorithm = Brotli or GZip

Examples:

– Makes executables for Linux, macOS and Windows
$ pkg index.js
– Takes package.json from cwd and follows 'bin' entry
$ pkg.
– Makes executable for particular target machine
$ pkg -t node16-win-arm64 index.js
– Makes executables for target machines of your choice
$ pkg -t node16-linux,node18-linux,node16-win index.js
– Bakes '--expose-gc' and '--max-heap-size=34' into executable
$ pkg --options "expose-gc,max-heap-size=34" index.js
– Consider packageA and packageB to be public
$ pkg --public-packages "packageA,packageB" index.js
– Consider all packages to be public
$ pkg --public-packages "*" index.js
– Bakes '--expose-gc' into executable
$ pkg --options expose-gc index.js
– reduce size of the data packed inside the executable with GZip
$ pkg --compress GZip index.js

The entrypoint of your project is a mandatory CLI argument. It may be:

  • Path to entry file. Suppose it is/path/app.js,then packaged app will work the same way asnode /path/app.js
  • Path topackage.json.Pkgwill followbinproperty of the specifiedpackage.jsonand use it as entry file.
  • Path to directory.Pkgwill look forpackage.jsonin the specified directory. See above.

Targets

pkgcan generate executables for several target machines at a time. You can specify a comma-separated list of targets via--targets option. A canonical target consists of 3 elements, separated by dashes, for examplenode18-macos-x64ornode14-linux-arm64:

  • nodeRange(node8), node10, node12, node14, node16 or latest
  • platformalpine, linux, linuxstatic, win, macos, (freebsd)
  • archx64, arm64, (armv6, armv7)

(element) is unsupported, but you may try to compile yourself.

You may omit any element (and specify justnode14for example). The omitted elements will be taken from current platform or system-wide Node.js installation (its version and arch). There is also an aliashost,that means that all 3 elements are taken from current platform/Node.js. By default targets are linux,macos,winfor current Node.js version and arch.

If you want to generate executable for different architectures, note that by defaultpkghas to run the executable of the targetarch to generate bytecodes:

  • Linux: configure binfmt withQEMU.
  • macOS: possible to buildx64onarm64withRosetta 2but not opposite.
  • Windows: possible to buildx64onarm64withx64 emulationbut not opposite.
  • or, disable bytecode generation with--no-bytecode --public-packages "*" --public.

macos-arm64is experimental. Be careful about themandatory code signing requirement. The final executable has to be signed (ad-hoc signature is sufficient) withcodesign utility of macOS (orldidutility on Linux). Otherwise, the executable will be killed by kernel and the end-user has no way to permit it to run at all.pkgtries to ad-hoc sign the final executable. If necessary, you can replace this signature with your own trusted Apple Developer ID.

To be able to generate executables for all supported architectures and platforms, run pkgon a Linux host with binfmt (QEMUemulation) configured andldidinstalled.

Config

During packaging processpkgparses your sources, detects calls torequire,traverses the dependencies of your project and includes them into executable. In most cases you don't need to specify anything manually.

However your code may haverequire(variable)calls (so called non-literal argument torequire) or use non-javascript files (for example views, css, images etc).

require('./build/'+cmd+'.js');
path.join(__dirname,'views/'+viewName);

Such cases are not handled bypkg.So you must specify the files - scripts and assets - manually inpkgproperty of yourpackage.jsonfile.

"pkg":{
"scripts":"build/**/*.js",
"assets":"views/**/*",
"targets":["node14-linux-arm64"],
"outputPath":"dist"
}

The above example will include everything inassets/and every.js file inbuild/,build only fornode14-linux-arm64, and place the executable insidedist/.

You may also specify arrays of globs:

"assets": [ "assets/**/*", "images/**/*" ]

Just be sure to callpkg package.jsonorpkg.to make use ofpackage.jsonconfiguration.

Scripts

scriptsis aglob or list of globs. Files specified asscriptswill be compiled usingv8::ScriptCompilerand placed into executable without sources. They must conform to the JS standards of those Node.js versions you target (seeTargets), i.e. be already transpiled.

Assets

assetsis aglob or list of globs. Files specified asassetswill be packaged into executable as raw content without modifications. Javascript files may also be specified asassets.Their sources will not be stripped as it improves execution performance of the files and simplifies debugging.

See also Detecting assets in source codeand Snapshot filesystem.

Options

Node.js application can be called with runtime options (belonging to Node.js or V8). To list them typenode --helpornode --v8-options.

You can "bake" these runtime options into packaged application. The app will always run with the options turned on. Just remove--from option name.

You can specify multiple options by joining them in a single string, comma (,) separated:

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

You may specify--outputif you create only one executable or--out-pathto place executables for multiple targets.

Debug

Pass--debugtopkgto get a log of packaging process. If you have issues with some particular file (seems not packaged into executable), it may be useful to look through the log.

Bytecode (reproducibility)

By default, your source code is precompiled to v8 bytecode before being written to the output file. To disable this feature, pass--no-bytecodetopkg.

Why would you want to do this?

If you need a reproducible build process where your executable hashes (e.g. md5, sha1, sha256, etc.) are the same value between builds. Because compiling bytecode is not deterministic (seehereor here) it results in executables with differing hashed values. Disabling bytecode compilation allows a given input to always have the same output.

Why would you NOT want to do this?

While compiling to bytecode does not make your source code 100% secure, it does add a small layer of security/privacy/obscurity to your source code. Turning off bytecode compilation causes the raw source code to be written directly to the executable file. If you're on *nix machine and would like an example, run pkgwith the--no-bytecodeflag, and use the GNU strings tool on the output. You then should be able to grep your source code.

Other considerations

Specifying--no-bytecodewill fail if there are any packages in your project that aren't explicitly marked as public by thelicensein theirpackage.json. By default,pkgwill check the license of each package and make sure that stuff that isn't meant for the public will only be included as bytecode.

If you do require building pkg binaries for other architectures and/or depend on a package with a broken licensein itspackage.json,you can override this behaviour by either explicitly whitelisting packages to be public using--public-packages "packageA,packageB"or setting all packages to public using--public-packages "*"

Build

pkghas so called "base binaries" - they are actually same nodeexecutables but with some patches applied. They are used as a base for every executablepkgcreates.pkg downloads precompiled base binaries before packaging your application. If you prefer to compile base binaries from source instead of downloading them, you may pass--build option topkg.First ensure your computer meets the requirements to compile original Node.js: BUILDING.md

Seepkg-fetchfor more info.

Compression

Pass--compress Brotlior--compress GZiptopkgto compress further the content of the files store in the exectable.

This option can reduce the size of the embedded file system by up to 60%.

The startup time of the application might be reduced slightly.

-Ccan be used as a shortcut for--compress.

Environment

Var Description
PKG_CACHE_PATH Used to specify a custom path for node binaries cache folder. Default is~/.pkg-cache
PKG_IGNORE_TAG Allows to ignore additional folder created onPKG_CACHE_PATHmatching pkg-fetch version
MAKE_JOB_COUNT Allow configuring number of processes used for compiling

Examples

#1 - Using export
exportPKG_CACHE_PATH=/my/cache
pkg app.js

#2 - Passing it before the script
PKG_CACHE_PATH=/my/cache pkg app.js

Usage of packaged app

Command line call to packaged app./app a bis equivalent tonode app.js a b

Snapshot filesystem

During packaging processpkgcollects project files and places them into executable. It is called a snapshot. At run time the packaged application has access to snapshot filesystem where all that files reside.

Packaged files have/snapshot/prefix in their paths (or C:\snapshot\in Windows). If you usedpkg /path/app.jscommand line, then__filenamevalue will be likely/snapshot/path/app.js at run time.__dirnamewill be/snapshot/pathas well. Here is the comparison table of path-related values:

value withnode packaged comments
__filename /project/app.js /snapshot/project/app.js
__dirname /project /snapshot/project
process.cwd() /project /deploy suppose the app is called...
process.execPath /usr/bin/nodejs /deploy/app-x64 app-x64and run in/deploy
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

Hence, in order to make use of a file collected at packaging time (requirea javascript file or serve an asset) you should take__filename,__dirname,process.pkg.defaultEntrypoint orrequire.main.filenameas a base for your path calculations. For javascript files you can justrequireorrequire.resolve because they use current__dirnameby default. For assets use path.join(__dirname, '../path/to/asset').Learn more about path.joinin Detecting assets in source code.

On the other hand, in order to access real file system at run time (pick up a user's external javascript plugin, json configuration or even get a list of user's directory) you should takeprocess.cwd() orpath.dirname(process.execPath).

Detecting assets in source code

Whenpkgencounterspath.join(__dirname, '../path/to/asset'), it automatically packages the file specified as an asset. See Assets.Pay attention thatpath.joinmust have two arguments and the last one must be a string literal.

This way you may even avoid creatingpkgconfig for your project.

Native addons

Native addons (.nodefiles) use is supported. Whenpkgencounters a.nodefile in arequirecall, it will package this like an asset. In some cases (like with thebindingspackage), the module path is generated dynamicaly andpkgwon't be able to detect it. In this case, you should add the.nodefile directly in theassetsfield inpackage.json.

The way Node.js requires native addon is different from a classic JS file. It needs to have a file on disk to load it, butpkgonly generates one file. To circumvent this,pkgwill create a temporary file on the disk. These files will stay on the disk after the process has exited and will be used again on the next process launch.

When a package, that contains a native module, is being installed, the native module is compiled against current system-wide Node.js version. Then, when you compile your project withpkg,pay attention to--targetoption. You should specify the same Node.js version as your system-wide Node.js to make compiled executable compatible with.nodefiles.

Note that fully static Node binaries are not capable of loading native bindings, so you may not use Node bindings withlinuxstatic.

API

const { exec } = require('pkg')

exec(args)takes an array of command line arguments and returns a promise. For example:

awaitexec(['app.js','--target','host','--output','app.exe']);
// do something with app.exe, run, test, upload, deploy, etc

Troubleshooting

Error: ENOENT: no such file or directory, uv_chdir

This error can be caused by deleting the directory the application is run from. Or, generally, deletingprocess.cwd()directory when the application is running.

Error: ERR_INSPECTOR_NOT_AVAILABLE

This error can be caused by usingNODE_OPTIONSvariable to force to runnodewith the debug mode enabled. Debugging options are disallowed , aspkgexecutables are usually used for production environments. If you do need to use inspector, you canbuild a debuggable Node.jsyourself.

Error: require(...).internalModuleStat is not a function

This error can be caused by usingNODE_OPTIONSvariable with some bootstrap ornodeoptions causing conflicts withpkg.Some IDEs, such asVS Code,may add this env variable automatically.

You could check onUnix systems(Linux/macOS) inbash:

$ printenv|grep NODE

Advanced

exploring virtual file system embedded in debug mode

When you are using the--debugflag when building your executable, pkgadd the ability to display the content of the virtual file system and the symlink table on the console, when the application starts, providing that the environement variable DEBUG_PKG is set. This feature can be useful to inspect if symlinks are correctly handled, and check that all the required files for your application are properly incorporated to the final executable.

$ pkg --debug app.js -o output
$ DEBUG_PKG=1 output

or

C:\> pkg --debug app.js -o output.exe
C:\> set DEBUG_PKG=1
C:\> output.exe

Note: make sure not to use --debug flag in production.