记录一次使用pkg将node工程打包成.exe的实践过程。
1. 下载
npm install -g pkg
2. 配置package.json
pkg可以通过在package.json 中的配置项来对代码进行打包
{
"type": "module",
"name": "server",
"version": "1.0.0",
"scripts": {
"start": "node index.js",
"pkg": "node build/build.js",
},
"bin": "index.js",
"pkg": {
"name": "my-server",
"targets": [
"node18-win-x64"
]
},
"output": "dist"
}
bin是入口文件路径name最终输出的可执行程序名称targets指定平台和 Node 版本,这里使用 node18output是输出可执行文件的目录
3. 当引入的node_modules中包含 .node时
我的项目中使用了better-sqlite3,打包时不会将better_sqlite3.node打包到文件中,此时需要手动配置,将该文件复制到打包后的文件中,这里我自己编译了一份脚本,将静态资源进行复制。
import { exec } from 'pkg'
import path from 'path';
import { mkdirSync } from "fs";
import { resolve, dirname } from 'path';
import fg from 'fast-glob';
import { createRequire } from 'module';
// 动态导入 fs-extra (兼容 CJS)
const require = createRequire(import.meta.url);
const fsExtra = require('fs-extra');
const build = async () => {
try {
// 先移除package文件夹中的内容
await fsExtra.remove('package');
// 获取输入文件
const entryFile = path.resolve(process.cwd(), './dist/index.cjs');
await exec([
entryFile,
'--targets', 'node18-win-x64',
'--output', 'package/my-service.exe'
// 如有其它 .node 原生模块文件,可继续添加 --assets 路径
]);
await copyStaticFile();
await fsExtra.remove('dist');
console.log('✅ 打包完成: package/my-service.exe');
} catch (err) {
console.error('❌ 打包失败:', err);
}
}
const copyStaticFile = async () => {
// 配置要拷贝的静态资源
const patterns = [
'db/*',
'public/**/*',
'better_sqlite3.node',
'.env',
];
const files = await fg(patterns, { dot: true, onlyFiles: false, followSymbolicLinks: true, ignore: [] });
files.forEach(file => {
const srcPath = resolve(file);
// 复制到pkg打包目录下
const pkgPath = resolve('package', file);
// 复制到pkg打包目录下
fsExtra.copySync(srcPath, pkgPath);
console.log(`[tsup] Copied: ${file} -> dist/${file}`);
})
}
build();