本篇介绍在 Electron
中配置 React
或者 Vue
框架开发,毕竟现在前端多是用框架开发,其实已早有现成的模板配置,自己动手用 Webpack
来搭建能更好的熟悉配置的过程。
Electron 基础配置
1 2
| mkdir <project name> && cd <project name> npm init -y && npm install [email protected]
|
国内直接安装 electron
会比较慢,可以配置淘宝源安装,直接输入:
1
| npm set registry http://registry.npm.taobao.org
|
但下载完成后记得改回来npm
官方的源,要不然会影响自己 npm
模块的发布:
1
| npm config set registry https://registry.npmjs.org
|
可用 npm get registry
查看本地所属的源
项目基础架构
package.json
修改改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "name": "electron-template", "version": "0.0.1", "description": "Electron + React 配置项目", "author": "余翼", "main": "./app/main/electron.js", "scripts": { "start:main": "electron ./app/main/electron.js" }, "dependencies": { "electron": "^13.1.1" } }
|
创建目录结构如下:
1 2 3 4 5 6
| electron-template |__app |__main |__electron.js |__index.html |__renderer
|
让我们修改以下两个文件:
用于启动Electron的窗口:app\main\electron.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const path = require('path'); const { app, BrowserWindow } = require('electron');
const createWindow = () => { const mainWindow = new BrowserWindow({ width: 1000, height: 600, webPreferences: { nodeIntegration: true, }, }) mainWindow.loadFile('index.html') }
app.whenReady().then(() => { createWindow() app.on('activate', () => if (BrowserWindow.getAllWindows().length === 0) createWindow() )})
|
app\main\index.html
1 2 3 4 5 6 7 8 9 10 11 12
| <!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>Electron template</title> </head> <body> <h1>hello world</h1> </body> </html>
|
运行 npm run start:main
可以看到界面:
React
1 2
| npm install react npm install react-router react-router-dom react-dom
|
配置 babel
babel
用来编译es6及jsx代码:
1 2 3 4 5 6 7 8 9
| npm install -S @babel/polyfill npm install -D @babel/core @babel/cli
npm install -D @babel/preset-env @babel/preset-react @babel/preset-typescript
npm install @babel/plugin-transform-runtime --save --dev
npm install @babel/plugin-transform-modules-commonjs --save --dev
|
编写 babel.config.js
,babel官网推荐的这种写法替换以前的 .babelrc
,api.cache(true);
据官网的说法,可缓存传进来的api,效率更高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| module.exports = function (api) { api.cache(true);
const presets = [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript', ] const plugins = [ '@babel/plugin-transform-runtime', [ '@babel/plugin-transform-modules-commonjs', { allowTopLevelThis: true, loose: true, lazy: true } ] ]
return { presets, plugins } }
|
配置 webpack
!注意,截至到我写该文为止,安装最新的webpack5和webpack-cli4,并不兼容,运行时会报很多莫名其妙的错误,所以必须指定webpack4和webpack-cli3的版本,其他版本我也试过了,只有这两个版本相容较为稳定
1 2 3 4
| npm i -D webpack@4 webpack-cli@3
npm i -D webpack-dev-server
|
我们会配置3个webpack文件,分别是
webpack.base.js
– 基础配置
webpack.render.js
– 主进程配置
webpack.main.js
– 渲染进程配置
所以安装这款插件,用于将下面两个个文件引入 webpack.base.js
中,减少webpack配置的代码:
安装各种loader,plugin
1 2 3
| npm i -D html-webpack-plugin@4 npm i -D clean-webpack-plugin npm i babel-loader
|
配置 cross-env
插件,用于执行不同环境的脚本:
webpack配置文件
webpack.base.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const path = require('path') const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = { resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'], alias: { '@src': path.join(__dirname, '../', 'app/renderer') } }, module: { rules: [ { test: /\.(jsx?|tsx?)$/, exclude: /node_modules/, use: { loader: 'babel-loader',}}, ] }, plugins: [ new CleanWebpackPlugin(), ], }
|
webpack.main.dev.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const path = require('path') const webpack = require('webpack') const baseConfig = require('./webpack.base.js') const webpackMerge = require('webpack-merge')
const mainConfig = { entry: path.resolve(__dirname, '../app/main/electron.js'), target: 'electron-main', output: { filename: 'electron.js', path: path.resolve(__dirname, '../dist'), }, devtool: 'inline-source-map', mode: 'development', plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"development"' }) ], }
module.exports = webpackMerge.merge(baseConfig, mainConfig)
|
webpack.render.dev.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const path = require('path') const webpackMerge = require('webpack-merge') const baseConfig = require('./webpack.base.js') const HtmlWebPackPlugin = require('html-webpack-plugin')
const devConfig = { mode: 'development', entry: { index: path.resolve(__dirname, '../app/renderer/App.jsx'), }, output: { filename: '[name].[hash].js', path: path.resolve(__dirname, '../dist'), }, target: 'electron-renderer', devtool: 'inline-source-map', devServer: { contentBase: path.join(__dirname, '../dist'), compress: true, host: '127.0.0.1', port: 7001, hot: true, }, plugins: [ new HtmlWebPackPlugin({ template: path.resolve(__dirname, '../app/renderer/index.html'), filename: path.resolve(__dirname, '../dist/index.html'), chunks: ['index'], }) ] }
module.exports = webpackMerge.merge(baseConfig, devConfig)
|
重组项目结构
由于react是在渲染进程中执行,所以,我们将入口文件 index.html
移动到 .app/render/
文件夹, 并创建 app.jsx
文件作为react的入口文件
1 2
| mv ./app/main/index.html ./app/renderer/ touch ./app/renderer/App.jsx
|
index.html
更改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!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>Electron Platform</title> <style> * { margin: 0; } </style> </head> <body> <div id="root"></div> </body> </html>
|
App.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react' import ReactDOM from 'react-dom' import { HashRouter as Router, Route, Switch } from 'react-router-dom'
function App() { return ( <Router> <Switch> <Route path='/'> <div>可视化开发平台</div> </Route> </Switch> </Router> ) }
ReactDOM.render(<App/>, document.getElementById('root'))
|
修改electron主线程配置,配合react做单页面应用
既然基础文件结构改了,那么 .app/main/electron.js
也得跟着增加以下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ... const isDev = () => { return process.env.NODE_ENV === 'development'; } ... const createWindow = () => { ...
if (isDev()) { mainWindow.loadURL('http://127.0.0.1:7001') } else { mainWindow.loadURL(`file://${path.join(__dirname, './dist/index.html')}`) } }
|
package.json
的 script
修改如下
1 2 3 4
| "scripts": { "start:main": "cross-env NODE_ENV=development webpack --config ./webpack.main.dev.js && electron ./dist/electron.js", "start:render": "webpack-dev-server --config ./webpack.renderer.dev.js" },
|
报错处理
如果出现报错:Uncaught ReferenceError: require is not defined,请检查你是否在主进程中添加这行代码,如果添加了,请确保你搭建项目的 Electron 与本应用的版本一致(当前项目的 Electron@^11.1.1)
请自检查一下你的版本是否正确,进入 node_modules,找到 electron,看看 package.json 中的 version 是否是 11.1.1。
Vue
Vue 或 React 均有一个现成的模板可用,这两个该模板共同好处是,只需要一个终端,便可跑起ELectron
的主进程和渲染进程,集成度更高,下面我们来看一下这两个模板的配置。
electron-react-boilerplate 集成两个终端是使用了webpack的一个api webpack deverserve before api,在其源码文件webpack.config.renderer.dev.babel.js里可以看到配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { spawn, execSync } from 'child_process'
export default merge(baseConfig, { ... before() { console.log('Starting Main Process...'); spawn('npm', ['run', 'start:main'], { shell: true, env: process.env, stdio: 'inherit', }) .on('close', (code) => process.exit(code)) .on('error', (spawnError) => console.error(spawnError)); }, }
|
其实很简单,利用了node的child_process
api 新开了个进程,让渲染进程跑在该进程之上
webpack.devserver.before
api 类似于 webpack
的生命周期函数,还有一个 webpack.devserver.after
electron-vue 是因为这个的脚本dev-runner.js,利用 Promise.all()
,函数进行调用,把两个终端操作融合到一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ...
function init () { greeting()
Promise.all([startRenderer(), startMain()]) .then(() => { startElectron() }) .catch(err => { console.error(err) }) }
init()
|