使用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM