【原創】從零開始搭建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、打包配置修改

為什么突然扯到打包配置修改了呢,因為牽扯到小版本的更新,那我們打包的時候就得把這個“小版本”給打出來,不然更新個🔨。因為下面還有一篇文章是專門介紹這個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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM