背景是这样的,公司有一个桌面端应用的开发需求,现需要支持 Win/Mac/Linux 平台,经调研后,采用了 Electron 来进行跨平台开发。
因为是第一次接触 Electron,经验为零,所以可以预料到,接下来会很难过,所以就有了这篇文章,我会将本次开发中所走的弯路都记录下来,希望能给后来者提供一些帮助。
文章会随着项目进度,持续更新到 9 月底项目交付。
因为要在 Electron 中使用 React,所以我直接使用了 electron-react-boilerplate 模版,初期我都会在 Mac 上进行开发,列表见下:
install failed
遇到的第一个问题就是 electron 安装不上啊,经过一顿 google,找到了下面方案,实测有效:
npm install electron --save-dev
改为:
npm install -g cnpm --registry=https://registry.npmmirror.com
cnpm install --save-dev electron
启动时间长
因为在 package.json 中的 scripts 字段下已经配置了 start 命令,支持我们使用 npm start
在开发模式下打开应用,但每次要等好久,才能启起来。
{
"scripts": {
"start": "electron ."
}
}
经排查,与 main.ts 下的 installExtensions
方法有关,该方法在 isDebug
模式下才会执行,目的是安装和配置 Electron 中使用的开发者工具扩展 React Developer Tools。
可能是国内网络导致的特别慢,但我换了几个镜像源后也没解决,看网上资料说,可以手动下载扩展的离线包文件,再通过 session
模块加载,但我没试。我是直接注释掉了,暂时不影响我开发。
const installExtensions = async () => {
const installer = require('electron-devtools-installer');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS; // 是否强制下载更新
const extensions = ['REACT_DEVELOPER_TOOLS']; // 定义要安装的扩展
return installer
.default(
extensions.map((name) => installer[name]), // 将扩展名映射到 installer 中对应的安装函数
forceDownload,
// { mirror: 'https://npm.taobao.org/mirrors/chrome/' }
// { mirror: 'https://registry.npmmirror.com' }
)
.catch(console.log);
};
搭配 .less 文件
在 .erb/configs 下的 webpack.config.renderer.dev.ts 和 webpack.config.renderer.prod.ts 两个文件内,做如下修改,module: { rules: [] }
内:
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true,
},
},
},
],
},
搭配 Cesium
在 Electron 中使用 Cesium,难点是配置 Webpack。经过一顿 google,终于找到了一个 CesiumGS 下的 cesium-webpack-example 的示例可参考。
在 .erb/configs 下的 webpack.config.renderer.dev.ts 和 webpack.config.renderer.prod.ts 两个文件内,做如下修改,其中 copy-webpack-plugin
下文会用到:
import CopyWebpackPlugin from 'copy-webpack-plugin';
// The path to the CesiumJS source code
const cesiumSource = "node_modules/cesium/Build/Cesium";
const cesiumBaseUrl = "cesiumStatic";
在 output 内添加 sourcePrefix
,兼容 CesiumJS 内的多行字符串:
output: {
...
// Needed to compile multiline strings in Cesium
sourcePrefix: '',
},
在 module: { rules: [] }
内添加:
{
test: /\.(png|gif|jpg|jpeg|svg|xml|json)$/,
type: "asset/inline",
}
在 module: { plugins: [] }
中添加 CopyWebpackPlugin
,将 Cesium Assets、Widgets、ThirdParty 和 Workers 复制到静态目录:
new CopyWebpackPlugin({
patterns: [
{
from: path.join(cesiumSource, "Workers"),
to: `${cesiumBaseUrl}/Workers`,
},
{
from: path.join(cesiumSource, "ThirdParty"),
to: `${cesiumBaseUrl}/ThirdParty`,
},
{
from: path.join(cesiumSource, "Assets"),
to: `${cesiumBaseUrl}/Assets`,
},
{
from: path.join(cesiumSource, "Widgets"),
to: `${cesiumBaseUrl}/Widgets`,
},
],
}),
在 module: { plugins: [] }
中再添加 DefinePlugin
,定义用于加载资源的相对基本路径:
new webpack.DefinePlugin({
CESIUM_BASE_URL: JSON.stringify(cesiumBaseUrl),
}),
在 module: { resolve: {} }
内添加 mainFiles
,指定模块的入口文件:
resolve: {
fallback: { https: false, zlib: false, http: false, url: false },
mainFiles: ["index", "Cesium"],
},
好了,最后在 React 组件中就能开心的使用 Cesium 了,Cesium 的具体用法就不说了。注意一定要导入 widgets.css,否则最明显的坑就是 Cesium canvas 大小一直是 300 * 150:
import * as Cesium from 'cesium';
import 'cesium/Build/Cesium/Widgets/widgets.css';
搭配使用Mapbox
在 Electron 中使用 mapbox-gl,坑点在于加载 layer 时会导致跨域问题,因为 mapbox 本身是在渲染进程内的。比较正常的思路是拦截 mapbox 的 tile 请求,在主进程中进行数据的获取,在获取数据后,再回到渲染进程并塞给 mapbox,但是吧 实现起来有点麻烦。
比较简单的解决方案是通过 session 针对 mapbox 报跨域的接口单独处理下,代码见下,在 main.ts createWindow()
后添加:
import { session } from 'electron';
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
const { url } = details;
const allowedOrigins = [
'aaa/data/tile',
'bbb/data/tile/',
'ccc/data/tile',
];
const header = {
'Access-Control-Allow-Origin': ['*'],
'Access-Control-Allow-Methods': ['GET, POST, PUT, DELETE, OPTIONS'],
'Access-Control-Allow-Headers': ['Content-Type, Authorization'],
};
if (allowedOrigins.some((item) => url.includes(item))) {
details.responseHeaders = {
...details.responseHeaders,
...header,
};
}
callback({
responseHeaders: details.responseHeaders,
cancel: false,
});
});
调用可执行文件
未完待续。