title: Electron:跨平台的視窗應用程式
date: 2017-09-20 08:00
categories: Training
keywords:
很多事情沒有經歷過就無法深刻體會,所以”感同身受”真的很難,但是軟體工程師是一個很特別的職務,尤其是與業務的隔閡,說給同業的朋友聽時,既使他沒遇過同樣的事情,但是也可以感同身受了解你的苦處。
Q業務:『客戶需要一個 Windows 程式。』
Q業務:『客戶的老闆是用 MacBook,所以系統要能支援 Mac,麻煩你加一下。』
Q業務:『客戶的產線主管希望操作員也可以直接重機台的電腦上使用系統,不過他們是用 Linux,你再花點時間改一下就好。』
Q業務:『產線有些嵌入式電腦(ARM),系統裝不起來,麻煩處理一下。』
當事情發生在別人身上時那是喜劇,你可以安慰他”撐過去就是你的”;
當事情發生在自己身上時那就變成悲劇,你可能會…
在 Node.js 興盛起來後全端工程師(Full Stack Developer)的議題又再被炒熱起來,因為透過 Node.js 讓原本侷限在前端的 JavaScript 變成可以開發後端程式,這讓門檻瞬間降低很多。
Node.js 為 JavaScript 提供一些 API 來替 JavaScript 與作業系統甚至硬體設備溝通,開發過 Ionic 或是 Cordova 程式的人應該就會發覺這跟 Cordova 在做的事情幾乎一樣。
在 Cordova 架構下,它前面透過 WebView 來呈顯網頁,後面提供一些 API 來與系統或硬體界接,中間則是讓我們用 JavaScript 撰寫邏輯,往前可以與前端網頁互動,往後可以與系統或硬體溝通,整個架構就像:HTML(WebView) <=> JavaScript <=> API(Cordova) <=> OS、Hardware
那桌面系統是不是也有相同的方案?
我們開啟 Electron 官方網站 在網站內可以看到目前 Electron 最新版本為 1.7.6 版、Node 為 7.9.0 版、Chromium 為 58.0.3029.110 版、V8為 5.8.283.38 版,也就是說 1.7.6 版的 Electron 內含 7.9.0 版的 Node.js、58.0.3029.110 版的 Chromium,JavaScript 解析引擎為 5.8.283.38 版V8引擎。
比對 Cordova 架構就會變成:HTML(Chromium) <=> JavaScript <=> API(Node.js/V8) <=> OS、Hardware
我們需要一個純前端的 Web 程式,這邊我們拿之前練習到 Angular 服務 的程式來當範例。
{% img /images/download.png 36 %}first-app_2017-09-14.zip
若要下載請記得先透過指令
npm install
來重新安裝 package。
透過 Angular CLI 指令 ng build --prod
來建置專案,預設專案會輸出到專案目錄下的 dist
資料夾內。
我們先建立一個資料夾 desktopApp
,接著透過 npm init
來建立一個 package.json
檔,entry point
的設定值改成 main.js
,因為這是 Electron 官方範本預設程式進入點。
接著安裝 electron,指令如下:npm install -d electron
開啟 package.json
並在 scripts
插入新指令 "start": "electron main.js"
。
{% codeblock package.json lang:json %}
{
“name”: “desktopapp”,
“version”: “1.0.0”,
“description”: “”,
“main”: “main.js”,
“scripts”: {
“test”: “echo "Error: no test specified" && exit 1”,
“start”: “electron main.js”
},
“author”: “”,
“license”: “ISC”,
“dependencies”: {
“electron”: “^1.7.6”
}
}
{% endcodeblock %}
再來將剛才建置好的 Angular 專案(dist 資料夾)複製到 desktopApp 資料夾內。
開啟官方在 GitHub 的 electron-quick-start 專案,將 main.js 複製到 desktopApp 資料夾內。
我們透過 npm start
來啟動,它會執行剛才加入的指令 electron main.js
,它會開啟一個 electron 程式 並自動載入 main.js
。
不過目前畫面一片雪白,按 F12
也無法開啟開發人員工具,Electron 不是內含 Chromium 嗎?怎麼開不起來?
檢視 main.js
,原來預設開發人員工具沒有開啟,取消註解,重新執行就可以看到開發人員工具。
雖然仍然沒畫面,但是從開發人員工具可以知道是起始頁面(index.html)的路徑錯誤造成的。
開啟 main.js
,修正起始頁面的路徑,順便可以調整預設視窗大小,重新執行。
再次失敗畫面仍然沒出現,錯誤訊息顯示 index.html 內的 js 檔與 css 檔找不到,切換到 Elements 頁籤可以發現 index.html 確實被載入了,但是怎麼 js 檔與 css 檔找不到?
看一下存取路徑怎麼怪怪的。
開啟 dist\index.html
將 base
tag 的 href 屬性改成 ./
。
重新執行就正常了。
最後做一些 main.js
的設定微調。
關閉開發者工具:將 mainWindow.webContents.openDevTools()
註解起來。
隱藏主選單列:在 createWindow
方法內加入 electron.Menu.setApplicationMenu(null);
。
{% codeblock main.js lang:js %}
const electron = require(‘electron’)
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const path = require(‘path’)
const url = require(‘url’)
// Keep a global reference of the window object, if you don’t, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 1200, height: 800})
electron.Menu.setApplicationMenu(null);
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, ‘/dist/index.html’),
protocol: ‘file:’,
slashes: true
}))
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on(‘closed’, function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on(‘ready’, createWindow)
// Quit when all windows are closed.
app.on(‘window-all-closed’, function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== ‘darwin’) {
app.quit()
}
})
app.on(‘activate’, function () {
// On OS X it’s common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
// In this file you can include the rest of your app’s specific main process
// code. You can also put them in separate files and require them here.
{% endcodeblock %}
重新執行,整體感覺已經跟一般桌面程式無異。
最後當然是最重要的如何佈署到平台上,首先安裝 electron-packager 套件,指令如下:npm install -d electron-packager
electron-packager 的語法如下:electron-packager <sourcedir> <appname> --platform=<platform> --arch=<arch> [optional flags...]
sourcedir
:表示來源資料夾,.
表示當前來源為資料夾。appname
:輸出的執行檔名稱。--platform
:表示要建置的平台,目前有:linux
、win32
、darwin
,、mas
、all
。--arch
:表示目前系統架構,選項有:ia32
、x64
、armv7l
、arm64
、all
。其他參數可參考 官方文件。
在 package.json
的 scripts
插入下列指令:electron-packager . WinApp --platform=win32 --arch=x64
執行 npm run build_win
來建置。
編譯後可以看到專案資料夾內多了一個 WinApp-win32-x64
資料夾,資料夾內有一隻 WinApp.exe
,執行後就會看到與剛才相同的操作介面。
我們打開 WinApp-win32-x64\resources\app\
資料夾其實可以發現程式都在裡面。
如果不希望別人可以輕易看到程式碼,可以加上 --asar
參數,讓 electron-packager 幫你封裝起來。"build_win": "electron-packager . WinApp --platform=win32 --arch=x64 --asar"
重新編譯後就可以看到我們的程式被封裝成 app.asar
。
如果資料夾已經存在,請先移除,否則請加上
--overwrite
參數來覆寫。
在 package.json
的 scripts
插入下列指令:electron-packager . LinuxApp --platform=linux --arch=x64 --asar
,並透過指令 npm run build_linux
編譯。
將編譯所產生的 LinuxApp-linux-x64
資料夾複製至 Linux 64位元系統(筆者測試環境是 Ubuntu 17 64位元),並執行 LinuxApp
,可以看到跟 Winodws 一樣的結果。
在 package.json
的 scripts
插入下列指令:electron-packager . ARMApp --platform=linux --arch=armv7l --asar
,並透過指令 npm run build_arm
編譯。
將編譯所產生的 ARMApp-linux-armv7l
資料夾複製至 Linux ARM 系統(筆者測試環境硬體是 Raspberry Pi 3,系統是 Raspbian),並執行 ARMApp
,可以看到跟 Winodws 一樣的結果。
因為筆者沒有 Mac 所以在此不做測試,但是方法應該大同小異。
最後我們來回想最前面的需求:客戶需要一種能在任何桌面系統上執行的程式。
在以往我們可能每個平台都需要額外學習很多技術來支援,甚至每種平台都需要專屬的開發人員,在時程上跟後續維護都會增加很多成本。
Electron 可以幫我們解決不少問題,當然它也有一些限制以及缺點,沒有最好的方案,只有最可行的辦法。
筆者以前主要用 C# 開發 Windows 程式,那時在客戶那 demo 完系統後,最怕遇到客戶說:『你們的系統能不能在瀏覽器上執行。』,現在真的有跨多平台需求時,我們可以對老闆說:『老闆,你找人來開發 Web 平台,剩下平台我一個人搞定。』。
{% img /images/download.png 36 %}desktopApp_2017-09-20.zip