前言
前前前前段時間做了個桌面端的項目(這篇文章拖了挺久了),功能大概是這樣的:
- 項目左右兩欄結構
- 左側
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();
}
});
其他一些需要關注的核心點
因爲這次項目功能很簡單,涉及到的點很少,所以這裏只介紹項目用到的一些核心點,沒用過的沒有發言權,就不提了。
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開發教程,有很多的示例)