導航:html
(一)Electron跑起來
(二)從零搭建Vue全家桶+webpack項目框架
(三)Electron+Vue+Webpack,聯合調試整個項目
(四)Electron配置潤色
(五)預加載及自動更新
(六)構建、發佈整個項目(包括client和web)(未完待續)vue
摘要:到目前爲止,咱們的項目已經具有了PC客戶端該有的一些基礎功能和調試環境,可是總感受缺了靈魂,那就是結合實際項目、實際業務的細節處理,缺着吧。。。這篇文章就介紹一下預加載和自動更新,文字功底有限,若有介紹的不清楚的地方,歡迎留言指正,或者跳過文字,直接去看代碼,項目完整代碼:https://github.com/luohao8023/electron-vue-template,隨博客更新。node
1、預加載webpack
一、什麼是預加載?什麼場景能用到? git
preload String (可選) -在頁面運行其餘腳本以前預先加載指定的腳本 不管頁面是否集成Node, 此腳本均可以訪問全部Node API 腳本路徑爲文件的絕對路徑。 當 node integration 關閉時, 預加載的腳本將從全局範圍從新引入node的全局引用標誌。
摘自electron官網的一段介紹,https://www.electronjs.org/docs/api/browser-window。github
preload是BrowserWindow類的參數webPreferences的一個可選配置項,咱們解讀一下官網的介紹:web
在頁面運行其餘腳本以前預先加載的指定的腳本:首先是個js文件沒錯了,再看加載時機,在頁面運行其餘腳本以前預先加載,這個頁面不是普通的某個h5頁面,而是指某個渲染進程(須要預加載js的渲染進程,由於渲染進程可能有多個,每一個就是一個窗口),咱們new一個BrowserWindow,打開了一個窗口,就是啓動了一個渲染進程,若是咱們不給這個窗口指定頁面,那它就是空白的,若是指定了頁面,那麼窗口就會加載這個頁面:chrome
const win = new BrowserWindow({ width: 800, height: 600 }); win.loadURL('https://www.baidu.com');
如上面代碼,咱們建立了一個窗口,而後加載百度首頁,而preload腳本的加載時機就是窗口建立後,百度首頁加載以前。若是有人問,若是不調用loadURL方法,不加載頁面,preload腳本會加載嗎?答案是會,但有什麼用呢?你起個殼子不給人家看頁面是什麼鬼?無論這些,重要的是咱們理解這個加載時機就行了;json
不管頁面是否集成Node,此腳本均可以訪問全部Node API:首先要說明的一點是,Electron5.x以上版本,默認沒法在渲染進程中訪問Node API,如需使用,須要預先配置: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。
二、怎麼用?
上面說了怎麼引用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就能迎刃而解的問題。
2、自動更新
咱們都知道,electron實際上是封了個chrome內核,拋開殼子不說,裏面運行的其實就是咱們的h5頁面,而就算咱們跑了個空項目,沒有任何內容,打包後的安裝包也得30M左右,咱們但願本身的程序有自動更新功能,可是更新機制是怎樣的呢?
若是咱們只改動了頁面某一處的文本,卻要用戶更新整個安裝包,那顯然太不合理了,一是體驗很差,二是咱們的流量啊......
基於這種考慮,加上electron主進程和渲染進程的劃分,那咱們能夠考慮以下更新機制:
主進程有改動時,那沒的說,用戶須要更新整個客戶端(固然有精力有條件的能夠作動態更新,官方好像是說支持,主要是我不會);渲染進程有改動時,咱們只須要把h5包下載到本地而後加載就好了,固然這須要咱們打包的時候能把h5包區分出來,在更新後能打開對應版本的h5包。
這裏咱們稱主進程的更新爲大版本更新,渲染進程的更新爲小版本更新。
一、打包配置修改
爲何忽然扯到打包配置修改了呢,由於牽扯到小版本的更新,那咱們打包的時候就得把這個「小版本」給打出來,否則更新個🔨。由於下面還有一篇文章是專門介紹這個Electron-vue項目的打包,因此這裏呢就只講一下怎麼把小版本的壓縮包給打出來。
修改build.js,思路是:使用webpack打包主進程、打包preload、打包渲染進程,獲得可執行文件目錄app,而後引入electrin-builder對app目錄進行打包,產生一個安裝包,而後把渲染進程的文件壓縮並標記版本號。這裏咱們只揀和本節相關的說,就是打包渲染進程和壓縮小版本文件,爲何能拆出來講呢,固然是分模塊封裝的好處啦,各個進程的打包邏輯封裝一下拆出來,能隨意組合還能複用,不然一個又臭又長的打包腳本文件,很難維護。
具體代碼就不貼出來了,太佔篇幅,也沒什麼用,能夠到https://github.com/luohao8023/electron-vue-template看完整代碼。
二、增長啓動頁,啓動頁顯示歡迎語等,在這裏檢查更新
這裏咱們暫且叫它檢查更新頁,這個檢查更新頁是個獨立的渲染進程,用戶打開程序時首先顯示檢查更新窗口,可是這個窗口也不必定顯示檢查更新字樣,偷偷的檢查就好了,有新版本就提示更新,沒有新版本就顯示歡迎語。
這兒的邏輯是單獨拆分出來的,不能是自動更新的時候把自動更新邏輯自己也給更新了,容易亂套。
修改主進程代碼,程序啓動時首先啓動自動更新窗口:
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。