предисловие
Electron — это кроссплатформенный фреймворк для создания настольных приложений, позволяющий нам использовать HTML/CSS/JS для создания кроссплатформенных настольных приложений. С развитием большого внешнего интерфейса, когда мы разрабатываем веб-интерфейс, мы обычно будем использовать инструменты построения, такие как Webpack и фреймворки MVVM, такие как React, для помощи в разработке. То же самое относится и к разработке Electron, поэтому в этой статье будет рассказано, как использовать Webpack/React для упаковки и сборки всего приложения Electron, а также использовать Electron-builder для сборки приложения. На самом деле сообщество предоставляет множество шаблонов и шаблонов для Electron Webpack, таких какelectron-forge
,electron-react-boilerplate
И так далее, но благодаря моим собственным исследованиям и конструированию (воспроизведению колеса) я могу глубже понять интерфейсную систему упаковки и конструирования.
содержание
- Об Электроне
- Электронная установка
- структурный дизайн
- Используйте веб-пакет для упаковки основных процессов и процессов рендеринга.
- Создавайте приложения с помощью электронного конструктора
- Поддержка модуля С++
- Интеграция Redux + React-маршрутизатор
- Интеграция вспомогательных средств разработки Devtron
- Суммировать
- Ссылаться на
Об Электроне
Electron — это фреймворк для создания собственных кроссплатформенных настольных приложений с использованием интерфейсных веб-технологий (HTML/CSS/JavaScript/React и т. д.), который можно рассматривать как комбинацию Chromium, Node.js и Native API.
Chromium является открытым исходным кодом Google и эквивалентен урезанной версии браузера Chrome и отвечает за визуализацию веб-интерфейса в Electron. Chromium позволяет разработчикам писать код веб-интерфейса без учета совместимости браузера.Node.js — это легкая и эффективная среда выполнения JavaScript, основанная на управляемой событиями неблокирующей модели ввода-вывода. В Electron он отвечает за вызов базового API системы для работы с собственным графическим интерфейсом и выполнения кода JavaScript основного потока, а такие модули, как utils и fs, обычно используемые в Node.js, также могут использоваться непосредственно в Electron.
Нативные API — это функции графического интерфейса, предоставляемые системой, такие как системные уведомления, системные меню, открытие диалоговых окон системных папок и т. д. Electron обеспечивает поддержку функций операционной системы для приложений путем интеграции собственных API.
В отличие от традиционных веб-сайтов, Electron основан на модели процесса «ведущий-ведомый».Каждое приложение Electron имеет один и только один основной процесс (Main Process) и один или несколько процессов рендеринга (Renderer Process), соответствующих нескольким веб-страницам. Кроме того, он также включает в себя другие процессы, такие как процесс GUP, процесс расширения и так далее.
Основной процесс отвечает за создание окон, координацию межпроцессного взаимодействия, регистрацию и распределение событий и так далее. Процесс рендеринга отвечает за рендеринг страниц пользовательского интерфейса и реализацию интерактивной логики. Однако в рамках этой модели процесса легко создать проблему с единой точкой отказа, то есть сбой или блокировка основного процесса приведет к тому, что все приложение не сможет ответить.Электронная установка
Проблема, встречающаяся в процессе установки электрона, может заключаться в том, что время ожидания сети (злая стена) происходит при загрузке электронного пакета, вызывая неудачную установку.
Решение, естественно, используется, здесь мы можем открытьnode_modules/@electron/get/dist/cjs/artifact-utils.js
, найти способ обработки зеркального отображенияmirrorVar
function mirrorVar(name, options, defaultValue) {
// Convert camelCase to camel_case for env var reading
const lowerName = name.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}_${b}`).toLowerCase();
return (process.env[`NPM_CONFIG_ELECTRON_${lowerName.toUpperCase()}`] ||
process.env[`npm_config_electron_${lowerName}`] ||
process.env[`npm_package_config_electron_${lowerName}`] ||
process.env[`ELECTRON_${lowerName.toUpperCase()}`] ||
options[name] ||
defaultValue);
}
и получить путь загрузкиgetArtifactRemoteURL
метод
async function getArtifactRemoteURL(details) {
const opts = details.mirrorOptions || {};
let base = mirrorVar('mirror', opts, BASE_URL); // ELECTRON_MIRROR 环境变量
if (details.version.includes('nightly')) {
const nightlyDeprecated = mirrorVar('nightly_mirror', opts, '');
if (nightlyDeprecated) {
base = nightlyDeprecated;
console.warn(`nightly_mirror is deprecated, please use nightlyMirror`);
}
else {
base = mirrorVar('nightlyMirror', opts, NIGHTLY_BASE_URL);
}
}
const path = mirrorVar('customDir', opts, details.version).replace('{{ version }}', details.version.replace(/^v/, '')); // ELECTRON_CUSTOM_DIR环境变量,并将{{version}}替换为当前版本
const file = mirrorVar('customFilename', opts, getArtifactFileName(details));
// Allow customized download URL resolution.
if (opts.resolveAssetURL) {
const url = await opts.resolveAssetURL(details);
return url;
}
return `${base}${path}/${file}`;
}
Видно, что для указания зеркала можно определить множество переменных окружения, таких как ELECTRON_MIRROR, ELECTRON_CUSTOM_DIR и т.д., которые собственно и указаны в официальной документации
Mirror
You can use environment variables to override the base URL, the path at which to look for Electron binaries, and the binary filename. The URL used by
@electron/get
is composed as follows:url = ELECTRON_MIRROR + ELECTRON_CUSTOM_DIR + '/' + ELECTRON_CUSTOM_FILENAME
For instance, to usethe China CDN mirror:
ELECTRON_MIRROR="https://cdn.npm.taobao.org/dist/electron/" ELECTRON_CUSTOM_DIR="{{ version }}"
Поэтому при загрузке Electron вам нужно всего лишь добавить две переменные окружения, чтобы решить проблему тайм-аута сети (стены).
ELECTRON_MIRROR="https://cdn.npm.taobao.org/dist/electron/" ELECTRON_CUSTOM_DIR="{{ version }}" npm install --save-dev electron
После установки электрона можно попробовать написать простейшее приложение для электрона Структура проекта следующая
project
|__index.js # 主进程
|__index.html # 渲染进程
|__package.json #
соответствующий основной процессindex.js
часть
const electron = require('electron');
const { app } = electron;
let window = null;
function createWindow() {
if (window) return;
window = new electron.BrowserWindow({
webPreferences: {
nodeIntegration: true // 允许渲染进程中使用node模块
},
backgroundColor: '#333544',
minWidth: 450,
minHeight: 350,
height: 350,
width: 450
});
window.loadFile('./index.html').catch(console.error);
window.on('close', () => window = null);
window.webContents.on('crashed', () => console.error('crash'));
}
app.on('ready', () => createWindow());
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', createWindow)
Соответствующий процесс рендерингаindex.html
часть
<!DOCTYPE>
<html lang="zh">
<head><title></title></head>
<style>
.box {color: white;font-size: 20px;text-align: center;}
</style>
<body>
<div class="box">Hello world</div>
</body>
</html>
Кpackage.json
Добавить команду запуска в
{
...,
"main": "index.js",
"script": {
"start": "electron ."
},
...
}
npm run start
Запустите, одна из простейших электронных приложений разработки завершена.
Структура проекта
Проекты Electron обычно состоят из основного процесса и процесса рендеринга.Основной процесс используется для реализации серверной части приложения.Как правило, C++ или rust используются для реализации основных функций и загружаются в основной процесс в виде плагинов Node. (например, Feishu, Feichat от ByteDance). Основной процесс реализован с использованием ржавчины), часть JavaScript похожа на слой клея, используемого для соединения Electron и сторонних плагинов, а процесс рендеринга заключается в реализации рисования веб-интерфейса и некоторая логика взаимодействия с пользовательским интерфейсом. Основной процесс и процесс рендеринга разрабатываются независимо, а для связи между процессами используется IPC, поэтому основной процесс и процесс рендеринга упаковываются отдельно, то есть два набора конфигураций webpack. среды разработки из производственной среды, также требуются два набора конфигураций веб-пакетов. Кроме того, при разработке электронных приложений потребуется несколько окон, поэтому процесс рендеринга упакован с несколькими страницами.Общая структура выглядит следующим образом.
project
|__src
|__main # 主进程代码
|__index.ts
|__other
|__renderer # 渲染进程代码
|__index # 一个窗口/页面
|__index.tsx
|__index.scss
|__other
|__dist # webpack打包后产物
|__native # C++代码
|__release # electron-builder打包后产物
|__resources # 资源文件
|__babel.config.js # babel配置
|__tsconfig.json # typescript配置
|__webpack.base.config.js # 基础webpack配置
|__webpack.main.dev.js # 主进程开发模式webpack配置
|__webpack.main.prod.js # 主进程生产模式webpack配置
|__webpack.renderer.dev.js # 渲染进程开发模式webpack配置
|__webpack.renderer.prod.js # 渲染进程生产模式webpack配置
Процесс упаковки и сборки на самом деле относительно прост: используйте webpack для упаковки основного процесса и процесса рендеринга, и, наконец, используйте electronic-builder для упаковки и сборки упакованного кода и, наконец, для сборки приложения.Для многооконной обработки каждый каталог в процессе рендеринга представляет собой окно (страницу) и помечается в записи записи веб-пакета.dist/${name}
В каталоге, когда основной процесс загружается, он загружается в соответствии с именем, указанным в записи веб-пакета.
Используйте веб-пакет для упаковки основных процессов и процессов рендеринга.
Сначала установите веб-пакет
npm install --save-dev webpack webpack-cli webpack-merge
установить реагировать
npm install --save react react-dom
установить машинопись
npm install --save-dev typescript
И установите пакет соответствующего типа
npm install --save-dev @types/node @types/react @types/react-dom @types/electron @types/webpack
написать соответствующийtsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "ES2018",
"module": "CommonJS",
"lib": [
"dom",
"esnext"
],
"declaration": true,
"declarationMap": true,
"jsx": "react",
"strict": true,
"pretty": true,
"sourceMap": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"moduleResolution": "Node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"allowJs": true,
"resolveJsonModule": true
},
"exclude": [
"node_modules",
"native",
"resources"
],
"include": [
"src/main",
"src/renderer"
]
}
Написать базовую конфигурацию веб-пакетаwebpack.base.config.js
, и основной процесс, и процесс рендеринга должны использовать эту конфигурацию веб-пакета.
const path = require('path');
// 基础的webpack配置
module.exports = {
module: {
rules: [
// ts,tsx,js,jsx处理
{
test: /\.[tj]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader', // babel-loader处理jsx或tsx文件
options: { cacheDirectory: true }
}
},
// C++模块 .node文件处理
{
test: /\.node$/,
exclude: /node_modules/,
use: 'node-loader' // node-loader处理.node文件,用于处理C++模块
}
]
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '.node'],
alias: {
'~native': path.resolve(__dirname, 'native'), // 别名,方便import
'~resources': path.resolve(__dirname, 'resources') // 别名,方便import
}
},
devtool: 'source-map',
plugins: []
};
Установитьbabel-loaderОбрабатывать файлы jsx или tsx,node-loaderОбрабатывать файлы .node
npm install --save-dev babel-loader node-loader
установить соответствующийПлагин Бабель
npm install --save-dev @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-proposal-optional-chaining @babel/plugin-syntax-dynamic-import @babel/plugin-transform-react-constant-elements @babel/plugin-transform-react-inline-elements
и установкабабель пресеты
npm install --save-dev @babel/preset-env @babel/preset-react @babel/preset-typescript
написать соответствующийbabel.config.js
Конфигурацию, код в режиме разработки и режиме производства нужно обрабатывать отдельно в конфигурации, то есть с использованием разных плагинов
const devEnvs = ['development', 'production'];
const devPlugins = []; // TODO 开发模式
const prodPlugins = [ // 生产模式
require('@babel/plugin-transform-react-constant-elements'),
require('@babel/plugin-transform-react-inline-elements'),
require('babel-plugin-transform-react-remove-prop-types')
];
module.exports = api => {
const development = api.env(devEnvs);
return {
presets: [
[require('@babel/preset-env'), {
targets: {
electron: 'v9.0.5' // babel编译目标,electron版本
}
}],
require('@babel/preset-typescript'), // typescript支持
[require('@babel/preset-react'), {development, throwIfNamespace: false}] // react支持
],
plugins: [
[require('@babel/plugin-proposal-optional-chaining'), {loose: false}], // 可选链插件
[require('@babel/plugin-proposal-decorators'), {legacy: true}], // 装饰器插件
require('@babel/plugin-syntax-dynamic-import'), // 动态导入插件
require('@babel/plugin-proposal-class-properties'), // 类属性插件
...(development ? devPlugins : prodPlugins) // 区分开发环境
]
};
};
Конфигурация упаковки веб-пакета основного процесса
Когда основной процесс запакован, необходимо толькоsrc/main
Все файлы ts упакованы вdist/main
Далее стоит отметить, что основной процесс соответствует нодовому проекту, если использовать webpack для упаковки напрямую, то он будетnode_modules
Модули также упакованы, поэтому здесь мы используемwebpack-node-externals
плагин для исключенияnode_modules
модуль
npm install --save-dev webpack-node-externals
Соответствующая конфигурация веб-пакета в режиме разработкиwebpack.main.dev.config.js
следующим образом
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const nodeExternals = require('webpack-node-externals');
const webpackBaseConfig = require('./webpack.base.config');
module.exports = merge.smart(webpackBaseConfig, {
devtool: 'none',
mode: 'development', // 开发模式
target: 'node',
entry: path.join(__dirname, 'src/main/index.ts'),
output: {
path: path.join(__dirname, 'dist/main'),
filename: 'main.dev.js' // 开发模式文件名为main.dev.js
},
externals: [nodeExternals()], // 排除Node模块
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'development'
})
],
node: {
__dirname: false,
__filename: false
}
});
Режим производства аналогичен режиму разработки, поэтому он соответствует конфигурации веб-пакета.webpack.main.prod.config.js
следующим образом
const path = require('path');
const merge = require('webpack-merge');
const webpack = require('webpack');
const webpackDevConfig = require('./webpack.main.dev.config');
module.exports = merge.smart(webpackDevConfig, {
devtool: 'none',
mode: 'production', // 生产模式
output: {
path: path.join(__dirname, 'dist/main'),
filename: 'main.prod.js' // 生产模式文件名为main.prod.js
},
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'production'
})
]
});
Конфигурация упаковки процесса рендеринга
Упаковка процесса рендеринга - это процесс упаковки обычного внешнего проекта. Учитывая требование нескольких окон в электронном проекте, процесс рендеринга упакован на нескольких страницах. Упакованная структура процесса рендеринга выглядит следующим образом.
dist
|__renderer # 渲染进程
|__page1 # 页面1
|__index.html
|__index.prod.js
|__index.style.css
|__page2 # 页面2
|__index.html
|__index.prod.js
|__index.style.css
производственный режим
Давайте сначала посмотрим на упаковку в режиме производства, установим соответствующие плагины и загрузчик, используйте здесьhtml-webpack-pluginПлагин генерирует HTML-шаблоны и должен создавать файл .html для каждой страницы.
npm install --save-dev mini-css-extract-plugin html-webpack-plugin
css-loader
,sass-loader
,style-loader
стили обработки,url-loader
,file-loader
работа с изображениями и шрифтами,resolve-url-loaderОбрабатывать файлы scssurl()
Проблема относительного пути в
npm install --save-dev css-loader file-loader sass-loader style-loader url-loader resolve-url-loader
Поскольку вы используете scss для написания стилей, вам необходимо установитьnode-sass
Мешок
npm install --save-dev node-sass
Установитьnode-sass
На самом деле ям довольно много.Обычная установка часто сталкивается с проблемой тайм-аута сети загрузки (и по вине стены), и общее решение - полагаться на зеркалирование.
--sass-binary-site
параметры, как показано ниже
npm install --save-dev node-sass --sass-binary-site=http://npm.taobao.org/mirrors/node-sass
Соответствующая конфигурация веб-пакета для производственного режимаwebpack.renderer.prod.config.js
следующим образом
// 渲染进程prod环境webpack配置
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge');
const webpackBaseConfig = require('./webpack.base.config');
const entry = {
index: path.join(__dirname, 'src/renderer/index/index.tsx'), // 页面入口
};
// 对每一个入口生成一个.html文件
const htmlWebpackPlugin = Object.keys(entry).map(name => new HtmlWebpackPlugin({
inject: 'body',
scriptLoading: 'defer',
template: path.join(__dirname, 'resources/template/template.html'), // template.html是一个很简单的html模版
minify: false,
filename: `${name}/index.html`,
chunks: [name]
}));
module.exports = merge.smart(webpackBaseConfig, {
devtool: 'none',
mode: 'production',
target: 'electron-preload',
entry
output: {
path: path.join(__dirname, 'dist/renderer/'),
publicPath: '../',
filename: '[name]/index.prod.js' // 输出则是每一个入口对应一个文件夹
},
module: {
rules: [ // 文件处理规则
// 处理全局.css文件
{
test: /\.global\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: { publicPath: './' }
},
{
loader: 'css-loader',
options: { sourceMap: true }
},
{loader: 'resolve-url-loader'}, // 解决样式文件中的相对路径问题
]
},
// 一般样式文件,使用css模块
{
test: /^((?!\.global).)*\.css$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
modules: { localIdentName: '[name]__[local]__[hash:base64:5]' },
sourceMap: true
}
},
{loader: 'resolve-url-loader'},
]
},
// 处理scss全局样式
{
test: /\.global\.(scss|sass)$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: { sourceMap: true, importLoaders: 1 }
},
{loader: 'resolve-url-loader'},
{
loader: 'sass-loader',
options: { sourceMap: true }
}
]
},
// 处理一般sass样式,依然使用css模块
{
test: /^((?!\.global).)*\.(scss|sass)$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
modules: { localIdentName: '[name]__[local]__[hash:base64:5]' },
importLoaders: 1,
sourceMap: true
}
},
{loader: 'resolve-url-loader'},
{
loader: 'sass-loader',
options: { sourceMap: true }
}
]
},
// 处理字体文件 WOFF
{
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: { limit: 10000, mimetype: 'application/font-woff' }
}
},
// 处理字体文件 WOFF2
{
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: { limit: 10000, mimetype: 'application/font-woff' }
}
},
// 处理字体文件 TTF
{
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: { limit: 10000, mimetype: 'application/octet-stream' }
}
},
// 处理字体文件 EOT
{
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
use: 'file-loader'
},
// 处理svg文件 SVG
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: { limit: 10000, mimetype: 'image/svg+xml' }
}
},
// 处理图片
{
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
use: {
loader: 'url-loader',
options: { limit: 5000 }
}
}
]
},
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'production'
}),
new MiniCssExtractPlugin({
filename: '[name]/index.style.css',
publicPath: '../'
}),
...htmlWebpackPlugin
]
});
На данный момент конфигурация упаковки основного процесса и конфигурация упаковки производственного режима процесса рендеринга завершены, и результаты упаковки производственной среды проекта могут быть непосредственно протестированы здесь.
первымpackage.json
Добавьте соответствующую команду запуска вbuild-main
упаковать основной процесс,build-renderer
процесс рендеринга пакета,build
Основной процесс и процесс рендеринга упакованы параллельно,start-main
Запустите проект Электрон
{
...
"main": "dist/main/main.prod.js",
"scripts": {
"build-main": "cross-env NODE_ENV=production webpack --config webpack.main.prod.config.js",
"build-renderer": "cross-env NODE_ENV=production webpack --config webpack.renderer.prod.config.js",
"build": "concurrently \"npm run build-main\" \"npm run build-renderer\"",
"start-main": "electron ./dist/main/main.prod.js"
},
...
}
используется в сценарияхcross-env, как следует из названия, обеспечивает кроссплатформенную поддержку переменных среды, аconcurrentlyДля параллельного запуска команд установите следующим образом
npm install --save-dev cross-env concurrently
Можно попробовать написать небольшой пример для проверки результата упаковки, основной процессsrc/main/index.ts
import { BrowserWindow, app } from 'electron';
import path from "path";
// 加载html,目前只对生产模式进行加载
function loadHtml(window: BrowserWindow, name: string) {
if (process.env.NODE_ENV === 'production') {
window.loadFile(path.resolve(__dirname, `../renderer/${name}/index.html`)).catch(console.error);
return;
}
// TODO development
}
let mainWindow: BrowserWindow | null = null;
// 创建窗口
function createMainWindow() {
if (mainWindow) return;
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true
},
backgroundColor: '#333544',
minWidth: 450,
minHeight: 350,
width: 450,
height: 350
});
loadHtml(mainWindow, 'index');
mainWindow.on('close', () => mainWindow = null);
mainWindow.webContents.on('crashed', () => console.error('crash'));
}
app.on('ready', () => { createMainWindow() });
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => { createMainWindow() })
Главная страница процесса рендерингаsrc/renderer/index/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import style from './index.scss'; // typescript不支持css模块,所以这么写编译器会不识别,建议加个@ts-ignore
function App() {
return (
<div className={style.app}>
<h3>Hello world</h3>
<button>+ Import</button>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById('app'));
использоватьbuild
Команда для параллельной упаковки кода основного процесса и процесса визуализации
npm run build
Упакованный результат показан ниже, поэтому путь основного процесса при загрузке html-файла../renderer/${name}/index.html
использоватьnpm run start-main
команда для запуска проекта.
режим разработки
В режиме разработки процесса рендеринга необходимо реализовать горячую загрузку модулей, которые используются здесьreact-hot-loaderpackage, и если вам нужно запустить службу webpack, вам также необходимо установитьwebpack-dev-server
Мешок.
npm install --save-dev webpack-dev-server
npm install --save react-hot-loader @hot-loader/react-dom
Измените конфигурацию babel и добавьте следующие плагины в среду разработки.
const devPlugins = [require('react-hot-loader/babel')];
Измените файл записи процесса рендеринга, то есть вrender
При оценке текущей среды и упаковкиReactHotContainer
import { AppContainer as ReactHotContainer } from 'react-hot-loader';
const AppContainer = process.env.NODE_ENV === 'development' ? ReactHotContainer : Fragment;
ReactDOM.render(
<AppContainer>
<App/>
</AppContainer>,
document.getElementById('app')
);
Соответствующая конфигурация веб-пакета для режима разработкиwebpack.renderer.prod.config.js
// 渲染进程dev环境下的webpack配置
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {spawn} = require('child_process');
const webpackBaseConfig = require('./webpack.base.config');
const port = process.env.PORT || 8080;
const publicPath = `http://localhost:${port}/dist`;
const hot = [
'react-hot-loader/patch',
`webpack-dev-server/client?http://localhost:${port}/`,
'webpack/hot/only-dev-server',
];
const entry = {
index: hot.concat(require.resolve('./src/renderer/index/index.tsx')),
};
// 生成html模版
const htmlWebpackPlugin = Object.keys(entry).map(name => new HtmlWebpackPlugin({
inject: 'body',
scriptLoading: 'defer',
template: path.join(__dirname, 'resources/template/template.html'),
minify: false,
filename: `${name}.html`,
chunks: [name]
}));
module.exports = merge.smart(webpackBaseConfig, {
devtool: 'inline-source-map',
mode: 'development',
target: 'electron-renderer',
entry,
resolve: {
alias: {
'react-dom': '@hot-loader/react-dom' // 开发模式下
}
},
output: { publicPath, filename: '[name].dev.js' },
module: {
rules: [
// 处理全局css样式
{
test: /\.global\.css$/,
use: [
{loader: 'style-loader'},
{
loader: 'css-loader',
options: {sourceMap: true}
},
{loader: 'resolve-url-loader'},
]
},
// 处理css样式,使用css模块
{
test: /^((?!\.global).)*\.css$/,
use: [
{loader: 'style-loader'},
{
loader: 'css-loader',
options: {
modules: { localIdentName: '[name]__[local]__[hash:base64:5]' },
sourceMap: true,
importLoaders: 1
}
},
{loader: 'resolve-url-loader'}
]
},
// 处理全局scss样式
{
test: /\.global\.(scss|sass)$/,
use: [
{loader: 'style-loader'},
{
loader: 'css-loader',
options: {sourceMap: true}
},
{loader: 'resolve-url-loader'},
{loader: 'sass-loader'}
]
},
// 处理scss样式,使用css模块
{
test: /^((?!\.global).)*\.(scss|sass)$/,
use: [
{loader: 'style-loader'},
{
loader: 'css-loader',
options: {
modules: { localIdentName: '[name]__[local]__[hash:base64:5]' },
sourceMap: true,
importLoaders: 1
}
},
{loader: 'resolve-url-loader'},
{loader: 'sass-loader'}
]
},
// 处理图片
{
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
use: {
loader: 'url-loader',
options: { limit: 5000 }
}
},
// 处理字体 WOFF
{
test: /\.woff(\?v=\d+\.\d+\/\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 5000,
mimetype: 'application/font-woff'
}
}
},
// 处理字体 WOFF2
{
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 5000,
mimetype: 'application/font-woff'
}
}
},
// 处理字体 TTF
{
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 5000,
mimetype: 'application/octet-stream'
}
}
},
// 处理字体 EOT
{
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
use: 'file-loader'
},
// 处理SVG
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 5000,
mimetype: 'image/svg+xml'
}
}
}
]
},
plugins: [
// webpack 模块热重载
new webpack.HotModuleReplacementPlugin({
multiStep: false
}),
new webpack.EnvironmentPlugin({
NODE_ENV: 'development'
}),
new webpack.LoaderOptionsPlugin({
debug: true
}),
...htmlWebpackPlugin
],
// webpack服务,打包后的页面路径为http://localhost:${port}/dist/${name}.html
devServer: {
port,
publicPath,
compress: true,
noInfo: false,
stats: 'errors-only',
inline: true,
lazy: false,
hot: true,
headers: {'Access-Control-Allow-Origin': '*'},
contentBase: path.join(__dirname, 'dist'),
watchOptions: {
aggregateTimeout: 300,
ignored: /node_modules/,
poll: 100
},
historyApiFallback: {
verbose: true,
disableDotRule: false
}
}
});
Кpackage.json
Добавьте команду запуска вdev-main
Упакуйте основной процесс и запустите проект Electron в режиме разработки,dev-renderer
Процесс рендеринга пакета в режиме разработки
{
...,
"start": {
...,
"dev-main": "cross-env NODE_ENV=development webpack --config webpack.main.dev.config.js && electron ./dist/main/main.dev.js",
"dev-renderer": "cross-env NODE_ENV=development webpack-dev-server --config webpack.renderer.dev.config.js",
"dev": "npm run dev-renderer"
},
...
}
Здесь процесс рендеринга может обновить код с помощью горячей загрузки модуля, но основной процесс не может, и файл .html, загруженный основным процессом, должен быть загружен после того, как процесс рендеринга упакован, поэтому изменитеwebpack.renderer.dev.config.js
Конфигурация, добавить логику упаковки и запуска основного процесса после упаковки процесса рендеринга
...,
devServer: {
before() {
// 启动渲染进程后执行主进程打包
console.log('start main process...');
spawn('npm', ['run', 'dev-main'], { // 相当于命令行执行npm run dev-main
shell: true,
env: process.env,
stdio: 'inherit'
}).on('close', code => process.exit(code))
.on('error', spawnError => console.error(spawnError));
}
},
...
изменить основной процессloadHtml
функция, режим разработки черезurl
загрузить соответствующую страницу
function loadHtml(window: BrowserWindow, name: string) {
if (process.env.NODE_ENV === 'production') {
window.loadFile(path.resolve(__dirname, `../renderer/${name}/index.html`)).catch(console.error);
return;
}
// 开发模式
window.loadURL(`http://localhost:8080/dist/${name}.html`).catch(console.error);
}
npm run dev
Запуск в режиме разработки выглядит следующим образом
renderer
новый каталогuserInfo
Каталог представляет собой окно с информацией о пользователе и добавляется в файл конфигурации в режиме разработки и режиме производства, т.е.webpack.renderer.dev.config.js
а такжеwebpack.renderer.prod.config
в вводной записи.
webpack.renderer.dev.config.js
часть
...
const entry = {
index: hot.concat(require.resolve('./src/renderer/index/index.tsx')), // 主页面
userInfo: hot.concat(require.resolve('./src/renderer/userInfo/index.tsx')) // userInfo页面
};
...
webpack.renderer.prod.config.js
часть
...
const entry = {
index: path.join(__dirname, 'src/renderer/index/index.tsx'), // 主页面
userInfo: path.join(__dirname, 'src/renderer/userInfo/index.tsx') // userInfo页面
};
...
Основной процесс реализуетuserInfo
Логика создания окна
function createUserInfoWidget() {
if (userInfoWidget) return;
if (!mainWindow) return;
userInfoWidget = new BrowserWindow({
parent: mainWindow,
webPreferences: { nodeIntegration: true },
backgroundColor: '#333544',
minWidth: 250,
minHeight: 300,
height: 300,
width: 250
});
loadHtml(userInfoWidget, 'userInfo');
userInfoWidget.on('close', () => userInfoWidget = null);
userInfoWidget.webContents.on('crashed', () => console.error('crash'));
}
Процесс рендеринга главного окна использует IPC для связи с основным процессом и отправляет сообщение, чтобы открыть окно с информацией о пользователе.
const onOpen = () => { ipcRenderer.invoke('open-user-info-widget').catch(); };
Основной процесс получает сообщение о процессе рендеринга и создаетuserInfo
окно
ipcMain.handle('open-user-info-widget', () => {
createUserInfoWidget();
})
результат операции
Создание приложений с помощью Electron-builder
Electron-builderЕго можно понимать как черный ящик, который может решить упаковку и сборку различных платформ (Mac, Window, Linux) проекта Electron и обеспечить поддержку автоматического обновления. Установка выглядит следующим образом, следует отметить, что электрон-билдер можно установить только наdevDependencies
Вниз
npm install --save-dev electron-builder
затем вpackage.json
Добавьте поле сборки в справочник конфигурации поля сборки:построить общую конфигурацию поля
{
...,
"build": {
"productName": "Electron App",
"appId": "electron.app",
"files": [
"dist/",
"node_modules/",
"resources/",
"native/",
"package.json"
],
"mac": {
"category": "public.app-category.developer-tools",
"target": "dmg",
"icon": "./resources/icons/app.icns"
},
"dmg": {
"backgroundColor": "#ffffff",
"icon": "./resources/icons/app.icns",
"iconSize": 80,
"title": "Electron App"
},
"win": {
"target": [ "nsis", "msi" ]
},
"linux": {
"icon": "./resources/icons/app.png",
"target": [ "deb", "rpm", "AppImage" ],
"category": "Development"
},
"directories": {
"buildResources": "./resources/icons",
"output": "release"
}
},
...
}
и кpackage.json
Добавьте команду запуска вpackage
упаковка для нескольких платформ,package-mac
собрать пакет платформы Mac,package-win
Соберите пакет оконной платформы,package-linux
Соберите пакет платформы Linux
{
...,
"script": {
"package": "npm run build && electron-builder build --publish never",
"package-win": "npm run build && electron-builder build --win --x64",
"package-linux": "npm run build && electron-builder build --linux",
"package-mac": "npm run build && electron-builder build --mac"
}
...
}
При выполнении упаковки электрон-билдер загружает электронный пакет, а нормальная загрузка истечет по тайм-ауту (стена снова в беде), что приведет к неудачной упаковке, и решение по-прежнему заключается в использовании зеркалирования.
ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ npm run package-mac
После завершения сборки вы можете увидеть упакованный результат в каталоге выпуска, который представляет собой файл .dmg под Mac.Дважды щелкните, чтобы установить на Mac
Поддержка модуля С++
Когда дело доходит до электронных приложений, может потребоваться поддержка модуля C++, например, некоторые функции реализованы на C++ или вызывают существующие библиотеки C++ или файлы dll. писать доwebpack.base.config.js
используется при настройкеnode-loader
Для обработки файлов .node, но при написании подключаемых модулей C++ под Electron следует учитывать, что движок V8, предоставляемый Electron, может не соответствовать версии движка V8, предоставляемой локально установленным Node, что приводит к проблеме несоответствия версий. Поэтому при разработке собственных модулей C++ может потребоваться вручную скомпилировать модули Electron, чтобы они соответствовали текущей версии V8 Node. Другой способ - использоватьnode-addon-api
пакет илиNan
package для написания собственных модулей C++ для автоматической адаптации к версии V8 в Electron. Модули Node C++ см. в статье:Загрузить код C++ в JavaScript.
Например, простой модуль вычисления сложения C++, часть C++
#include <node_api.h>
#include <napi.h>
using namespace Napi;
Number Add(const CallbackInfo& info) {
Number a = info[0].As<Number>();
Number b = info[1].As<Number>();
double r = a.DoubleValue() + b.DoubleValue();
return Number::New(info.Env(), r);
}
Object Init(Env env, Object exports) {
exports.Set("add", Function::New(env, Add));
return exports;
}
NODE_API_MODULE(addon, Init)
воплощать в жизньnode-gyp rebuild
Создайте файл .node, основной процесс — загрузка файла .node и регистрация вызова IPC.
import { add } from '~build/Release/addon.node';
ipcMain.handle('calc-value', (event, a, b) => {
return add(+a, +b);
})
Процесс рендеринга отправляет вызов IPCcalc-value
Сообщение получает результат и отображает его на странице
const onCalc = () => {
ipcRenderer.invoke('calc-value', input.a, input.b).then(value => {
setResult(value);
});
};
Интеграция Redux + React-Router
На данный момент структура проекта в основном построена, а остальное - добавить некоторые основные библиотеки состояний или библиотеки обработки маршрутизации.В проекте используется Redux для управления состоянием, а React-Router обрабатывает маршрутизацию.Установка выглядит следующим образом
npm install --save redux react-redux react-router react-router-dom history
npm install --save-dev @types/redux @types/react-redux @types/react-router @types/react-router-dom @types/history
использоватьHashRouter
как базовый шаблон маршрутизации
const router = (
<HashRouter>
<Switch>
<Route path="/" exact>
<Page1/>
</Route>
<Route path="/page2">
<Page2/>
</Route>
</Switch>
</HashRouter>
);
react-router-dom
при условииuseHistory
Хуки удобны для получения истории для выполнения операций, связанных с маршрутизацией, таких как переход на страницу маршрутизации.
const history = useHistory();
const onNext = () => history.push('/page2');
Часть Redux можно использоватьuseSelector
а такжеuseDispatch
Хуки, напрямую выбирают состояние в хранилище и подключают отправку, чтобы избежать проблем с избыточным кодом, вызванных использованием компонентов высокого порядка подключения.
const count = useSelector((state: IStoreState) => state.count);
const dispatch = useDispatch();
const onAdd = () => dispatch({ type: ActionType.ADD });
const onSub = () => dispatch({ type: ActionType.SUB });
результат операции
Интеграция вспомогательных средств разработки Devtron
Devtron — это инструмент отладки Electron, который упрощает проверку, мониторинг и отладку приложений. Вы можете визуализировать зависимости пакетов в основном процессе и процессе рендеринга, отслеживать и проверять сообщения, отправляемые основным процессом и процессом рендеринга друг другу, отображать зарегистрированные события и прослушиватели, а также проверять наличие возможных проблем в приложении.
Способ установкиnpm install --save-dev devtron
Используйте следующим образом
app.whenReady().then(() => {
require('devtron').install();
});
Также вы можете использоватьelectron-devtools-installer
, используется для установки расширений Devtools, таких как Redux, расширений React, обычно используемых в браузерах и т. д. Он автоматически переходит в магазин приложений Chrome для загрузки расширений Chrome и их установки, но из-за стены существует высокая вероятность того, что они не скачивается (злая стена опять грядет беда)
npm install --save-dev electron-devtools-installer @types/electron-devtools-installer
Как использовать
import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS, REACT_PERF } from 'electron-devtools-installer';
app.whenReady().then(() => {
installExtension([REACT_PERF, REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS]).then(() => {});
require('devtron').install();
});
Суммировать
В первые дни, когда я связался с электроном, я напрямую использовал готовый шаблон реакции для разработки, но вслепую использовал шаблон сообщества, который было сложно найти при возникновении проблемы, а функции, предоставляемые шаблоном сообщества, не помогали. не обязательно удовлетворять мои собственные потребности.Вы также можете многому научиться в процессе вращения. Проект опирается наelectron-react-bolierplate
Режим упаковки , оптимизирует и настраивает некоторые места и добавляет некоторые соответствующие функции. Следующим TODO является проверка оптимизации разделения пакетов и корректировка структурной оптимизации процесса рендеринга и основного процесса.
Ссылаться на
общая конфигурация электронного построителя
Electron создает кроссплатформенные приложения для Mac/Windows/Linux
Загрузить код C++ в JavaScript
Адрес проекта на GitHub: GitHub.com/солнечные часы-электрические…