使用pkg打包ThinkJS项目的方法步骤
在ThinkJS的用户群里,经常有开发者提出需要对源码进行加密保护的需求。我们知道JavaScript是一门动态语言,不像其他静态语言可以编译成二进制包防止源码泄露。所以就出现了pkg、nexe之类的工具,支持将JS代码连同Node一块打包成一个可执行文件,一来解决了环境依赖的问题,二来解决了大家关心的源码保护的问题。
在pkg模块的README中,罗列了它的几大用处,如果你有下面的几个需求的话建议不妨试试。
- 为应用提供商业发行版而不用暴露源码
- 为应用提供demo而不用暴露源码
- 一键打包所有平台可执行文件而不需要对应平台环境依赖
- 提供自解压或自安装的解决方案
- 运行应用不需要安装Node.js和npm
- 部署仅需要一份单文件,不需要通过npm安装大量的依赖
- 资源打包后让应用迁移起来更加方便
- 在指定Node.js版本下对应用进行测试而不需要安装对应的版本
如何使用
关于pkg模块的基础使用,大家可以看《把你的NodeJS程序给没有NodeJS的人运行》这篇文章。通过npminstall-gpkg在全局安装上模块后就可以在命令行中使用pkg命令了。pkg除了支持在命令行中指定参数之外,还支持在package.json中进行配置。
{ ... "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表示的就是基于Node10打包在MacOS平台上执行的可执行程序。scripts,assets和targets都支持数组配置多个。
将入口文件、依赖的脚本和资源、需要编译的平台配置好之后,执行npmrunpkg即可完成编译。
如何打包ThinkJS
pkg的原理大概是提供一个虚拟的文件系统,将__filename,__dirname等变量以及官方API中的IO操作方法指向本地文件系统的变量修改成指向虚拟系统。通过该虚拟文件系统读取压缩打包后的程序源码,提供脚本执行的环境。需要注意的是该虚拟文件系统是只读的,所以如果程序中有基于__dirname进行读写操作的方法,需要规避规避掉。
代码预处理
在ThinkJS项目中会有以下两个地方有文件写入操作:
- 项目启动后会在runtime/config/${env}.json下写入最终的配置文件
- 生产环境下默认会在logs/目录中写入线上日志
这些目录默认都是基于当前项目文件夹的,所以基于之前的理论都需要规避。pkg的README中告诉我们process.cwd()还是会指向到真实的环境中,所以我们可以修改以上目录的位置到process.cwd()来解决这个问题。
//pkg.js constpath=require('path'); constApplication=require('thinkjs'); constinstance=newApplication({ //在启动文件中可以自定义配置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'); constisDev=think.env==='development'; constisPkg=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自带的一些服务,如果项目中有用到其它的一些服务,或者说本身的业务逻辑中有涉及到文件写入的也都需要修改配置。
打包配置
项目的写入操作规避掉之后我们就可以正常的配置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.orggit:(master)npmrunpkg-build >thinkjs-official@1.2.0pkg-build/Users/lizheming/workspace/thinkjs/www.thinkjs.org >pkg./--out-path=dist >pkg@4.4.0 ➜www.thinkjs.orggit:(master)✗ls-alhdist total577096 drwxr-xr-x5lizhemingstaff160B122817:35. drwxr-xr-x@30lizhemingstaff960B122817:34.. -rwxr-xr-x1lizhemingstaff87M122817:34thinkjs-official-linux -rwxr-xr-x1lizhemingstaff87M122817:35thinkjs-official-macos -rw-r--r--1lizhemingstaff82M122817:35thinkjs-official-win.exe ➜www.thinkjs.orggit:(master)✗
后记
项目打包后有一个问题是配置没办法修改了,如果有动态配置的需求的话就不是很方便了。这里提供两个思路解决该问题:
- 将动态的配置配置到环境变量中,程序通过读取环境变量覆盖默认的配置。
- 利用ThinkJS提供的beforeStartServer()钩子在启动前读取真实目录下的配置文件进行配置覆盖。
//pkg.js constpath=require('path'); think.beforeStartServer(()=>{ constconfigFile=path.join(process.cwd(),'config.js'); constconfig=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/mirrors/sqlite3这里下载,自行选择对应的版本和平台即可。
本文说的打包配置都已在ThinkJS官网项目中实现,想要尝试的同学可以直接克隆官网项目,安装完依赖后执行npmrunpkg-build即可在dist/目录中获得二进制可执行文件。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。