mit einem Klick
performances-compare
// Run packaging performance benchmarks comparing Standard PKG vs SEA modes (with/without bundling) on a Node.js project. Default target is zwave-js-ui. Measures build time, binary size, and startup time.
// Run packaging performance benchmarks comparing Standard PKG vs SEA modes (with/without bundling) on a Node.js project. Default target is zwave-js-ui. Measures build time, binary size, and startup time.
Debug and troubleshoot @yao-pkg/pkg packaging issues — build failures, runtime crashes, missing assets, bloated binaries, native addon errors, cross-compile regressions, SEA sentinel problems, and patches/dictionaries.
Cross-compile test harness for @yao-pkg/pkg. Builds a tiny hello.js for every (mode × target) combination and runs what can be executed on a Linux host (native x64, arm64 via docker+qemu, win-x64 via docker-wine). Use when the user wants to verify pkg cross-compilation claims, reproduce issues #87/#181, sanity-check a pkg PR, or compare Standard vs Enhanced SEA across Node 20/22/24. Trigger: "test cross-compile", "run pkg matrix", "verify xcompile", or /pkg-xcompile-test.
| name | performances-compare |
| description | Run packaging performance benchmarks comparing Standard PKG vs SEA modes (with/without bundling) on a Node.js project. Default target is zwave-js-ui. Measures build time, binary size, and startup time. |
| user-invocable | true |
| disable-model-invocation | false |
| argument-hint | ["project-path"] |
| allowed-tools | Read Bash Grep Glob Agent Edit Write TaskCreate TaskUpdate TaskGet TaskList |
| effort | high |
Compare packaging performance across 4 methods using @yao-pkg/pkg:
pkg --sea (enhanced VFS, no bytecode)pkg --sea directly on the project (walker intercepts all files)$0 (optional) - Absolute path to the target project. If omitted, defaults to the zwave-js-ui project.zwave-js-ui is an open-source Z-Wave control panel and MQTT gateway. It's the primary benchmark target because:
@serialport/bindings-cpp)zwave-js, @zwave-js/*)exports with wildcards and #importsThe project must be cloned and built before running benchmarks. If not already available:
git clone https://github.com/zwave-js/zwave-js-ui.git <project-path>
cd <project-path>
npm ci
npm run build # Compiles TypeScript (server/) and Vite frontend (dist/)
server/bin/www.js (compiled JS, ESM)api/bin/www.tsdist/ (Vite build output)"type": "module" (ESM)>= 20.19store/ (relative to CWD, contains settings.json)Listening on port 8091 when readyThe project uses esbuild to pre-bundle for pkg. The bundler script is esbuild.js:
cd <project-path>
node esbuild.js --js-entrypoint # Use --js-entrypoint to bundle from compiled JS
This produces a build/ directory containing:
build/index.js - Single bundled entry pointbuild/package.json - Patched package.json with bin: "index.js" and pkg assets configbuild/node_modules/ - External dependencies that can't be bundled:
@serialport/bindings-cpp/prebuilds (native addon prebuilts)zwave-js/package.json@zwave-js/server/package.json@zwave-js/config/package.json and @zwave-js/config/config/ (device database)@zwave-js/config/build/ (compiled config module)build/dist/ - Frontend assetsbuild/snippets/ - Code snippetsThe bundle process:
server/bin/www.js into build/index.js (CJS output).node files are handled by a custom esbuild plugin (excluded from bundle, path-rewritten)build/node_modules/package.json is patched: devDependencies/scripts removed, bin set to index.js, pkg assets configuredWhen running pkg from the bundle:
cd build
pkg . -t node22-linux-x64 --output <output-path> # Standard PKG
pkg . --sea -t node22-linux-x64 --output <output-path> # SEA mode
The project's package.json contains pkg config for the non-bundled case:
{
"pkg": {
"scripts": ["server/**/*", "node_modules/axios/dist/node/*"],
"assets": [
"dist/**/*",
"snippets/**",
"node_modules/@serialport/**",
"node_modules/@zwave-js/serial/node_modules/@serialport/**",
"node_modules/zwave-js/node_modules/@serialport/**",
"node_modules/@zwave-js/config/config/**"
]
}
}
$0 if provided, otherwise look for zwave-js-ui adjacent to the pkg repo (e.g., ../zwave-js-ui)package.json with a bin entry/tmp/pkg-bench-<timestamp>/store/settings.json from the project to /tmp/pkg-bench-<timestamp>/store/settings.jsonserver/bin/www.js and dist/index.html exist)npm run build in the projectRun from the pkg repo directory:
npm run build # Ensure pkg is built
Use the local pkg binary directly:
node <pkg-repo>/lib-es5/bin.js ... # Avoids npx re-installing from npm
Create the benchmark output directory and run each method, measuring wall-clock time with date +%s%N.
IMPORTANT: Run each build method 3 times and report the average. Build times can vary significantly due to disk cache, CPU thermal throttling, and background processes. The first run warms caches; subsequent runs give more stable numbers. Report all individual times and the average.
cd <project>
START=$(date +%s%N)
node <pkg-repo>/lib-es5/bin.js . --options experimental-require-module \
-t node22-linux-x64 --output <outdir>/pkg-nobundle
END=$(date +%s%N)
Note: This typically fails for ESM projects with ReferenceError: module is not defined in ES module scope because the bytecode compiler can't handle ESM syntax. Record the failure and note it in results.
cd <project>
START=$(date +%s%N)
node esbuild.js --js-entrypoint
cd build
node <pkg-repo>/lib-es5/bin.js . --options experimental-require-module \
-t node22-linux-x64 --output <outdir>/pkg-bundle
END=$(date +%s%N)
Build time = esbuild + pkg combined.
cd <project>
START=$(date +%s%N)
node esbuild.js --js-entrypoint
cd build
node <pkg-repo>/lib-es5/bin.js . --sea \
-t node22-linux-x64 --output <outdir>/sea-bundle
END=$(date +%s%N)
Build time = esbuild + SEA combined.
cd <project>
START=$(date +%s%N)
node <pkg-repo>/lib-es5/bin.js . --sea \
-t node22-linux-x64 --output <outdir>/sea-nobundle
END=$(date +%s%N)
For each produced binary, measure the time until the application is ready. Use this measurement function:
measure_startup() {
local binary="$1"
local label="$2"
local port="${3:-8091}"
local ready_pattern="${4:-Listening on port}"
fuser -k $port/tcp 2>/dev/null 2>&1; sleep 0.3
local START=$(date +%s%N)
$binary > /tmp/pkg-bench-out.log 2>&1 &
local PID=$!
while ! grep -q "$ready_pattern" /tmp/pkg-bench-out.log 2>/dev/null; do
sleep 0.01
if ! kill -0 $PID 2>/dev/null; then
echo "$label: FAILED (process died)"
return
fi
done
local END=$(date +%s%N)
kill $PID 2>/dev/null; wait $PID 2>/dev/null
echo "$label: $(( (END - START) / 1000000 ))ms"
}
For each working binary:
store/settings.json next to the binary in a store/ subdirectoryrm -rf ~/.cache/pkg/fuser -k 8091/tcp; sleep 0.3Example:
rm -rf ~/.cache/pkg/
for i in 1 2 3 4 5; do measure_startup ./sea-bundle "Run $i"; done
# Average = (Run2 + Run3 + Run4 + Run5) / 4
Present results as a markdown table. All times should be averages from multiple runs (3 for build, 4 for startup after discarding cold run):
| Method | Build Time (avg of 3) | Binary Size | Startup (avg of 4) | Status |
|-----------------------------|-----------------------|-------------|---------------------|--------|
| Standard PKG (no bundle) | Xs | X MB | Xms | OK/FAIL|
| Standard PKG (with bundle) | Xs | X MB | Xms | OK |
| SEA with bundle | Xs | X MB | Xms | OK |
| SEA without bundle | Xs | X MB | Xms | OK |
Also include a raw data section below the summary table showing every individual run:
### Raw Data
#### Build Times
| Method | Run 1 | Run 2 | Run 3 | Average |
|--------|-------|-------|-------|---------|
| ... | Xs | Xs | Xs | Xs |
#### Startup Times
| Method | Run 1 (cold) | Run 2 | Run 3 | Run 4 | Run 5 | Average (2-5) |
|--------|-------------|-------|-------|-------|-------|---------------|
| ... | Xms | Xms | Xms | Xms | Xms | Xms |
Include analysis:
fuser -k 8091/tcpbuild/ directory in the target project: rm -rf <project>/buildTo use with a non-zwave-js-ui project, the user must provide:
$0package.json with a bin fieldready_pattern in measure_startup to match the project's ready message (e.g., "Server started", "listening on")port if the project uses a different port"type": "module") because the V8 bytecode compiler can't handle ESM syntax. This is expected.warning: Can't find string offset for section name '.note.100' messages during SEA injection are harmless.node files are extracted to ~/.cache/pkg/ on first run (adds ~200ms to cold start)