黑盒子 Electron

Electron开发环境 - 图1 可以看做是包含 Node 和 Chromium 黑盒的程序启动器,在官网有这样一段代码。

  1. $ npm i -D electron@latest
  2. # Electron 2.0.4
  3. # Node 8.9.3
  4. # Chromium 61.0.3163.100

当我们安装好了一个 electron 程序,它自身会有一个版本号,同时包含了 Node 环境与 Chromium 浏览器环境,我们再来看一下安装好的依赖是怎么样的。

electron-bin

当我们运行 electron 的时候,它运行的其实是这个二进制文件,就像运行 node 一样,运行的时候,我们会指定一个 index.js 文件作为入口点,在这个入口点里面创建窗口,操作本地系统等等。这也是为什么我说它是一个黑盒。

当然运行这个二进制,不是把它加入到 PATH 环境里面,而是通过 Node 的 child_process 呼起一个进程。这个 Electron.app 文件,在 Mac 下面是可以直接运行的,下面就是运行的结果。

electron-empty

必知的进程概念

当我们开发一个本地应用的时候,免不了的就要访问本地的文件,浏览器很明显不具备这样的功能。同时还要有访问网络的能力,或许 Node 可以胜任,因为它可以访问文件和访问网络,那么用户交互界面怎么办呢?普通用户会愿意去面对一个控制台么?于是乎,为啥不把浏览器和 Node 结合起来,浏览器负责显示界面,Node 负责访问网络和其他的这不就好了么。

是的,没错,就这样 Electron 诞生了,但是这里面有非常多的工作,比如环境的整合,执行的架构等等。在前面提到交互界面与访问本地文件与网络两个功能,在 Electron 中,就刚好有两个进程相互对应,有点类似于前端框架的服务端渲染,也有是说它有两个运行环境。

electron_2process

但是呢,在渲染进程里面,还是可以访问本地文件模块的,也就是说 Node 的 API 是交融在一起的,两个进程之间通过 ipc 进行通行,官方封装好了对应的模块。通信代码会与下面的代码类似。

在主进程中

  1. const { ipcMain } = require('electron')
  2. // 异步返回
  3. ipcMain.on('asynchronous-message', (event, arg) => {
  4. event.sender.send('asynchronous-reply', 'pong')
  5. })
  6. // 同步返回 -> 会阻塞渲染进程
  7. ipcMain.on('synchronous-message', (event, arg) => {
  8. event.returnValue = 'pong'
  9. })

在渲染进程中

  1. const { ipcRenderer } = require('electron')
  2. // 同步接收
  3. console.log(ipcRenderer.sendSync('synchronous-message', 'ping'))
  4. // 异步接收
  5. ipcRenderer.on('asynchronous-reply', (event, arg) => {
  6. console.log(arg)
  7. })

开发环境的更新与重启

Electron 程序中,对于渲染进程实现热替换跟浏览器其实是一样的,当我们新建了一个窗口的时候,在生产环境的时候可能是载入一个 html 文件,当在开发环境的时候则是载入一段 http 协议的网址,当更新了的时候通过 websocket 通知页面刷新即可。

而对于主进程则没有热更新,只能把进程杀死再重启,等会我会讲解 electron-vue 脚手架的核心原理。

我自己的解决方案

在我写的图片压缩应用开发环境 - 图5中,使用了 chokidar 和一段脚本实现了热重启。在启动脚本中有这样一段 scripts 命令

  1. "dev": "chokidar './src/*.js' -c './scripts/dev.sh' --initial"

启动脚本为

  1. #!/usr/bin/env bash
  2. function check(){
  3. count=`ps -ef |grep $1 |grep -v "grep" |wc -l`
  4. if [ 0 -ne $count ];then
  5. echo "Kill Electron"
  6. killall $1
  7. fi
  8. }
  9. check "Electron" && yarn start

这样的方式虽然可以很简单,但是会误杀掉所有的 Electron 程序,通过下面的方式杀死进程可能会更好一些。

  1. pkill -f project_name

electron-vue 的解决方案

其他类的脚手架也是类似的原理,通过编程的方式,操作 process 来杀死进程。首先打开 dev-runner.js开发环境 - 图6 文件,关于打包的事,我就不多说了,做这种打包方案难倒是不难,参考非常多,就是查 Webpack API 比较麻烦,需要花不少时间,特别是做服务端渲染配置的时候也是这样,

startRenderer 函数,载入了渲染进程 webpack 配置,通过 WebpackDevServer 监听了 9080端口,而 startMain 函数里面有这样一段。它监听了主线程文件修改,当修改通过 process.kill 杀死,并调用 startElectron 重启,并且有一个 manualRestart 标志位避免频繁重启,

  1. compiler.watch({}, (err, stats) => {
  2. if (err) {
  3. console.log(err)
  4. return
  5. }
  6. logStats('Main', stats)
  7. if (electronProcess && electronProcess.kill) {
  8. manualRestart = true
  9. process.kill(electronProcess.pid)
  10. electronProcess = null
  11. startElectron()
  12. setTimeout(() => {
  13. manualRestart = false
  14. }, 5000)
  15. }
  16. })

startElectron 中通过子进程启动 electron 程序、

  1. function startElectron() {
  2. electronProcess = spawn(electron, ['--inspect=5858', '.'])
  3. electronProcess.stdout.on('data', data => {
  4. electronLog(data, 'blue')
  5. })
  6. electronProcess.stderr.on('data', data => {
  7. electronLog(data, 'red')
  8. })
  9. electronProcess.on('close', () => {
  10. if (!manualRestart) process.exit()
  11. })
  12. }

electronforge

electronforge开发环境 - 图7 提供了一体化的 electron 开发体验,从 node 版本替换到打包,自动上传等等,是非常方便的,但是呢,本教程不会用这个脚手架,基于两点考虑。

  • v6 版本尚未发布,一直处于 beta 阶段,估计要等 electron3

  • 封装的太高了,反而啥都学不到了,我会尽量使用偏底层的各种包,让大家接触更多技术点。

electron-webpack

这将会是我们使用的开发脚手架,我会以添加 svelte 框架为例,教大家自定义 webpack 配置。它的原理其实就是上面 electron-vue 所提到的那样,不过它的 config 载入的逻辑要多很多。阅读 文档开发环境 - 图8 获取更多帮助信息。

现在我们来初始化我们的脚手架。

  1. git clone https://github.com/electron-userland/electron-webpack-quick-start.git reading-app
  2. cd reading-app
  3. rm -rf .git
  4. npm install

为了使下载速度更快,我们可以使用国内的镜像。对于 zsh 或者 bash ,设置 ELECTRON_MIRROR 环境变量即可

  1. export ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/

fish 使用

  1. set -x ELECTRON_MIRROR https://npm.taobao.org/mirrors/electron/

安装依赖

要使用 webpack 构建 svelte ,首先要安装它的依赖,由于 svelte 是编译型的框架,所以安装到开发依赖即可。

  1. npm install svelte svelte-loader --save-dev

然后我们开始自定义渲染进程的 webpack 配置,我们需要参考一下 electron-webpack 文档开发环境 - 图9svelte 官网模板配置开发环境 - 图10 假如你完全按照它的来,你得到的错误会莫名奇妙,不信你可以尝试一下,假如你的对调试不熟悉一些的话,可能你永远也不会知道它问题出在哪。

首先修改 package.json

  1. "electronWebpack": {
  2. "renderer": {
  3. "webpackConfig": "conf/webpack.renderer.additions.js"
  4. }
  5. },

然后创建该文件,配置如下:

  1. module.exports = {
  2. resolve: {
  3. extensions: ['.js', '.svelte'],
  4. mainFields: ['svelte', 'browser', 'module', 'main']
  5. },
  6. module: {
  7. rules: [
  8. {
  9. test: /\.svelte$/,
  10. exclude: /node_modules/,
  11. use: {
  12. loader: 'svelte-loader',
  13. options: {
  14. skipIntroByDefault: true,
  15. nestedTransitions: true,
  16. emitCss: true,
  17. hotReload: true
  18. }
  19. }
  20. }
  21. ]
  22. }
  23. }

不要配置 css,因为会造成重复,所以才导致莫名奇妙的问题,配置 mainFields 是为了假如你想导入 svelte 组件,可以被识别。

不使用 html 后缀是为了避免冲突,要不然 js 代码会被当做字符串载入。

添加渲染进程内容

关于 Svelte 可以查看官方文档开发环境 - 图11,我录制的视频开发环境 - 图12可能有些老了

修改 renderer/index.js 的内容

  1. import App from './App.svelte'
  2. const app = new App({
  3. target: document.querySelector('#app'),
  4. data: {
  5. name: 'world'
  6. }
  7. })

新增 renderer/App.svelte 文件

  1. <h1>Hello {name}!</h1>
  2. <style>
  3. h1 {
  4. color: purple;
  5. }
  6. </style>

运行 App

  1. npm run dev

electron-svelte

学习 Svelte 的方法

先看一遍它的 demo开发环境 - 图14,然后看文档开发环境 - 图15