electron開發採坑小記

前言

前前前前段時間做了個桌面端的項目(這篇文章拖了挺久了),功能大概是這樣的:

  • 項目左右兩欄結構
  • 左側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應用的腳手架。可以幫助我們做不少的vueelectron之間結合的基礎工作,讓我們的開發更加便捷。那麼我們就來看看使用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 
  • windowsGit bash上初始化選擇插件時候無法用上下鍵交互的問題可以換用powershell

開發

項目初始化、安裝依賴後我們就可以開發了。這時候可以跑一下項目,他會有個默認的界面。

npm run dev

clipboard.png

對應的目錄是這樣的(目錄會根據安裝插件的不同而存在差異)。

├── 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下有個兩個目錄:mainrenderer。我們大致可以理解爲:

  • 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中很重要的概念,介紹這塊內容的文章也有很多,所以這裏就不過多贅述,簡單介紹一下大概情況。
通信主要靠兩個工具完成:ipcRendereripcMain

在主進程裏這樣使用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窗體關閉事件攔截

窗體關閉事件攔截利用的是BrowserWindowclose事件來做處理的。需要注意的是BrowserWindow實例還有個closed事件,區別在於:

  • close在窗口要關閉的時候觸發
  • closed窗口已經關閉時觸發

這個兩個事件一個字母的差距,但是觸發時機卻大有差距,千萬分清楚,當時我就是沒看清,浪費了不少時間。

窗體關閉事件攔截的代碼實現大致如下:

  mainWindow = new BrowserWindow({
    height: 563,
    useContentSize: true,
    width: 1000
  })

  mainWindow.on('close', event => {
      return event.preventDefault();
  })

遇到的一些問題

webviewbeforeunload彈窗問題

如果內嵌的webview中對beforeunload事件有監聽,並有彈窗操作,那麼這個事件觸發的時候,electron是沒有彈窗提示的。用戶是沒有感知的,這個問題還沒有解決辦法,是個坑。

clipboard.png
clipboard.png
(electronwebview是不會顯示這些彈窗)

監聽webview中注入請求的響應

我的項目裏有一個需求是要向webview中注入ajax請求,並且要拿到請求的響應結果。但是electron中比較早的版本中只有對發起的請求有監聽,並不支持監聽請求的響應,所以要拿到請求的響應結果就是個問題。
但是也不是沒辦法解決。我這裏是藉助webviewconsole-message事件來處理。監聽webviewconsole-message事件,當拿到請求的響應的時候把響應的內容按照約定的格式打出來,這樣就間接的實現了對注入請求的監聽。

electron低版本webview中不能跳轉

這是一個在electron低版本中存在的問題,在一些新的版本中已經解決了。
這個問題的具體表現就是:
內嵌webview的頁面中如果發生302301跳轉的話頁面會卡在發生跳轉的請求那裏不再向下進行,查看webviewnetwork可以看到這個問題。
這個問題的解決很簡單,升級到最新的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無法捕獲來自命令行的信號量。如果有這個需求的話還是要留意的。

一些不錯的博客

在開發過程中也是一邊學一邊做,發現了一些不錯的文章和博客,從裏面也獲得了不少的幫助,貼出來分享一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章