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配置。

项目所在文件夹中缺少package.json文件的情况下:

npm init -f //强迫初始化文件
npm install bluebird --save

使用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模块。

使用pkg打包Node.js应用的方法步骤的更多相关文章

  1. 使用pkg打包node.js项目(egg框架)为可执行包

    问题: 公司有个工具型项目使用node.js 开发,需要部署到客户的服务器中,遇到的问题: 1.客户的服务器没有外网.环境配置,依赖安装等都比较麻烦,只能手工上传,最好能一个文件直接搞定: 2.直接包 ...

  2. TODO:Node.js pm2使用方法

    TODO:Node.js pm2使用方法 pm2 是一个带有负载均衡功能的Node应用的进程管理器. 当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载, PM2是完 ...

  3. Centos7 中 Node.js安装简单方法

    最近,我一直对学习Node.js比较感兴趣.下面是小编给大家带来的Centos7 中 Node.js安装简单方法,在此记录一下,方便自己也方便大家,一起看看吧! 安装node.js 登陆Centos ...

  4. Nginx 配置HTTPS 与Node.js 配置HTTPS方法

    前段时间公司网站要求加上HTTPS安全CA证书,公司服务器全是阿里云服务器,并且配有负载均衡,所以选择直接在阿里云购买CA证书,阿里云有一种证书可以免费试用一年,决定申请此证书,阿里云证书需要验证,阿 ...

  5. 从官网学习Node.js FS模块方法速查

    最新文档请查看仓库 https://github.com/wangduandu... 1. File System 所有文件操作提供同步和异步的两种方式,本笔记只记录异步的API 异步方式其最后一个参 ...

  6. Node.js 常用Mongoose方法

    Node.js 手册查询-Mongoose 方法 一.Schema 一种以文件形式存储的数据库模型骨架,无法直接通往数据库端,也就是说它不具备对数据库的操作能力.可以说是数据属性模型(传统意义的表结构 ...

  7. Node.js常用express方法

    Node.js 手册查询-Express 方法 1.send方法 send 方法向浏览器发送一个响应信息,并可以智能处理不同类型的数据 send方法在输出响应时会自动进行一些设置,比如HEAD信息.H ...

  8. 20.Node.js EventEmitter的方法和事件

    转自:http://www.runoob.com/nodejs/nodejs-tutorial.html EventEmitter 提供了多个属性,如 on 和 emit.on 函数用于绑定事件函数, ...

  9. Node.js FS模块方法速查

    1. File System 所有文件操作提供同步和异步的两种方式,本笔记只记录异步的API 异步方式其最后一个参数是回调函数.回调函数的第一个参数往往是错误对象,如果没有发生参数,那么第一个参数可能 ...

随机推荐

  1. mapper代理方式开发

    使用mapper代理方式开发: 需要编写mapper接口,UserMapper.java需要编写映射文件,UserMapper.xml需要遵循一些开发规范,mybatis便可以自动生成mapper接口 ...

  2. JavaScript学习备忘

    1.您只能在 HTML 输出流中使用 document.write 如果在文档已加载后使用它(比如在函数中),会覆盖整个文档 2.数组 : [40, 100, 1, 5, 25, 10] <== ...

  3. JavaScript中判断整字类型最简洁的实现方法

    这篇文章主要介绍了JavaScript中判断整字类型最简洁的实现方法,本文给出多个判断整数的方法,最后总结出一个最短.最简洁的实现方法,需要的朋友可以参考下 我们知道JavaScript提供了type ...

  4. @RequestBody Spring MVC 示例

    1.前端的访问请求 <script type="text/javascript"> $(document).ready(function(){ var saveData ...

  5. SpringBoot设置文件上传大小限制--默认为1M

    SpringBoot默认上传文件大小不能超过1MB,超过之后会报以下异常:org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeL ...

  6. Android-TextView 控件常用属性以及基本用法

    github地址:https://github.com/1165863642/TextViewDemo 前言 这是我第一次写博客,第一次的笔记,不足之处多谅解.开门见山,这一篇博客主要讲一下在Andr ...

  7. (网页)jQueryAJAXtimeout超时问题详解(转)

    先给大家分析下超时原因: 1.网络不通畅. 2.后台运行比较慢(服务器第一次运行时,容易出现) 超时结果:JQ中 timeout设置请求超时时间. 如果服务器响应时间超过了 设置的时间,则进入 ERR ...

  8. C++反射机制:可变参数模板实现C++反射

    1. 概要   本文描述一个通过C++可变参数模板实现C++反射机制的方法.该方法非常实用,在Nebula高性能网络框架中大量应用,实现了非常强大的动态加载动态创建功能.Nebula框架在Github ...

  9. c++屏蔽Win10系统快捷键

    很久之前实现的功能,也是参考其他人的实现,时间太久,具体参考哪里已经记不得了. 这里不仅能屏蔽一般的快捷键,还可以屏蔽ctrl+atl+del. ; HHOOK keyHook = NULL; HHO ...

  10. LeetCode题解之Find Bottom Left Tree Value

    1.题目描述 2.问题分析 使用层序遍历思想 3.代码 int findBottomLeftValue(TreeNode* root) { if (root == NULL) ; queue<T ...