最近需要做一个桌面应用,相信很多前端选择制作桌面应用肯定是会选择electron,查了好久有一个electron-vue,由于最近主要写的react,所以想找找有没有react的,实在找不到了,只能自己搭一个了。 开发electron有两个进程,渲染进程和主进程,首先我们先搭建主进程。
主进程
初始化文件夹
mkdir electron-react&&npm init --yes
安装依赖
我们这边都是使用ts来开发,所以把ts也一起安装了,这边electron可能需要科学上网才能正常安装,不然装的可能会比较慢,这边我的electron安装的版本是15.1.2
yarn add electron typescript -D
安装完后初始化ts
tsc --init
这样就生成了一份tsconfig.json文件,我们对这一份tsconfig.json改个名字,改为tsconfig.main.json然后我们配置下几个基本的ts配置项
{
"compilerOptions": {
"target": "esNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"outDir": "main-dist", /* Redirect output structure to the directory. */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include" :["./main/**/*"],
"exclude": ["dist","node_modules"]
}
然后我们在根目录下新建main/index.ts
main/index.ts,从字面意思很好理解,就是主进程是控制窗口的,而渲染进程就是负责渲染的,主进程创建了窗口可以加载url,也可以加载本地文件,这边我们就直接打开本地文件了.
在新建的main/index.ts输入一下内容来打开一个窗口
import {BrowserWindow,app} from 'electron'
function createWindow(){
const window:BrowserWindow=new BrowserWindow({
height:1000,
width:1000,
webPreferences:{
webSecurity:false,
contextIsolation:false,
nodeIntegration:true,
}
})
window.loadFile('../main/index.html');
return window;
}
app.on('ready',()=>{
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS']='true'//关闭web安全警告
createWindow();
})
上述内容就是创建一个窗口并且加载同级下的index.html文件,我们来执行下主进程,首先我们需要编译ts,在我们的package.json下写入脚本命令
"scripts": {
"start:main": "tsc --p tsconfig.main.json" //--p指定ts配置文件
},
然后执行yarn start:main,我们就可以看到根目录下多了一个main-dist文件,这个文件就是ts编译好后的js文件,我们需要的就是执行这个js文件,我们在添加一个命令
"scripts": {
"build:main": "tsc --p tsconfig.main.json",
"start:electron": "electron ./main-dist/index"
},
在执行这个命令前我们需要创建一个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="app">hello electron</div>
</body>
</html>
然后执行yarn build:electron,我们就会看到我们启动了一个窗口,窗口里显示的就是hello electron,到这为止我们的主进程就搭建完成啦,当然后续渲染进程搭建完后这边也会有所修改. 来现在我们来搭建渲染进程,
渲染进程
渲染进程我们使用react,这边我们就用webpack开搭建一个react项目,搭建之前我们先把之前的tsconfig.main.json复制一份改名为tsconfig.json
{
"compilerOptions": {
"target": "esNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"outDir": "./render/dist", /* Redirect output structure to the directory. */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include" :["./render/**/*"],
"exclude": ["dist","node_modules"]
}
安装依赖
安装webpack依赖
yarn add webpack webpack-cli webpack-dev-server webpack-merge html-webpack-plugin clean-webpack-plugin -D
安装react依赖
yarn add react react-dom react-router-dom @types/react @types/react-dom @types/react-router-dom
安装loader
yarn add ts-loader style-loader url-loader file-loader css-loader less-loader -D
在根目录下新建三个文件
render/config/webpack.common.js
render/config/webpack.dev.js
render/config/webpack.prod.js
webpack.common.js
const webpack = require("webpack");
const path = require("path");
module.exports = {
entry: "./render/index",
output: {
filename: "[name].js",
path: path.join(__dirname, "../dist-render"),
},
// target:'web',
target: "electron-renderer",
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
alias: {},
},
module: {
rules: [
{
test: [/\.js$/, /\.ts$/, /\.tsx$/],
exclude: /node_modules/,
use: [
{
loader: "ts-loader",
},
],
},
{
test: [/\.less$/, /\.css$/],
// exclude:/node_modules/,
use: [
"style-loader",
{
loader: "css-loader",
options: {
modules: false,
sourceMap: false,
},
},
"less-loader",
],
},
{
test: /.(jpg|png|gif)$/,
use: {
loader: "file-loader",
options: {
//
name: "[name]_[hash].[ext]",
},
},
},
],
},
plugins: [],
};
本文不对webpack配置做过多说明,这边我们对ts、js、tsx使用ts-loader来进行编译,css、less和静态文件都使用最常用的loader.
需要注意的是,由于electron这边可以在web中使用node,所以target我们不能设置成web而要使用electron-renderer webpack.dev.js
const webpack = require("webpack");
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const mode = "development";
module.exports = merge(common, {
mode: "development",
devtool: "inline-source-map",
devServer: {
hot: true,
static: path.join(__dirname, "../dist-main"),
historyApiFallback: {
index: "./index.html",
},
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, "../public/index.html"),
filename: "index.html",
}),
new webpack.HotModuleReplacementPlugin(),
],
});
webpack.prod.js
const webpack = require("webpack");
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
const path=require('path')
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = merge(common, {
mode: "production",
devtool: "source-map",
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.join(__dirname, "../public/index.html"),
filename: "index.html",
}),
],
});
新建render/public/index.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="app"></div>
</body>
</html>
新建render/src/app.tsx,初始化一个根组件
import { App } from "electron";
import React from "react";
export default function App() {
return <div>hello react</div>;
}
新建render/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import App from "./src/app";
ReactDOM.render(<App />, document.getElementById("App"));
接下来我们就可以来配置渲染进程脚本命令啦
"scripts": {
"start:react": "webpack serve --config render/config/webpack.dev.js",
"build:react": "webpack --config render/config/webpack.prod.js",
"start:main": "tsc --p tsconfig.main.json",
"start:electron": "electron ./main-dist/index"
},
我们这边执行yarn start:react,然后打开8080端口就会看到下面这个报错
这个报错呢,是正常的,因为我们使用electron-renderer打包出来的只能在electron环境下才可以运行,所以我们修改我们的主进程让他来加载我们本地服务,我们需要安装一个依赖叫做electron-is-dev,当我们不是开发环境的时候就要加载yarn build:react打包出来的文件了。
yarn add electron-is-dev
然后修改我们的main/index.ts
import {BrowserWindow,app} from 'electron'
import isDev from 'electron-is-dev'
import {resolve} from 'path'
function createWindow(){
const window:BrowserWindow=new BrowserWindow({
height:1000,
width:1000,
webPreferences:{
webSecurity:false,
contextIsolation:false,
nodeIntegration:true,
}
})
if(isDev){
window.webContents.openDevTools();//打开控制台
window.loadURL('http://localhost:8080')
}else{
window.loadFile(resolve(__dirname,"../render/dist-render/index.html"));
}
return window;
}
app.on('ready',()=>{
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS']='true'//关闭web安全警告
createWindow();
})
然后我们在执行一下yarn build:main打包主进程,我们每次都要修改主进程都需要重新打包这就很麻烦,所以我们给主进程加上热更新,怎么做呢,很简单只要使用tsc -w就行了,w意思就是watch监听
"start:main":"tsc -w --p tsconfig.main.json",
我们每次就可以执行yarn start:main了,这样修改主进程文件后他就会自动重新打包了
接下来我们执行yarn start:electron 我们会发现我们能看到hello react了,这就说明我们的整个electron应用就搭建完成咯。
热重载
electron热重载我们可以使用electron-reloader
yarn add electron-reloader -D
然后在main.ts中添加一下代码就行啦
if(isDev){
try {
require('electron-reloader')(module, {});
} catch (_) { }
window.webContents.openDevTools();
window.loadURL('http://localhost:8080')
}
打包
如果我们开发完了然后要打包我们该怎么办呢,我们需要安装一个electron-builder
yarn add electron-builder -D
然后我们需要在package.json中做一些配置,首先需要指定项目主入口,然后就是打包配置了
"main":"./main-dist/index.js",
"build": {
"productName": "test",
"appId": "org.react.vas",
"mac": {
"icon": "assets/icon.icns",
"type": "distribution",
"target": [
"dmg",
"zip"
]
},
"win": {
"icon": "build/favicon.ico",
"target": [
"nsis"
]
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": false
}
},
这边需要注意icon这里mac一般用的icns,icns的生成可以参考快速生成 Mac App icns 图标,生成后放入指定文件夹里就行,我这就直接放在了根目录下的assets下。 然后我们加入打包命令,build:mac是打包mac安装包的,build:win是打包windows安装包的,我们都是打的对应系统的安装包
"build:mac": "electron-builder --mac",
"build:win": "electron-builder --win"
执行yarn build:mac,耐心等待后你会发现根目录下多了一个dist文件,里面就有你打包出来的安装包咯,打开安装包就是下面这个样子的咯
拖入后然后打开我们的test应用你就会发现打开的应用是一片空白,这是什么原因呢,其实是老生常谈的问题了,修改我们webpack配置,在webpack.common.js中添加一段代码,再重新打包就行啦
output: {
publicPath: './',
filename: "[name].js",
path: path.join(__dirname, "../dist-render"),
},
到这边我们自己搭建的electron应用就搭建完成啦
仓库地址