Electron 應用實戰 (架構篇)

以下內容已整合到腳手架:sorrycc/dva-boilerplate-electron

近期,我們在內部做了一個類似 IDE 性質的應用,基於 electron。過程中趟過不少坑,也有了些心得,記錄如下。

包含:

  • 數據通訊
  • 架構方案
  • Two-Package 目錄結構
  • 源碼打包
  • 應用打包

數據通訊

數據通訊方案決定整體的架構方案。

翻翻 Electron 文檔,應該不難發現,Electron 有兩個進程,分別為 main 和 renderer,而兩者之間是通過 ipc 進行通訊。main 端有 ipcMain,renderer 端有 ipcRenderer,分別用於通訊。

一個簡單的讀取文件的例子:

main 端

ipcMain.on("readFile", (event, { filePath }) => { content content = fs.readFileSync(filePath, "utf-8"); event.sender.send("readFileSuccess", { content });});

renderer 端

ipcRenderer.on("readFileSuccess", (event, { content }) => { console.log(`content: ${content}`);});ipcRender.send("readFile", { filePath: "/path/to/file",});

我們剛開始也是這麼做,但過了幾星期發現太繞,於是重構成通過 remote 方式。 remote 是一種簡化的通訊方案,內部也是 ipc,所以運行起來和前面的方案並無差別,但使用上簡化很多。比如,上面的例子可簡化如下:

main 端

renderer 端

const content = remote.require("fs").readFileSync("/path/to/file");

參考

  • electron.atom.io/docs/a
  • jlord.us/essential-elec

架構選擇

架構方案有多種,選擇適合自己的。

選擇

在架構方案的選擇上糾結過很久,不過這很大程度是和前面的通訊方案有關的。

方案一

傳統 ipc 方案,main 端用 ipcMain, renderer 端用 ipcRenderer。

方案二

main 端和 renderer 端分別部署一個 dva(不了解 dva 的可以理解為 redux),封裝 ipc 基於 action 通訊。main 端的 action 如果包含 toRenderer 會自動走到 renderer 端的,反之 renderer 端的 action 如果包含 toMain 則自動走到 main 端。

最終方案

上述兩個方案的缺點是:

  1. main 和 renderer 均包含業務邏輯
  2. 通訊邏輯書寫複雜

我們的最終方案是:

  1. main 端無邏輯,全部抽象為 services,提供函數級的方案,就和 restful 的服務端一樣,寫完後等著被調
  2. 由於打包的原因,我們把 main 和 renderer 分別打包成一個文件。所以 main 的 services 要暴露到全局變數,比如 global.services,這樣在 renderer 里才能通過 remote.getGlobals("services") 調用到
  3. renderer 端包含大量業務邏輯,需和 main 通訊時通過 remote 來調
  4. renderer 端我們選擇 react + dva 來組織代碼,把所有邏輯存於 model,保證數據和視圖的徹底分離,以及邏輯的清晰
  5. 目前還沒有遇到 main 主動推消息到 renderer 的需求

參考

  • github.com/dvajs/dva

Two-Package 目錄結構

定完整體架構之後,就要確定目錄結構了,以及如何做構建和打包等等。我們在這也是繞了好大一圈,因為 electron 官網沒有推薦這個,後面慢慢翻文檔才發現這種組織方式的好處。

先說結論,我們採用的是 Two-Package 的目錄結構,並且基於 webpack 打包 main 和 renderer 。

啥是 Two-Package Structure?

Two-Package Structure 是 pack 工具 electron-builder 給的約定,也是目前業界用的較多的方案。

+ dist // pack 完後的輸出,.dmg, .exe, .zip, .app 等文件+ build // background.png, icon.icns, icon.ico+ app // 用於 pack 給用戶的目錄 + dist // src 目錄打包完放這裡 + assets // 字體、圖片等資源文件 + pages // 存放頁面 - package.json // 生產依賴,存 dependencies+ src // 源碼 + main // main + renderer // renderer + shared // main 和 renderer 公用文件- package.json // 開發依賴,存 devDependencies

為啥用 Two-Package Structure?

最大的好處是可以很好地分離開發依賴和生成環境依賴。開發依賴存 package.json,生產依賴存 app/package.json,這樣在 pack 後交付給用戶時就不會包含 webpack, mocha 等等的開發依賴了。

那麼怎麼區別依賴類型呢? 比如:

  • main 端依賴了 fs-extra 是不是生產環境依賴?
  • renderer 端依賴了 react 是不是生產環境依賴?

這沒有標準答案,和源碼打包策略有關,即 src 目錄的源碼是如何到 app/dist 下的。

資源

  • electron-userland/electron-builder

源碼打包

首先打包我們是用的 webpack + babel,分別把 src/main 和 src/renderer 下的文件打包為 app/dist/main.js 和 app/dist/renderer.js。打包 renderer 可以理解,打包 main 可能有人會有疑問。我們打包 main 是為了編碼風格的一致。

externals

我們需要 externals 掉一些不能或不應該被打包到一起的依賴。

  1. renderer 端我們只 externals 了 electron
  2. main 端我們 externals 了所有的依賴

這樣,renderer 端所有的依賴都是開發依賴,main 端的所有依賴都是生產依賴。

所以,在這種打包機制下,前面的問題就有了答案:

  • main 端依賴了 fs-extra 是不是生產環境依賴? -- 是
  • renderer 端依賴了 react 是不是生產環境依賴? -- 不是

externals 配置

main

externals(context, request, callback) { callback(null, request.charAt(0) === "." ? false : `require("${request}")`);},

renderer

externals(context, request, callback) { let isExternal = false; const load = [ "electron", ]; if (load.includes(request)) { isExternal = `require("${request}")`; } callback(null, isExternal);},

資源

  • configuration

應用打包

翻下 electron 開源應用的源碼,我們會發現有些是用 electron-packager,有些是用 electron-builder 。這兩個是什麼關係?我們應該用哪個呢?

答案是用 electron-builder。 electron-builder 是基於 electron-packager 實現的,並在此基礎上做了 Two-Package.json Structure 的約定,以及自動更新等等功能。

Rebuild native-module

由於我們用了 pty.js,包含 C++ 的原生實現。所以在 papck 前需先用 electron-rebuild 做 rebuild。

npm scripts

{ "build": "NODE_ENV=production webpack", "rebuild": "electron-rebuild -d=https://gh-contractor-zcbenz.cnpmjs.org/atom-shell/dist/ -m ./app/node_modules", "pack": "npm run build && npm run rebuild && build"}

Tips

  • rebuild 如果很慢,可能是要翻牆,可嘗試 cnpmjs.org 提供的鏡像,electron-rebuild -d=gh-contractor-zcbenz.cnpmjs.org

資源

  • github.com/electron/ele
  • github.com/electron-use
  • electron.atom.io/docs/t

(完)


推薦閱讀:

基於 Webpack 的應用包體尺寸優化
React 實現一個漂亮的 Table
React Conf 2017 不能錯過的大起底——Day 1!
解析 Redux 源碼
我對Flexbox布局模式的理解

TAG:React | Electron | Redux |