electron 使用 Node.js 原生模組

發表於2019-03-28
  • March 28, 2018

electron-node-addons.jpg

Node.js 原生模組是用 C++ 編寫的 Node.js 擴充套件。C++ 原始碼通過 node-gyp 編譯為 .node 字尾的二進位制檔案(類似於 .dll 和 .so)。在 Node.js 環境中可以直接用 require() 函式將 .node 檔案初始化為動態連結庫。一些 npm 包會包含 C++ 擴充套件,例如: node-ffinode-iconvnode-usb,但都是原始碼版本,在安裝後需要編譯後才能被 Node.js 呼叫。

Electron 同樣也支援 Node 原生模組,但由於和官方的 Node 相比使用了不同的 V8 引擎,如果你想編譯原生模組,則需要手動設定 Electron 的 headers 的位置。

環境準備

不管是 Node.js 環境或是 Electron 中使用原生模組,都需要準備一個編譯工具 node-gyp。我們這裡使用的是 windows 環境開發,參考 node-gyp 的安裝說明還需要安裝 windows-build-tools

用管理員許可權開啟 CMD 或 PowerShell 視窗,執行以下命令:

npm i -g node-gyp
npm i -g --production windows-build-tools複製程式碼

windows-build-tools 安裝時間可能會長一點,要耐心等待。

專案配置

在安裝 npm 模組之前還要設定一些環境變數,建議在專案目錄下放一個 .npmrc 檔案,內容如下:

registry=https://registry.npm.taobao.org
NODEJS_ORG_MIRROR=https://npm.taobao.org/mirrors/node
ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron
arch=ia32
target_arch=ia32
msvs_version=2015
disturl=https://atom.io/download/electron
runtime=electron
target=1.8.4
build_from_source=true複製程式碼

引數說明:
registry - 配置 npm 包映象
NODEJS_ORG_MIRROR - 配置 Node.js 標頭檔案下載映象
ELECTRON_MIRROR - Electron 下載映象
disturl - Electron 標頭檔案映象(用了 electron-rebuild 模組才需要)
arch、target_arch - 根據目標環境定義為 ia32 或 x64

編譯

通過以上配置不需要 electron-rebuild ,直接用 npm 或 yarn 安裝新原生包的時候,會自動編譯為適用當前 electron 版本的原生模組到 {module_name}/build/Release/xxx.node。由於這個構建後的路徑是動態的,node-ffi 等第三方模組會使用 bindings 去動態找到這個 .node 檔案。bindings 的原理是,首先定位到當前包的目錄,然後通過預設一些搜尋路徑,一個個嘗試讀取,直到找到為止。

專案構建

問題:找不到 .node 檔案

bindings 的搜尋方式在 js 原始碼未壓縮的情況下當然沒問題,但我們的專案中通常還使用了 webpack。
在開發模式下能夠找到 node_modules 下的檔案也沒有問題,構建到生產環境後就沒有 node_modules 了,而且 webpack 也不支援打包動態路徑的檔案。我想到兩種解決方案:

方案一、將 node-ffi 拷貝一份修改 bindings 為寫死路徑,當然每個用到 bindings 的包都要修改。

// ffi 下的 bindings.js
module.exports = require('bindings')('ffi_bindings.node')
// 改為
module.exports = require('../build/Release/ffi_bindings.node')複製程式碼

方案二、自己實現一個 bindings 對映,並利用 webpack 的 alias 功能替換 bindings 模組。

  1. 增加 bindings.js
function bindings (name) {
  if (name === 'ffi_bindings.node') {
    return require('ffi/build/Release/ffi_bindings.node')
  }
  if (name === 'binding') {
    return require('ref/build/Release/binding.node')
  }
}

module.exports = bindings複製程式碼
  1. 配置 webpack 別名
// webpack.main.config.js
module.exports = {
  ...,
  resolve: {
    extensions: ['.js', '.json', '.node'],
    alias: {
      // 如果用 bindings 包,就會找不到 .node 模組,這裡替換成自己的實現
      'bindings': path.resolve(__dirname, '../addons/bindings.js')
    }
  },
  ...複製程式碼

顯然方案二更好一點,只需要自己實現一個 bindings.js,而不用去動第三方包的原始碼,所以我們直接用了方案二。

問題:打包 asar 後,提取出 .node 檔案

Electron 官方文件應用程式打包有說明,二進位制檔案不要在 asar 中執行,需要 unpack 出來。我們用了 electron-packager 可以通過 asar 引數配置:

  asar: {
    unpack: '*.node'
  }複製程式碼

這樣,會把 .node 檔案都提取到 app.asar.unpacked 目錄。但是它只負責提取並不會自動更新 .node 檔案的訪問地址到新的路徑。所以我想到了用 webpack 的 file-loader

// webpack.main.config.js
module: {
  rules: [
    {
      test: /\.node$/,
      loader: 'file-loader',
      options: {
        name: '[name].[ext]',
        outputPath: 'addons',
        publicPath: '../../app.asar.unpacked/addons'
      }
    }
  ]
}複製程式碼

我 app 打包後的專案結構是這樣的

app.asar.unpacked
app
---main
---renderer
---package.json複製程式碼

JS 是在 main 目錄的主執行緒裡面訪問原生模組的,打包出來的路徑也是這樣。所以相對路徑是要向上兩級找到 app.asar.unpacked 目錄。

執行一下 npm run build 打包。

20180328194535.png

檔案按我預期那樣生成好了!

好的,執行一下程式:

TypeError: Cannot read property 'int64' of undefined

程式跑不起來了!

20180328194639.png

bindings 也是直接 require('addon.node') 呀,Node.js 官網也是這樣說的。怎麼構建後就不行了呢?

後面,我在webpack 的 loader 中找到 node-loader,裡面說明

在 enhanced-require 中執行 node add-ons
所以,node-loader 是針對魔改過的 require(非 node 環境 require)的。這有可能是在 Electron 或是 webpack 發生的。

於是我對.node檔案增加一個node-loader

module: {
  rules: [
    {
      test: /\.node$/,
      use: [
        'node-loader',
        {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'addons',
            publicPath: '../../app.asar.unpacked/addons'
          }
        }
      ]
    }
  ]
}複製程式碼

然後重新構建。

20180328204230.png

一切問題都解決了!

相關資料:

electronjs.org/docs/tutori…
nodejs.org/dist/latest…
github.com/nodejs/node…
doc.webpack-china.org/loaders/nod…



相關文章