使用pkg打包Node.js应用的方法步骤


Posted in Javascript onOctober 19, 2018

Node.js应用不需要经过编译过程,可以直接把源代码拷贝到部署机上执行,确实比C++、Java这类编译型应用部署方便。然而,Node.js应用执行需要有运行环境,意味着你需要先在部署机器上安装Node.js。虽说没有麻烦到哪里去,但毕竟多了一个步骤,特别是对于离线环境下的部署机,麻烦程度还要上升一级。假设你用Node.js写一些小的桌面级工具软件,部署到客户机上还要先安装Node.js,有点“大炮打蚊子”的感觉。更严重的是,如果部署机器上游多个Node.js应用,而且这些应用要依赖于不同的Node.js版本,那就更难部署了。

理想的情况是将Node.js打包为一个单独的可执行文件,部署的时候直接拷贝过去就行了。除了部署方便外,因为不需要再拷贝源代码了,还有利于保护知识产权。

将Node.js打包为可执行文件的工具有pkg、nexe、node-packer、enclose等,从打包速度、使用便捷程度、功能完整性来说,pkg是最优秀的。这篇文章就来讲一讲半年来我使用pkg打包Node.js应用的一些经验。

pkg的打包原理简单来说,就是将js代码以及相关的资源文件打包到可执行文件中,然后劫持fs里面的一些函数,使它能够读到可执行文件中的代码和资源文件。例如,原来的require('./a.js')会被劫持到一个虚拟目录require('/snapshot/a.js')。

安装

pkg既可以全局安装也可以局部安装,推荐采用局部安装:

npm install pkg --save-dev

用法

pkg使用比较简单,执行下pkg -h就可以基本了解用法,基本语法是:

pkg [options] <input>

<input>可以通过三种方式指定:

1.一个脚本文件,例如pkg index.js;
2.package.json,例如pkg package.json,这时会使用package.json中的bin字段作为入口文件;
3.一个目录,例如pkg .,这时会寻找指定目录下的package.json文件,然后在找bin字段作为入口文件。

[options]中可以指定打包的参数:

1.-t指定打包的目标平台和Node版本,如-t node6-win-x64,node6-linux-x64,node6-macos-x64可以同时打包3个平台的可执行程序;
2.-o指定输出可执行文件的名称,但如果用-t指定了多个目标,那么就要用--out-path指定输出的目录;
3.-c指定一个JSON配置文件,用来指定需要额外打包脚本和资源文件,通常使用package.json配置。

使用pkg的最佳实践是:在package.json中的pkg字段中指定打包参数,使用npm scripts来执行打包过程,例如:

{
 ...
 "bin": "./bin/www",
 "scripts": {
  "pkg": "pkg . --out-path=dist/"
 },
 "pkg": {
  "scripts": [...]
  "assets": [...],
  "targets": [...]
 },
 ...
}

scripts和assets用来配置未打包进可执行文件的脚本和资源文件,文件路径可以使用glob通配符。这里就浮现出一个问题:为什么有的脚本和资源文件打包不进去呢?

要回答这个问题,就涉及到pkg打包文件的机制。按照pkg文档的说法,pkg只会自动地打包相对于__dirname、__filename的文件,例如path.join(__dirname, '../path/to/asset')。至于require(),因为require本身就是相对于__dirname的,所以能够自动打包。假设文件中有以下代码:

require('./build/' + cmd + '.js')
path.join(__dirname, 'views/' + viewName)

这些路径都不是常量,pkg没办法帮你自动识别要打包哪个文件,所以文件就丢失了,所以这时候就使用scripts和assets来告诉pkg,这些文件要打包进去。那么我们怎么知道哪些文件没有被打包呢?难倒要一行行地去翻源代码吗?其实很简单,只需要把打包好的文件运行下,报错信息一般就会告诉你缺失哪些文件,并且pkg在打包过程中也会提示一些它不能自动打包的文件。

注意事项

如果说pkg还有哪儿还可以改进的地方,那就是无法自动打包二进制模块*.node文件。如果你的项目中引用了二进制模块,如sqlite3,那么你需要手动地将*.node文件复制到可执行文件同一目录,我通常使用命令cp node_modules/**/*.node .一键完成。但是,如果你要跨平台打包,例如在windows上打包linux版本,相应的二进制模块也要换成linux版本,通常需要你手动的下载或者编译。

那为什么pkg不能将二进制模块打包进去呢?我猜想是require载入一个js文件和node文件,它们的机制是不一样的。另外从设计来说,不自动打包二进制模块也是合理的,因为二进制模块都是平台相关的。如果我在windows上生成一个linux文件,那么就需要拉取linux版本的.node文件,这是比较困难的。并且有些二进制模块不提供预编译版本,需要安装的时候编译,pkg再牛也不可能模拟一个其他平台的编译环境吧。nexe可以自动打包二进制模块,但是只能打包当前平台和当前版本的可执行文件。这意味着如果Node.js应用引用了二进制包,那么这个应用就不能跨平台打包了,所以我认为这方面,nexe不能算是一个好的设计。

还有一点就是关于项目中的配置文件处理,比如数据库连接参数、环境变量等。因为这些配置文件会跟着不同的部署环境进行更改,所以为了方便更改,一般不希望把配置文件打包到exe。为了避免pkg自动地将配置文件打包到exe中,代码中不要采用以下方式读取配置文件:

fs.readFile(path.join(__dirname, './config.json')), callback)

而是采用相对于process.CWD()的方法读取:

fs.readFile(path.join(process.CWD(), './config.json'), callback)
// 或者
fs.readFile('./config.json', callback)

如果配置文件是js格式的,那么不要直接require('./config'),而是采用动态require:

const config = require(process.CWD() + './config')

另外要提及的是pkg打包之后动态载入js文件会有安全性问题,即用户可以在js文件写任何处理逻辑,注入到打包后的exe中。例如,可以读取exe里面的虚拟文件系统,把源代码导出来。所以,尽量不要采用JS作为配置文件,也不要动态载入js模块。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js 动态添加标签(新增一行,其实很简单,就是几个函数的应用)
Mar 26 Javascript
ASP.NET jQuery 实例15 通过控件CustomValidator验证CheckBoxList
Feb 03 Javascript
Jquery实现兼容各大浏览器的Enter回车切换输入焦点的方法
Sep 01 Javascript
让javascript加载速度倍增的方法(解决JS加载速度慢的问题)
Dec 12 Javascript
JS动态添加选项案例分析
Oct 17 Javascript
JS中跨页面调用变量和函数的方法(例如a.js 和 b.js中互相调用)
Nov 01 Javascript
微信小程序中顶部导航栏的实现代码
Mar 30 Javascript
Javascript中click与blur事件的顺序详析
Apr 25 Javascript
Angular 项目实现国际化的方法
Jan 08 Javascript
JS动画实现回调地狱promise的实例代码详解
Nov 08 Javascript
微信小程序 动态修改页面数据及参数传递过程详解
Sep 27 Javascript
gojs实现蚂蚁线动画效果
Feb 18 Javascript
简化版的vue-router实现思路详解
Oct 19 #Javascript
vue中el-upload上传图片到七牛的示例代码
Oct 19 #Javascript
浅析vue-router原理
Oct 19 #Javascript
vue-cli3.0 脚手架搭建项目的过程详解
Oct 19 #Javascript
vue-quill-editor+plupload富文本编辑器实例详解
Oct 19 #Javascript
vue+VeeValidate 校验范围实例详解(部分校验,全部校验)
Oct 19 #Javascript
浅析JS中什么是自定义react数据验证组件
Oct 19 #Javascript
You might like
php 遍历显示文件夹下所有目录、所有文件的函数,没有分页的代码
2008/11/14 PHP
PHP中防止SQL注入实现代码
2011/02/19 PHP
session在php5.3中的变化 session_is_registered() is deprecated in
2013/11/12 PHP
浅谈使用 PHP 进行手机 APP 开发(API 接口开发)
2014/08/11 PHP
PHP文件缓存类实现代码
2015/10/26 PHP
CodeIgniter配置之SESSION用法实例分析
2016/01/19 PHP
PHP二维数组去重实例分析
2016/11/18 PHP
yii2.0整合阿里云oss的示例代码
2017/09/19 PHP
基于KMP算法JavaScript的实现方法分析
2013/05/03 Javascript
js代码实现点击按钮出现60秒倒计时
2021/01/28 Javascript
JQuery日期插件datepicker的使用方法
2016/03/03 Javascript
微信小程序 倒计时组件实现代码
2016/10/24 Javascript
利用HTML5+Socket.io实现摇一摇控制PC端歌曲切换
2017/01/13 Javascript
angularjs+bootstrap菜单的使用示例代码
2017/03/07 Javascript
vue-star评星组件开发实例
2018/03/01 Javascript
详解用Node.js写一个简单的命令行工具
2018/03/01 Javascript
Vue实现侧边菜单栏手风琴效果实例代码
2018/05/31 Javascript
JS实现的贪吃蛇游戏完整实例
2019/01/18 Javascript
vue中@change兼容问题详解
2019/10/25 Javascript
JS实现字体背景跑马灯
2020/01/06 Javascript
如何基于viewport vm适配移动端页面
2020/11/13 Javascript
[01:06]DOTA2小知识课堂 Ep.02 吹风竟可解梦境缠绕
2019/12/05 DOTA
Python时区设置方法与pytz查询时区教程
2013/11/27 Python
Python兔子毒药问题实例分析
2015/03/05 Python
python监控进程脚本
2018/04/12 Python
pytorch cnn 识别手写的字实现自建图片数据
2018/05/20 Python
详解Django-restframework 之频率源码分析
2019/02/27 Python
通过cmd进入python的实例操作
2019/06/26 Python
浅谈keras中的batch_dot,dot方法和TensorFlow的matmul
2020/06/18 Python
浅谈移动端网页图片预加载方案
2018/11/05 HTML / CSS
中国领先的专业演出票务网:永乐票务
2016/08/29 全球购物
青年文明号口号
2014/06/17 职场文书
教师个人自我剖析材料
2014/09/29 职场文书
python实现语音常用度量方法的代码详解
2021/05/25 Python
MySQL数据库优化之通过索引解决SQL性能问题
2022/04/10 MySQL
详解Anyscript开发指南绕过typescript类型检查
2022/09/23 Javascript