Electron Preload 预加载

[转自]从零开始搭建Electron+Vue+Webpack项目框架(五)预加载和Electron自动更新

什么是预加载

来看看electron 官网的介绍: https://www.electronjs.org/docs/api/browser-window
preload String (optional) - Specifies a script that will be loaded before other scripts run in the page. This script will always have access to node APIs no matter whether node integration is turned on or off. The value should be the absolute file path to the script. When node integration is turned off, the preload script can reintroduce Node global symbols back to the global scope.
翻译过来如下:
preload字符串(可选)-指定在页面中其他脚本运行之前被加载的脚本。 无论打开还是关闭 integratioin,此脚本始终可以访问node API。 该值应该是脚本的绝对文件路径。 关闭node integration后,预加载脚本将从全局局限重新引入node的全局引用标志。

  1. preload是BrowserWindow类的参数webPreferences的一个可选设置项,我们解读一下官网的先容:在页面运行其他脚本之前预先加载的指定的脚本:首先是个js文件没错了,再看加载时机,在页面运行其他脚本之前预先加载,这个页面不是通俗的某个h5页面,而是指某个渲染进程(需要预加载js的渲染进程,由于渲染进程可能有多个,每个就是一个窗口),我们new一个BrowserWindow,打开了一个窗口,就是启动了一个渲染进程,若是我们不给这个窗口指定页面,那它就是空缺的,若是指定了页面,那么窗口就会加载这个页面:
    const win = new BrowserWindow({
        width: 800,
        height: 600
    });
    win.loadURL('https://www.baidu.com');
  1. 如上面代码,我们建立了一个窗口,然后加载百度首页,而preload脚本的加载时机就是窗口建立后,百度首页加载之前。若是有人问,若是不挪用loadURL方式,不加载页面,preload剧本会加载吗?谜底是会,但有什么用呢?你起个壳子不给人家看页面是什么鬼?不管这些,主要的是我们明白这个加载时机就好了;
    无论页面是否集成Node,此脚本都可以调用所有Node API:首先要说明的一点是,Electron5.x以上版本,默认无法在渲染进程中调用Node API,如需使用,需要预先设置:
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: true
        }
    });
  1. 然后还要清楚一点,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')
        }
    });

怎么用preload

明白应该差不多了,但什么场景能用到这玩意儿呢?按正常的逻辑来想,主进程启动后启动渲染历程,渲染进程加载页面就完事儿了,哪会用到这个preolad呢?

想一下,若是我们有以下场景:

a、若是我们启动了一个窗口(渲染进程),加载了一个线上的页面,本地没有页面文件,但要做一些错误处置,好比网络错误,页面加载失败,然后在页面空缺但时刻插入一些元素;

b、若是我们的一套代码部署在web端和客户端,需要用一个变量判断是在web端还是客户端;
………..
上面两个场景若是用preload来解决的话,思路是利用prelaod中能调用window工具的特点,好比b,代码中可以用window.isClient来判断是否在客户端,默以为false,然后在preload中把window.isClient设置为true,而对于部署在web端的代码来说,这个值就是false。

上面所说的场景b的preload 例子如下:

// 引入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单文件打包注意target要”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"
}

自动更新

我们都知道,electron其实是封了个chrome内核,抛开壳子不说,里面运行的就是我们的h5页面,而就算我们跑了个空项目,没有任何内容,打包后的安装包也得30M左右,我们希望自己的程序自动更新,那么更新机制是怎样的呢?

若是我们只改动了页面某一处的脚本,却要用户更新整个安装包,那显然太不合理了,一是体验不好,二是浪费流量……

基于这种度量,加上electron主进程和渲染进程的划分,那我们可以思考如下更新机制:
主进程有改动时,用户需要更新整个客户端(可以做动态更新,官方好像是说支持);渲染进程有改动时,我们只需要把h5包下载到本地然后加载就行了,这需要我们打包时能把h5包区分出来,在更新后能打开对应版本的h5包。
这里我们称主进程的更新为大版本更新,渲染进程的更新为小版本更新。

1、打包设置修改

由于牵扯到小版本的更新,那我们打包的时刻就得把这个“小版本”给打出来。这里只讲一下怎么把小版本的压缩包给打出来。

修改build.js,使用webpack打包主进程、打包preload、打包渲染进程,获得可执行文件目录app,然后引入electrin-builder对app目录进行打包,build一个安装包,然后把渲染进程的文件压缩并生成版本号。将渲染进程打包和压缩小版本文件拆分出来,因为分模块封装的好处,各个进程的打包逻辑拆出来,能随意组合还能复用。

详细代码就不贴出来了,太占篇幅,也没什么用,可以到https://github.com/luohao8023/electron-vue-template看完整代码。

2、增加启动页,启动页显示欢迎语等,在这里检查更新

这里我们暂且叫它检查更新页,这个检查更新页是渲染进程,用户打开程序时首先显示检查更新窗口,然而这个窗口也不一定显示检查更新字样,偷偷的检查就行了,有新版本就提醒更新,没有新版本就显示欢迎语。

这儿的逻辑是单独拆分出来的,不是自动更新的时刻把自动更新逻辑自己也给更新了,容易乱套。

修改主进程代码,程序启动时首先启动自动更新窗口:

<pre style="box-sizing: border-box; margin: 0px; padding: 0px; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; font-size: 13px; display: block; line-height: 1.42857; color: rgb(51, 51, 51); word-break: break-all; overflow-wrap: break-word; background-color: rgb(245, 245, 245); border: 1px solid rgb(204, 204, 204); border-radius: 4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">app.on('ready', () => { //注册快捷键打开控制台事宜
    shortcut.register('Command+Control+Alt+F5');
    mainWindow = updateWin.create();
});</pre>

然后注册监听事件,由于自动更新窗口逻辑完成之后需要唤醒主窗口,需要主进程来协调:

<pre style="box-sizing: border-box; margin: 0px; padding: 0px; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; font-size: 13px; display: block; line-height: 1.42857; color: rgb(51, 51, 51); word-break: break-all; overflow-wrap: break-word; background-color: rgb(245, 245, 245); border: 1px solid rgb(204, 204, 204); border-radius: 4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//启动主窗体
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();
});</pre>

自动更新窗口只需专注于更新逻辑就行了,逻辑竣事后呼起主窗口:

<pre style="box-sizing: border-box; margin: 0px; padding: 0px; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; font-size: 13px; display: block; line-height: 1.42857; color: rgb(51, 51, 51); word-break: break-all; overflow-wrap: break-word; background-color: rgb(245, 245, 245); border: 1px solid rgb(204, 204, 204); border-radius: 4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">        // 更新逻辑看下面伪代码
        const v1 = getOnlineVersion();
        const v2 = getLocalVersion();
        const needUpdate = checkVersion(v1, v2); if (needUpdate) {
            downloadVersion();
        } this.runMain();</pre>

在呼起主窗口的同时给主窗口通报参数,并通知主窗口有没有更新版本,以及主窗口需要加载哪个小版本的包,而主窗口在loadURL时也要做下改动:

<pre style="box-sizing: border-box; margin: 0px; padding: 0px; overflow: auto; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; font-size: 13px; display: block; line-height: 1.42857; color: rgb(51, 51, 51); word-break: break-all; overflow-wrap: break-word; background-color: rgb(245, 245, 245); border: 1px solid rgb(204, 204, 204); border-radius: 4px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">    let wwwroot = global.wwwroot.path ? global.wwwroot.path : __dirname;
    let filePath = url.pathToFileURL(path.join(wwwroot, 'index.html')).href;</pre>

而wwwrot就是当前小版本包的根路径,由主历程来维护,自动更新小版本后会修改这个值,以告诉主进程加载哪个版本。

好了,烦琐了一大堆,很多多少地方没贴代码,感受贴了代码的话,篇幅就不受控制了,照样去github看完整项目吧,自动更新这一块是伪代码,只实现了渲染历程的切换(即自动更新窗口呼起主窗口),详细的更新逻辑实现起来的话还要拿线上版本去对照,这个照样留给人人在现实项目中去调试吧。

好啦,有什么问题可以留言交流,也可以直接去看代码https://github.com/luohao8023/electron-vue-template

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容