cover_image

Electron应用开发实践

王上明 贝壳产品技术 2021年06月11日 04:26
图片


前言


Electron是由Github开发,使用Html,Css,Javascript作为开发语言的开源框架, 它将Node.js和Chromium集成于同一运行环境,并且可以打包输出成多平台(MacOS,Windows,Linux)运行的桌面应用。此篇文章将介绍如何开发一个Electron应用。


基本介绍


对于前端开发工程师而言,Electron框架也许不算家喻户晓,但是由Electron构建出来的应用肯定不会陌生 —— 常用的编辑器VsCode和Atom都是由Electron开发构建。Electron的核心由3部分组成:

图片

· Chromium是Google开源的一个浏览器项目,常用的Chrome浏览器正是在它的基础上开发,提供将Html,Css,Javascript渲染成页面的能力。


· Node.js提供Node执行环境的运行时,这大大提升了视图层页面与系统底层Api的交互能力。


· Electron内置的Api提供了构建应用的辅助能力,如程序的菜单栏,全局的快捷键,对话框提示等,并且在内部抹平了MacOS,Windows,Linux在系统调用的差异性,提供给开发者相同的调用结构。


核心概念


Electron框架有两个核心概念:Main Process (主进程) 和 Renderer Process(渲染进程)。我们来分别看一下两者的构成:

图片
图片

· Main Process(主进程)在一个Electron应用中有且仅能拥有一个,控制着Electron程序的窗口,全局事件,快捷键等。所有的Renderer process(渲染进程)都是由Main Process(主进程)的BrowserWindow模块进行实例化创建的。


· Renderer Process(渲染进程)在Electron应用中可以由多个,每一个由BrowserWindow创建的窗口默认是一个新的渲染进程。每一个窗口等同于浏览器的一个渲染窗口,负责将Html,Css,Javascript等渲染成实际的视图。

图片


一图以蔽之,主进程和渲染进程的关系就像浏览器的操作栏和页面Tab。


前置工作


首先,我们新建一个electron-demo目录,并且进行npm初始化生成默认的package.json

mkdir electron-demo

cd electron-demo

npm init -y


这里我们使用npm的方式安装Electron:

npm install Electron --save


在安装过程中Electron的脚本会去根据开发者当前的系统下载Electron可执行应用,由于默认下载源是国外网络节点,这一步很有可能下载缓慢超时或者失败,这里笔者推荐使用国内镜像源。经过开发测试,华为云的镜像云比较稳定,我们在安装前加入自定义安装源参数:

ELECTRON_MIRROR=https://mirrors.huaweicloud.com/electron/

npm install Electron --save


为了后续安装方便,可以在项目目录下新建一个.npmrc的文件,保存npm安装时的变量信息,在文件内添加:

electron_mirror=https://mirrors.huaweicloud.com/electron/


后续就可以使用常规方式安装了。至此,我们的开发前置工作就大功告成。


开始上手


这里我们用一个简单的demo,体验一下Electron相对于浏览器特有的可调用Node.js运行时Api的能力,前端部分选用的是react作为开发框架,项目的代码在笔者的github Electron-demo(https://github.com/simonwang6666/electron-demo) ,项目目录结构如下:


electron-demo

test

  └──content.txt                // 测试读取的文件

public                          // 公共资源文件夹

webpack                         // webpack相关配置

script                          // 打包构建脚本

src

  ├── App.js                    // App应用

  ├── index.less                // 样式文件

  ├── main.js                   // 前端页面入口文件

electron.main.js                // electron应用入口文件

.npmrc                          // npm相关环境变量

package.json  


下面是视图页面的逻辑:


src/App.js

import React, { useEffect, useState } from "react";
import fs from "fs";
import './index.less'
const App = () => {
  const [content, setContent] = useState("");
  useEffect(() => {
    const fileContent = fs.readFileSync(
      './test/content.txt',
      "utf-8"
    );
    setContent(fileContent);
  },
  );
  return (
     <div className="app">你用Electron的特性读取到了文件内容:
            <span className="content">{content}</span>
     </div>

     );
};
export default App;


content.txt是我们测试读取的一个文件,里面的内容如下:


test/content.txt

你发现我了

可以看到,我们的App.js中试图在web应用中调用Node.js的fs模块去读取一个文件。相比之下,main.js就熟悉的多了,完全是一个react应用的标准入口文件:


src/main.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

前端服务的webpack配置项和标准的web应用也毫无差别,唯一的区别在于构建target我们使用了"electron-renderer"


webpack/common.config.js line8

target"electron-renderer"

关于它的说明可以参阅 webpack target配置

 

可以发现,一个electron项目的渲染进程应用部分与传统的web应用高度相似,唯一区别是多了一个electron.main.js的主进程入口文件,我们来看看这个文件做了什么


electron.main.js

const { app, BrowserWindow } = require('electron')
function createWindow({
  // 创建浏览器窗口
  let win = new BrowserWindow({
    width1200,
    height800,
    webPreferences: {
      nodeIntegrationtrue,
      webSecurityfalse,
      enableRemoteModuletrue
    }
  })
/*
在执行 npm run start 后,经常会窗口已经显示出来了,
但代码还未构建好,此时捕获到 did-fail-load 事件,在之后延迟重载
*/

  win.webContents.on('did-fail-load'function ({
    console.log(`createWindow: did-fail-load, reload soon...`)
    setTimeout(() => {
      win.reload()
    }, 1000)
  })
  if (process.env.NODE_ENV === 'development') {
    win.loadURL('http://localhost:9090')
  } else {
    win.loadFile('./dist/index.html')
  }
}
app.whenReady().then(createWindow)


我们引用到了Electron在主进程最核心的两个模块 —— app模块和BrowserWindow模块。app模块控制着整个app的行为,并且处理app在不同阶段的事件响应。而每一个BrowserWindow实例即是一个渲染进程。首先来看一下渲染进程的创建部分,主要逻辑在createWindow函数:


electron.main.js -> createWindow() line5 -- line13

  let win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: true,
      webSecurity: false,
      enableRemoteModule: true
    }
  })

这里我们新建了一个渲染进程实例,设定了渲染进程的相关参数,例如初始的高度,宽度。这里我们开启的nodeIntergration(Node.js的api在渲染进程的注入),关闭了web安全校验(也就是跨域拦截),更多配置参数可以参考文档 BroserWindow模块


electron.main.js -> createWindow() line26 - line30

  if (process.env.NODE_ENV === 'development') {
    win.loadURL('http://localhost:9090')
  } else {
    win.loadFile('./dist/index.html')
  }


我们根据NODE_ENV参数判断是否处于开发环境, 这和我们开发一个node作为服务端的web应用非常相似。在开发环境去加载我们使用webpackDevServer启动的可以热更新,调试的本地服务,而在生产环境的应用去加载web应用的最终产物。


electron.main.js -> createWindow() line34

app.whenReady().then(createWindow)

最后,在Electron主进程执行完成自己内部的初始化逻辑后,我们在其暴露出的"whenReady"方法后开启应用。


开发模式


在开发模式中,我们需要做两件事:

1.     启动Electron主进程逻辑

2.     构建Electron渲染进程所渲染的web应用

 

在package.json中加入开发环境所需要的脚本:


package.json line4 - line8

  "main""main.electron.js",
  "scripts": {
   "start""NODE_ENV=development concurrently \"npm run serve\" \"npm run electron \"",
    "serve""node script/serve.js",
    "electron""electron . ",
  },

其中,serve命令是通过webpackDevServer启动了一个前端页面的服务,electron命令即是通过main字段对应的文件作为主进程开启一个Electron应用。我们将整体逻辑合并到start命令中,由于主进程启动和web应用的开发服务构建不存在依赖耦合性,所以我们加入了concurrently帮助我们同时执行两个命令。

 

这时候大家可能会有疑问:如果Electron服务启动好的时候,web应用还没有构建完成该怎么办呢?我们在介绍main.electron.js的时候遗漏了一段逻辑介绍,位于createWindow函数中:


electron.main.js -> createWindow() line19 - line24

/*
在执行 npm start 后,经常会窗口已经显示出来了,
但代码还未构建好,此时捕获到 did-fail-load 事件,在之后延迟重载
*/

  win.webContents.on('did-fail-load'function ({
    console.log(`createWindow: did-fail-load, reload soon...`)
    setTimeout(() => {
      win.reload()
    }, 1000)
  })

通过捕获加载失败的事件,每隔1s去重新请求一下web应用的本地开发服务,直到服务完成构

建启动,正所谓“让子弹飞一会儿”。最终运行效果如下:

图片


可以看到,在视图文件中成功读取到了content.txt文件中的内容,Electron渲染层对于Node.js的Api调用能力诚不欺我。


调试


通过Cmd +Shift + I, 也可以像浏览器一样打开调试者工具,使用方式和Chrome体验完全一致,可以进行dom节点,样式,网络请求查看操作。在运行时,也可提通过渲染进程实例的win.webContents.openDevTools( ) 进行调试工具的打开。

图片


构建打包


关于electron应用的打包,目前主流的方式是使用electron-builder或者electron-packger。这里笔者推荐使用electron-builder,只需要简单的一点配置即可生成不同系统可运行的安装文件。我们来添加一点基础的配置:


package.json line40 - line84

 "build": {
    "asar"false,
    "appId""electron-demo",
    "icon""public/logo.png",
    "productName""Electron示例",
    "directories": {
      "output""./bin"
    },

    "win": {
      "target": [
        "nsis",
        "zip"
      ]
    },

    "files": [
      "dist/**/*",
      "*.js",
      "!node_modules"
    ],

    "dmg": {
      "sign""false",
      "contents": [
        {
          "x": 410,
          "y": 150,
          "type""link",
          "path""/Applications"
        },

        {
          "x": 130,
          "y": 150,
          "type""file"
        }
      ]
    },

    "nsis": {
      "oneClick"false,
      "allowElevation"true,
      "allowToChangeInstallationDirectory"true,
      "createDesktopShortcut"true,
      "createStartMenuShortcut"true,
      "shortcutName""Electron-demo"
    }
  }

每个字段对应的含义可以参照  Electron-buidler配置文档 。从简单的产品名称,产品图标配置,到windows自定义安装的复杂配置都有涵盖,我们通过electron-builder提供的命令行工具对项目按照配置项进行构建,为了方便使用,继续在package.json中添加打包成各个平台可执行文件的脚本:


package.json line11 -  line13

"compile:mac""electron-builder --mac --arm64",   // for macos
"compile:win64""electron-builder --win --x64",     // for win64
"compile:win32""electron-builder --win --ia32"    // for win32

由于输出成不同操作系统的安装包需要下载不同操作系统所依赖的文件,这里也会涉及到资源下载的问题,这里我们继续使用electron-builder的镜像进行下载:


.npmrc line3

electron_builder_binaries_mirror=https://npm.taobao.org/mirrors/electron-builder-binaries/

我们以构建Mac平台的产出为例

npm run complie:mac

最后产物如下:

图片



ok,熟悉的dmg安装包,大功告成!


总结


Electron作为一个将node.js和chromium结合的框架大大拓展了Javascript调用系统Api的能力,多端复用的特性也可以有效减少多平台开发成本,有兴趣的同学不妨一试。


参考资料:

Electron主进程和渲染进程

Electron-builder打包


图片



大前端 · 目录
上一篇五分钟带你认识渐进式网页应用pwa下一篇从0到1搭建多端小游戏
继续滑动看下一个
贝壳产品技术
向上滑动看下一个