--- title: "使用Electron+React开发跨平台桌面应用" date: 2019-07-29T19:59:59+08:00 draft: false toc: true tags: [electron, react] categories: web images: ["img/electron.png"] --- 使用Electron开发跨平台桌面应用,已经被越来越多的人接受。从开发者的角度,以前需要很多代码才能做到的自动布局、脏区裁剪、图像栅格化、GPU加速,现在通通不用管了,即使要处理,也是几行代码的事;从企业角度,前端开发人员众多,比较容易招聘,另外,资深前端相对于资深C++客户端,薪资还是有差距的,也节省了不少成本。本文主要着重说明使用Electron+React搭建开发环境的步骤。 ## 技术栈 - [**Electron**](https://electronjs.org/) - [**React - Create React App**](https://github.com/facebook/create-react-app) - [**Rescripts**](https://github.com/harrysolovay/rescripts) - [**Electron Builder**](https://github.com/electron-userland/electron-builder) ## 开发环境设置 创建一个新的React项目 ```shell npx create-react-app web-designer-test cd web-designer-test ``` 添加依赖库 ```shell yarn add electron wait-on concurrently --dev yarn add electron-is-dev ``` - electron 用于界面开发的核心框架 - electron-builder 用于构建安装包 - wait-on concurrently 由于electron需要在react启动之后启动,所以增加这两个库用于同步进程启动顺序 - electron-is-dev 判断当前运行环境 创建文件`public/main.js` ```js const electron = require('electron'); const app = electron.app; const BrowserWindow = electron.BrowserWindow; const path = require('path'); const isDev = require('electron-is-dev'); let mainWindow; function createWindow() { mainWindow = new BrowserWindow({width: 900, height: 680, webPreferences: {nodeIntegration: true}}); mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`); if (isDev) { // Open the DevTools. //BrowserWindow.addDevToolsExtension(''); mainWindow.webContents.openDevTools(); } mainWindow.on('closed', () => mainWindow = null); } app.on('ready', createWindow); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (mainWindow === null) { createWindow(); } }); ``` 增加下面的命令到`package.json`文件中的`scripts`标签内 ```js "electron-dev": "concurrently \"yarn start\" \"wait-on http://localhost:3000 && electron .\"" ``` 增加入口文件到`package.json`文件中 ```javascript "main": "public/main.js" ``` 到现在为止,`package.json`文件应该类似于下面这样 ```json { "name": "web-designer-test", "version": "0.1.0", "private": true, "dependencies": { "electron-is-dev": "^1.1.0", "react": "^16.8.7", "react-dom": "^16.8.6", "react-scripts": "3.0.1" }, "main": "public/main.js", "scripts": { "start": "rescripts start", "build": "rescripts build", "test": "rescripts test", "eject": "react-scripts eject", "electron-dev": "concurrently \"yarn start\" \"wait-on http://localhost:3000 && electron .\"" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "concurrently": "^4.1.1", "electron": "^5.0.8", "wait-on": "^3.3.0" } } ``` 为了开发环境下,不启动默认浏览器,需要设置`BROWSER`环境变量,在根目录创建`.env`文件 ```ini BROWSER=non ``` 这个时候可以使用下面命令运行程序 ```shell yarn electron-dev ``` 如果在新的程序窗口内出现了React的欢迎页,则表示一切已经准备OK。但是现在JavaScript的运行环境是浏览器,无法访问宿主机的资源,比如读取文件或注册表,所以需要切换到Node.js环境,使用`electron-renderer`作为[Webpack target](https://webpack.js.org/configuration/target/),我们使用`Rescripts`来处理这个问题。 安装依赖库 ```shell yarn add @rescripts/cli @rescripts/rescript-env --dev ``` 修改`package.json`文件中`scripts`标签内的启动脚本 ```json "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", ``` 修改为 ```json "start": "rescripts start", "build": "rescripts build", "test": "rescripts test", ``` 在根目录新建文件`.rescriptsrc.js` ```javascript module.exports = [require.resolve('./.webpack.config.js')] ``` 最后,在根目录新建另一个文件`.webpack.config.js` ```javascript // define child rescript module.exports = config => { config.target = 'electron-renderer'; return config; } ``` 现在就切换到了Node.js运行环境,可以随意访问主机资源了。 ## 打包环境设置 首先,要添加依赖库 ```shell yarn add electron-builder --dev ``` CRA(**C**reate **R**eactive **A**pplication)默认创建的`index.html`,会使用绝对路径来访问资源,在Electron中会加载资源失败,所以需要修改配置,在`package.json`中增加`homepage`属性 ```json "homepage": "./", ``` 接下来添加打包命令,在`package.json`中的`scripts`标签中 ```json "postinstall": "electron-builder install-app-deps", "preelectron-pack": "yarn build", "electron-pack": "electron-builder -mw" ``` - `"postinstall": "electron-builder install-app-deps"`用于确保本地依赖库都已经安装 - `"preelectron-pack": "yarn build"`会保证在打包前构建应用 - `"electron-pack": "electron-builder -mw"`会为Mac(m)和Windows(w)平台进行App打包 在执行打包命令前,还需要设置打包参数,在`package.json`中添加如下信息 ```json "author": { "name": "lniwn", "email": "lniwn@live.com", "url": "https://oaoa.me" }, "build": { "appId": "me.oaoa.web-designer-test", "productName": "WebDesignerTest", "copyright": "Copyright © 2019 ${author}", "mac": { "category": "public.productivity.utilities" }, "win": { "icon": "assets/icon.png", "target": "nsis" }, "nsis": { "allowToChangeInstallationDirectory": true, "allowElevation": false, "createDesktopShortcut": true, "menuCategory": true, "oneClick": false }, "files": [ "build/**/*", "node_modules/**/*" ], "directories": { "buildResources": "assets" } } ``` 可以在[这里](https://www.electron.build/configuration/configuration)查看Electron Builder的所有选项。 创建一个assets文件夹,用于存放图片资源。 最好在一个平台只构建当前平台的可执行程序,这里针对Windows平台的构建进行了详细设置。 最终,package.json文件内容如下: ```json { "name": "web-designer-test", "version": "0.1.0", "private": true, "dependencies": { "electron-is-dev": "^1.1.0", "react": "^16.8.6", "react-dom": "^16.8.6", "react-scripts": "3.0.1" }, "main": "public/main.js", "homepage": "./", "description": "Web页面设计器", "scripts": { "start": "rescripts start", "build": "rescripts build", "test": "rescripts test", "eject": "react-scripts eject", "electron-dev": "concurrently \"yarn start\" \"wait-on http://localhost:3000 && electron .\"", "postinstall": "electron-builder install-app-deps", "preelectron-pack": "yarn build", "electron-pack": "electron-builder -mw", "electron-pack-win": "electron-builder -c.extraMetadata.main=build/main.js --win --x64" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@rescripts/cli": "^0.0.11", "@rescripts/rescript-env": "^0.0.10", "concurrently": "^4.1.1", "electron": "^5.0.8", "electron-builder": "^21.1.5", "wait-on": "^3.3.0" }, "author": { "name": "lniwn", "email": "lniwn@live.com", "url": "https://oaoa.me" }, "build": { "appId": "me.oaoa.web-designer-test", "productName": "WebDesignerTest", "copyright": "Copyright © 2019 ${author}", "mac": { "category": "public.productivity.utilities" }, "win": { "icon": "assets/icon.png", "target": "nsis" }, "nsis": { "allowToChangeInstallationDirectory": true, "allowElevation": false, "createDesktopShortcut": true, "menuCategory": true, "oneClick": false }, "files": [ "build/**/*", "node_modules/**/*" ], "directories": { "buildResources": "assets" } } } ``` 由于Electron默认入口文件为`build/Electron.js`,如果想自定义,需要在参数中指定入口文件,否则编译会报错 `"electron-pack-win": "electron-builder -c.extraMetadata.main=build/main.js --win --x64"` 最后,执行打包命令 ```shell yarn electron-pack-win ``` 会在dist文件夹下生成对应安装包 文件夹结构如下: ```txt F:\Project\electron\web-designer-test ├.env ├.gitignore ├.rescriptsrc.js ├.webpack.config.js ├assets │ ├icon.png ├build │ ├asset-manifest.json │ ├favicon.ico │ ├index.html │ ├main.js │ ├manifest.json │ ├precache-manifest.3ef21b1d6090801b808aaff5b52f1a17.js │ ├service-worker.js │ ├static ├dist │ ├.icon-ico │ ├builder-effective-config.yaml │ ├WebDesignerTest Setup 0.1.0.exe │ ├WebDesignerTest Setup 0.1.0.exe.blockmap │ ├win-unpacked ├node_modules ├package.json ├public │ ├favicon.ico │ ├index.html │ ├main.js │ ├manifest.json ├README.md ├src │ ├App.css │ ├App.js │ ├App.test.js │ ├index.css │ ├index.js │ ├logo.svg │ ├serviceWorker.js ├yarn.lock ``` ## 开发笔记 ### 填坑记录 - 启动报错`'require()' is not defined.` 从Electron 5.0版本开始,`nodeIntegration`默认值由`true`改为`false`,具体可以参考[官方文档](https://github.com/electron/electron/blob/master/docs/api/breaking-changes.md#planned-breaking-api-changes-50),修改方式是在创建窗口时指定值为`true`即可。 ```javascript mainWindow = new BrowserWindow({width: 900, height: 680, webPreferences: {nodeIntegration: true}}); ``` - 使用VSCode进行主进程调试 添加一个文件`.vscode/launch.json`在项目根目录,然后即可使用VSCode进行调试。 ```json { "version": "0.2.0", "configurations": [ { "name": "Debug Main Process", "type": "node", "request": "launch", "cwd": "${workspaceRoot}", "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", "windows": { "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" }, "args" : ["."], "outputCapture": "std" } ] } ``` 参考文档* - https://www.codementor.io/randyfindley/how-to-build-an-electron-app-using-create-react-app-and-electron-builder-ss1k0sfer - https://juejin.im/post/5c356a396fb9a049e30848f0