pkg

repository·main·Indexed 12 days ago

https://github.com/vercel/pkg

A command line interface that packages Node.js projects into standalone executables for easier distribution and cross-platform deployment. Version 5.8.1 is officially deprecated; users are encouraged to use forked versions or Node.js 21's native single executable applications support.

Tokens
5.4K
Snippets
14
Records
26
Agent score
98%

What's inside pkg

  1. Overview of `pkg` functionality

    main
    The pkg command line interface allows you to package a Node.js project into a single executable file. This executable can run on devices that do not have Node.js or npm installed, making it useful for distributing applications as standalone files.
  2. Working with Native Addons (.node files)

    main

    Native addons are supported. pkg automatically packages .node files encountered during a require call.

    Manual Asset Configuration

    If a module path is generated dynamically (e.g., using the bindings package), pkg cannot detect it automatically. In these cases, you must add the .node file explicitly to the assets field in your package.json.

    Runtime Behavior

    Because Node.js requires native addons to exist on a physical disk, pkg creates a temporary file on the disk at runtime. These files persist after the process exits and are reused on subsequent launches.

    Compatibility Requirements

    1. Target Version: When compiling, ensure the --target option matches the Node.js version of your system-wide Node.js used to compile the native module. This ensures compatibility between the executable and the .node files.
    2. Static Binaries: Fully static Node binaries (linuxstatic) cannot load native bindings. Do not use Node bindings with linuxstatic.
  3. Understand Bytecode generation and reproducibility

    main

    By default, pkg precompiles source code into V8 bytecode. This provides a layer of security/obscurity by not including raw source code in the executable.

    Disabling Bytecode with --no-bytecode

    When to disable it:

    • Reproducible Builds: Bytecode compilation is non-deterministic. If you need the executable's hash (MD5, SHA, etc.) to be identical across builds, you must disable bytecode.

    When to keep it enabled:

    • Security/Privacy: Bytecode prevents users from easily grepping your source code from the binary.

    Important Constraints:

    • If you use --no-bytecode, all packages in your project must be explicitly marked as public (via the license field in their package.json).
    • You can override this behavior by using --public-packages "*" or by whitelisting specific packages with --public-packages "packageA,packageB".
  4. Common use cases for `pkg`

    main

    You might use pkg to:

    • Protect Source Code: Create commercial, demo, or trial versions of your application without exposing the original source files.
    • Cross-Compilation: Instantly generate executables for different platforms.
    • Simplify Deployment: Deploy your application as a single file, avoiding the need to run npm install or download hundreds of dependencies on the target machine.
    • Portability: Bundle assets directly inside the executable to create a highly portable package.
    • Environment Testing: Test your application against different Node.js versions without needing to install them locally.
    • Self-Extracting Archives: Create installers or self-extracting archives.
  5. Configure target machines with --targets

    main

    You can specify a comma-separated list of targets using the --targets option. A canonical target follows the format nodeRange-platform-arch:

    • nodeRange: node8, node10, node12, node14, node16, or latest.
    • platform: alpine, linux, linuxstatic, win, macos, or freebsd.
    • arch: x64, arm64, armv6, or armv7.

    Note on omissions: If you omit an element, pkg uses the current platform/architecture or the system-wide Node.js version. You can use the alias host to represent the current platform/Node.js environment.

    Example targets:

    • node18-macos-x64
    • node14-linux-arm64

    Cross-architecture considerations: By default, pkg must run the executable of the target architecture to generate bytecodes. To build for different architectures on a single host, you may need:

    • Linux: Configure binfmt with QEMU.
    • macOS: Use Rosetta 2 to build x64 on arm64 (but not vice versa).
    • Windows: Use x64 emulation to build x64 on arm64 (but not vice versa).
    • Alternative: Disable bytecode generation using --no-bytecode --public-packages "*" --public.
  6. How the snapshot filesystem works

    main

    When pkg packages your project, it collects your files into a virtual filesystem called a snapshot. At runtime, your application can access these files using a specific prefix.

    Path Mapping

    On Unix-like systems, files reside under /snapshot/. On Windows, they reside under C:\snapshot\.

    ValueWith nodePackagedNotes
    __filename/project/app.js/snapshot/project/app.js
    __dirname/project/snapshot/project
    process.cwd()/project/deployThe directory where the executable is run
    process.execPath/usr/bin/nodejs/deploy/app-x64The path to the executable
    process.argv[0]/usr/bin/nodejs/deploy/app-x64
    process.argv[1]/project/app.js/snapshot/project/app.js
    process.pkg.entrypointundefined/snapshot/project/app.js
    process.pkg.defaultEntrypointundefined/snapshot/project/app.js
    require.main.filename/project/app.js/snapshot/project/app.js

    Best Practices for Path Resolution

    • To access packaged files (assets or JS files): Use __filename, __dirname, process.pkg.defaultEntrypoint, or require.main.filename as your base.
      • For JS files: Use require or require.resolve (they use __dirname by default).
      • For assets: Use path.join(__dirname, '../path/to/asset').
    • To access the real host filesystem (external configs, plugins, or user directories): Use process.cwd() or path.dirname(process.execPath).
  7. Detecting assets in source code

    main

    You can avoid manual configuration in package.json by using specific code patterns. When pkg encounters path.join(__dirname, '../path/to/asset'), it automatically detects and packages the specified file as an asset.

    Requirement: The path.join call must have exactly two arguments, and the last argument must be a string literal.

    // pkg will automatically package this file
    path.join(__dirname, '../path/to/asset');
  8. How to use pkg to package applications

    main

    The pkg CLI requires an entrypoint as a mandatory argument. You can provide:

    1. Path to an entry file: e.g., /path/app.js. The packaged app will behave like node /path/app.js.
    2. Path to a package.json: pkg will follow the bin property in that file to find the entry point.
    3. Path to a directory: pkg will look for a package.json in that directory.

    Common CLI Examples

    • Default packaging (makes executables for Linux, macOS, and Windows): pkg index.js
    • Using package.json from current directory: pkg .
    • Targeting a specific machine: pkg -t node16-win-arm64 index.js
    • Targeting multiple machines: pkg -t node16-linux,node18-linux,node16-win index.js
    • Baking V8 options into the executable: pkg --options "expose-gc,max-heap-size=34" index.js
    • Reducing size with GZip compression: pkg --compress GZip index.js
    pkg index.js
  9. Run the Express-Example demo

    main

    This example demonstrates how to use pkg to package a simple Express-based application into a single executable.

    To run the demo:

    1. Install dependencies using npm install.
    2. Package the application using pkg ..

    Once the process completes, pkg will generate an executable named express-example in the root directory of the project. To run the application, execute the generated file and navigate to http://localhost:8080/ in your browser.

    npm install
    pkg .
    ./express-example
  10. Explore the virtual filesystem in debug mode

    main

    If you build your executable with the --debug flag, you can inspect the virtual filesystem and symlink table at runtime. This is useful for verifying that all required files and symlinks were correctly incorporated into the executable.

    To trigger the display of the filesystem content, set the DEBUG_PKG environment variable to 1 when running the output executable.

    ```bash
    # Build with debug enabled
    $ pkg --debug app.js -o output
    
    # Run the output with DEBUG_PKG set
    $ DEBUG_PKG=1 ./output

    Note: Do not use the --debug flag in production environments.

  11. Specify compilation targets

    main

    Targets allow you to build executables for different Node.js versions, platforms, and architectures. The format is node<version>-<platform>-<arch>.

    Target Formats

    • Single target: node16-macos-x64
    • Multiple targets: node16-macos-x64,node16-linux-x64
    • Host machine: Use host to target the current environment.

    If no targets are specified:

    • If an output path is provided, it defaults to the host.
    • If no output path is provided, it defaults to linux, macos, win.
    pkg --target node16-macos-x64,node16-linux-x64 index.js