diff --git a/source/_posts/electron-with-react-typescript.md b/source/_posts/electron-with-react-typescript.md new file mode 100644 index 0000000..1809e3a --- /dev/null +++ b/source/_posts/electron-with-react-typescript.md @@ -0,0 +1,232 @@ +--- +title: 基于React与Typescript构建Electron应用 +tags: + - 前端 + - React + - Typescript + - Electron +categories: + - - 前端 + - React +keywords: '前端,React,Electron,Typescript,package.json' +date: 2021-07-19 23:17:08 +--- + + +Electron 可以利用前端技术构建跨平台的 GUI 应用,但是 Electron 主要提供的是使用基础 HTML 5 技术来展现 UI 界面的,而且后端技术也是因为基于 Node.js,而限定在了 Javascript 上。这就对计划采用 React 搭配 Typescript 开发 GUI 应用相当的不友好,也就需要更多的配置环节。这里将通过对构建和配置过程进行记录来为基于 React 和 Typescript 开发 GUI 应用提供一条通道。 + +## 构建目标及原则 + +由于 Electron 是分为渲染线程和主线程两套代码的,所以对于代码的处理也是需要有两套。但是总结起来,项目整体的构建目标主要有以下几点。 + +1. 渲染线程和主线程均采用 Typescript 编写。 +1. 渲染线程基于 React 框架编写。 +1. 在开发过程中,渲染线程与主线程的代码要能够做到热重载。 +1. 开发与发布的过程要尽可能的自动化,减少命令键入的次数。 + +另外为了简化项目整体的构建和配置复杂性,在寻找解决方案的时候,将遵照以下原则来完成拣选。 + +1. 不使用 Eject 来做项目的详细配置。 +1. 尽可能使用`create-react-app`工具自身提供的功能完成配置。 +1. 尽可能使用更少的辅助库。 +1. 在 Windows、macOS、Linux 上都能够得到一致的配置方法和运行效果。 + +## 构建过程 + +因为整个应用项目中,渲染线程的部分占据的比例非常大,所以项目的构建过程先从前端部分开始。 + +```bash +npx create-react-app electron-app --template typescript +``` + +首先使用`create-react-app`工具利用`typescript`模版构建一个使用 Typescript 开发 React 应用的前端项目。然后我们就需要进入到已经创建好的项目目录中,继续添加要使用的其他依赖库。 + +```bash +# 进入到项目目录中 +cd electron-app + +# 安装项目中所需要用到的功能库 +# 安装React配置工具以及跨平台的环境变量设置工具 +npm install -D react-app-rewired customize-cra cross-env + +# 安装前端路由库 +npm install react-router react-router-dom + +# MobX状态管理库可以根据需要换成其他状态管理库,例如Redux +npm install mobx mobx-react-lite mobx-state-tree + +# 安装Ajax请求处理工具,SWR为自动化远程数据缓存 +npm install axios swr + +# 安装前端样式库,这里采用了Fluent UI,可根据需要更换 +npm install @fluentui/react + +# 安装Electron +npm install -D electron +``` + +完成这些内容的安装以后,实际上就可以开始一个前端项目的开发了。但是如果要让项目运行在 Electron 中,还有一些工作要做。 + +由于位于渲染线程的React APP需要调用`electron`的一些内容,所以需要修改一下项目根目录中的`config-overrides.js`来使项目的编译目标改为`electron-renderer`。 + +```js +const { override } = require('customize-cra'); + +function addRendererTarget(config){ + config.target = 'electron-renderer'; + return config; +} + +module.exports = override( + addRendererTarget +); +``` + +!!! caution "" + 这样一来,React APP将无法在浏览器中正常运行,加入这个改动以后,就不要再用浏览器调试React APP了,下文中使用`BROWSER=none`的环境变量也是为了防止CRA自动打开浏览器。 + +因为项目中已经安装了 Typescript 库,所以可以直接在 Electron 的主线程中使用 Typescript,当然前提只需要配置好 Typescript 的编译。在这个项目中,设计应用的渲染线程代码位于`src`目录中,主线程的代码位于`src-main`目录中,其中主线程代码所在的目录可以根据需要修改。 + +!!! note "" + `src`这个目录被 CRA 的配置占据了,而且修改起来不是太容易所以就保留了。 + +渲染线程和主线程的代码分目录放的原因主要有以下几点: + +1. 渲染线程和主线程所使用的技术和库不同,使用独立目录存放可以从视觉和习惯上进行区分。 +1. 渲染线程和主线程在使用`tsc`进行编译的时候可以采用不同的配置。 + +因为开发的过程中需要热重载技术,而 React 中的热重载技术是由 Webpack DevServer 提供的,所以在开发过程中,要实现 Electron 中的热重载,也必须让 Electron 加载 Webpack DevServer 提供的地址`http://localhost:3000`,打包发布的时候再去加载打包目录中的`index.html`。这样一来就需要区分开发环境和生产环境了。主线程代码的实时编译是由`tsc`提供的,所以除 Webpack DevServer 以外,还需要再运行一个`tsc`实例。而且这个`tsc`实例需要配置一些与 React 应用不同的内容,为了方便,可以直接把 React 的`tsconfig.json`复制一份修改一下。这里仅举出一部分的关键设置。 + +```json +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "CommonJS", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "outDir": "./build" + }, + "include": ["src-main/**/*"] +} +``` + +在这里,我们把这个用于编译主线程的 Typescript 配置命名为`tsconfig.main.json`,然后与之前已有的`tsconfig.json`放置在一起。 + +这样一来,要启动整个应用的调试就需要同时运行 Webpack DevServer,也就是`npm start`命令,还有`electron`,和用于编译主线程代码的`tsc`。为了达到这个目的,就需要更多的工具来支持了,在这里我们需要安装一个库:concurrently。 + +```bash +# 安装concurrently +npm install -D concurrently +``` + +Concurrently 可以同时运行多条命令,正好可以满足我们同时启动 Webpack DevServer、`tsc`和 Electron 的需要。这时我们就可以把`package.json`中`scripts`一节改变一下样子。 + +```json +{ + "scripts": { + "start": "concurrently \"npm:start:renderer\" \"npm:build:main\" \"npm:start:electron\"", + "start:renderer": "cross-env BROWSER=none react-app-rewired start", + "build:main": "tsc --project tsconfig.main.json --watch", + "start:electron": "cross-env NODE_ENV=development electron ." + }, + "main": "./build/main.js" +} +``` + +现在直接运行`npm start`应该可以同时启动三个进程了,但是又出现了一个新的问题,那就是 Electron 启动以后什么也没有加载。如果打开看一下 DevTools,可以发现 Electron 根本没有加载 DevServer 提供的内容。那是因为 Electron 根本就没有等待 DevServer 启动完毕就直接尝试加载`http://localhost:3000`这个 URL 了。所以我们还需要再改进一下,这样就需要`wait-on`库的支持了。 + +```bash +# 安装wait-on +npm install -D wait-on +``` + +`wait-on`库可以提供在指定条件就绪的时才继续执行下一条命令的功能。所以借助`wait-on`的支持,`package.json`中`scripts`的样子还可以再改进一下。 + +```json +{ + "scripts": { + "start": "concurrently \"npm:start:renderer\" \"npm:build:main\" \"npm:start:electron\"", + "start:renderer": "cross-env BROWSER=none react-app-rewired start", + "build:main": "tsc --project tsconfig.main.json --watch", + "start:electron": "wait-on http://localhost:3000 && cross-env NODE_ENV=development electron ." + }, + "main": "./build/main.js" +} +``` + +现在直接执行`npm start`可以顺利的打开Electron并让Electron加载DevServer提供的内容了。但是当我们实际编写一些主线程的代码以后会发现,Electron还是维持着编辑之前的代码,要打算让新代码生效,就必须手动重启Electron。这显然也不是我们追求自动化的风格,所以我们就需要再引入一个库了:`electronmon`。 + +```bash +# 安装Electronmon +npm install -D electronmon +``` + +`electronmon`可以监视与主线程脚本有关的所有变化,并在这些变化发生的时候,自动重启Electron。这功能完美的匹配了我们的需求,于是`package.json`就修改为了以下这个样子。 + +```json +{ + "scripts": { + "start": "concurrently \"npm:start:renderer\" \"npm:build:main\" \"npm:start:electron\"", + "start:renderer": "cross-env BROWSER=none react-app-rewired start", + "build:main": "tsc --project tsconfig.main.json --watch", + "start:electron": "wait-on http://localhost:3000 && cross-env NODE_ENV=development electronmon ." + }, + "main": "./build/main.js" +} +``` + +好了,现在再来启动一下吧,无论渲染线程还是主线程,都会根据变化自动重启加载了。 + +## 关于发布 + +对于Electron应用来说,最简单的发布工具就是[electron-builder](https://www.electron.build/),但是这个工具并没有什么特殊的,只需要使用默认配置就可以完成三个平台的编译打包。 + +## 踩过的一些坑 + +用Typescript开发使用React作为渲染线程的Electron应用不是没有坑的,这里仅记录一些目前已经遇到的。 + +### 各种`require() is not defined` + +在DevTools上出现这种提示,往往是因为`BrowserWindow`创建的时候`webPreferens`配置给的不正确。因为React APP和基于Typescript的主线程都需要`require()`,所以`webPreferences`的配置需要参考以下示例。 + +```js +webPreferences: { + // 启动nodeIntegration是为了在渲染线程中使用require + nodeIntegration: true, + nodeIntegrationInWorker: true, + nodeIntegrationInSubFrames: true, + enableRemoteModule: true, + // 当开启contextIsolation以后,preload.js中就不能使用require了。 + // 但是如何即能使用preload.js,又能够开启contextIsolation以使用contextBridge功能,还有待于进一步研究。 + contextIsolation: false +} +``` + +### 出现了`Electron failed to install correctly` + +这种情况一般在使用淘宝的npm镜像的时候容易发生,而发生这种情况的主要原因是Electron没有完全安装。要解决这个问题可以直接安装一个辅助库来完成Electron的安装。 + +```bash +# 安装electron-fix +npm install -D electron-fix +# 执行已经安装的electron-fix即可重新开始Electron的下载。 +npx electron-fix start +``` + +## 一个更加迅速的方法 + +基于以上调试经验,我制作了一个CRA的项目模版[cra-template-typescript-electron](https://www.npmjs.com/package/cra-template-typescript-electron),可以直接构建一个完整的Electron应用,这个模版可以使用以下命令在本地创建项目。 + +```bash +npx create-react-app my-app --template typescript-electron +``` +