Electron 可以利用前端技术构建跨平台的 GUI 应用,但是 Electron 主要提供的是使用基础 HTML 5 技术来展现 UI 界面的,而且后端技术也是因为基于 Node.js,而限定在了 Javascript 上。这就对计划采用 React 搭配 Typescript 开发 GUI 应用相当的不友好,也就需要更多的配置环节。这里将通过对构建和配置过程进行记录来为基于 React 和 Typescript 开发 GUI 应用提供一条通道。
构建目标及原则
由于 Electron 是分为渲染线程和主线程两套代码的,所以对于代码的处理也是需要有两套。但是总结起来,项目整体的构建目标主要有以下几点。
- 渲染线程和主线程均采用 Typescript 编写。
- 渲染线程基于 React 框架编写。
- 在开发过程中,渲染线程与主线程的代码要能够做到热重载。
- 开发与发布的过程要尽可能的自动化,减少命令键入的次数。
另外为了简化项目整体的构建和配置复杂性,在寻找解决方案的时候,将遵照以下原则来完成拣选。
- 不使用 Eject 来做项目的详细配置。
- 尽可能使用
create-react-app
工具自身提供的功能完成配置。 - 尽可能使用更少的辅助库。
- 在 Windows、macOS、Linux 上都能够得到一致的配置方法和运行效果。
构建过程
因为整个应用项目中,渲染线程的部分占据的比例非常大,所以项目的构建过程先从前端部分开始。
|
|
首先使用create-react-app
工具利用typescript
模版构建一个使用 Typescript 开发 React 应用的前端项目。然后我们就需要进入到已经创建好的项目目录中,继续添加要使用的其他依赖库。
|
|
完成这些内容的安装以后,实际上就可以开始一个前端项目的开发了。但是如果要让项目运行在 Electron 中,还有一些工作要做。
由于位于渲染线程的React APP需要调用electron
的一些内容,所以需要修改一下项目根目录中的config-overrides.js
来使项目的编译目标改为electron-renderer
。
|
|
BROWSER=none
的环境变量也是为了防止CRA自动打开浏览器。
因为项目中已经安装了 Typescript 库,所以可以直接在 Electron 的主线程中使用 Typescript,当然前提只需要配置好 Typescript 的编译。在这个项目中,设计应用的渲染线程代码位于src
目录中,主线程的代码位于src-main
目录中,其中主线程代码所在的目录可以根据需要修改。
src
这个目录被 CRA 的配置占据了,而且修改起来不是太容易所以就保留了。
渲染线程和主线程的代码分目录放的原因主要有以下几点:
- 渲染线程和主线程所使用的技术和库不同,使用独立目录存放可以从视觉和习惯上进行区分。
- 渲染线程和主线程在使用
tsc
进行编译的时候可以采用不同的配置。
因为开发的过程中需要热重载技术,而 React 中的热重载技术是由 Webpack DevServer 提供的,所以在开发过程中,要实现 Electron 中的热重载,也必须让 Electron 加载 Webpack DevServer 提供的地址http://localhost:3000
,打包发布的时候再去加载打包目录中的index.html
。这样一来就需要区分开发环境和生产环境了。主线程代码的实时编译是由tsc
提供的,所以除 Webpack DevServer 以外,还需要再运行一个tsc
实例。而且这个tsc
实例需要配置一些与 React 应用不同的内容,为了方便,可以直接把 React 的tsconfig.json
复制一份修改一下。这里仅举出一部分的关键设置。
|
|
在这里,我们把这个用于编译主线程的 Typescript 配置命名为tsconfig.main.json
,然后与之前已有的tsconfig.json
放置在一起。
这样一来,要启动整个应用的调试就需要同时运行 Webpack DevServer,也就是npm start
命令,还有electron
,和用于编译主线程代码的tsc
。为了达到这个目的,就需要更多的工具来支持了,在这里我们需要安装一个库:concurrently。
|
|
Concurrently 可以同时运行多条命令,正好可以满足我们同时启动 Webpack DevServer、tsc
和 Electron 的需要。这时我们就可以把package.json
中scripts
一节改变一下样子。
|
|
现在直接运行npm start
应该可以同时启动三个进程了,但是又出现了一个新的问题,那就是 Electron 启动以后什么也没有加载。如果打开看一下 DevTools,可以发现 Electron 根本没有加载 DevServer 提供的内容。那是因为 Electron 根本就没有等待 DevServer 启动完毕就直接尝试加载http://localhost:3000
这个 URL 了。所以我们还需要再改进一下,这样就需要wait-on
库的支持了。
|
|
wait-on
库可以提供在指定条件就绪的时才继续执行下一条命令的功能。所以借助wait-on
的支持,package.json
中scripts
的样子还可以再改进一下。
|
|
现在直接执行npm start
可以顺利的打开Electron并让Electron加载DevServer提供的内容了。但是当我们实际编写一些主线程的代码以后会发现,Electron还是维持着编辑之前的代码,要打算让新代码生效,就必须手动重启Electron。这显然也不是我们追求自动化的风格,所以我们就需要再引入一个库了:electronmon
。
|
|
electronmon
可以监视与主线程脚本有关的所有变化,并在这些变化发生的时候,自动重启Electron。这功能完美的匹配了我们的需求,于是package.json
就修改为了以下这个样子。
|
|
好了,现在再来启动一下吧,无论渲染线程还是主线程,都会根据变化自动重启加载了。
关于发布
对于Electron应用来说,最简单的发布工具就是electron-builder,但是这个工具并没有什么特殊的,只需要使用默认配置就可以完成三个平台的编译打包。
踩过的一些坑
用Typescript开发使用React作为渲染线程的Electron应用不是没有坑的,这里仅记录一些目前已经遇到的。
各种require() is not defined
在DevTools上出现这种提示,往往是因为BrowserWindow
创建的时候webPreferences
配置给的不正确。因为React APP和基于Typescript的主线程都需要require()
,所以webPreferences
的配置需要参考以下示例。
|
|
另外还有一种情况,就是之前用于customize-cra
的config-overrides.js
中把Webpack的编译目标设为了electron-renderer
,这就导致了Webpack在不支持require
的文件中也同样使用了require
,从而导致运行的时候报出了require() is not defined
错误。这也是在计划使用preload.js
,开启了contextIsolation
以后出现的错误的根本原因。所以如果计划使用Electron推荐的安全策略,采用preload.js
控制对于底层API的暴露,那么可以去掉config-overrides.js
中Webpack的target
设置,然后将WebPerferences
的配置改为以下形式。
|
|
出现了Electron failed to install correctly
这种情况一般在使用淘宝的npm镜像的时候容易发生,而发生这种情况的主要原因是Electron没有完全安装。要解决这个问题可以直接安装一个辅助库来完成Electron的安装。
|
|
一个更加迅速的方法
基于以上调试经验,我制作了一个CRA的项目模版cra-template-typescript-electron,可以直接构建一个完整的Electron应用,这个模版可以使用以下命令在本地创建项目。
|
|