導航:
(一)Electron跑起來
(二)從零搭建Vue全家桶+webpack項目框架
(三)Electron+Vue+Webpack,聯合調試整個項目
(四)Electron配置潤色
(五)預加載及自動更新
(六)構建、發布整個項目(包括client和web)
摘要:到目前為止,我們的項目已經具備了PC客戶端該有的一些基礎功能和調試環境,但是總感覺缺了靈魂,那就是結合實際項目、實際業務的細節處理,缺着吧。。。這篇文章就介紹一下預加載和自動更新,文字功底有限,如有介紹的不清楚的地方,歡迎留言指正,或者跳過文字,直接去看代碼,項目完整代碼:https://github.com/luohao8023/electron-vue-template,隨博客更新。
一、預加載
1、什么是預加載?什么場景能用到?
preload String (可選) -在頁面運行其他腳本之前預先加載指定的腳本 無論頁面是否集成Node, 此腳本都可以訪問所有Node API 腳本路徑為文件的絕對路徑。 當 node integration 關閉時, 預加載的腳本將從全局范圍重新引入node的全局引用標志。
摘自electron官網的一段介紹,https://www.electronjs.org/docs/api/browser-window。
preload是BrowserWindow類的參數webPreferences的一個可選配置項,我們解讀一下官網的介紹:
在頁面運行其他腳本之前預先加載的指定的腳本:首先是個js文件沒錯了,再看加載時機,在頁面運行其他腳本之前預先加載,這個頁面不是普通的某個h5頁面,而是指某個渲染進程(需要預加載js的渲染進程,因為渲染進程可能有多個,每個就是一個窗口),我們new一個BrowserWindow,打開了一個窗口,就是啟動了一個渲染進程,如果我們不給這個窗口指定頁面,那它就是空白的,如果指定了頁面,那么窗口就會加載這個頁面:
const win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL('https://www.baidu.com');
如上面代碼,我們創建了一個窗口,然后加載百度首頁,而preload腳本的加載時機就是窗口創建后,百度首頁加載之前。如果有人問,如果不調用loadURL方法,不加載頁面,preload腳本會加載嗎?答案是會,但有什么用呢?你起個殼子不給人家看頁面是什么鬼?不管這些,重要的是我們理解這個加載時機就好了;
無論頁面是否集成Node,此腳本都可以訪問所有Node API:首先要說明的一點是,Electron5.x以上版本,默認無法在渲染進程中訪問Node API,如需使用,需要預先配置:
const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true } });
然后還要清楚一點,preload腳本是運行在渲染進程中的,可以仔細考慮一下。再有一點就是,preload腳本中可以訪問window對象(渲染進程其實就是起了個瀏覽器殼子),preload腳本運行在渲染進程,提前於頁面和其他所有js的加載,又能訪問Node API;
腳本文件路徑為絕對路徑,當node integration關閉時,預加載的腳本將從全局范圍重新引入node的全局引用標志:結合前面兩點理解就好了。
那么,到底什么是預加載?用白話定義一下:
某一個渲染進程,在頁面加載之前加載一個本地腳本,這個腳本能訪問所有Node API、能訪問window對象。用法如下:
const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } });
理解應該差不多了,但什么場景能用到這玩意兒呢?按正常的邏輯來想,主進程啟動后啟動渲染進程,渲染進程加載頁面就完事兒了,哪會用到這個preolad呢?
想一下,如果我們有以下場景:
a、如果我們啟動了一個窗口(渲染進程),加載了一個線上的頁面,本地沒有頁面文件,但要做一些錯誤處理,比如網絡錯誤,頁面加載失敗,然后在頁面空白但時候插入一些元素;
b、如果我們的一套代碼部署在web端和客戶端,需要用一個變量判斷是在web端還是客戶端;
...........
感覺舉的例子好勉強啊,不要見怪,就是大概這么個意思,沒准哪天就遇到了非preload解決不了的問題呢,畢竟這玩意兒還是有它的特殊之處的;
上面兩個場景如果用preload來解決的話,思路是利用prelaod中能訪問window對象的特點,比如b,代碼中可以用window.isClient來判斷是否在客戶端,默認為false,然后在preload中把window.isClient設置為true,而對於部署在web端的代碼來說,這個值就是false。
2、怎么用?
上面說了怎么引用preload腳本,現在說一下怎么寫,下面開始xxoo亂寫亂畫了:
// 訪問electron對象 const { remote, ipcRenderer } = require('electron'); // 訪問node模塊 const fs = require('fs'); const path = require('path'); // 訪問window對象 window.isClient = true; window.sayHello = function() { console.log('hello'); }; // 操作dom const div = document.createElement('div'); div.innerText = 'I am a div'; document.body.appendChild(div); // ...
如果preoad里面邏輯比較復雜,有可能還要用webpack打包一下,單獨拎出來打包就行了,webpack單文件打包,注意targer要"electron-renderer":
/* Tip: preload 打包配置 */ const path=require('path'); const { dependencies } = require('../package.json'); module.exports = { mode:process.env.NODE_ENV, entry: { preload:['./src/preload/index.js'] }, output: { path: path.join(__dirname, '../app/'), libraryTarget: 'commonjs2', filename: './[name].js' }, optimization: { runtimeChunk: false, minimize: true }, node: { fs: 'empty', __dirname:false }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ } ] }, externals: [ ...Object.keys(dependencies || {}) ], resolve: { extensions: ['.js'], alias: { '@': path.resolve(__dirname, "../src"), '@public': path.resolve(__dirname, "../public") } }, plugins:[], target:"electron-renderer" }
我相信,總會遇到使用preload就能迎刃而解的問題。
二、自動更新
我們都知道,electron其實是封了個chrome內核,拋開殼子不說,里面運行的其實就是我們的h5頁面,而就算我們跑了個空項目,沒有任何內容,打包后的安裝包也得30M左右,我們希望自己的程序有自動更新功能,但是更新機制是怎樣的呢?
如果我們只改動了頁面某一處的文本,卻要用戶更新整個安裝包,那顯然太不合理了,一是體驗不好,二是我們的流量啊......
基於這種考慮,加上electron主進程和渲染進程的划分,那我們可以考慮如下更新機制:
主進程有改動時,那沒的說,用戶需要更新整個客戶端(當然有精力有條件的可以做動態更新,官方好像是說支持,主要是我不會);渲染進程有改動時,我們只需要把h5包下載到本地然后加載就行了,當然這需要我們打包的時候能把h5包區分出來,在更新后能打開對應版本的h5包。
這里我們稱主進程的更新為大版本更新,渲染進程的更新為小版本更新。
1、打包配置修改
為什么突然扯到打包配置修改了呢,因為牽扯到小版本的更新,那我們打包的時候就得把這個“小版本”給打出來,不然更新個🔨。因為下面還有一篇文章是專門介紹這個Electron-vue項目的打包,所以這里呢就只講一下怎么把小版本的壓縮包給打出來。
修改build.js,思路是:使用webpack打包主進程、打包preload、打包渲染進程,得到可執行文件目錄app,然后引入electrin-builder對app目錄進行打包,產生一個安裝包,然后把渲染進程的文件壓縮並標記版本號。這里我們只揀和本節相關的說,就是打包渲染進程和壓縮小版本文件,為什么能拆出來說呢,當然是分模塊封裝的好處啦,各個進程的打包邏輯封裝一下拆出來,能隨意組合還能復用,否則一個又臭又長的打包腳本文件,很難維護。
具體代碼就不貼出來了,太占篇幅,也沒什么用,可以到https://github.com/luohao8023/electron-vue-template看完整代碼。
2、增加啟動頁,啟動頁顯示歡迎語等,在這里檢查更新
這里我們暫且叫它檢查更新頁,這個檢查更新頁是個獨立的渲染進程,用戶打開程序時首先顯示檢查更新窗口,但是這個窗口也不一定顯示檢查更新字樣,偷偷的檢查就行了,有新版本就提示更新,沒有新版本就顯示歡迎語。
這兒的邏輯是單獨拆分出來的,不能是自動更新的時候把自動更新邏輯本身也給更新了,容易亂套。
修改主進程代碼,程序啟動時首先啟動自動更新窗口:
app.on('ready', () => { //注冊快捷鍵打開控制台事件 shortcut.register('Command+Control+Alt+F5'); mainWindow = updateWin.create(); });
然后注冊監聽事件,因為自動更新窗口邏輯完成之后需要呼起主窗口,需要主進程來調度:
//啟動主窗體 ipcMain.on('create-main',(event,arg) => { // h5頁面指向指定版本 // global.wwwroot.path = arg.newVersionPath ? arg.newVersionPath : __dirname; // if (arg.version) setVal('version','smallVersion', arg.version); indexWin.create(); mainWindow.destroy(); });
自動更新窗口只需專注於更新邏輯就行了,邏輯結束后呼起主窗口:
// 更新邏輯看下面偽代碼 const v1 = getOnlineVersion(); const v2 = getLocalVersion(); const needUpdate = checkVersion(v1, v2); if (needUpdate) { downloadVersion(); } this.runMain();
在呼起主窗口的同時給主窗口傳遞參數,並告知主窗口有沒有更新版本,以及主窗口需要加載哪個小版本的包,而主窗口在loadURL時也要做下改動:
let wwwroot = global.wwwroot.path ? global.wwwroot.path : __dirname;
let filePath = url.pathToFileURL(path.join(wwwroot, 'index.html')).href;
而wwwrot就是當前小版本包的根路徑,由主進程來維護,自動更新小版本后會修改這個值,以告訴主進程加載哪個版本。
好了,啰嗦了一大堆,好多地方沒貼代碼,感覺貼了代碼的話,篇幅就不受控制了,還是去github看完整項目吧,自動更新這一塊是偽代碼,只實現了渲染進程的切換(即自動更新窗口呼起主窗口),具體的更新邏輯實現起來的話還要拿線上版本去比較,這個還是留給大家在實際項目中去調試吧,就是上面這個思路。
好啦,有什么問題可以留言交流,也可以直接去看代碼https://github.com/luohao8023/electron-vue-template。