目录

  1. 界面源码介绍

页面元素渲染结构分析

界面源码介绍

前面介绍了整个环境的创建过程,这一节我们来看具体页面渲染的过程。
由于页面渲染都是在渲染进程完成的,我们就从渲染进程的配置文件来看入口在哪里。
前面介绍过,渲染进程公用了两个配置文件,一个是electron-vue/dev-client.js,他负责在界面上提示当前的编译步骤,而另一个配置文件在webpack.renderer.config.js中定义:

1
2
3
4
5
6
7
let rendererConfig = {
...
entry: {
renderer: path.join(__dirname, '../src/renderer/main.js')
},
...
}

也就是说,渲染进程的真正入口文件在src/renderer/main.js里面。
我们来看这个文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import App from './App'
import router from './router'

if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.config.productionTip = false

new Vue({
components: { App },
router,
template: '<App/>'
}).$mount('#app')

这个文件内容很简单,就是创建一个Vue对象,并将App这个Vue组件挂载到页面的app元素挂载上去。
这里的App组件根据import语句看到,他就是当前目录下的App.vue文件,由于在webpack配置的extensions字段中说明了所有vue格式文件在引用时都无需添加后缀,因此这里的import只需要使用App即可。
那么这个App组件究竟挂载到哪里的html上呢?我们还要从webpack配置说起。
渲染进程的webpack中使用了一个html-webpack-plugin的插件,可以根据模板生成首页的html:

1
2
3
4
5
6
7
8
9
10
11
12
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
nodeModules: process.env.NODE_ENV !== 'production'
? path.resolve(__dirname, '../node_modules')
: false
}),

这说明模板就是src/index.ejs文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>electron-vue-framework</title>
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<script>
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
</script>
<% } %>
</head>
<body>
<div id="app"></div>
<script>
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
</script>

</body>
</html>

这个模板会被html-webpack-plugin插件生成为index.html文件,而这个模板中就包含了唯一的

元素:app
也就是说,App这个Vue组件的挂载点就在生成的index.html文件上,并且是整个页面唯一的元素。
我们接下来看App这个组件的内容:

1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="app">
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'electron-vue-framework'
}
</script>

这个组件说明,内部包含一个路由控件,并且没有指定具体路由地址(那么Vue就会加载路由中的默认地址),我们来看router/index.js中为Vue定义的路由表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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: '/'
}
]
})

这说明默认路由叫做landing-page,对应的组件是LandingPage.vue,我们来看这个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template>
<div id="wrapper">
<img id="logo" src="~@/assets/logo.png" alt="electron-vue">
<main>
<div class="left-side">
<span class="title">
Welcome to your new project!
</span>
<system-information></system-information>
</div>

<div class="right-side">
<div class="doc">
<div class="title">Getting Started</div>
<p>
electron-vue comes packed with detailed documentation that covers everything from
internal configurations, using the project structure, building your application,
and so much more.
</p>
<button @click="open('https://simulatedgreg.gitbooks.io/electron-vue/content/')">Read the Docs</button><br><br>
</div>
<div class="doc">
<div class="title alt">Other Documentation</div>
<button class="alt" @click="open('https://electron.atom.io/docs/')">Electron</button>
<button class="alt" @click="open('https://vuejs.org/v2/guide/')">Vue.js</button>
</div>
</div>
</main>
</div>
</template>
<script>
...
</script>
<style>
...
</style>

这里很清晰的看到,这个页面包含了left-sideright-side两个div,分列页面左右(具体排列可以查看css中描述),左侧的布局还嵌套了一个system-information的组件,而右侧布局中就是一些文字+按钮。
system-information的组件就是SystemInformation.vue文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div>
<div class="title">Information</div>
<div class="items">
<div class="item">
<div class="name">Path:</div>
<div class="value">{{ path }}</div>
</div>
<div class="item">
<div class="name">Route Name:</div>
<div class="value">{{ name }}</div>
</div>
<div class="item">
<div class="name">Vue.js:</div>
<div class="value">{{ vue }}</div>
</div>
<div class="item">
<div class="name">Electron:</div>
<div class="value">{{ electron }}</div>
</div>
<div class="item">
<div class="name">Node:</div>
<div class="value">{{ node }}</div>
</div>
<div class="item">
<div class="name">Platform:</div>
<div class="value">{{ platform }}</div>
</div>
</div>
</div>
</template>
<script>
...
</script>
<style>
...
</style>

这都是一些系统属性。
至此,我们明白了整个页面是怎么被渲染出来的。他的布局如下:

通过vue-devtools工具我们也可以看出他的渲染结构:

至此,整个页面的渲染过程我们就分析完了。

评论和共享

目录

  1. 1.渲染进程启动过程分析
    1. 1.1.渲染进程创建webpack对象
      1. 渲染进程的dev-client配置文件
      2. 渲染进程的webpack.renderer.config配置文件
    2. 1.2.渲染进程利用webpack对象来创建WebpackDevServer对象
    3. 1.3.渲染进程监听webpack编译过程
  2. 2.主进程过程分析
    1. 1.1.主进程的webpack创建过程
      1. 主进程的index.dev.js配置文件
      2. 主进程的webpack.main.config.js配置文件
    2. 2.2.主进程的编译过程跟踪
    3. 2.3.主进程的代码”热更新”
  3. 3.Electron过程分析

这一节我们来看开发环境的启动流程。
该框架主要修改是对开发环境的优化,包括了于开发环境的配置文件隔离,主进程和渲染进程配置文件隔离,编译过程提示等功能,因此这一节内容才是整个框架的核心。
我们从开发人员用到的启动命令说起。
从package中我们看到启动命令就是:

1
"dev": "node .electron-vue/dev-runner.js",

也就是在终端使用npm run dev就可以了,而这个命令对应的脚本就是dev-runner.js。

1
2
3
4
5
6
7
8
9
10
11
function init() {
greeting();

Promise.all([startRenderer(), startMain()])
.then(() => {
startElectron();
})
.catch(err => {
console.error(err);
});
}

这是脚本运行时执行的唯一方法,其中的greeting()就是在终端输出一个Log,如图所示:

然后做了三个操作分别启动了渲染进程、主进程和Electron:

  • startRenderer()
  • startMain()
  • startElectron()

下面我们逐个来看具体的启动流程。

1.渲染进程启动过程分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function startRenderer() {
return new Promise((resolve, reject) => {
//加载webpack配置文件
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer);

//创建webpack
const compiler = webpack(rendererConfig);
//创建webpack-hot-middleware
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500
});

//编译状态监控
compiler.plugin('compilation', compilation => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({action: 'reload'});
cb();
});
});

compiler.plugin('done', stats => {
logStats('Renderer', stats);
});

//创建webpack-dev-server
const server = new WebpackDevServer(
compiler,
{
contentBase: path.join(__dirname, '../'),
quiet: true,
before(app, ctx) {
app.use(hotMiddleware);
ctx.middleware.waitUntilValid(() => {
resolve();
});
}
}
);

server.listen(9080);
});
}

在这个方法里,共完成了三个操作:

  • 创建webpack对象
  • 利用webpack对象来创建WebpackDevServer对象
  • 监听webpack编译过程

我们分别来看这三个操作的具体情况。

1.1.渲染进程创建webpack对象

webpack对象创建时使用的配置文件是我们分析的重点。我们来看webpack的配置文件,也就是rendererConfig变量:

1
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer);

这说明webpack的配置文件来自于两个文件:dev-client模块和rendererConfig.entry.renderer变量。
这里看一下源码就知道,concat方法连接而成的数组中包含了两个元素,一个是”dev-client”,另一个是根目录的”../src/renderer/main.js”文件,也就是说,webpack的entry参数实际上是这种形式:

1
2
3
entry: {
renderer: ['dev-client',path.join(__dirname, '../src/renderer/main.js')]
},

再来看一下output参数内容(在webpack.renderer.config.js):

1
2
3
4
5
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},

这种用法包含了三个信息:

  • webpack将会把dev-client模块和main.js文件同时打包进output指定的文件中
  • dev-client是一个模块,根据源码查找,这个模块就是dev-client.js文件
  • entry内容以key–value形式定义,那么output中的name变量就是entry中的key

综合来说,这种用法的作用就是:同时把dev-client.js和main.js文件打包,输出到根目录下的/dist/electron/render.js文件中。
这个信息非常重要,后面要用到。
接下来我们先来解决这两个文件的作用。

渲染进程的dev-client配置文件

这个文件内容很简单,直接贴出源码:

1
2
3
4
5
6
7
8
9
10
11
12
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true');
//注册webpack-hot-middleware监听器
hotClient.subscribe(event => {
//这里只处理了Main进程发送的"compiling"的事件,实际上在Render进程中还发送了"reload"的消息
if (event.action === 'compiling') {
...
<div id="dev-client">
Compiling Main Process...
</div>
`;
}
});

我们直接来说作用,他负责在编译过程中,在界面上显示“Compiling Main Process…”的提示语。
效果如下:

至于他如何检测编译过程,我们稍后来说。

渲染进程的webpack.renderer.config配置文件

接下来我们来看渲染进程的主配置文件的主要内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//将vue模块列为白名单
let whiteListedModules = ['vue'];
let rendererConfig = {
//指定sourcemap方式
devtool: '#cheap-module-eval-source-map',
entry: {
renderer: path.join(__dirname, '../src/renderer/main.js')
},
externals: [
//编译白名单
...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
],
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
},
{
test: /\.html$/,
use: 'vue-html-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
},
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
extractCSS: process.env.NODE_ENV === 'production',
loaders: {
sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
scss: 'vue-style-loader!css-loader!sass-loader'
}
}
}
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'imgs/[name]--[folder].[ext]'
}
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name]--[folder].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: {
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name]--[folder].[ext]'
}
}
}
]
},
node: {
//根据版本信息确定__dirname和__filename的行为
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
plugins: [
//css文件分离
new ExtractTextPlugin('styles.css'),
//自动生成html首页
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../src/index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true
},
nodeModules: process.env.NODE_ENV !== 'production'
? path.resolve(__dirname, '../node_modules')
: false
}),
//热更新模块
new webpack.HotModuleReplacementPlugin(),
//在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段
new webpack.NoEmitOnErrorsPlugin()
],
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},
resolve: {
alias: {
//在代码中使用@代表renderer目录
'@': path.join(__dirname, '../src/renderer'),
//精确指定vue特指vue.esm.js文件
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['.js', '.vue', '.json', '.css', '.node']
},
//指定编译为 Electron 渲染进程
target: 'electron-renderer'
};

这个配置文件的主要配置项都给出了注释,作用很容易理解,这里不再详细介绍,webpack更多配置可以查看这里

1.2.渲染进程利用webpack对象来创建WebpackDevServer对象

我们先来区分一下三个概念:

  • webpack

    webpack本身只是打包工具,不具备热更新功能

  • webpack-hot-middleware

    An express-style development middleware for use with webpack bundles and allows for serving of the files emitted from webpack. This should be used for development only.
    The middleware installs itself as a webpack plugin, and listens for compiler events.
    他是一个运行于内存中的(非常重要的概念,作用稍后会提到)一个文件系统,可以检测文件改动自动编译(到内存中)。

  • webpack-dev-server

    Use webpack with a development server that provides live reloading. This should be used for development only. It uses webpack-dev-middleware under the hood, which provides fast in-memory access to the webpack assets.
    他是利用webpack-hot-middleware搭建了Express的服务器,从而实现实时刷新的功能。

了解了这三个概念之后,我们来看具体如何创建WebpackDevServer对象,其实很简单,把我们刚才创建的webpack作为参数来初始化WebpackDevServer即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//创建webpackHotMiddleware
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500
});

//创建WebpackDevServer
const server = new WebpackDevServer(
//以webpack对象作为参数
compiler,
{
contentBase: path.join(__dirname, '../'),
quiet: true,
before(app, ctx) {
//使用webpackHotMiddleware
app.use(hotMiddleware);
ctx.middleware.waitUntilValid(() => {
resolve();
});
}
}
);

//服务器运行在9080端口
server.listen(9080);

这个创建过程很清晰,需要注意的是,webpackHotMiddleware的heartbeat的参数作用并不是检测文件的频率,而是保持服务器链接存活的心跳频率:

heartbeat - How often to send heartbeat updates to the client to keep the connection alive. Should be less than the client’s timeout setting - usually set to half its value.

1.3.渲染进程监听webpack编译过程

经过以上的步骤,渲染进程的初始化就基本上结束了,但是我们看到在创建过程中有两个“小插曲”:

1
2
3
4
5
6
7
8
9
10
compiler.plugin('compilation', compilation => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({action: 'reload'});
cb();
});
});

compiler.plugin('done', stats => {
logStats('Renderer', stats);
});

其中第二个logStats作用就是在终端屏幕上输出编译过程(后面我们在看main进程编译过程也会输出到终端中),如图所示:

而第一个hotMiddleware.publish()作用是什么呢?
这其实是一个钩子函数,检测webpack的编译状态,把其中的html-webpack-plugin-after-emit状态,发布到webpackHotMiddleware中。

我们可以检测多种状态,比如

  • html-webpack-plugin-before-html-processing
  • html-webpack-plugin-after-html-processing
  • html-webpack-plugin-after-emit

然后我们就可以在渲染进程中检测到这一状态。
然后还记得之前的dev-client模块吗?我们再来看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true');
//注册webpack-hot-middleware监听器
hotClient.subscribe(event => {
//这里只处理了Main进程发送的"compiling"的事件,实际上在Render进程中还发送了"reload"的消息
if (event.action === 'compiling') {
...
<div id="dev-client">
Compiling Main Process...
</div>
`;
}
});

也就是说,在整个webpack-hot-middleware编译过程中发送的编译消息,将会在界面展示一个提示框提示开发者,编译器正在进行的工作。
从这一点来看也发现,该框架的作者对细节的把握多么的重视。

2.主进程过程分析

前面分析了渲染进程的启动过程,现在来看主进程的启动过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function startMain() {
return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main);
//创建主进程的webpack
const compiler = webpack(mainConfig);

compiler.plugin('watch-run', (compilation, done) => {
//向webpack-hot-middleware发布"compiling"的消息,用于页面显示
hotMiddleware.publish({action: 'compiling'});
done();
});

compiler.watch({}, (err, stats) => {
if (err) {
return;
}
...
if (electronProcess && electronProcess.kill) {
//主进程文件发生改变,重启Electron
manualRestart = true;
process.kill(electronProcess.pid);
electronProcess = null;
startElectron();

setTimeout(() => {
manualRestart = false;
}, 5000);
}

resolve();
});
});
}

主进程的启动和渲染进程非常相似,共经历了三个步骤:

  • 创建webpack
  • 实时发布hotMiddleware状态(用于页面展示编译过程)
  • 主进程的代码”热更新”

下面我们也来分别介绍这三个过程。

1.1.主进程的webpack创建过程

webpack创建的关键在于配置文件,和渲染进程一样,主进程也有两个配置文件:

  • /src/main/index.dev.js
  • /.electron-vue/webpack.main.config.js

下面分别介绍他们内容。

主进程的index.dev.js配置文件

我们直接来看这个文件内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
process.env.NODE_ENV = 'development'

//安装`electron-debug`工具
require('electron-debug')({ showDevTools: true })

//安装Vue的一个chrome开发工具`vue-devtools`
require('electron').app.on('ready', () => {
let installExtension = require('electron-devtools-installer')
installExtension.default(installExtension.VUEJS_DEVTOOLS)
.then(() => {})
.catch(err => {
console.log('Unable to install `vue-devtools`: \n', err)
})
})
...

这个配置文件的作用就是安装了electron-debugvue-devtools两个工具,其中vue-devtools工具因为网络原因无法安装,可以自己手动安装。

主进程的webpack.main.config.js配置文件

这个文件主要配置项如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
let mainConfig = {
entry: {
main: path.join(__dirname, '../src/main/index.js')
},
externals: [
...Object.keys(dependencies || {})
],
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
}
]
},
node: {
__dirname: process.env.NODE_ENV !== 'production',
__filename: process.env.NODE_ENV !== 'production'
},
output: {
filename: '[name].js',
libraryTarget: 'commonjs2',
path: path.join(__dirname, '../dist/electron')
},
plugins: [
new webpack.NoEmitOnErrorsPlugin()
],
resolve: {
extensions: ['.js', '.json', '.node']
},
//编译为 Electron 主进程
target: 'electron-main'
};

他和渲染进程最大不同就是主进程处理的文件很有限,他不会(不需要)处理vue、图片、css、html等文件类型。

2.2.主进程的编译过程跟踪

这里和渲染进程的跟踪稍有不同,我们对比一下:
渲染进程

1
2
3
4
5
6
compiler.plugin('compilation', compilation => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
hotMiddleware.publish({action: 'reload'});
cb();
});
});

主进程

1
2
3
4
5
compiler.plugin('watch-run', (compilation, done) => {
logStats('Main', chalk.white.bold('compiling...'));
hotMiddleware.publish({action: 'compiling'});
done();
});

从这里发现,他们分别监听了编译的不同事件。渲染进程监听的是compilation,而主进程监听的是watch-run

这里的”watch-run”只会出现在webpack3.X版本中,在webpack4.x版本上,”watch-run”事件被修改为了”watchRun”事件。
webpack还支持的一些事件包括:

  • afterPlugins
  • afterResolvers
  • environment
  • afterEnvironment
  • beforeRun
  • run
  • beforeCompile
  • afterCompile

从webpack说明文档看出,”compilation”会在编译器创建时触发:

Runs a plugin after a compilation has been created

“watchRun”也是会在编译器创建之后被触发,不同的是,这个这个事件只有在”watch mode”模式下才会生效:

Executes a plugin during watch mode after a new compilation is triggered but before the compilation is actually started.

由于渲染进程使用了WebpackDevServer的热更新,因此可以检测compilation事件来跟踪事件。
主进程在初始化过程中,由于没有使用WebpackDevServer,而是开启了watch模式,所以可以检测到这个事件。

2.3.主进程的代码”热更新”

主进程没有使用WebpackDevServer的方式自动更新界面,而是通过webpack的watch模式,不断重启Electron实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
compiler.watch({}, (err, stats) => {
if (err) {
return;
}

if (electronProcess && electronProcess.kill) {
manualRestart = true;
process.kill(electronProcess.pid);
electronProcess = null;
//重启Electron
startElectron();

setTimeout(() => {
manualRestart = false;
}, 5000);
}

resolve();
});

3.Electron过程分析

前面两节分析了主进程和渲染进程的创建流程,在调试模式命令的最后一步就是开启Electron:

1
2
3
4
5
6
7
8
function init() {
Promise.all([startRenderer(), startMain()])
.then(() => {
startElectron();
})
.catch(err => {
});
}

下面我们来看如何开启Electron:

1
2
3
4
function startElectron() {
electronProcess = spawn(electron, ['--inspect=5858', '.']);
...
}

原来是通过node的spawn方法运行了electron,并传递了两个参数。
两个参数分别代表打开5858的调试端口和electron的运行目录(也就是当前目录)。
至此,调试环境运行时的流程就介绍完了,我们用一张流程图来归纳一下这个过程:

评论和共享

目录

我们先从生产环境打包流程来分析。
从package.json文件入口来看打包命令和调用的脚本:

1
2
3
4
5
6
7
8
9
10
"scripts": {
"build": "node .electron-vue/build.js",
"build:darwin": "cross-env BUILD_TARGET=darwin node .electron-vue/build.js",
"build:linux": "cross-env BUILD_TARGET=linux node .electron-vue/build.js",
"build:mas": "cross-env BUILD_TARGET=mas node .electron-vue/build.js",
"build:win32": "cross-env BUILD_TARGET=win32 node .electron-vue/build.js",
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
...
},

这说明打包的入口文件为build.js,我们就从这个入口文件来分析打包的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//用到的配置文件
const buildConfig = require('./build.config')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')

//除了`clean`和`web`的命令外,其他指令都会进行build()的操作:
if (process.env.BUILD_TARGET === 'clean') clean()
else if (process.env.BUILD_TARGET === 'web') web()
else build()


function build () {
...
//构建app
bundleApp()

...
//打包主进程
pack(mainConfig).then(result => {
}).catch(err => {
})

...
//打包渲染进程
pack(rendererConfig).then(result => {
}).catch(err => {
})
}
function bundleApp () {
packager(buildConfig, (err, appPaths) => {
...
})
}

function pack (config) {
return new Promise((resolve, reject) => {
webpack(config, (err, stats) => {
...
})
})
}

这个构建步骤中,分别进行了三个操作:

  • 使用buildConfig(也就是build.config.js)–构建app
  • 使用mainConfig(也就是webpack.main.config.js)–打包主进程
  • 使用rendererConfig(也就是webpack.renderer.config.js)–打包渲染进程

主进程和渲染进程使用的配置文件我们在稍后的开发流程中分析,这里主要看构建app用的配置文件,也就是build.config文件:
文件内容很简单,我们直接贴出源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const path = require('path')
module.exports = {
//目标架构和平台
arch: 'x64',
platform: process.env.BUILD_TARGET || 'all',
//是否启用asar压缩打包
asar: true,
//打包目录
dir: path.join(__dirname, '../'),
//应用图标
icon: path.join(__dirname, '../build/icons/icon'),
ignore: /(^\/(src|test|\.[a-z]+|README|yarn|static|dist\/web))|\.gitkeep/,
out: path.join(__dirname, '../build'),
overwrite: true
}

这个文件内容很简单,用于electron-packager打包时读取,主要配置了最终生成exe文件的一些参数,包括:

  • arch
  • platform
    目标架构和平台
  • asar
    是否启用asar压缩打包
  • out
  • dir
    指定生成目录
  • icon
    图标
  • ignore
    忽略那些文件
  • overwrite
    覆盖模式打包

该文件中配置的参数其实都可以通过命令的形式实现:

1
electron-packager ./app <name> --platform=win32 --arch=x64 --overwrite --ignore=dev-settings

写在配置文件中可以实现“傻瓜式”的打包目的。
这就是生产环境打包的过程。
下一节我们看开发环境的启动过程。

评论和共享

目录

  1. 1.下载架构模板
  2. 2.代码结构

本系列文章将介绍electron-vue前端框架的作用、结构、使用方法。

electron-vue是SimulatedGREG基于vue-cli搭建的Vue+Webpack+Electron脚手架,可以用来开发跨PC平台的应用,源码地址在这里
其主要功能/特色包括:

  • 主进程和渲染进程配置文件分离
  • 代码热更新
  • 详细的Log输出
  • 除了必备的Electron、Vue、Webpack等插件外,还可以一键配置:Axios\Vue-router\Vuex\Eslint\等插件

由于其自带的说明文件仅仅说明了该项目的概要、使用方法,并没有整个结构的解释,本系列文章就来从源码角度分析这个脚手架如何管理代码、如何分离编译环境、如何进行热更新等问题。

1.下载架构模板

由于electron-vue基于vue-cli进行了二次封装,因此在使用之前,需要先安装vue-cli的脚手架:

1
npm install -g vue-cli

然后初始化electron-vue的项目:

1
vue init simulatedgreg/electron-vue my-project

然后在项目根目录下使用npm或yarn安装依赖即可使用:

1
2
3
cd my-project
yarn # or npm install
yarn run dev # or npm run dev

2.代码结构

我们先来看一下原始版的代码结构:
图图图
从这个结构中可以看到该框架下的文件大致可以分为四个部分:

  • webpack配置文件
  • 生成目录&依赖目录
  • 源码目录
  • 全局配置文件

接下来的几个小节我们分别来介绍他们的作用。

评论和共享

  • 第 1 页 共 1 页
作者的图片

杜少峰

胆小认生,话少闷骚


Android & 前端


上海