【原創】從零開始搭建Electron+Vue+Webpack專案框架(五)預載入和Electron自動更新
導航:
(一)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、打包配置修改
為什麼突然扯到打包配置修改了呢,因為牽扯到小版本的更新,那我們打包的時候就得把這個“小版本”給打出來,不然更新個