使用 pkg 打包 ThinkJS 項目

在 ThinkJS 的用戶羣裏,常常有開發者提出須要對源碼進行加密保護的需求。咱們知道 JavaScript 是一門動態語言,不像其餘靜態語言能夠編譯成二進制包防止源碼泄露。因此就出現了 pkgnexe 之類的工具,支持將 JS 代碼連同 Node 一塊打包成一個可執行文件,一來解決了環境依賴的問題,二來解決了你們關心的源碼保護的問題。node

pkg 模塊的 README 中,羅列了它的幾大用處,若是你有下面的幾個需求的話建議不妨試試。linux

  • 爲應用提供商業發行版而不用暴露源碼
  • 爲應用提供 demo 而不用暴露源碼
  • 一鍵打包全部平臺可執行文件而不須要對應平臺環境依賴
  • 提供自解壓或自安裝的解決方案
  • 運行應用不須要安裝 Node.js 和 npm
  • 部署僅須要一份單文件,不須要經過 npm 安裝大量的依賴
  • 資源打包後讓應用遷移起來更加方便
  • 在指定 Node.js 版本下對應用進行測試而不須要安裝對應的版本

如何使用

關於 pkg 模塊的基礎使用,你們能夠看 《把你的NodeJS程序給沒有NodeJS的人運行》 這篇文章。經過 npm install -g pkg 在全局安裝上模塊後就能夠在命令行中使用 pkg 命令了。pkg 除了支持在命令行中指定參數以外,還支持在 package.json 中進行配置。git

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

以上就是一個簡單的配置。bin 用來指定最終打包的入口文件,pkg.scriptspkg.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 表示的就是基於 Node 10 打包在 MacOS 平臺上執行的可執行程序。scripts, assetstargets 都支持數組配置多個。github

將入口文件、依賴的腳本和資源、須要編譯的平臺配置好以後,執行 npm run pkg 便可完成編譯。sql

如何打包 ThinkJS

pkg 的原理大概是提供一個虛擬的文件系統,將 __filename, __dirname 等變量以及官方 API 中的 IO 操做方法指向本地文件系統的變量修改爲指向虛擬系統。經過該虛擬文件系統讀取壓縮打包後的程序源碼,提供腳本執行的環境。須要注意的是該虛擬文件系統是隻讀的,因此若是程序中有基於 __dirname 進行讀寫操做的方法,須要規避規避掉。macos

代碼預處理

在 ThinkJS 項目中會有如下兩個地方有文件寫入操做:npm

  1. 項目啓動後會在 runtime/config/${env}.json 下寫入最終的配置文件
  2. 生產環境下默認會在 logs/ 目錄中寫入線上日誌

這些目錄默認都是基於當前項目文件夾的,因此基於以前的理論都須要規避。pkgREADME 中告訴咱們 process.cwd() 仍是會指向到真實的環境中,因此咱們能夠修改以上目錄的位置到 process.cwd() 來解決這個問題。json

//pkg.js
const path = require('path');
const Application = require('thinkjs');

const instance = new Application({
  //在啓動文件中能夠自定義配置 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');
const isDev = think.env === 'development';
const isPkg = 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 自帶的一些服務,若是項目中有用到其它的一些服務,或者說自己的業務邏輯中有涉及到文件寫入的也都須要修改配置。bash

打包配置

項目的寫入操做規避掉以後咱們就能夠正常的配置 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.org git:(master) npm run pkg-build

> thinkjs-official@1.2.0 pkg-build /Users/lizheming/workspace/thinkjs/www.thinkjs.org
> pkg ./ --out-path=dist

> pkg@4.4.0
➜  www.thinkjs.org git:(master) ✗ ls -alh dist
total 577096
drwxr-xr-x   5 lizheming  staff   160B 12 28 17:35 .
drwxr-xr-x@ 30 lizheming  staff   960B 12 28 17:34 ..
-rwxr-xr-x   1 lizheming  staff    87M 12 28 17:34 thinkjs-official-linux
-rwxr-xr-x   1 lizheming  staff    87M 12 28 17:35 thinkjs-official-macos
-rw-r--r--   1 lizheming  staff    82M 12 28 17:35 thinkjs-official-win.exe
➜  www.thinkjs.org git:(master) ✗

後記

項目打包後有一個問題是配置沒辦法修改了,若是有動態配置的需求的話就不是很方便了。這裏提供兩個思路解決該問題:

  1. 將動態的配置配置到環境變量中,程序經過讀取環境變量覆蓋默認的配置。
  2. 利用 ThinkJS 提供的 beforeStartServer() 鉤子在啓動前讀取真實目錄下的配置文件進行配置覆蓋。

    //pkg.js
    const path = require('path');
    think.beforeStartServer(() => {
      const configFile = path.join(process.cwd(), 'config.js');
      const config = 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/mirror... 這裏下載,自行選擇對應的版本和平臺便可。

本文說的打包配置都已在 ThinkJS 官網 項目中實現,想要嘗試的同窗能夠直接克隆官網項目,安裝完依賴後執行 npm run pkg-build 便可在 dist/ 目錄中得到二進制可執行文件。

相關文章
相關標籤/搜索