作者:鐘離窗慎,酷家樂PC客戶端負(fù)責(zé)人
原文地址:https://webfe.kujiale.com/electron-ku-jia-le-ke-hu-duan-kai-fa-shi-jian-fen-xiang-ru-keng-zhi-nan/
酷家樂客戶端:下載地址 https://www.kujiale.com/activity/136
文章背景:在酷家樂客戶端在V12改版成功后鹊碍,我們積累了許多的寶貴的經(jīng)驗和最佳實踐。前端社區(qū)里關(guān)于Electron知識相對較少步责,因此希望將這些內(nèi)容以系列文章的形式分享出來。
系列文章:
本文的初衷
Electron所使用的技術(shù)棧(JavaScript潜慎、NodeJs搀玖、HTML莉炉、CSS)和web前端工程師完美契合。于是陕悬,越來越多的前端工程師题暖,用Electron來開發(fā)桌面客戶端的開發(fā),我也是其中的一員捉超。
雖然Electron技術(shù)棧對前端工程師比較友好胧卤,但是概念較多,和web前端開發(fā)還是有很大差別的拼岳,寫個入坑指南希望能幫助讀者快速上手Electron枝誊。
了解客戶端
首先拋出一個問題,web應(yīng)用是桌面客戶端嗎惜纸?顯然不是叶撒。那么,問題來了耐版,什么樣的軟件才是桌面客戶端呢祠够?我們既然要從web前端轉(zhuǎn)到客戶端開發(fā),那么就需要了解客戶端椭更,就像我們當(dāng)初了解web應(yīng)用一樣哪审。
回到剛剛那個問題,桌面客戶端有兩個重要的特點:
- 獨立運行于操作系統(tǒng)上(桌面客戶端只是PC虑瀑,那么限定windows、MacOS叽奥、linux這幾個主流PC操作系統(tǒng))
- 有自己的GUI(用戶圖形界面 graphical user interface)
web應(yīng)用有自己的GUI朝氓,必須在瀏覽器中執(zhí)行,因此不是桌面客戶端赵哲。
瀏覽器能直接運行在操作系統(tǒng)上枫夺,而且有自己的GUI橡庞,因此瀏覽器是桌面客戶端。
Electron的能力
在剛剛接觸Electorn的時候丑勤,文檔看的我是眼花繚亂法竞。在某個加班的深夜爪喘,我不禁對天長嘆:這個東西到底能干啥?
這東西能干啥泛豪?在經(jīng)歷了Electron的反復(fù)摩擦之后诡曙,我總結(jié)了Electron的幾個關(guān)鍵能力:
- NodeJs全部能力,與操作系統(tǒng)交互
- operation system 與操作系統(tǒng)相關(guān)的操作
- HTTP(s)劝萤、HTTP2
- process床嫌、child process 進程相關(guān)
- file system 文件系統(tǒng)
- ...省略
- Electron提供的基礎(chǔ)模塊厌处,主要與操作系統(tǒng)交互
- app 主進程聲明周期管理阔涉,控制MacOS任務(wù)欄dock瑰排、windows任務(wù)欄taskbar
- BrowserWindow 控制窗口,在MacOS和windows中窗口非常重要郭毕!
- screen 操作用戶顯示器
- globalShortcut 系統(tǒng)級別快捷鍵
- ...省略
- Chromium提供的能力显押,主要提供GUI圖形界面
- 解析HTML乘碑、CSS兽肤、JS
- ajax請求
- cookie资铡、localstorage
- ...省略
能力越大笤休,責(zé)任越大
如果用戶安裝了我們的桌面客戶端店雅,那么我們的軟件在用戶電腦上運行時闹啦,就有了非常大的權(quán)利辕坝,這是把雙刃劍酱畅。
用戶選擇了我們的軟件圣贸,我們也要對用戶的電腦負(fù)責(zé)。能力越大滑负,責(zé)任也就越大:
1.注意內(nèi)存的占用矮慕,特別是chromium,簡直是內(nèi)存怪獸瘟斜÷菥洌可以通過os來獲取用戶電腦的配置蛇尚,然后根據(jù)電腦的配置和可用資源取劫,來制定合理的策略谱邪。
- 為軟件增加代碼簽名惦银,提升安全性
- 謹(jǐn)慎操作注冊表璧函、用戶敏感目錄
一旦被貼上【流氓軟件】、【不好用】的標(biāo)簽撩幽,就很難再改變用戶的印象了窜醉。
主進程、渲染進程
生命周期
主進程:從整個應(yīng)用啟動到結(jié)束,該進程一直存在侠碧。主進程只有一個弄兜。
渲染進程:主進程可用創(chuàng)建/銷毀渲染進程替饿,因此渲染進程的生命周期是不固定的视卢。渲染進程可以有多個腾夯。
執(zhí)行環(huán)境
在Electron的API文檔中,會在文檔頂部標(biāo)識該模塊在哪個進程可用榨呆,例如:ipcRenderer
職責(zé)劃分
主進程 | 渲染進程 |
---|---|
控制app的生命周期,為app注冊關(guān)鍵事件 | 解析HTML庸队,渲染窗口內(nèi)容 |
阻止一些默認(rèn)行為积蜻,例如webContents的跳轉(zhuǎn)、download事件的默認(rèn)行為等等(在渲染進程無法做到) | 處理窗口的交互邏輯 |
創(chuàng)建BrowserWindow彻消,也就是渲染進程竿拆。合理設(shè)置窗口的參數(shù),控制窗口的生命周期(例如何時銷毀窗口)宾尚,決定BrowserWindow加載何處的HTML | 與主進程通信丙笋,實現(xiàn)高級交互 |
窗口、前端資源
我們回顧一下剛剛講到的執(zhí)行流程煌贴,其中有一個有趣的點御板,就是Electron的窗口會加載一個HTML來渲染窗口的內(nèi)容。
HTML牛郑,以及HTML加載的css钉答、js文件砌创,統(tǒng)稱為前端資源
如果不加載HTML的,客戶端還能用嗎颂翼?不妨來試試
// main process
const win1 = new BrowserWindow();
const win2 = new BrowserWindow();
上述代碼在主進程中執(zhí)行呻疹,創(chuàng)建了兩個窗口,窗口并沒加載HTML文件。但是窗口卻是真實存在的,帶有系統(tǒng)標(biāo)準(zhǔn)的控制欄占贫,可拖動厢汹,是貨真價實的系統(tǒng)窗口!
我們可以發(fā)現(xiàn)条获,前端資源和窗口是分離的。由主進程創(chuàng)建的的窗口(BrowserWindow)称诗,既是一個系統(tǒng)原生窗口袜香,同時也是一個加載&渲染前端資源的容器
窗口通常會通過file協(xié)議和http(s)協(xié)議來加載前端資源欢策,接下來我們看看這兩種方式的區(qū)別辣卒。
通過file協(xié)議加載HTML
在Electron的官方入門例子中啡莉,就是通過file協(xié)議來加載HTML的
通過file協(xié)議加載HTML疗杉,無論有沒有網(wǎng)絡(luò),都可以加載到HTML文件,這是file協(xié)議核心優(yōu)勢言蛇。缺點也比較明顯:
- 如果頁面資源要更新,那么只能通過發(fā)版來解決(如果你用webview婿斥,那么webview的內(nèi)容就可以自動更新,不過webview也需要有網(wǎng)絡(luò)才能加載)
- 在file協(xié)議下,無法通過ajax來請求數(shù)據(jù)(協(xié)議不同)勘高,只能通過NodeJs的http(s)模塊來發(fā)起網(wǎng)絡(luò)請求
通過http協(xié)議加載HTML
通過http協(xié)議加載HTML宾抓,優(yōu)點是可以隨時通過web頁面的部署孵班,更新渲染進程的資源折柠,并且在https協(xié)議下,你可以在頁面中使用前端熟悉的ajax請求來獲取數(shù)據(jù)扇售。
當(dāng)然前塔,缺點也比較明顯:
- 沒有網(wǎng)絡(luò),并且在你沒有做HTML的緩存時承冰,你的窗口內(nèi)容無法加載
- 必須通過https來加載华弓,保證頁面內(nèi)容的安全性
代碼示例
方便讀者更好理解上文的內(nèi)容,寫了一個小demo困乒,源代碼地址 https://github.com/littlecold233/electron-demo寂屏,例子有以下特點:
創(chuàng)建主窗口,阻止關(guān)閉主窗口關(guān)閉的默認(rèn)事件,不銷毀窗口迁霎。(大部分客戶端的主窗口吱抚,關(guān)閉主窗口的時候,實際上是隱藏了該窗口考廉,例如QQ秘豹、微信)
應(yīng)用退出時,會嘗試關(guān)閉所有窗口昌粤,再退出應(yīng)用既绕。如果主窗口的關(guān)閉行為默認(rèn)事件被阻止,那么會導(dǎo)致主窗口無法關(guān)閉涮坐,整個應(yīng)用無法退出凄贩。因此使用
forceQuit
這個變量來控制。使用http或者file協(xié)議加載窗口前端資源(例子中袱讹,默認(rèn)加載的是微信)
const { app, BrowserWindow } = require('electron')
async function main () {
await app.whenReady();
let forceQuit = false;
const majorWindow = new BrowserWindow({
title: '主窗口',
width: 1000,
height: 750,
minWidth: 1000,
minHeight: 750,
backgroundColor: '#f2f2f2',
}); // 主窗口
// 阻止標(biāo)題更新
majorWindow.on('page-title-updated', (e) => {
e.preventDefault();
});
majorWindow.on('close', (e) => {
// 用戶希望退出的時候疲扎,不作處理,默認(rèn)會銷毀這個窗口
if (forceQuit) return;
e.preventDefault();
// macOS全屏的處理
if (majorWindow.isFullScreen()) {
majorWindow.once('leave-full-screen', () => {
majorWindow.hide();
});
majorWindow.setFullScreen(false);
} else {
majorWindow.hide(); // 隱藏窗口
}
});
// 點擊dock打開主窗口
app.on('activate', () => {
majorWindow.show();
});
// 用戶使用cmd + Q廓译、代碼中調(diào)用app.quit等情況
// 此時用戶希望能夠退出應(yīng)用评肆,因此將forceQuit改為true
app.on('before-quit', () => {
forceQuit = true;
});
app.dock.setIcon('./img/icon.png'); // 在app打包后,這一句代碼其實是不需要的
majorWindow.loadURL('https://wx.qq.com'); // http協(xié)議加載前端資源非区,隨便加載一個微信試試
// majorWindow.loadURL('file://index.html'); // file協(xié)議加載前端資源
}
main();
在本地跑一下這個例子最后
歡迎大家在評論區(qū)討論瓜挽,技術(shù)交流 & 內(nèi)推 -> zhongli@qunhemail.com