Electron + Vue + Vscode构建跨平台应用(一)知识点补充
<https://blog.csdn.net/yi_master/article/details/83990973>
Electron + Vue + Vscode构建跨平台应用(二)Electron + Vue环境搭建
<https://blog.csdn.net/yi_master/article/details/84028602>
Electron + Vue + Vscode构建跨平台应用(三)利用webpack搭建vue项目
<https://blog.csdn.net/yi_master/article/details/84539739>
Electron + Vue + Vscode构建跨平台应用(四)利用Electron-Vue构建Vue应用详解
<https://blog.csdn.net/yi_master/article/details/84065884>


  通过上面几篇文章我们已经基本了解了如何利用Electron-Vue来构建Vue项目,但是项目是这么运行的,初始项目代码之间有什么关系,我们还不太清楚,所以这篇我们将主要解决如下四个问题

  1: 整个初始项目是怎么运行的
  2: npm run dev背后做了什么
  3: npm install到底根据什么策略去下载对应的依赖
  4: 如何打包成跨平台的文件,比如打包成windows平台的exe文件

针对第1个问题:整个初始项目是怎么运行的

  我们需要先了解一下electron当中的 app 模块和 BrowserWindow 模块

app模块 简介:

  1)
app模块属于Electron里面的一个子模块,他负责整个应用的生命周期,有点类似Android里面的Applicatio类,当通过import关键字导致之后,便可以在项目当中使用了。

  2) 运行在主线程当中

  3)主要生命周期方法
    3.1)ready:当 Electron 完成初始化时被触发,可用来做初始化动作

    3.2)window-all-closed:当所有的窗口都被关闭时触发。
如果没有监听此事件,当所有窗口都已关闭时,默认行为是退出应用程序。但如果你监听此事件, 将由你来控制应用程序是否退出。 如果用户按下了 Cmd + Q,
或者开发者调用了 app.quit() ,Electron 将会先尝试关闭所有的窗口再触发 will-quit 事件, 在这种情况下
window-all-closed 不会被触发。

    3.3)activate:当应用被激活时触发,常用于点击应用的 dock 图标的时候。

    3.4)before-quit:在应用程序开始关闭它的窗口的时候被触发。 调用 event.preventDefault()
将会阻止终止应用程序的默认行为。

    3.5)browser-window-created:当一个 BrowserWindow 被创建的时候触发。

  4)监听方法:可以通过app.on方法可监听Electron的整个生命周期中的事件,如监听ready生命周期
app.on('ready', createWindow)
BrowserWindow模块 简介:

BrowserWindow模块属于Electron里面的一个子模块,他负责创建和控制浏览器窗口,当通过import关键字导致之后,便可以在项目当中使用了。

2)常见属性设置
2.1)width Integer - 窗口宽度,单位像素. 默认是 800
2.2)height Integer - 窗口高度,单位像素. 默认是 600
2.3)center Boolean - 窗口屏幕居中

3)主要事件方法
3.1)page-title-updated 当文档改变标题时触发,使用 event.preventDefault() 可以阻止原窗口的标题改变.

3.2)close 在窗口要关闭的时候触发. 它在DOM的 beforeunload and unload 事件之前触发.使用
event.preventDefault() 可以取消这个操作

3.3)focus 在窗口获得焦点的时候触发.

4)监听事件
可以通过BrowserWindow.on方法可监听整个窗体的的事件,如监听closee事件
mainWindow = new BrowserWindow({ height: 563, useContentSize: true, width:
1000 }) mainWindow.on('closed', () => { mainWindow = null })
有了这些知识的铺垫,我们就能更好的解答第1个问题了,先看下主进程的入口文件src/main/index.js
'use strict' import { app, BrowserWindow } from 'electron' /** * Set
`__static` path to static files in production *
https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
*/ if (process.env.NODE_ENV !== 'development') { global.__static =
require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') } let
mainWindow const winURL = process.env.NODE_ENV === 'development' ?
`http://localhost:9080` : `file://${__dirname}/index.html` function
createWindow () { /** * Initial window options */ mainWindow = new
BrowserWindow({ height: 563, useContentSize: true, width: 1000 })
mainWindow.loadURL(winURL) mainWindow.on('closed', () => { mainWindow = null })
} app.on('ready', createWindow) app.on('window-all-closed', () => { if
(process.platform !== 'darwin') { app.quit() } }) app.on('activate', () => { if
(mainWindow === null) { createWindow() } })
第1行,在js文件的开头引入严格模式,严格模式的意义简单理解就是规范代码编写,提早发现代码缺陷
第3行,通过import的方式,引入app模块和BrowserWindow模块
第18~26行,定义createWindow方法,创建BrowserWindow实例mainWindow,并设置窗口宽高分别为1000px,563px
第30行,监听窗口closed事件,当窗口关闭,将mainWindow赋值为null
第35行,监听应用程序ready事件,当应用程序准备运行的时候,调用createWindow方法

  总结: 主进程的入口文件src/main/index.js采用严格模式,对窗口和应用的生命周期进行检测,创建出窗体,这样渲染进程就可以在这个窗体展示内容了

  当主进程运行之后,这时候就到渲染进程了,渲染进程的入口文件是src/renderer/main.js
import Vue from 'vue' import axios from 'axios' import App from './App' import
router from './router' import store from './store' if (!process.env.IS_WEB)
Vue.use(require('vue-electron')) Vue.http = Vue.prototype.$http = axios
Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({
components: { App }, router, store, template: '<App/>' }).$mount('#app')
  第1行,通过import的方式引入vue模块,我们看下引入vue的路径:from ‘vue’,之前我们通过npm
install命令安装了一些依赖,你可以在node_modules文件夹中看到vue的源码,这里的from
‘vue’的具体位置其实就是node_modules文件夹中的vue
  第4行:引入App模板,引入位置from ‘./App’,你可以参考项目源码,可以发现这个目录其实是src/renderer/App.vue
  第5行:引入路由router,路由简单理解就是配置了跳转链接,它和a href标签最大的区别是通过路由进行的跳转页面不会发生刷新,但是通过a
href标签进行的跳转页面会刷新一次
  第6行:引入状态管理仓库,你可以理解为一个小型的数据库,其他模块可以对这个数据库的数据进行读写操作
  第13行,匿名创建了Vue的实例类
  第14行,声明组件App
  第15行,在整个Vue实例中有路由属性router
  第16行,在整个Vue实例中有仓储属性store
  第17行,使用组件App,这个App组件的具体位置为src/renderer/App.vue,当使用了这个组件之后,那么App.vue即将开始渲染

  第18行,将创建的Vue实例和元素id为app的标签进行绑定,这个id为app的标签来自于App.vue,我们也可以通过el:的方式将Vue实例和标签元素进行绑定

  总结: 渲染进程的入口文件src/renderer/main.js主要目的是挂载Vue实例到App.vue模板中的app元素当中,开始渲染整个窗口

  终于开始在窗口里面展示内容了,我们移步到App.vue中
<template> <div id="app"> <router-view></router-view> </div> </template>
<script> export default { name: 'ele-vue-learning' } </script> <style> /* CSS
*/ </style>
  第1行,申明一个模板

  第2行,创建一个id为app的层,这个就是vue挂载的具体位置,为什么要给vue挂载到一个具体的位置上,我的理解是类似java的类作用域,此处可理解为vue在层app内可见

  第3行,通过‘router-view’标签使用路由,路由的位置为src/renderer/router/index.js,有了路由之后,就可以完成页面的跳转
  第7行~第10行,一个Vue可以理解为一个模板或者一个java类,当使用export对其进行导出之后,其他模板或者类就可以通过import的方式使用它


  当加载完App.vue的时候,App.vue会根据路由route的配置跳转的其他页面,我们看下route的配置,其文件位置为:src/renderer/router/index.js
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export
default new Router({ routes: [ { path: '/', name: 'landing-page', component:
require('@/components/LandingPage').default }, { path: '*', redirect: '/' } ] })

  第2行,引入vue-router路由,这里的vue-router和我们之前的src/renderer/router的路由不一样,vue-router是内置的路由模块,页面的跳转是由vue-router完成的,而src/renderer/router是一个配置文件
  第2行,使vue具备路由功能
  第7行,路由的地址配置是由routes数组负责创建
  第9~11行,配置的路由名为‘landing-page’,具体地址为components/LandingPage.vue
这里使用LandingPage.vue模板是通过component: require方式执行的,他是异步加载组件的方法

  总结:
App.vue中app标签挂在到Vue组件中,然后通过route-view的方式进入到router/index.js中,在index.js配置了路由功能,是窗口展示LandingPage.vue组件内容

  针对第2个问题:npm run dev背后做了什么

  在建立项目的时候,可以看见一个package.json的文件,如下图

  这个文件里面封装了很多脚本命令,当我们执行npm run
dev的时候,其实就是执行package.json中的scripts中的dev命令,这个dev其实执行的是

node .electron-vue/dev-runner.js"

  这段脚本代码可以理解为在命令行模式中,执行
.electron-vue/dev-runner.js,我们看下dev-runner.js文件,我只截取了关键代码进行分析
function init () { greeting() Promise.all([startRenderer(), startMain()])
.then(() => { startElectron() }) .catch(err => { console.error(err) }) } init()
dev-runner.js的主方法为init方法

  第2行,调用greeting方法,greeting方法主要作用是打印一些欢迎信息,所以当你执行npm run dev之后,你会发现如下的打印信息

他就是greeting方法打印出来的,他使用的字体是simple3d,黄色…

  重点看下第4行,通过调用 Promise.all方法,传入参数为数组[startRenderer(),
startMain()];all方法传入的数组参数其内容返回的是Promise对象,我们可以看一下startRenderer方法,他返回的就是一个Promise,同理startMain方法也是返回一个Promise对象;
function startRenderer () { return new Promise((resolve, reject) => { ...... }
function startMain () { return new Promise((resolve, reject) => { ...... }

  Promise.all方法的含义是只有当参数成功执行之后,才会执行then之后的方法,也就是第6行的startElectron()方法,如果其中任意一个方法执行失败,则执行第9行的代码,打印erro信息

  我们看一下startRenderer方法的具体实现
function startRenderer () { return new Promise((resolve, reject) => {
rendererConfig.entry.renderer = [path.join(__dirname,
'dev-client')].concat(rendererConfig.entry.renderer) rendererConfig.mode =
'development' const compiler = webpack(rendererConfig) hotMiddleware =
webpackHotMiddleware(compiler, { log: false, heartbeat: 2500 })
compiler.hooks.compilation.tap('compilation', compilation => {
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit',
(data, cb) => { hotMiddleware.publish({ action: 'reload' }) cb() }) })
compiler.hooks.done.tap('done', stats => { logStats('Renderer', stats) }) const
server = new WebpackDevServer( compiler, { contentBase: path.join(__dirname,
'../'), quiet: true, before (app, ctx) { app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => { resolve() }) } } ) server.listen(9080) })
}
  第22行,创建WebpackDevServer对象,WebpackDevServer用于创建一个http服务器
  第22行,第36行,监听9080端口

  总结: startRenderer主要作用是创建一个监听9080端口的http服务器

  我们看一下startMain方法的具体实现
function startMain () { return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname,
'../src/main/index.dev.js')].concat(mainConfig.entry.main) mainConfig.mode =
'development' const compiler = webpack(mainConfig) ... ... compiler.watch({},
(err, stats) => { if (err) { console.log(err) return } logStats('Main', stats)
if (electronProcess && electronProcess.kill) { manualRestart = true
process.kill(electronProcess.pid) electronProcess = null startElectron()
setTimeout(() => { manualRestart = false }, 5000) } resolve() }) }) }
  第5行,调用webpack方法,传入mainConfig作为参数
  第7行,启用 Watch 模式。这意味着在初始构建之后,webpack 将继续监听任何已解析文件的更改。

  总结: startMain方法主要作用是启用watch模式,当项目文件发生变化的时候,重新编译;所以你会发现当你修改项目并保存之后,npm run
dev似乎有重新执行了一遍;

  当startRenderer和startMain顺利执行之后,便开始执行startElectron方法
function startElectron () { var args = [ '--inspect=5858',
path.join(__dirname, '../dist/electron/main.js') ] // detect yarn or npm and
process commandline args accordingly if
(process.env.npm_execpath.endsWith('yarn.js')) { args =
args.concat(process.argv.slice(3)) } else if
(process.env.npm_execpath.endsWith('npm-cli.js')) { args =
args.concat(process.argv.slice(2)) } electronProcess = spawn(electron, args)
electronProcess.stdout.on('data', data => { electronLog(data, 'blue') })
electronProcess.stderr.on('data', data => { electronLog(data, 'red') })
electronProcess.on('close', () => { if (!manualRestart) process.exit() }) }
  第14行,通过调用spawn方法,spawn方法实际实现是require(‘child_process’),这样开启一个子线程
  第16~24行,在线程上监听服务器流信息

  针对第3个问题:npm install到底根据什么策略去下载对应的依赖

    当执行完npm
install命令之后,我们可以发现在项目结构中多了一个node_modulesde的文件夹,这个文件夹存储的就是依赖文件包,这些依赖的配置也是根据package.json中的devDependencies配置来下载的,如下图


  在package.json中出现了两个关键属性devDependencies和dependencies,
其中devDependencies用于本地开发环境,是只会在开发环境下依赖的模块,生产环境不会被打入包内
而dependencies用于用户发布环境,dependencies依赖的包不仅开发环境能使用,生产环境也能使用

  针对第4个问题:如何打包成跨平台的文件,比如打包成windows平台的exe文件
  在package.json中其实已经为我们配置好了相关的命令

所以我们只要执行如下命令即可

npm run build:win32

编译如果没有问题,会有如下提示


然后查看项目路径,你会发现在项目文件夹中的build目录中多了一个ele-vue-learning-win32-x64文件夹,这里面就是我们生成的exe文件


打开界面如下