- 安装:
npm install -g vue-cli
【注意是2.x版本的cli,并不是3x】- 文档:simulatedgreg.gitbooks.io/electron-vu…
- 创建:
vue init simulatedgreg/electron-vue projectName
electron-vue是SimulatedGREG基于vue-cli搭建的Vue+Webpack+Electron脚手架,可以用来开发跨PC平台的应用
功能与特色
- 主进程和渲染进程配置文件分离
- 代码热更新
- 详细的Log输出
- 除了必备的Electron、Vue、Webpack等插件外,还可以一键配置:Axios\Vue-router\Vuex\Eslint\等插件
目录架构
.electron-vue
webpack配置文件build/dist/node_modules
生成目录 依赖目录src/*
源码main
主进程index.dev.js
开发index.js
主进程
renderer
渲染进程- vue,类似于新建的vue项目,包括
main.js App.vue
组件 路由等等
- vue,类似于新建的vue项目,包括
static
静态资源.babelsrc .gitignore .eslintsrc.js package.json..
全局配置文件
目录分析
生产环境打包过程分析
{
"build": "node .electron-vue/build.js && electron-builder",
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
}
打包命令大概就是上面四个,可以看到所有的打包命令都会执行node build.js
,也就是打包的入口文件为build.js
build.js
分析
'use strict'
// 环境说明
process.env.NODE_ENV = 'production'
// 一个对console.log字体修饰的库,dev的时候,会出现一个花式输出Electron-vue
const { say } = require('cfonts')
// 终端输出字符串样式,字体样式,字体颜色,背景颜色
const chalk = require('chalk')
// 一个使用globs删除文件和目录的模块, del模块支持多个文件删除[数组],这是node的原生模块
const del = require('del')
// node原生模块,用来创建子进程
const { spawn } = require('child_process')
// webpack
const webpack = require('webpack')
// 多个cli滚轮,比如同时安装多个模块,然后在终端会显示滚轮,类似于进度显示
const Multispinner = require('multispinner')
// 需要的配置文件
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
const webConfig = require('./webpack.web.config')
const doneLog = chalk.bgGreen.white(' DONE ') + ' '
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
const isCI = process.env.CI || false
// 除了clean|web命令,是执行自己的操作,其他指令都是使用build()
if (process.env.BUILD_TARGET === 'clean') clean()
else if (process.env.BUILD_TARGET === 'web') web()
else build()
// 同步删除build文件
function clean () {
del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
console.log(`\n${doneLog}\n`)
process.exit()
}
function build () {
greeting()
del.sync(['dist/electron/*', '!.gitkeep'])
const tasks = ['main', 'renderer']
const m = new Multispinner(tasks, {
preText: 'building',
postText: 'process'
})
let results = ''
m.on('success', () => {
process.stdout.write('\x1B[2J\x1B[0f')
console.log(`\n\n${results}`)
console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
process.exit()
})
// 打包主进程
pack(mainConfig).then(result => {
results += result + '\n\n'
m.success('main')
}).catch(err => {
m.error('main')
console.log(`\n ${errorLog}failed to build main process`)
console.error(`\n${err}\n`)
process.exit(1)
})
// 打包渲染进程
pack(rendererConfig).then(result => {
results += result + '\n\n'
m.success('renderer')
}).catch(err => {
m.error('renderer')
console.log(`\n ${errorLog}failed to build renderer process`)
console.error(`\n${err}\n`)
process.exit(1)
})
}
function pack (config) {
return new Promise((resolve, reject) => {
config.mode = 'production'
webpack(config, (err, stats) => {
if (err) reject(err.stack || err)
else if (stats.hasErrors()) {
let err = ''
stats.toString({
chunks: false,
colors: true
})
.split(/\r?\n/)
.forEach(line => {
err += ` ${line}\n`
})
reject(err)
} else {
resolve(stats.toString({
chunks: false,
colors: true
}))
}
})
})
}
// 先删除dist目录,除了.gitkeep文件
function web () {
del.sync(['dist/web/*', '!.gitkeep'])
webConfig.mode = 'production'
webpack(webConfig, (err, stats) => {
if (err || stats.hasErrors()) console.log(err)
console.log(stats.toString({
chunks: false,
colors: true
}))
process.exit()
})
}
// 致意信息 映入眼帘
function greeting () {
const cols = process.stdout.columns
let text = ''
if (cols > 85) text = 'lets-build'
else if (cols > 60) text = 'lets-|build'
else text = false
if (text && !isCI) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n lets-build'))
console.log()
}
开发环境打包分析
"dev": "node .electron-vue/dev-runner.js",
开发环境使用的配置文件是build.runner.js
build.runner.js
主进程 + 渲染进程 打包
Electron-vue修改了对开发环境的优化,包括了于开发环境的配置文件隔离,主进程和渲染进程配置文件隔离,编译过程提示等功能
'use strict'
const chalk = require('chalk')
const electron = require('electron')
const path = require('path')
const { say } = require('cfonts')
const { spawn } = require('child_process')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const webpackHotMiddleware = require('webpack-hot-middleware')
const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
let electronProcess = null
let manualRestart = false
let hotMiddleware
function logStats (proc, data) {
let log = ''
log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
log += '\n\n'
if (typeof data === 'object') {
data.toString({
colors: true,
chunks: false
}).split(/\r?\n/).forEach(line => {
log += ' ' + line + '\n'
})
} else {
log += ` ${data}\n`
}
log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n'
console.log(log)
}
// 启动渲染进程
// 整体作用:将dev-client.js和renderer/main.js合并到webpack entry字段,输出到根目录下的/dist/electron/main.js文件中
function startRenderer () {
// 加载webpack配置
return new Promise((resolve, reject) => {
rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
rendererConfig.mode = 'development'
// 创建webpack
const compiler = webpack(rendererConfig)
// 创建webpack热加载
// webpackHotMiddleware的heartbeat的参数作用并不是检测文件的频率,而是保持服务器链接存活的心跳频率
hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
heartbeat: 2500
})
// 编译状态监控
# 监听的是 compilation事件
compiler.hooks.compilation.tap('compilation', compilation => {
compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
// 钩子函数,检测webpack的编译状态,把其中的html-webpack-plugin-after-emit状态,发布到webpackHotMiddleware中
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// 输出打包编译过程
compiler.hooks.done.tap('done', stats => {
logStats('Renderer', stats)
})
// 创建webpack-dev-server
const server = new WebpackDevServer(
compiler,
{
contentBase: path.join(__dirname, '../'),
quiet: true,
before (app, ctx) {
// 使用webpackHotMiddleware
app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => {
resolve()
})
}
}
)
// 监听窗口
server.listen(9080)
})
}
// 启动主进程 - 过程与渲染进程是类似的
// 创建webpack,实时发布hotMiddleware,主代码热更新
function startMain () {
// 导入index.dev.js,该文件主要安装electron-debug工具,同时也安装了vue-devtools工具
// webpack.config.main.js与render相比,main不需要去去处理vue,图片, css,html等,只需要对js处理
return new Promise((resolve, reject) => {
mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
mainConfig.mode = 'development'
// 创建主进程的 webpack
const compiler = webpack(mainConfig)
# 监听的是watch-run事件
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
logStats('Main', chalk.white.bold('compiling...'))
hotMiddleware.publish({ action: 'compiling' })
done()
})
// 与render不同,没有使用WebpackDevServer的方式自动更新界面,而是通过webpack的watch模式,不断重新启动程序,类似于electron-forge start
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()
})
})
}
// 启动electron
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()
})
}
function electronLog (data, color) {
let log = ''
data = data.toString().split(/\r?\n/)
data.forEach(line => {
log += ` ${line}\n`
})
if (/[0-9A-z]+/.test(log)) {
console.log(
chalk[color].bold('┏ Electron -------------------') +
'\n\n' +
log +
chalk[color].bold('┗ ----------------------------') +
'\n'
)
}
}
// electron-vue Log输出
function greeting () {
const cols = process.stdout.columns
let text = ''
if (cols > 104) text = 'electron-vue'
else if (cols > 76) text = 'electron-|vue'
else text = false
if (text) {
say(text, {
colors: ['yellow'],
font: 'simple3d',
space: false
})
} else console.log(chalk.yellow.bold('\n electron-vue'))
console.log(chalk.blue(' getting ready...') + '\n')
}
// 初始函数
function init () {
greeting()
// 主进程和渲染进程都启动完毕
Promise.all([startRenderer(), startMain()])
.then(() => {
startElectron()
})
.catch(err => {
console.error(err)
})
}
init()
dev.client.js
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') {
document.body.innerHTML += `
<style>
#dev-client {
background: #4fc08d;
border-radius: 4px;
bottom: 20px;
box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
color: #fff;
font-family: 'Source Sans Pro', sans-serif;
left: 20px;
padding: 8px 12px;
position: absolute;
}
</style>
<div id="dev-client">
Compiling Main Process...
</div>
`
}
})
页面会显示Compiling Main Process...
render webpack打包过程中,hotMiddleware.publish
将各种编译状态发送给了hotClient.subscribe
。
整个webpack-hot-middleware
编译过程中发送的编译消息,将会在界面展示一个提示框提示开发者,当然这里也可以自定义订阅事件。
打包流程图
electron开发环境加载流程 .png

页面渲染
页面的渲染都是通过渲染进程完成的,通过webpack.renderer.config.js
可以看到渲染进程的入口文件是src/renderer/main.js
在普通的electron文件,主进程main.js
通过mainWindow.loadURL()
标识出渲染进程的html文件,也可以在渲染进程中调用Node的API。
在webpack.renderer.config,js
中,可以看到使用了一个plugins html-webpack-plugin
,这个插件将src/index.ejs
模板生成首页的html文件。