手把手教你搭建electron+react+ts环境

4,482 阅读5分钟

前言

  • 为什么是react?
    • 因为react可以完美的使用tsx,tsx使用ts-loader不经过babel转换就可以编译成js代码,配合Chromium的高兼容性
  • 为什么是ts?
    • 可以使用import语法+N原因
  • 为什么自己搭建?
    • 实际也可以用cra,但是需要使用customize-cra react-app-rewired修改cra的默认配置
  • vite搭建的项目做渲染线程可以吗
    • 不可以,因为vite不支持fs等nodejs内置模块,如果有配置方法,可以留言下

必备文档汇总

electron中文文档

react中文文档

webpack官方文档 中文版本感觉和官方英文文档有差异,推荐看官方的

我的环境信息:

Nodejs: v12.18.3
npm: 6.14.6
操作系统: Macos 10.15.7
项目所需依赖都是用最新版本

开始搭建

1.安装依赖

  • 初始化npm
mkdir react-electron && cd react-electron
npm init -y
  • 支持Typescript
npm i typescript ts-loader source-map-loader -D
tsc --init
  • 支持React
npm i react react-dom @types/react @types/react-dom react-router-dom @types/react-router-dom -S
  • 支持webpack,不指定版本号,默认使用webpack5
npm i webpack webpack-cli webpack-dev-server html-webpack-plugin  webpack-merge clean-webpack-plugin hoist-non-react-statics -D
  • 支持electron
npm i electron electron-is-dev

2.配置tsconfig

/tsconfig.json

第一个ts配置文件,用于react项目(渲染线程),这里为了节省篇幅把注释都删除了,建议保留

{
  "compilerOptions": {
    "target": "esNext",   
    "module": "commonjs", 
    "jsx": "react",
    "outDir": "./render/dist", 
    "strict": true, 
    "noImplicitAny": true, 
    "strictNullChecks": true, 
    "baseUrl": ".",  
    "paths": {
     
      },  
    "esModuleInterop": true, 
    "skipLibCheck": true, 
    "forceConsistentCasingInFileNames": true 
    },
    "include": [
      "./render/**/*"
    ]
}

/tsconfig-main.json

第二个ts配置文件,用于编译主线程

{
  "compilerOptions": {
    "target": "esNext",
    "module": "commonjs",    "outDir": "dist",
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "baseUrl": ".",
    "paths": {
    },
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "./main/**/*"
  ]
}

3.搭建react环境

新建文件夹/render/render/main , 分别含义为渲染线程文件夹,和渲染线程主页面,未来可能有多个页面

/render/main/index.html

最基础的一个html

<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <div id="root"></div>
    </body>
</html>

/render/main/index.tsx

react入口文件,没有任何逻辑,仅渲染一个div

import React, {useEffect, useState} from 'react'
import ReactDom from 'react-dom'

const App = () => {
    return <div>
        <div>hello react</div>
    </div>
}
ReactDom.render(<App></App>, document.getElementById('root'))

4.配置webpack

新建文件夹/render/config 这里面放webpack的配置文件

/render/config/htmlWebpackPlugin.js

htmlWebpackPlugin逻辑封装(因为有时候要配置cdn),这里可以都写进主配置文件

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");

function htmlWebpackPlugin(mode){
  return new HtmlWebpackPlugin({
    template: path.join(__dirname, "../main/index.html"),
    filename: 'index.html',
  })
}
module.exports ={
  htmlWebpackPlugin
}

/render/config/webpack.common.js

webpack主配置文件,注意这里的target: "electron-renderer",这样修改才能使用electronfs等模块,但是修改为electron-renderer后只能在electron环境中运行,自行灵活配置

对应文档:www.webpackjs.com/configurati…

或者通过 const {ipcRenderer}=window.require('electron') 方式来解决,webpack不会解析electron依赖

const webpack = require('webpack')
const path = require('path');

module.exports = {
    entry: './render/main/index',
    output: {
        filename: "[name].[hash].js",
        path: path.join(__dirname, "../dist-main"),
    },
    target: "electron-renderer",
    resolve: {
        // 引入的默认后缀名,一个个找
        extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
        alias: {
            //   "@": path.resolve("src"), // 这样配置后 @ 可以指向 src 目录
        },
    },
    module: {
        rules: [{
            test: /\.tsx?$/,
            loader: "ts-loader",
            exclude: /node_modules/,
        }],
    },
    plugins: [],
}

/render/config/webpack.dev.js

webpack开发环境配置,没什么特殊,主要是配置一个开发服务器

const webpack = require('webpack')
const {
    merge
} = require('webpack-merge');
const common = require('./webpack.common.js');
const path = require('path');
const {htmlWebpackPlugin} = require('./htmlWebpackPlugin')
const mode = 'development'

module.exports = merge(common, {
    mode,
    devtool: 'inline-source-map',
    devServer: {
        hot: true,
        contentBase: path.join(__dirname, "../dist"),
        historyApiFallback: {
            index: "./index.html",
        },
    },
    plugins: [
        htmlWebpackPlugin(mode),
        new webpack.HotModuleReplacementPlugin()
    ],
})

/render/config/webpack.prod.js

webpack打包配置

const webpack = require('webpack')
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const {htmlWebpackPlugin} = require('./htmlWebpackPlugin')
const mode = 'production'
module.exports = merge(common,{
    mode,
    devtool: 'source-map',
    plugins: [
        // 打包时候才需要clear
        new CleanWebpackPlugin(),
        htmlWebpackPlugin(mode),
    ],
})

5.启动webpack开发服务器

package.json加上两条执行命令,start:react对应启动开发服务器,build:react对应执行打包

"start:react": "webpack serve --config render/config/webpack.dev.js",
"build:react": "webpack  --config render/config/webpack.prod.js"
npm run start:react

如果启动过程没有报错.而且浏览器可以正常打开默认的ip端口http://localhost:8080,就说明这一步完成了

6.配置主线程

新建文件夹/main,所有主线程的逻辑都在这里写

/main/mainWindow.ts

一个常见的主线程打开窗口的逻辑,封装为create函数,electron-is-dev包可以让我们识别现有的环境, 开发环境和打包环境分别load

import {BrowserWindow} from 'electron'
import isDev from 'electron-is-dev'
import {resolve} from 'path'

let win: BrowserWindow;

export function create() {
    win = new BrowserWindow({
        width: 600,
        height: 600,
        webPreferences: {               // 网页功能设置
            nodeIntegration: true,      // 是否在node工作器中启用工作集成默认false
            enableRemoteModule: true,   // 是否启用remote模块默认false
        }
    })
    if (isDev) {
        win.webContents.openDevTools() //打开控制台
        win.loadURL('http://localhost:8080')
    } else {
        // 线上模式, 用react打包的
        win.loadFile(resolve(__dirname, '../render/dist-main/index.html'))
    }
    return win
}

/main/index.ts

主线程入口文件

import {app, BrowserWindow} from 'electron'
import {create} from './mainWindow'
app.on('ready', () => {
    process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; // 关闭web安全警告
    create()
})

package.json添加npm执行命令

dev:main,tsc监控文件变化并实时编译,这里使用-p手动执行ts配置文件

"build:main": "tsc --p tsconfig-main.json",
"dev:main": "tsc -w --p tsconfig-main.json"

配置好了之后可以尝试执行下npm run dev:main,如果都按照我的配置,主线程打包文件在根目录的dist文件夹下面

7.启动主线程

  • 修改package.json
    • 配置main字段
    • 配置start:electron命令
    • author和description也可以先配置上,后面打包要用到

对应文档:www.electronjs.org/docs/tutori…

{
  "name": "react-electron",
  "version": "1.0.0",
  "author": "",
  "description": "My Electron app",
  "main": "dist/index.js",
  "scripts": {
    "start:react": "webpack serve --config render/config/webpack.dev.js",
    "build:react": "webpack  --config render/config/webpack.prod.js",
    "build:main": "tsc --p tsconfig-main.json",
    "dev:main": "tsc -w --p tsconfig-main.json",
    "start:electron": "electron ."
  }
}

执行命令:

npm run start:electron

如果窗口成功弹出并且带着react渲染,就说明成功了

8.打包发布版本

  • 执行webpack打包命令
npm run build:react

如果按照我的配置文件,打包出来的文件在/render/dist-main

  • 修改主线程的load逻辑
...
if (isDev) {
       ...
    } else {
        // 引用webpack的打包内容
        win.loadFile(resolve(__dirname, '../render/dist-main/index.html'))
}
...
  • 打包主线程
npm run build:main
  • 导入 Electron Forge 到应用文件夹
npx @electron-forge/cli import
  • 创建一个分发版本,软件包在out文件夹
npm run make

对应文档:www.electronjs.org/docs/tutori…

可能遇到的问题

  • 安装electron报红Permission denied,mkdir xxx
请勿将electron作为全局依赖安装,如果有请手动卸载

代码仓库,注意是1.base_env分支

https://github.com/Licht-club/react-electron/tree/1.base_env

下一期主题(已更新)

electron主线程和渲染线程之间的交互

这期忘记截图了,所以可能有点枯燥,下次一定注意