在 ThinkJS 的用户群里,常常有开发者提出须要对源码进行加密保护的需求。咱们知道 JavaScript 是一门动态语言,不像其余静态语言能够编译成二进制包防止源码泄露。因此就出现了 pkg、nexe 之类的工具,支持将 JS 代码连同 Node 一块打包成一个可执行文件,一来解决了环境依赖的问题,二来解决了你们关心的源码保护的问题。node
在 pkg 模块的 README 中,罗列了它的几大用处,若是你有下面的几个需求的话建议不妨试试。linux
关于 pkg 模块的基础使用,你们能够看 《把你的NodeJS程序给没有NodeJS的人运行》 这篇文章。经过 npm install -g pkg
在全局安装上模块后就能够在命令行中使用 pkg
命令了。pkg
除了支持在命令行中指定参数以外,还支持在 package.json
中进行配置。git
{ ... "bin": "production.js", "scripts": { "pkg": "pkg . --out-path=dist/" }, "pkg": { "scripts": [...] "assets": [...], "targets": [...] }, ... }
以上就是一个简单的配置。bin
用来指定最终打包的入口文件,pkg.scripts
和 pkg.assets
用来指定除了入口文件以外须要打包进可执行文件中的内容,其中前者用来指定其余 .js
文件,后者用来指定非.js
的资源。pkg.targets
则是用来指定须要打包的平台,平台名称结构以下,node${version}-${platform}-${arch}
。version
用来指定具体 Node 的版本,platform
用来指定编译的平台,能够是 freebsd
, linux
, alpine
, macos
或者 win
,最后 arch
用来指定编译平台的架构,能够是 x64
, x86
, armv6
或者 armv7
。例如 node10-macos-x64
表示的就是基于 Node 10 打包在 MacOS 平台上执行的可执行程序。scripts
, assets
和 targets
都支持数组配置多个。github
将入口文件、依赖的脚本和资源、须要编译的平台配置好以后,执行 npm run pkg
便可完成编译。sql
pkg
的原理大概是提供一个虚拟的文件系统,将 __filename
, __dirname
等变量以及官方 API 中的 IO 操做方法指向本地文件系统的变量修改为指向虚拟系统。经过该虚拟文件系统读取压缩打包后的程序源码,提供脚本执行的环境。须要注意的是该虚拟文件系统是只读的,因此若是程序中有基于 __dirname
进行读写操做的方法,须要规避规避掉。macos
在 ThinkJS 项目中会有如下两个地方有文件写入操做:npm
runtime/config/${env}.json
下写入最终的配置文件logs/
目录中写入线上日志这些目录默认都是基于当前项目文件夹的,因此基于以前的理论都须要规避。pkg
的 README 中告诉咱们 process.cwd()
仍是会指向到真实的环境中,因此咱们能够修改以上目录的位置到 process.cwd()
来解决这个问题。json
//pkg.js const path = require('path'); const Application = require('thinkjs'); const instance = new Application({ //在启动文件中能够自定义配置 runtime 目录 RUNTIME_PATH: path.join(process.cwd(), 'runtime'), ROOT_PATH: __dirname, proxy: true, env: 'pkg', }); instance.run();
基于 production.js
咱们新建一个 pkg.js
启动文件,定义项目启动后的 RUNTIME_PATH
路径,并将 env
赋值为 pkg
,方便后续的配置中经过 think.env === 'pkg'
来切换配置。数组
//src/config/adapter.js const {Console, DateFile} = require('think-logger3'); const isDev = think.env === 'development'; const isPkg = think.env === 'pkg'; exports.logger = { type: isDev ? 'console' : 'dateFile', console: { handle: Console }, dateFile: { handle: DateFile, level: 'ALL', absolute: true, pattern: '-yyyy-MM-dd', alwaysIncludePattern: true, filename: path.join(isPkg ? process.cwd() : think.ROOT_PATH, 'logs/app.log') } };
在 adapter 配置中咱们将原来基于 think.ROOT_PATH
的路径修改为基于 process.cwd()
。除了日志服务以外,若是业务中有使用到 cache 和 session 等服务,它们若是也是基于文件存储的话,也须要修改对应的文件存储配置。固然这些都是 ThinkJS 自带的一些服务,若是项目中有用到其它的一些服务,或者说自己的业务逻辑中有涉及到文件写入的也都须要修改配置。bash
项目的写入操做规避掉以后咱们就能够正常的配置 pkg 而后进行打包处理了。一份简单的 pkg 模块的配置大概是这样的:
//package.json { "bin": "pkg.js", "pkg": { "assets": [ "src/**/*", "view/**/*", "www/**/*" ], "targets": [ "node10-linux-x64", "node10-macos-x64", "node10-win-x64" ] } }
这里咱们指定了 pkg.js
为打包的入口文件,指定了须要编译出 linux
, macos
, win
三个平台的可执行脚本,同时指定了须要将 src/
, view/
, www/
三个目录做为资源一块打包进去。这是由于 ThinkJS 是动态 require 的项目,具体的业务逻辑都是在执行的时候经过遍历文件目录读取文件的形式载入的,对于 pkg
模块打包来讲没法在编译的时候知道这些依赖关系,因此须要做为启动依赖的“资源”一块打包进去。
配置好后直接在项目目录下执行 pkg .
,若是一切 OK 的话应该能在当前目录中看到三个可执行文件,直接执行对应平台的二进制文件便可启动服务了。
➜ www.thinkjs.org git:(master) npm run pkg-build > thinkjs-official@1.2.0 pkg-build /Users/lizheming/workspace/thinkjs/www.thinkjs.org > pkg ./ --out-path=dist > pkg@4.4.0 ➜ www.thinkjs.org git:(master) ✗ ls -alh dist total 577096 drwxr-xr-x 5 lizheming staff 160B 12 28 17:35 . drwxr-xr-x@ 30 lizheming staff 960B 12 28 17:34 .. -rwxr-xr-x 1 lizheming staff 87M 12 28 17:34 thinkjs-official-linux -rwxr-xr-x 1 lizheming staff 87M 12 28 17:35 thinkjs-official-macos -rw-r--r-- 1 lizheming staff 82M 12 28 17:35 thinkjs-official-win.exe ➜ www.thinkjs.org git:(master) ✗
项目打包后有一个问题是配置没办法修改了,若是有动态配置的需求的话就不是很方便了。这里提供两个思路解决该问题:
利用 ThinkJS 提供的 beforeStartServer()
钩子在启动前读取真实目录下的配置文件进行配置覆盖。
//pkg.js const path = require('path'); think.beforeStartServer(() => { const configFile = path.join(process.cwd(), 'config.js'); const config = require(configFile); think.config(config); });
另外随着项目的复杂度提升,业务内可能会引入大量的第三方模块。前文只是解决了 ThinkJS 项目自己的动态引入问题,若是引入的第三方模块也有动态引入的话也须要在 pkg.assets
配置中显示指定出来。还有就是针对 C++ 模块,pkg
目前尚未办法作到自动引入,一样须要在 pkg.assets
中指定依赖资源。
//package.json { "pkg": { "assets": [ //以 node-sqlite3 模块为例 "node_modules/sqlite3/lib/binding/node-v64-darwin-x64/node_sqlite3.node" ] } }
其中 node-v64-darwin-x64
可能会根据平台不同致使名字不太同样。没法引入 .node
模块的缘由是由于 C++ 模块安装的时候会经过 node-gyp
进行动态编译,该操做是和平台相关的。也就是说该特性和 pkg
模块在一个平台上能打包全部平台的二进制包特性是冲突的,毕竟 pkg
模块也没办法在 Mac 平台上编译 Windows 平台的模块。因此在这种状况下除了须要手动引入编译后的 .node
模块以外,还须要注意引入的该 .node
模块和 pkg.targets
指定的编译平台的一致性。
获取 .node
模块除了在对应平台模块安装以外,也能够选择下载其它同窗提供编译好的模块。淘宝源上提供了不少二进制模块的编译后结果,以 node-sqlite3
为例,它的全部编译模块能够在 https://npm.taobao.org/mirror... 这里下载,自行选择对应的版本和平台便可。
本文说的打包配置都已在 ThinkJS 官网 项目中实现,想要尝试的同窗能够直接克隆官网项目,安装完依赖后执行 npm run pkg-build
便可在 dist/
目录中得到二进制可执行文件。