Electron鸿蒙PC适配全攻略:从环境搭建到打包发布
·
Electron鸿蒙PC适配全攻略:从环境搭建到打包发布
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
本文是Electron应用迁移到鸿蒙PC的完整实战指南,涵盖环境搭建、API适配、原生模块编译、签名打包、踩坑排错等全链路内容,帮助Electron开发者快速将应用迁移到鸿蒙PC平台。
引言:Web技术栈开发者的鸿蒙之路
在PC应用开发领域,Electron凭借"JavaScript+HTML+CSS"的技术栈,让Web开发者能够构建跨平台桌面应用。VS Code、Slack、Figma、Discord——这些大名鼎鼎的应用都基于Electron。
随着鸿蒙PC的推出,华为与Electron社区合作,提供了鸿蒙PC版的Electron运行时。这意味着:
- 你现有的Electron应用可以通过少量修改,在鸿蒙PC上运行
- Web前端团队的技能可以直接复用到鸿蒙PC开发
- 数以万计的npm包可以继续使用
本文将带你完整走过这条迁移之路。
第一章:Electron鸿蒙PC版概述
1.1 架构差异对比
标准Electron架构:
┌─────────────────────────────┐
│ Renderer Process │
│ (HTML + CSS + JS) │
│ ┌───────────────────┐ │
│ │ Chromium Engine │ │
│ └───────────────────┘ │
├─────────────────────────────┤
│ Main Process (Node.js) │
│ ┌───────────────────┐ │
│ │ Native Modules │ │
│ └───────────────────┘ │
├─────────────────────────────┤
│ Operating System Layer │
│ (Windows / macOS / Linux) │
└─────────────────────────────┘
鸿蒙PC版Electron架构:
1.2 关键差异
| 特性 | 标准Electron | 鸿蒙PC Electron |
|---|---|---|
| 渲染引擎 | Chromium | ArkWeb |
| 原生API访问 | Node.js Addon | NAPI (Node-API) |
| 包格式 | asar + exe/dmg/AppImage | HNP / HAP |
| 进程模型 | Main + Renderer | 相同 |
| 签名 | 代码签名证书 | binary-sign-tool |
| 分发 | 网站 / 应用商店 | 鸿蒙应用商店 |
第二章:环境搭建
2.1 开发环境准备
系统要求:
# 开发主机(任选其一)
# - Windows 10/11
# - macOS 10.15+
# - Ubuntu 20.04+
# 目标环境
# - 鸿蒙PC(API 12+)
# Node.js版本
# - 推荐 v18.x 或 v20.x LTS
安装Node.js:
# 使用nvm管理Node版本
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
node --version # 应显示 v20.x.x
npm --version # 应显示 10.x.x
2.2 安装鸿蒙版Electron
# 创建新项目
mkdir my-electron-ohos
cd my-electron-ohos
npm init -y
# 安装鸿蒙版Electron
npm install electron@ohos-latest
# 或者在package.json中指定
{
"devDependencies": {
"electron": "npm:@ohos/electron@latest"
}
}
2.3 项目结构
my-electron-ohos/
├── package.json # 项目配置
├── electron-builder.yml # 打包配置
├── src/
│ ├── main/ # 主进程
│ │ ├── main.js # 入口文件
│ │ ├── menu.js # 菜单配置
│ │ ├── tray.js # 系统托盘
│ │ └── native/ # 原生模块
│ │ └── binding.gyp
│ └── renderer/ # 渲染进程
│ ├── index.html
│ ├── styles/
│ ├── scripts/
│ └── assets/
├── resources/ # 平台资源
│ ├── icon.png
│ └── ohos/
│ ├── icon.png
│ └── config.json
└── scripts/ # 构建脚本
├── build.sh
└── sign.sh
第三章:主进程适配
3.1 入口文件适配
// src/main/main.js
const { app, BrowserWindow, Menu, Tray, dialog, shell } = require('electron');
const path = require('path');
// 检测运行平台
const isOHOS = process.platform === 'ohos';
let mainWindow = null;
function createWindow() {
const windowOptions = {
width: 1280,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
// 鸿蒙PC特殊配置
...(isOHOS ? {
sandbox: true,
webSecurity: true,
// ArkWeb特有配置
additionalArguments: ['--enable-ark-web-features'],
} : {})
},
// 窗口外观
titleBarStyle: isOHOS ? 'default' : 'hiddenInset',
frame: !isOHOS, // OHOS使用系统标题栏
show: false,
backgroundColor: '#ffffff',
// 鸿蒙特有窗口属性
...(isOHOS ? {
resizable: true,
maximizable: true,
minimizable: true,
fullscreenable: true,
} : {})
};
mainWindow = new BrowserWindow(windowOptions);
// 加载页面
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173');
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
// 窗口准备好后显示
mainWindow.once('ready-to-show', () => {
mainWindow.show();
// 鸿蒙PC:最大化启动
if (isOHOS && app.getLoginItemSettings().wasOpenedAtLogin) {
mainWindow.maximize();
}
});
// 鸿蒙PC:处理窗口关闭事件
if (isOHOS) {
mainWindow.on('close', (event) => {
// 最小化到托盘而不是退出
if (tray) {
event.preventDefault();
mainWindow.hide();
}
});
}
return mainWindow;
}
3.2 菜单适配
// src/main/menu.js
function createMenu() {
const isOHOS = process.platform === 'ohos';
const isMac = process.platform === 'darwin';
const template = [
// 应用菜单(macOS/OHOS)
...(isMac || isOHOS ? [{
label: app.getName(),
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),
// 文件菜单
{
label: '文件',
submenu: [
{
label: '打开文件',
accelerator: isMac ? 'Cmd+O' : 'Ctrl+O',
click: async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [
{ name: '所有文件', extensions: ['*'] }
]
});
if (!result.canceled) {
mainWindow.webContents.send('file-opened', result.filePaths[0]);
}
}
},
{
label: '保存',
accelerator: isMac ? 'Cmd+S' : 'Ctrl+S',
click: () => {
mainWindow.webContents.send('save-file');
}
},
{ type: 'separator' },
...(isOHOS ? [] : [{ role: 'quit' }])
]
},
// 编辑菜单
{
label: '编辑',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'selectAll' }
]
},
// 视图菜单
{
label: '视图',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// 帮助菜单
{
label: '帮助',
submenu: [
{
label: '关于',
click: () => {
dialog.showMessageBox({
type: 'info',
title: '关于',
message: `My App v${app.getVersion()}`,
detail: isOHOS ? '运行在鸿蒙PC' : '桌面版'
});
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
3.3 系统托盘适配
// src/main/tray.js
function createTray() {
let trayIcon;
if (process.platform === 'ohos') {
// 鸿蒙PC托盘图标(需要PNG格式)
trayIcon = path.join(__dirname, '../../resources/ohos/tray-icon.png');
} else if (process.platform === 'darwin') {
// macOS使用Template图像
trayIcon = path.join(__dirname, '../../resources/tray-iconTemplate.png');
} else {
trayIcon = path.join(__dirname, '../../resources/tray-icon.png');
}
tray = new Tray(trayIcon);
const contextMenu = Menu.buildFromTemplate([
{
label: '显示窗口',
click: () => mainWindow.show()
},
{
label: '设置',
click: () => {
mainWindow.show();
mainWindow.webContents.send('open-settings');
}
},
{ type: 'separator' },
{
label: '退出',
click: () => {
app.isQuitting = true;
app.quit();
}
}
]);
tray.setToolTip('My Electron App');
tray.setContextMenu(contextMenu);
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
});
}
第四章:原生模块处理
4.1 Node.js原生模块编译
鸿蒙PC需要重新编译Node.js原生模块(C++ Addon):
// src/main/native/binding.gyp
{
"targets": [
{
"target_name": "native_module",
"sources": [
"native_addon.cc"
],
"conditions": [
["OS=='ohos'", {
"cflags": [
"-fPIC",
"-D__MUSL__=1",
"-D__OHOS__"
],
"ldflags": [
"-fuse-ld=lld"
],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
]
}]
],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
]
}
]
}
4.2 NAPI桥接实现
// src/main/native/native_addon.cc
#include <napi.h>
#include <string>
// 简单示例:获取鸿蒙系统信息
#ifdef __OHOS__
#include <string>
#include <fstream>
std::string getOHOSVersion() {
std::ifstream file("/proc/version");
std::string line;
if (std::getline(file, line)) {
return line;
}
return "Unknown";
}
#endif
// NAPI函数:获取系统信息
Napi::Value GetSystemInfo(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Object result = Napi::Object::New(env);
result.Set("platform", Napi::String::New(env,
#ifdef __OHOS__
"ohos"
#elif defined(_WIN32)
"win32"
#elif defined(__APPLE__)
"darwin"
#else
"linux"
#endif
));
#ifdef __OHOS__
result.Set("ohosVersion", Napi::String::New(env, getOHOSVersion()));
result.Set("isOHOS", Napi::Boolean::New(env, true));
#else
result.Set("isOHOS", Napi::Boolean::New(env, false));
#endif
return result;
}
// 模块初始化
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("getSystemInfo", Napi::Function::New(env, GetSystemInfo));
return exports;
}
NODE_API_MODULE(native_module, Init)
第五章:渲染进程适配
5.1 预加载脚本
// src/main/preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 安全地暴露API到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 平台信息
platform: process.platform,
isOHOS: process.platform === 'ohos',
// IPC通信
send: (channel, data) => {
const validChannels = ['save-file', 'open-settings'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, callback) => {
const validChannels = ['file-opened', 'update-available'];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => callback(...args));
}
},
// 文件对话框
openFileDialog: async (options) => {
return await ipcRenderer.invoke('dialog:openFile', options);
},
// 窗口控制
windowControl: {
minimize: () => ipcRenderer.send('window:minimize'),
maximize: () => ipcRenderer.send('window:maximize'),
close: () => ipcRenderer.send('window:close'),
}
});
5.2 响应式布局适配
<!-- src/renderer/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
<title>My Electron App</title>
<link rel="stylesheet" href="styles/main.css">
</head>
<body>
<div id="app">
<!-- 侧边栏(PC可见) -->
<aside class="sidebar" data-platform-visible="pc,ohos">
<nav class="nav-menu">
<a href="#home" class="nav-item active">
<span class="icon">🏠</span>
<span class="label">首页</span>
</a>
<a href="#projects" class="nav-item">
<span class="icon">📁</span>
<span class="label">项目</span>
</a>
<a href="#settings" class="nav-item">
<span class="icon">⚙️</span>
<span class="label">设置</span>
</a>
</nav>
</aside>
<!-- 主内容区 -->
<main class="content">
<header class="topbar">
<h1 id="page-title">首页</h1>
<div class="window-controls ohos-only">
<button onclick="window.electronAPI.windowControl.minimize()">─</button>
<button onclick="window.electronAPI.windowControl.maximize()">□</button>
<button onclick="window.electronAPI.windowControl.close()">✕</button>
</div>
</header>
<div class="main-content">
<!-- 动态内容 -->
</div>
</main>
</div>
<script src="scripts/app.js"></script>
</body>
</html>
/* src/renderer/styles/main.css */
:root {
--sidebar-width: 240px;
--topbar-height: 48px;
/* 平台变量 */
--platform-gap: 16px;
--platform-radius: 8px;
--platform-font-size: 14px;
}
/* 鸿蒙PC特殊样式 */
[data-platform="ohos"] {
--platform-gap: 12px;
--platform-radius: 10px;
--platform-font-size: 13px;
/* 使用鸿蒙设计变量 */
--color-primary: #007AFF;
--color-background: #F5F5F5;
--color-surface: #FFFFFF;
--color-text: #1A1A1A;
--color-text-secondary: #666666;
}
/* 侧边栏 */
.sidebar {
width: var(--sidebar-width);
height: 100vh;
background: var(--color-surface);
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
padding: 12px 0;
user-select: none;
}
/* 鸿蒙PC上的隐藏控制 */
.ohos-only {
display: none;
}
[data-platform="ohos"] .ohos-only {
display: flex;
gap: 4px;
}
/* 窗口控制按钮 */
.window-controls button {
width: 32px;
height: 28px;
border: none;
background: transparent;
cursor: pointer;
border-radius: 4px;
color: var(--color-text);
}
.window-controls button:hover {
background: rgba(0, 0, 0, 0.05);
}
.window-controls button:last-child:hover {
background: #E81123;
color: white;
}
第六章:打包与签名
6.1 electron-builder配置
# electron-builder.yml
appId: com.example.my-electron-app
productName: MyApp
copyright: Copyright © 2024
directories:
output: dist
buildResources: resources
files:
- src/main/**/*
- src/renderer/**/*
- package.json
- "!node_modules/**/*"
ohos:
target:
- hnp
icon: resources/ohos/icon.png
category: Utility
packageName: com.example.myapp
permissions:
- ohos.permission.INTERNET
- ohos.permission.FILE_READ_WRITE
- ohos.permission.NOTIFICATION
mac:
target:
- dmg
- zip
icon: resources/icon.icns
category: public.app-category.utilities
hardenedRuntime: true
entitlements: build/entitlements.mac.plist
win:
target:
- nsis
- portable
icon: resources/icon.ico
6.2 鸿蒙PC签名脚本
#!/bin/bash
# scripts/sign.sh - 鸿蒙PC签名脚本
PACKAGE_DIR="./dist/ohos/MyApp"
OUTPUT_DIR="./dist/ohos/signed"
mkdir -p $OUTPUT_DIR
echo "=== 开始签名 ==="
# 签名可执行文件
echo "签名主程序..."
binary-sign-tool sign \
-inFile "$PACKAGE_DIR/MyApp" \
-outFile "$OUTPUT_DIR/MyApp" \
-selfSign "1"
# 签名所有 .so 文件
echo "签名动态库..."
find "$PACKAGE_DIR" -name "*.so" -type f | while read lib; do
rel_path="${lib#$PACKAGE_DIR/}"
output_path="$OUTPUT_DIR/$rel_path"
mkdir -p "$(dirname "$output_path")"
binary-sign-tool sign \
-inFile "$lib" \
-outFile "$output_path" \
-selfSign "1"
echo " 已签名: $rel_path"
done
# 签名 Node.js 原生模块
echo "签名 Node.js 原生模块..."
find "$PACKAGE_DIR" -name "*.node" -type f | while read addon; do
rel_path="${addon#$PACKAGE_DIR/}"
output_path="$OUTPUT_DIR/$rel_path"
mkdir -p "$(dirname "$output_path")"
binary-sign-tool sign \
-inFile "$addon" \
-outFile "$output_path" \
-selfSign "1"
echo " 已签名: $rel_path"
done
# 复制非二进制文件
echo "复制静态资源..."
rsync -av --exclude='*.so' --exclude='*.node' --exclude='MyApp' \
"$PACKAGE_DIR/" "$OUTPUT_DIR/"
# 设置权限
chmod -R 755 "$OUTPUT_DIR"
echo "=== 签名完成 ==="
echo "产物路径: $OUTPUT_DIR"
6.3 打包为HNP格式
// hnp.json
{
"type": "hnp-config",
"name": "my-electron-app",
"version": "1.0.0",
"description": "基于Electron的鸿蒙PC应用",
"license": "MIT",
"arch": "arm64-v8a",
"install": {
"bin": ["usr/bin/my-electron-app"],
"lib": ["usr/lib/*.so"],
"share": ["usr/share/my-electron-app"]
}
}
第七章:性能优化
7.1 启动优化
// 延迟加载非关键模块
const loadNonCriticalModules = () => {
setTimeout(() => {
require('./updater');
require('./analytics');
require('./crashReporter');
}, 1000);
};
app.whenReady().then(() => {
createWindow();
createMenu();
loadNonCriticalModules(); // 延迟1秒加载
});
7.2 内存管理
// 监控内存使用
setInterval(() => {
const memoryInfo = process.getProcessMemoryInfo();
if (memoryInfo.private > 500 * 1024 * 1024) {
console.warn('内存使用超过500MB');
// 触发GC
if (global.gc) {
global.gc();
}
}
}, 30000);
第八章:踩坑合集(Electron专属)
8.1 常见问题与解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
| ArkWeb渲染异常 | CSS属性不支持 | 检查CSS兼容性,使用WebKit前缀 |
| 原生模块编译失败 | musl libc差异 | 添加__MUSL__宏 |
| 打包后启动闪退 | 签名缺失或版本so缺失 | 检查所有.so是否签名 |
| 托盘图标不显示 | 图标格式错误 | 使用PNG格式,尺寸16x16或24x24 |
| 快捷键冲突 | 系统快捷键抢占 | 使用非标准组合键或自定义 |
| preload.js报错 | contextIsolation配置 | 检查contextBridge用法 |
8.2 调试技巧
# 开启Electron调试
electron --inspect=9229 .
# 查看进程日志
hdc hilog | grep "ELECTRON"
# 检查签名状态
binary-sign-tool verify -inFile /path/to/binary
# 查看依赖库
llvm-readelf -d /path/to/binary | grep NEEDED
结语
Electron+鸿蒙PC的组合,为Web开发者打开了一扇通往新平台的大门。虽然目前还处于早期阶段,存在一些兼容性问题,但随着社区贡献和官方推进,这条路会越来越通畅。
如果你是一个Electron开发者,现在就是最好的时机来尝试鸿蒙PC适配。因为你不仅能将自己的应用带到新平台,还能在这个过程中成为生态建设的先行者。
参考资源:
- Electron官方文档:https://www.electronjs.org/docs
- 鸿蒙PC Electron适配:https://harmonypc.csdn.net/
- 三方库移植经验:https://blog.csdn.net/qq8864
更多推荐





所有评论(0)