前言
背景
最近做了一个 Egg.js 的项目,有为客户私有化部署但是需要保护源代码的需求。之前没有接触过 Node.js 项目打包的,只是用过 Webpack 打包的前端的东西,更别说后端的项目打包了。搜索了一下,发现有不少方案,但是都只是解决了其中一部分问题,比如代码混淆。最后发现一个叫做 Pkg的方案应该能够满足需求。
目标
基础目标
- 将 Egg.js 项目打包成不包含源文件的可执行文件
进阶目标
- 确保包含了项目中所有依赖的文件
- 确保打包结果不包含源代码
- 确保结果不会被轻松反编译
安装
$ npm i -g pkg
简单使用
命令行
可以先用帮助看一下 pkg 的用法和参数,并不是很复杂。
$ pkg --help
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
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 node6-alpine-x64 index.js
– Makes executables for target machines of your choice
$ pkg -t node4-linux,node6-linux,node6-win index.js
– Bakes '--expose-gc' into executable
$ pkg --options expose-gc index.js
最简单的打包命令如下。
$ pkg index.js
使用以上命令即可完成对入口文件为 index.js
项目的打包,打包程序会自动遍历 js 文件中引用的其他文件并打到包内。完成后会分别得到 Mac、Linux 和 Windows 三个平台的可执行文件。
打包脚本
如果经常使用打包命令,最好还是在 package.json
文件里面添加一条打包命令。
{
"name": "pkg",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "pkg . -t node12-linux-x64 -o server -d"
},
"bin": "./index.js"
}
然后直接运行下面的命令即可打包。
$ yarn build
$ npm run build // 或者
参数说明如下:
--target
或-t
表示目标 Node 版本、操作系统平台和架构--output
或-o
表示得到的可执行文件名--debug
或-d
表示调试模式,将所有调试信息都在控制台打印出来
Egg 项目下使用
Egg 项目有些特殊的情况,因此需要特殊处理。
具体的特殊情况如下所示:
- 没有入口文件
run
目录运行时要写入文件public
目录存放静态文件- 其他文件
没有入口文件
在根目录下创建一个 entry.js
文件。
// entry.js
'use strict';
require(__dirname + '/node_modules/egg-scripts/bin/egg-scripts');
package.json
{
"bin": "entry.js"
}
光是这些还不够,因为入口文件并没有实际引用项目的文件,还需要在配置中直接指出要打包的项目文件。
package.json
{
"bin": "entry.js"
"pkg": {
"scripts": [
"app.js",
"app/**/*.js",
"config/*.js"
]
}
}
Pkg 的文档中指出有两种方式可以手动指定要打包的文件,一种是 assets
,另一种是 scripts
,这里我们使用 scripts
来指定,因为 assets
主要是存放静态文件的,在打包的过程中不会做任何编译而直接把文件打进来,这样的话稍微一反编译文件就全都出来了,现在我们指定的文件正是我们便携的源代码文件,如果这样打包就失去了为了防止源代码泄露的意义了;而 scripts
表示的是要转成 V8 bytecode 的文件,并且打包工具还对 Node 运行时做了修补,告诉 V8 引擎在编译成 bytecode 时不保留源代码。
run
目录运行时要写入文件
在 config.default.js
中配置运行目录的地址,这样可以在实际运行的时候在可执行文件的目录下创建一个 run
目录。
config.rundir = process.cwd() + '/run';
public
目录存放静态文件
因为当前的项目没有使用静态文件,因此直接就关闭 static
插件就好了。
// config/plugin.js
static: {
enable: false,
},
其他文件
目前还存在一些其他文件的问题,比如
- app 目录下的
.graphql
定义文件 - node_modules 下的文件
- app 目录下的安装的插件自带 node_modules 目录
首先是前两种情况文件没有被包含进来,而第三种情况则是被手动包含进来以后要编译成 V8 的 bytecode,而有些 ES6 的原文件不能被当前的运行时所识别直接报错。
因此需要在 scripts
中排除所有 node_modules
下的文件,并在 assets
中包含进来。注意这里用的是 glob 的语法,需要将要排除的 pattern 放在最后面才能生效。
"bin": "entry.js",
"pkg": {
"scripts": [
"app.js",
"app/**/*.js",
"config/*.js",
"!**/node_modules"
],
"assets": [
"app/**/*.json",
"app/**/*.graphql",
"**/node_modules/**/*"
]
}
然后在打包的时候输入以下命令就可以了。
$ yarn build
$ npm run build // 或者
在使用可执行文件的时候输入以下命令。
$ ./server start /snapshot/<project-name> --daemon --title=<egg-application-title>
<project-name>
表示当前项目所在的目录名称<egg-application-title>
表示应用名称