pkg: Executable Builder cho ứng dụng Node.js
pkg đóng gói một dự án Node.js thành file thực thi độc lập từ trước khi Node.js tự làm được điều đó, nhưng chính README của pkg xác nhận 5.8.1 là bản release cuối cùng trước khi Vercel archive repo. Nó vẫn dùng tốt cho các build trên những phiên bản Node mà pkg còn hỗ trợ, nhưng dự án mới thì nên dùng tính năng single executable applications có sẵn trong Node. Đóng băng, không phải chết hẳn — chỉ đừng mong có bản vá mới.
pkg là gì?
pkg là một CLI của Vercel, gom một dự án Node.js — dependency, asset, và một bản Node binary đã vá — thành một file thực thi duy nhất, chạy được trên máy không cài Node.js. Mặc định pkg build cho cả Linux, macOS và Windows, quét các lệnh require() từ entry file để xác định cần đóng gói những gì. Theo README, pkg đã bị deprecated kể từ bản 5.8.1, và repo hiện đã được archive.
Các tính năng cốt lõi
- ✓Cross-compilation qua --targets: build cho node16-win-arm64, node18-linux và nhiều tổ hợp nền tảng khác trong một lệnh, mà không cần sở hữu phần cứng đó cho phần lớn trường hợp.
- ✓Snapshot filesystem: các file được đóng gói nằm dưới /snapshot/ bên trong binary, và __dirname hay require.main.filename tự trỏ về đó lúc runtime.
- ✓Compile sang V8 bytecode theo mặc định, có flag --no-bytecode để giữ nguyên mã nguồn JS khi cần build có hash lặp lại được (reproducible).
- ✓Đóng gói được native addon (file .node), pkg ghi addon ra một file tạm trên ổ đĩa lúc runtime vì Node không load native addon trực tiếp từ snapshot được.
- ✓Nén bằng Brotli hoặc GZip qua -C/--compress, README nói có thể giảm dung lượng filesystem nhúng bên trong tới 60%.
- ✓Baked sẵn các option runtime của V8/Node qua --options, nên các flag như expose-gc hay max-old-space-size được thiết lập sẵn bên trong file thực thi.
- ✓Có API dạng lập trình — const { exec } = require('pkg') — để đưa việc build vào script riêng thay vì gọi CLI trực tiếp.
Khi nào nên đóng gói Node.js thành file thực thi
- •Phát hành bản thương mại của ứng dụng mà không giao mã nguồn cho khách hàng.
- •Tạo bản demo hoặc dùng thử che giấu mã nguồn bên dưới.
- •Cross-compilation ra file thực thi cho nền tảng bạn không có máy để build.
- •Deploy dưới dạng một file duy nhất thay vì chạy npm install hàng trăm file trên server đích.
- •Dựng một installer hoặc self-extracting archive quanh asset của ứng dụng.
- •Test codebase với một bản Node.js mới mà không cần cài nó vào máy.
Cài đặt pkg
Cài pkg trên toàn hệ thống bằng npm install -g pkg, sau đó xác nhận đã có trong PATH bằng cách chạy pkg --help để xem toàn bộ flag. Không cần bước cấu hình riêng: chỉ cần trỏ pkg vào entry file (hoặc một package.json/thư mục chứa nó) và pkg tự quét require() để lo phần còn lại. Điều nên quyết định trước là danh sách --targets, vì nếu bỏ trống, pkg mặc định build cho linux, macos và win theo đúng phiên bản Node và kiến trúc máy bạn đang dùng.
Tạo file thực thi đầu tiên
Trỏ pkg thẳng vào entry file — pkg index.js — pkg sẽ tạo file thực thi cho cả Linux, macOS và Windows theo mặc định. Chạy pkg . thay vào đó thì pkg đọc trường bin trong package.json ở thư mục hiện tại. Cần một build cụ thể? pkg -t node16-win-arm64 index.js sẽ giới hạn vào đúng một target. Những gì pkg không tự quét được — lệnh require(variable) với tham số không phải chuỗi literal, hay asset không phải JS như view, ảnh — phải khai báo thủ công trong khối pkg của package.json, dùng glob pattern ở scripts và assets kèm outputPath tuỳ chọn. API cũng làm y hệt CLI: await exec(['app.js', '--target', 'host', '--output', 'app.exe']) trả về một promise, ghép được thẳng vào pipeline build của riêng bạn.
Ưu điểm chính
- ✓Gom cả dự án Node — dependency, asset, base binary — thành một file, nên việc deploy không còn là chạy npm install trên máy đích nữa.
- ✓--targets cho phép cross-compilation sang nền tảng bạn không có phần cứng, chỉ trong một lệnh.
- ✓Compile sang V8 bytecode theo mặc định giúp mã nguồn khó đọc hơn so với chỉ minify thông thường.
- ✓Nén Brotli/GZip giảm dung lượng filesystem nhúng bên trong, README nói có thể tới 60%.
- ✓API exec() dạng lập trình cho phép đưa việc đóng gói vào script build riêng thay vì gọi CLI thủ công.
Hạn chế đã biết
- △Dự án đã bị archive — README ghi rõ 5.8.1 là bản release cuối, nên sẽ không còn bản vá hay hỗ trợ phiên bản Node mới từ chính vercel/pkg nữa.
- △Build cross-arch cần cấu hình thêm: QEMU/binfmt trên Linux, Rosetta 2 trên macOS, hoặc x64 emulation trên Windows, trừ khi tắt bytecode generation.
- △Build macos-arm64 vẫn ở dạng thử nghiệm, và việc code-signing trên macOS là bắt buộc, nếu không hệ điều hành sẽ kill file thực thi ngay lập tức.
- △Các lệnh require() không dùng chuỗi literal và asset không phải JS không được tự động phát hiện — phải liệt kê thủ công trong cấu hình pkg của package.json, nếu không build sẽ âm thầm thiếu file.
- △Native addon chỉ được đóng gói dưới dạng file thô mà pkg ghi ra một đường dẫn tạm lúc runtime, và target linuxstatic thì không load được native binding nào cả.
Lựa chọn thay thế cho pkg
Câu hỏi thường gặp
pkg không còn được bảo trì tích cực: README ghi rõ bản 5.8.1 là release cuối cùng và Vercel đã archive repo. Một số fork cộng đồng vẫn tiếp tục thêm tính năng, nhưng bản thân dự án vercel/pkg gốc không nhận thêm cập nhật nào.
pkg là một CLI bên thứ ba, phải tải về và vá base binary của Node để tạo ra file thực thi, trong khi tính năng single executable applications có sẵn trong Node.js 21 làm đúng việc đó ngay bên trong Node. README của pkg chỉ thẳng ra tính năng có sẵn này là lý do dự án bị deprecated.
pkg đóng gói được native addon: khi gặp một file .node trong lệnh require, pkg xử lý nó như một asset thông thường. Vì Node cần addon nằm trên ổ đĩa mới load được, pkg ghi ra một bản tạm lúc runtime, và file đó vẫn còn lại giữa các lần chạy.
Asset không phải JavaScript cần được khai báo trong glob pattern của trường assets trong khối pkg của package.json, hoặc dùng lệnh path.join(__dirname, 'file') với hai tham số để pkg tự phát hiện. Bất cứ thứ gì pkg không quét ra được theo hai cách này sẽ bị bỏ sót khỏi build mà không báo lỗi.
ERR_INSPECTOR_NOT_AVAILABLE xảy ra vì file thực thi do pkg tạo ra mặc định chặn chế độ debug/inspector của Node, do pkg nhắm tới môi trường production. Lỗi này thường do biến môi trường NODE_OPTIONS ép bật debug mode; muốn dùng inspector với pkg thì phải tự build một bản Node.js có hỗ trợ debug riêng.
Truyền --compress Brotli hoặc --compress GZip (hoặc dùng tắt -C) cho pkg để nén phần file được nhúng bên trong file thực thi. README nói cách này có thể giảm dung lượng filesystem đóng gói tới 60%, dù thời gian khởi động có thể chậm đi một chút để đánh đổi.
Vấn đề mà pkg giải quyết
Trước khi Node.js tự có tính năng single executable applications, muốn đưa một ứng dụng Node.js lên máy không cài Node thì hoặc phải bắt người dùng cài Node rồi chạy npm install, hoặc tự viết cơ chế đóng gói binary dựa trên snapshot và bytecode của V8. pkg gói toàn bộ bài toán đó — quét các lệnh require(), compile sang bytecode, nhúng một snapshot filesystem, cross-compilation sang nền tảng khác — vào một lệnh CLI duy nhất, đó là lý do các dự án ra đời trước Node 21 vẫn phụ thuộc vào pkg dù bản thân pkg đã ngừng phát triển.
Nên dùng — và không nên dùng
Dùng pkg nếu bạn đang bảo trì một dự án sẵn có đã phụ thuộc vào nó, phiên bản Node mục tiêu vẫn nằm trong danh sách pkg hỗ trợ, hoặc cần build bản demo/dùng thử mà không lộ mã nguồn. Bỏ qua pkg nếu bắt đầu dự án mới: Node 21+ đã có sẵn tính năng single executable applications, và vì pkg dừng lại ở bản 5.8.1 nên sẽ không có hỗ trợ cho phiên bản Node mới hơn hay bản vá nào tiếp theo.
Repo liên quan
Muốn nghe thêm một ý kiến về pkg?
Hỏi một AI đọc được trang này — một cú bấm là có ngay nhận định về pkg.
