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

问题:

  公司有个工具型项目使用node.js 开发,需要部署到客户的服务器中,遇到的问题:

  1、客户的服务器没有外网。环境配置,依赖安装等都比较麻烦,只能手工上传,最好能一个文件直接搞定;

  2、直接包源码部署到客户的机器中,存在源码泄露的风险。

方案:

  使用pkg npm包可以很好的解决我们以上的问题。

  先参考前人的经验 : Egg.js线上部署——利用pkg打包Egg.js工程 

pkg原理:

  pkg打包工具主要会按平台(支持window、mac、linux)分别打包。

  pkg中会包含node的可执行文件,还会包含你要打包进去的代码。代码通过一个虚拟的文件系统把所有的代码和资源文件都挂载到 /snapshot/${被打包项目的文件夹名} 下面(pkg hack了 fs 的很多方法,拦截文件操作,如果发现读的文件路径是在挂载目录下就特殊处理,返回打包进去的文件信息,如果不在挂载目录下,则按node默认逻辑进行)

  pkg 会根据被打包项目的package.json 中的licence声明判断是否要对源码进行编译,npm上安装的依赖包基本都是开源的,会以源码的形式打包;而用户的源码如果没有声明开源的协议,则会把js文件编译为v8字节码进行保存(在项目中也就没法通过fs读取到源码,会给出抛错,即使是破解安装包也只能拿到v8字节码)

实现:

1、安装(也可以考虑只安装到项目内,通过npm script 调用打包) 

npm install pkg -g

2、在egg配置文件中把涉及到写文件的路径都移到包外(pkg的虚拟文件系统只是用来应对读的行为,所有写相关都得移出包外) 

复制代码
// 通过process.cwd()获取当前执行文件执行的路径
config.rundir = process.cwd() + '/run';// 配置执行时临时文件的路径
config.logger = {
    dir: path.join(process.cwd(), 'logs', logDir),//配置普通日志文件地址
  };
  config.customLogger = {
    scheduleLogger: {
      file: path.join(process.cwd(), 'logs', logDir, 'egg-schedule.log'),//配置定时任务日志的地址
    },
  };
  config.static = { // 必须把public移出项目,否则在pkg的包中egg的static中间件会有对public操作(确保文件夹),会有抛错
    prefix: '/',
    dir: process.cwd() + '/public',//配置静态文件的地址
  };
复制代码

 3、修改package.json文件

复制代码
{
    ... 


  "bin": "pkg-entry.js", // 执行包的入口文件,可执行包启动的时候默认会调用该文件

  "pkg": {// 以下主要是声明那些文件需要被打包(pkg会解析require中的静态路径,但在egg.js中很多文件都是通过框架引用的,无法依赖解析)
    "scripts": [ // 这里是声明需要打包的js文件,这里的声明的js文件都会被编译为v8字节码(建议主动声明,不要依赖pkg自动引入)
      "./app/**/*.js",
      "./config/**/*.js",
      "./normalJs/**/*.js", // 不只是egg的文件,只要要用到就要声明打包
      "./app.js",
      "./agent.js"
    ],
    "assets": [ // 这里是声明需要打包的静态文件(即使有js文件也不进行编译)。
      "./lib/**/*",// lib中是我打算开放的一些egg组件,所有不需要编译
      "./app/public/**/*",// 如果要把前端静态文件打包进来,就直接声明(但是在egg中static中间件会有抛错,需要hack egg或者 hack pkg)
      "./node_modules/**/*"// npm安装的所有依赖包全部打包进来,不要依赖自动引入,很容易导致部分文件没打包,出现各种意料外的错误
    ]
  },
}
复制代码

 4、增加入口文件pkg-entry.js(名字保持和package.json中一致)

复制代码
const fs = require('fs');

// 如果是egg的ts项目,由于egg-script会给ts项目通过-r引入sourcemap的注入文件,但是pkg的spawn不支持,所以把项目标识为飞ts
// 如果不是ts项目忽略一下两行
const pkgInfo = require('./package');
pkgInfo.egg.typescript = false; // 防止egg-script识别为 typescript 自动添加soucemap支持(--require 在pkg的spawn中不支持)

//我自己的工具包的配置文件是直接打包到安装包里面的,这样就不方便修改配置了。于是把提供给运维配置的配置都适用dotenv来配置,以下引入dotenv的预执行脚本
//也可以考虑把配置文件放到包外,不过因为包内执行包外js,会增加被攻击的风险
require('dotenv/config');


// 由于egg-script是默认以当前执行proccess.cwd() 路径为默认项目的,打包后需要每次输入 /snapshot/${项目文件夹名} 作为指定目录
// 所以,以下为修改参数,自动嵌入“/snapshot/${项目文件夹名}”
const baseDir = '/snapshot/' + fs.readdirSync('/snapshot')[0];
console.log('baseDir:', baseDir);
// 当 start 的时候,自动嵌入bashDir为 /snapshot/${项目文件夹名}
const startIndex = process.argv.indexOf('start');
if (startIndex > -1) {
    process.argv = [].concat(
        process.argv.slice(0, startIndex + 1),
        baseDir,
        process.argv.slice(startIndex + 1),
    );
}

// 然后直接调起egg-scripts执行
require('./node_modules/egg-scripts/bin/egg-scripts.js');
复制代码

 5、 执行打包

## --targets 用于制定平台和node版本,不指定时默认为3个平台以package.json中的node版本配置为准
## --out-path 指定执行包输出文件夹,默认为当前文件夹
## --debug 用于调试,可了解哪些文件被打包
pkg .  --targets node8-linux-x64 --out-path /usr/dist  --debug

6、运行

> chmod a+x ./appName #给可执行包增加执行权限
> ./appName start   # 启动项目,除项目路径其它参数都和egg-scripts一致 可以用--title指定egg服务名
>./appName stop # 关闭项目(会关闭当前服务器的所有egg服务,如果有多个,最好用--title来指定要关闭的项目)

ps:

  如果需要动态配置或者包中使用了c++编译的node依赖包,可参考该文章  Egg.js线上部署——利用pkg打包Egg.js工程 。

可能遇到问题:

第一次打包的时候,会遇到下包很慢很可能超时的问题。如下:

到https://github.com/zeit/pkg-fetch/releases下载对应的包,然后
~/.pkh-cache/2.5/目录下,改名为fetched-v8.11.3-macos-x64(参考运行时下的包名字改)即可。参考https://github.com/zeit/pkg/issues/419

猜你喜欢

转载自www.cnblogs.com/zzsdream/p/12023926.html