平时交付 node 应用,前后端混淆一下代码,打包成一个docker镜像就交付了。虽然懂的人进入 docker 下依旧能拿到所有代码,但好歹防了一手小白。突然碰到一个客户,要求应用在windows下使用,还不允许装 docker。如果进行裸部,显得十分不专业。
项目中用了 egg + egg-mysql。
心路历程
- 如果裸部,则需要给客户安装 node 环境,安装 node 依赖(还包括内部私有的依赖),混淆代码。而客户现场又不通网,简直是地狱难度。
UglifyJs不支持 ES6。项目使用 node 12 来写,直接就用了 ES6 语法。如果要使用这个混淆工具,还要配置一遍 babel,过于繁琐了。- egg 的配置是动态载入的,混淆时一个不小心,这部分还可能出问题,很忧伤。
- 去 node 社区一番寻觅,发现有很多打包的解决方案。
pkg是一款不错的打包工具。它支持 win、linux、macos 等平台,构建产物是对应平台下的一个可执行文件(包含了 node 环境),一键执行,十分便捷。
过程
配置
安装了pkg后,在 package.json 中添加相关配置。
1 | { |
bin很重要,它是你应用对外暴露的入口。我们使用了 egg,这里需要把启动命令提出来:
1 | // run.js |
pkg-assets/scripts是需要的资源文件。pkg-targets是配置产物运行的环境。
下载 node 环境的依赖
配置完了,进入项目目录输入一下命令开始构建应用。
pkg . –out-path ./dist –debug
因为网络问题,第一步下载依赖就卡住了。
通过看pkg源码,它使用了一个pkg-fetch的包来管理依赖文件。它会去os.homedir()/.pkg-cache/v2.6/目录下看有没有缓存,没有就下载,有就直接使用。那么我们去这里把依赖下下来,放到这个目录下就好了。记得改名适应它的匹配规则:
ps:node12会默认下载 latest 的版本,node 发版本了会造成缓存失效,又进行依赖下载。可以指定node12.13.1来使用以前下的缓存。
ps:过了几天在su下执行了构建,发现又要下依赖。心想这不科学啊,一番跟踪发现su下的os.homedir()和普通用户的 home 目录不同~~
构建出错
我们在构建时加了--debug,输出日志方便定位问题。
Error! This experimental syntax requires enabling one of the following parser plugin(s): ‘decorators-legacy, decorators’ (14:0)
/Users/bm/Documents/personal-project/projectprojectname/node_modules/loaders.css/loaders.css
这个错误是因为pkg会分析配置文件里的dependencies的各种包。有些前端的包含有 less 文件,有@语法,引起了这个报错。我们在上面进行构建时,没有特别指定配置文件,pkg则使用了默认的 package.json,由于前后端都放在一个工程开发了,这才引起了这个问题。
我们可以指定自己的配置文件,剔除前端的依赖。
运行出错
解决了构建时的问题,美滋滋的得到了第一个产物包。一运行,发现数据库实例没有初始化,egg 也没有报错。又经过了漫长的追踪,发现因为 plugin 是 egg 动态引入的,pkg 在构建时无法分析出这一点,所以 egg-mysql 不会自动被打入构建产物中(虽然它已经在 dependencies 中了)。我们要在pkg/assets中手动加入。
使用脚本构建
1 | // package.json中 |
运行
我们成功的打出了包,该怎么运行呢?我们的构建产物就是一个可执行文件,和原来的启动脚本执行相同的操作,只是文件路径变了,会有一个前缀/snapshot。进入文件目录下,执行:
1 | ./app-macos start /snapshot/app --port=7001 --env=prod --workers=2 |
snapshot是pkg虚拟出来的目录结构。
原理
抄了一段pkg的打包原理:
pkg 的打包原理简单来说,就是将 js 代码以及相关的资源文件打包到可执行文件中,然后劫持 fs 里面的一些函数,使它能够读到可执行文件中的代码和资源文件。例如,原来的 require(‘./a.js’)会被劫持到一个虚拟目录 require(‘/snapshot/a.js’)。
和node-packer的对比:
Pkg hacked fs.* API’s dynamically in order to access in-package files, whereas Node.js Compiler leaves them alone and instead works on a deeper level via libsquash. Pkg uses JSON to store in-package files while Node.js Compiler uses the more sophisticated and widely used SquashFS as its data structure.
参考
Egg.js线上打包部署
例子
node打包讨论帖子
http://enclose.io/
pkg-decompile
结尾
看看这文章,吧唧吧唧就这么点,也不是很困难嘛。可是我回想起捣鼓的这一天,一步一步调试的绝望和挣扎,就在质疑当时的自己,搞什么 egg,搞事情。以防遗忘,记录一下。
顺便说一句,总有大牛还能反编译出咱的代码~~