本文主要有以下内容:

  • Electron环境
  • Electron 主进程、渲染进程环境
  • Electron 主进程、渲染进程
  • 渲染进程、窗口、index.html之间的关系
  • webpack如何处理主进程、渲染进程: target: ‘electron-renderer’, electron-main
  • 神奇的remote-主进程、渲染进程通信

在看这篇之前,如果你对Electron基础已经了解,则可以继续,如果不了解,建议先看下:

Electron环境

Electron是一个跨平台的桌面应用技术,从开发的角度,可以片面的理解为js的一个执行环境。比如: 浏览器是js的一个执行环境,node也是js的一个执行环境,同样Electron也是js的一个执行环境。那么什么叫执行环境呢,其实我的理解是一个执行环境包含2个内容:

1
2
可解析、执行该语言的解析器
会有一些环境下可运行的基础设施(api)提供给该语言运行

举个例子:

  • 浏览器里有v8引擎解析执行js, 在window对象上也有丰富的api提供给运行的js使用。符合上述2点。当然离开浏览器,我们可以用node去运行js,但是没法使用浏览器的api。就是因为node环境并不存在那些基础设施。

  • node也有v8解析执行js,同时也有丰富的api提供给js使用,比如fs这种,但你在浏览器里运行,则不会有这样的基础设施api可以使用。

    因此,Electron本质上是一个js的执行环境,所以这个环境包含哪些内容呢,请看下图:

    可见Electron环境其实包含了浏览器环境Node环境系统原生的一些API,包含了浏览器环境Node环境也就意味着其实也包含了v8语言解释器。因此在Electron里可以运行浏览器的api,node的api,部分系统的api调用。

    Electron 主进程、渲染进程环境

    上面我们讲到,Electron其实是js的执行环境,包含浏览器环境Node环境系统原生的一些API,那么意味着在Electron里写的js,都可以使用上述三个环境的各种api嘛?当然不是,如果是这样,就会造成混乱。因此,在Electron这个大环境里,又分了js运行的小环境:主进程环境渲染进程环境。不同环境里,有他们可以执行的api。请看下图:

    由图可见,在Electron大环境里,分了Main processRenderer Process环境,不同环境有可执行的不同的api,当然也有共同可以执行的一些api。Main processRenderer Process环境的相同点是,都会有v8可以解析js,不同点是可以使用的api不全相同。

    那么,有人就会问了,为什么在这个Electron大环境里,还需要去分Main processRenderer Process两个小环境,去限制不同环境的api调用呢?那接下来大家得知道,在Electron里什么是主进程、渲染进程了,知道这个是什么,才知道为什么要限制他们的运行环境api。

    Electron 主进程、渲染进程

    习惯了浏览器的单进程编写,可能接触Electron的”多进程”有点困扰,但还好,在写代码的时候还是单进程的写法。只是需要理解一点进程的东西。
    通过任务管理器,我们可以看到,其实Electron启动后会有好多进程,除了主进程、渲染进程、还会有一些辅助进程。这里,我们主要描述主进程、渲染进程的作用和意义。

    在这之前,大家可以自己去了解下进程和线程的概念。我就说一个进程的特点:彼此进程是不会共享内存和状态的。举个例子:在浏览器里面,一个Tab页其实就是在单个进程里,2个tab之间不会共享内容,比如tab1网页中的window上挂在一个window.a, 但在tab2中的window上你并不会获得window.a这个变量,因为他们在不同的进程里。这样的一个好处就是,一个Tab死掉了,并不会影响另一个网页的内容,也并不会卡死浏览器。

    基于此,我们会以浏览器这个大家熟悉的东西来理解Electron的主进程、渲染进程,以及他们之间的关系。

    其实浏览器这个应用本身,是一个进程,打开它就会运行起一个主进程(当然会有其余辅助子进程,暂不考虑),当我们打开一个标签页的时候,就开启了一个渲染进程,大家可以简单的看一下这个图:

    他们之间有这些关系:

    • 一个标签页死了,另一个标签页不会死,照常运行
    • 一个标签页死了,浏览器不会卡死,还是可以开启其他窗口
    • 一个标签页死了,浏览器也会死

    说好的,进程之间互不影响呢,怎么一个标签页死了,还会出现浏览器死了的情况,这就涉及到主进程和渲染进程之间的一个基本关系了。浏览器是主进程,他可以创建标签页这种子进程。就好比,浏览器是地球,一个标签页就是一个人。一个人挂了,地球不会挂。也不会影响另一个人挂不挂,但是假如这个人在挖洞,把地球挖炸了,那其实地球就挂了,所有人也挂了。

    可以看出,主进程会管理(创建、销毁)渲染进程(标签页),有以下特点:

    • 渲染进程结束,主进程不会结束(标签页关了,浏览器还在)
    • 主进程结束,渲染进程都会结束(浏览器关闭,标签页都会关闭)
    • 渲染进程如果太过分,资源用光,也会导致主进程崩溃

    接下来我们在Electron里来看一下,进程的特点。
    首先,我们来新建一个小demo:

    1
    2
    3
    4
    mkdir electron-process-test // 新建文件夹electron-process-test
    cd electron-process-test
    npm init
    ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ yarn add electron@6.0.9 -D

    再新建一个main.js, 写入:

    1
    console.log('hello');

    接着我们用electron运行这个js,跟node运行js类似(node main.js),此处在package.json中添加命令:

    1
    2
    3
    4
    5
    {
    ...
    "dev": "electron main.js",
    ...
    }

    最后运行 yarn dev, 打出了’hello’, 我们在任务管理器可以看到:

    上述步骤,我们就启动了一个Electron主进程,即图中的带图标的那个Electron进程,他启动了一个额外的辅助进程Electron helper,是GPU处理进程。主要是那个主进程。
    接着我们打开一个窗口,这个窗口其实就是一个渲染进程,我们来试一下,在main.js中:

    1
    2
    3
    4
    5
    6
    7
    const {app, BrowserWindow} = require('electron');
    app.on('ready', () => {
    const win = new BrowserWindow({
    width: 400,
    height: 400
    });
    });

    我们再次运行:yarn dev,发现打开了一个窗口,我们在看下任务管理器:

    还是2个进程,一个Electron, 一个Electron Helper,因为这个窗口在没有加载页面(本地html文件或网页)的时候,不会开启渲染进程。所以我们加载一个页面试试:

    1
    2
    3
    4
    5
    6
    7
    8
    const {app, BrowserWindow} = require('electron');
    app.on('ready', () => {
    const win = new BrowserWindow({
    width: 400,
    height: 400
    });
    win.loadURL('https://www.baidu.com');
    });

    在看任务管理器:

    发现多了一个Electron Helper, 虽然不知道哪个helper是这个窗口的,因为都叫这个名字,但是这个就是渲染进程了。在一个窗口加载一个页面的时候就会新开一个。
    我们试试再开一个窗口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const {app, BrowserWindow} = require('electron');
    app.on('ready', () => {
    const win = new BrowserWindow({
    width: 400,
    height: 400
    });
    win.loadURL('https://www.baidu.com');

    const win1 = new BrowserWindow({
    width: 400,
    height: 400
    });
    win1.loadURL('https://www.baidu.com');

    });

    yarn dev 再看任务管理器, 就会发现又多了一个Electron helper, 多了一个渲染进程

    以上过程,我们直观感受到了什么是主进程渲染进程:

  • 主进程就是使用electron main.js的时候,就会开启一个主进程:Electron.

  • 渲染进程会在新建窗口后,loadURL的时候开始渲染进程:Electron Helper.
    ps: loadURL也可以加载一个HTML,就跟我们浏览器的Tab差不多了。

    1
    win.loadURL(`file://${__dirname}/../index.html`);

渲染进程、窗口、index.html之间的关系

他们之间有如下关系:

  • 主进程中new BrowserWindow()会开启一个系统窗口,但不会开启一个独立的进程,窗口由主进程管理。
  • 窗口可以loadURL加载一个页面,此时会开启这个页面的渲染进程。
  • loadURL加载一个index.html文件的时候,此时渲染进程里运行的就是这个index.html的内容。

webpack如何处理主进程、渲染进程: target: ‘electron-renderer’, ‘electron-main’

前面,我们讲述了:主进程、渲染进程,以及他们之间的关系,那开发、发布的时候,我们该怎么处理相关的js呢。首先,我们应当有如下概念:

  • 不同进程的js,肯定是不同的入口,因此采用webpack4的多入口方式打包即可。
  • 主进程js(就是electron main.js中main.js这个入口相关的js),需要一个生产环境webpack配置文件。因为开发环境下不需要webpack做什么,所以不需要。
  • 渲染进程js(即index.html里面加载的js)开发环境下需要用webpack-dev-server做热刷新,所以会单独一个webpack配置文件。
  • 渲染进程js在生产环境下,需要一个webpack配置文件。
  • 为什么主进程的和渲染进程的js生产环境下不使用同一个配置文件,因为处理方式有些不同,因此不再同一个文件里写多入口方式配置而写成两个文件。
  • 如果打开两个及以上窗口,即会有很多个渲染进程,则只需要在render的配置文件里使用多入口配置就好了。

所以会有如下3个配置文件:

  • webpack.config.main.prod.js // 主进程生产环境配置文件
  • webpack.config.render.prod.js // 渲染进程生产环境配置文件
  • webpack.config.render.dev.js // 渲染进程开发环境配置文件

具体的配置内容,可以看Electron-从零到一搭建-编写基础看框架。这里我讲一下webpack配置里的:target
我们比较熟悉的就是在webpack中配置:

  • target: ‘node’
  • target: ‘web’
    表示的就是,处理js的时候采用node(采用web)环境去处理,这样,在识别到一些api比如node的fs的时候,就不会报错,知道是什么。
    同理,我们在处理我们的js的时候也需要根据其运行环境去处理,这样就能识别一些语法、api用法等。
  • 主进程设置:target: ‘electron-main’
  • 渲染进程设置:target: ‘electron-renderer’

神奇的remote-主进程、渲染进程通信

这个我就不写了,写累了,自己去看下面推荐的文章吧,去体会。

此处给大家推荐一些文章去深入了解: