使用Electron+React开发跨平台桌面应用

使用Electron开发跨平台桌面应用,已经被越来越多的人接受。从开发者的角度,以前需要很多代码才能做到的自动布局、脏区裁剪、图像栅格化、GPU加速,现在通通不用管了,即使要处理,也是几行代码的事;从企业角度,前端开发人员众多,比较容易招聘,另外,资深前端相对于资深C++客户端,薪资还是有差距的,也节省了不少成本。本文主要着重说明使用Electron+React搭建开发环境的步骤。

技术栈

开发环境设置

创建一个新的React项目

npx create-react-app web-designer-test
cd web-designer-test

添加依赖库

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

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('<location to your react chrome extension>');
    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标签内

"electron-dev": "concurrently \"yarn start\" \"wait-on http://localhost:3000 && electron .\""

增加入口文件到package.json文件中

"main": "public/main.js"

到现在为止,package.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文件

BROWSER=non

这个时候可以使用下面命令运行程序

yarn electron-dev

如果在新的程序窗口内出现了React的欢迎页,则表示一切已经准备OK。但是现在JavaScript的运行环境是浏览器,无法访问宿主机的资源,比如读取文件或注册表,所以需要切换到Node.js环境,使用electron-renderer作为Webpack target,我们使用Rescripts来处理这个问题。

安装依赖库

yarn add @rescripts/cli @rescripts/rescript-env --dev

修改package.json文件中scripts标签内的启动脚本

"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",

修改为

"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",

在根目录新建文件.rescriptsrc.js

module.exports = [require.resolve('./.webpack.config.js')]

最后,在根目录新建另一个文件.webpack.config.js

// define child rescript
module.exports = config => {
  config.target = 'electron-renderer';
  return config;
}

现在就切换到了Node.js运行环境,可以随意访问主机资源了。

打包环境设置

首先,要添加依赖库

yarn add electron-builder --dev

CRA(Create Reactive Application)默认创建的index.html,会使用绝对路径来访问资源,在Electron中会加载资源失败,所以需要修改配置,在package.json中增加homepage属性

"homepage": "./",

接下来添加打包命令,在package.json中的scripts标签中

"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中添加如下信息

"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 Builder的所有选项。

创建一个assets文件夹,用于存放图片资源。

最好在一个平台只构建当前平台的可执行程序,这里针对Windows平台的构建进行了详细设置。

最终,package.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"

最后,执行打包命令

yarn electron-pack-win

会在dist文件夹下生成对应安装包

文件夹结构如下:

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,具体可以参考官方文档,修改方式是在创建窗口时指定值为true即可。

  mainWindow = new BrowserWindow({width: 900, height: 680, webPreferences: {nodeIntegration: true}});
  • 使用VSCode进行主进程调试

添加一个文件.vscode/launch.json在项目根目录,然后即可使用VSCode进行调试。

  {
    "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"
      }
    ]
  }

参考文档*