electron開發采坑小記

前言
原文地址: electron開發采坑小記-SegmentFault 思否
前前前前段時間做了個桌面端的項目(這篇文章拖了挺久了),功能大概是這樣的:
- 項目左右兩欄結構
- 左側
feed
流信息展示 - 右側
webview
打開一個網站 - 將左側信息注入右側網站中
需求很簡單,提高運營效率的輔助工具,但是因為一些原因需要做成一個桌面端。
從前端一下子跨到PC桌面端開發,聽起來跨度有點大,但在實際的開發中因為有了electron
的加持,這一切都變的非常便利,絲毫沒有跨度的痕跡,完全變成了web開發那一套,讓人不禁感嘆js生態的完善,就像有句話說的:
能用JavaScript書寫的終將會用JavaScript書寫。
實際上我並非直接采用electron
,而是使用了更加懶人的electron-vue。通過這一工具將web
開發那一套又轉移到vue
開發上,再輔以iview
做UI,開發起來一下子高效了不少。
我們知道electron
,但是electron-vue
是個什么東西呢,我們還需要了解一下。這東西的官方介紹是這樣的。
An Electron & Vue.js quick start boilerplate with vue-cli scaffolding, common Vue plugins, electron-packager/electron-builder, unit/e2e testing, vue-devtools, and webpack.
從這個介紹大致可以看出來這是個基於vue-cli
的使用vue
開發electron
應用的腳手架。可以幫助我們做不少的vue
與electron
之間結合的基礎工作,讓我們的開發更加便捷。那么我們就來看看使用electron-vue
怎么做開發。我們先從基礎的安裝開始。
安裝
我覺得這個步驟叫安裝並不是很嚴謹,因為electron-vue
並不是一個單獨的npm
包,而是vue-cli
的一個模板配置。所以使用起來沒有難度,可以按照官方文檔的說明即可完成,只有兩行命令。
# 安裝 vue-cli 和 腳手架樣板代碼 npm install -g vue-cli vue init simulatedgreg/electron-vue my-project
如此,我們便得到了一個初始化的electron
項目,安裝完依賴包就能開發了。初始化過程很簡單,但是需要提示一些vue cli
使用過程中需要注意的問題:
-
Vue CLI
的包名稱由vue-cli
改成了@vue/cli
,導致最新版的vue cli
使用vue init
命令會報錯,而electron-vue
的安裝文檔比較老舊,需要用vue init
這個命令,我們可以用一個命令來做橋接:npm install -g @vue/cli-init
windows
在Git bash
上初始化選擇插件時候無法用上下鍵交互的問題可以換用powershell
開發
項目初始化、安裝依賴后我們就可以開發了。這時候可以跑一下項目,他會有個默認的界面。
npm run dev
對應的目錄是這樣的(目錄會根據安裝插件的不同而存在差異)。
├── appveyor.yml ├── build │ └── icons │ ├── 256x256.png │ ├── icon.icns │ └── icon.ico ├── dist │ ├── electron │ └── web ├── package.json ├── README.md ├── src │ ├── index.ejs │ ├── main │ │ ├── index.dev.js │ │ └── index.js │ └── renderer │ ├── App.vue │ ├── assets │ ├── components │ ├── main.js │ ├── router │ └── store └── static
大致的套路跟vue
開發一樣,不同的是src
下有個兩個目錄:main
和renderer
。我們大致可以理解為:
main
目錄存放與electron
相關的內容,是主進程renderer
目錄存放與我們頁面相關的內容,是渲染進程
這個通過這個demo頁面可以驗證。搞清楚這個后開發就很簡單了,不需要解釋太多。更多的介紹可以看文檔
調試
開發過程中調試是不可避免的,根據我的經驗這里主要介紹兩種調試:
普通頁面調試
這種調試就像我們平時在vue
開發web
應用中使用devtool
一樣。這個在初始化的代碼中已經寫好了,在dev
模式下默認是開啟的。
在/src/main/index.dev.js
可以找到這段代碼,我們不用太關心。
require('electron-debug')({ showDevTools: true })
內嵌webview
頁面調試
這個是因為我這個項目需要才涉及到的,需要自己代碼實現。 在我的邏輯中大致這樣處理的:
<webview ref="webview" src="xxx" ></webview>
this.$refs.webview.addEventListener('dom-ready', res => { if (process.env.NODE_ENV === 'development') { this.$refs.webview.openDevTools(); } });
打包
開發完之后我們需要把代碼打包成一個exe
的可執行文件,這個操作也很簡單,已經在package.json
上被安排好了,我們直接用就行了。
npm run build
然后,build
目錄下有會有你想要的exe
文件了。拷走安裝就可以用了。
如果你需要自定義一些圖標啥的可以改package.json
中build
的相關配置,操作簡單,不用多提。
一些需要關注的核心點
因為這次項目功能很簡單,涉及到的點很少,所以這里只介紹項目用到的一些核心點,沒用過的沒有發言權,就不提了。
webview
代碼注入
electron
提供了webview
標簽可讓我們嵌入自己的網頁。想要往里面注入代碼可以使用webview
上提供的executeJavaScript
方法。具體的參數和使用方法這些都是文檔層面的內容,不用多提,需要注意的是這個方法的第三個參數是個callback
,文檔上是這樣描述的
callback Function (可選) - 在腳本被執行后被調用。
文檔只是描述了這個回調被觸發的時機,而沒有描述回調觸發時候傳的參數。
關於觸發時機和參數,更具體的描述是這樣的。
- 如果你注入的是同步代碼,像
querySelector
這樣的,callback
會在注入代碼執行完之后觸發,並且把你最后querySelector
獲得的值傳入callback
- 如果你注入的是異步代碼,像
ajax
請求這樣的,callback
是不會等這個請求有了響應才執行的,而是執行完ajax
請求就執行,當然,這種情況下callback
是沒有傳參的。我們后面會介紹怎么拿到注入ajax
的返回值。
主進程和渲染進程通信
主進程和渲染進程通信是electron
中很重要的概念,介紹這塊內容的文章也有很多,所以這里就不過多贅述,簡單介紹一下大概情況。
通信主要靠兩個工具完成:ipcRenderer
和ipcMain
。
在主進程里這樣使用ipcMain
:
const { ipcMain } = require('electron'); ipcMain.on('exit_app', res => { app.exit(); })
在渲染進程中這樣使用ipcRenderer
:
const { ipcRenderer } = require('electron'); ipcRenderer.send('exit_app', true);
需要注意的是:消息只能是由渲染進程主動發起,主進程被動監聽,然后主進程作響應,不存在由主進程主動向渲染進程通信的方式。代碼實現如下:
// 在主進程中 const { ipcMain } = require('electron') ipcMain.on('asynchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.reply('asynchronous-reply', 'pong') }) ipcMain.on('synchronous-message', (event, arg) => { console.log(arg) // prints "ping" event.returnValue = 'pong' })
//在渲染器進程 (網頁) 中 const { ipcRenderer } = require('electron') console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong" ipcRenderer.on('asynchronous-reply', (event, arg) => { console.log(arg) // prints "pong" }) ipcRenderer.send('asynchronous-message', 'ping')
electron
窗體關閉事件攔截
窗體關閉事件攔截利用的是BrowserWindow
的close事件來做處理的。需要注意的是BrowserWindow
實例還有個closed
事件,區別在於:
close
在窗口要關閉的時候觸發closed
窗口已經關閉時觸發
這個兩個事件一個字母的差距,但是觸發時機卻大有差距,千萬分清楚,當時我就是沒看清,浪費了不少時間。
窗體關閉事件攔截的代碼實現大致如下:
mainWindow = new BrowserWindow({ height: 563, useContentSize: true, width: 1000 }) mainWindow.on('close', event => { return event.preventDefault(); })
遇到的一些問題
webview
中beforeunload
彈窗問題
如果內嵌的webview
中對beforeunload
事件有監聽,並有彈窗操作,那么這個事件觸發的時候,electron
是沒有彈窗提示的。用戶是沒有感知的,這個問題還沒有解決辦法,是個坑。
(electron
的webview
是不會顯示這些彈窗)
監聽webview
中注入請求的響應
我的項目里有一個需求是要向webview
中注入ajax
請求,並且要拿到請求的響應結果。但是electron
中比較早的版本中只有對發起的請求有監聽,並不支持監聽請求的響應,所以要拿到請求的響應結果就是個問題。
但是也不是沒辦法解決。我這里是借助webview
的console-message
事件來處理。監聽webview
的console-message
事件,當拿到請求的響應的時候把響應的內容按照約定的格式打出來,這樣就間接的實現了對注入請求的監聽。
electron
低版本webview
中不能跳轉
這是一個在electron
低版本中存在的問題,在一些新的版本中已經解決了。
這個問題的具體表現就是:
內嵌webview
的頁面中如果發生302
、301
跳轉的話頁面會卡在發生跳轉的請求那里不再向下進行,查看webview
的network
可以看到這個問題。
這個問題的解決很簡單,升級到最新的electron
就能解決這個問題。我這里升級到4.1.4
解決了這個問題。
因為electron-vue
長時間沒更新,里面使用的electron
版本還是2.0.4
,比較老舊,所以你要是遇到一些奇奇怪怪的問題不妨升級electron
來嘗試解決。
ctrl+c
之后仍有electron
進程駐守
調試時候發現的這個問題,雖然是在electron
中開發,但是因為還是vue
那一套,所以我們改完代碼就不用重啟就能在electron
中看到效果,就是熱更新嘛,但是有時候我還是會手動的ctrl + c
,這么手動的次數多了之后我發現我的電腦會變的非常卡。排查后我發現ctrl + c
並不能完全關閉整個electron
應用,每次這么做都會遺留2個electron
進程保持活躍,多次之后會遺留很多electron
進程,電腦就會變的非常卡,每次都需要手動殺一下進程。這個問題在把模板中自帶的
app.on('window-all-closed', event => { app.quit(); });
替換為
app.on('window-all-closed', event => { app.exit(); });
之后解決(在寫文章的時候發現這個問題並沒得到復現,不知道是不是新的版本修復了這個問題),其中差異感興趣的可以深入研究一下。
無法捕獲信號量的問題
解決上面問題的時候發現electron
無法捕獲來自命令行的信號量。如果有這個需求的話還是要留意的。
一些不錯的博客
在開發過程中也是一邊學一邊做,發現了一些不錯的文章和博客,從里面也獲得了不少的幫助,貼出來分享一下。
- Electron 進程通信(主講通信)
- Electron webview完全指南(主講webview)
- 程序如此靈動~蘇南大叔(比較全的electron開發教程,有很多的示例)