导航: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。