说起 Electron,大家能定不会感觉到陌生,庞大的体积,内置浏览器,Hello World 都有 200+M... 我个人是很反感跨段应用的,虽然对于开发来说,节省了很多时间,但是站在用户的角度来讲,体验就不是那么称心如意了。但是最近一些业务需要用到 Electron,折腾过程中也踩了不少坑,总结一下。
## 开发环境的搭建
平时我们在开发前端应用时,一般都是使用 Webpack 去打包,在开发环境中,也是由 Webpack dev server 来实现 HMR。在 Electron 中也是可以使用 Webpack 的。
我们使用 electron-wepack
包,简单搭建一下环境。
yarn add source-map-support
yarn add -D electron electron-webpack electron-builder webpack
然后我们参考这个项目结构建立目录:
project/
├─ resources/
│ ├─ icon // 程序图标
├─ src/
│ ├─ main/ // 主进程
│ │ └─ index.ts
│ ├─ renderer/ // 渲染层(启动界面)
│ └─ index.js
└─ static/ // 静态资源
src
目录下的分别为存放 Electron 主进程逻辑(main) 和 渲染层(renderer)。入口文件必须为 index
或 main
yarn add electron-webpack-ts typescript -D
安装完以上依赖,electron-webpack
会识别支持 TypeScript。
在 src/renderer/index.ts
中,你可以操作 DOM 树。electron-wepack
默认会提供一个空白的 HTML 文档,只有一个 #app
节点供你使用,你无法通过一般操作自定义一个入口 index.html
, 但是你也可以用其他手段达到这个目标,在此不多赘述 (参看 issue)。
// src/renderer/index.ts
const $app = document.getElementById('app')!
$app.textContent = 'Hello World'
在 src/main/index.ts
中, 简单建立一个 app
import { app, BrowserWindow } from 'electron'
import { createWindow } from './common/window'
let mainWindow: BrowserWindow
app.on('ready', () => {
mainWindow = createWindow()
app.show()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if (mainWindow === null) {
createWindow()
}
})
其次是 window.ts
,建立一个 window
import { BrowserWindow } from 'electron'
import path from 'path'
import { isDev } from '../utils'
import { format } from 'url'
export function createWindow() {
const mainWindow = new BrowserWindow({
height: 620,
width: 400,
webPreferences: { nodeIntegration: true }, // 一定要加!!!
})
if (isDev) {
mainWindow.loadURL(
`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`, // 开发环境
)
} else {
mainWindow.loadURL(
format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true,
}),
)
}
return mainWindow
}
在 package.json
中添加。
{
"scripts": {
"prebuild": "rm -rf dist",
"build": "cross-env NODE_ENV=production electron-webpack",
"start": "electron-webpack dev",
"package": "yarn build && electron-builder build --publish never"
}
}
执行 yarn start
。发现正确显示了 Hello World。
使用 yarn package
来生成 dmg 也是没有问题的。一般教程到此就结束了,但是我们的需求并不是这么简单,我们还需要配置其他,比如 app version,app icon,app sign key... 而这些配置也有很多坑。
应用图标需要不同大小的几张 png 以及 icns 等格式的图片,手动操作比较麻烦,我们可以用一张 png 去生成,使用 electron-icon-builder
工具就能轻松转换到我们想要的结果。
npx electron-icon-builder -i ./path-your-icon-file.png -o output
.
├── icon.icns
├── icon.ico
├── icon.png
└── icons
├── 1024x1024.png
├── 128x128.png
├── 16x16.png
├── 24x24.png
├── 256x256.png
├── 32x32.png
├── 48x48.png
├── 512x512.png
└── 64x64.png
把生成的文件放入 resources
文件夹内,如不存在则新建。
在 package.json
中加入 build
字段,用于配置 electron-builder
。
{
"build": {
"appId": "com.innei.electron-template",
"productName": "template",
"extraMetadata": {
"main": "main.js" // **必须**
},
"copyright": "Copyright © 2019-2020 ${author}",
"mac": {
"category": "public.app-category.utilities"
},
"files": [
"package.json",
"resources/**/*", // **必须**
"static",
{
"from": "dist/main" // **必须**
},
{
"from": "dist/renderer" // **必须**
}
],
"extends": null,
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"win": {
"icon": "resources/icon.ico",
"target": [
"nsis",
"msi"
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"installerIcon": "resources/icon.ico"
},
"linux": {
"target": [
"deb",
"rpm",
"AppImage"
],
"category": "Development"
},
"directories": {
"buildResources": "resources",
"output": "release"
},
"extraResources": [
{
"from": "resources/", // **必须**
"to": "resources/" // **必须**
},
{
"from": "static",
"to": "static"
}
]
}
}
这里是个大坑,因为我们自定义了配置,覆盖了原来 electron-webpack
的配置,所以有几个地方是必须要这么写的,否则就会在打包之后无法显示 renderer 或者 找不到入口文件。这是我自己摸索出来的,比较 hack 的方法。因为我实在找不到答案。
如果你需要使用 __static
常量的话,
{ // 也是必须的
"from": "static",
"to": "static"
}
最后,附上 GitHub 地址:
https://github.com/Innei/electron-typescript-starter