很多企业用 Electron 开发桌面软件,宣传语写的是"一次开发,三平台运行"。现实却很骨感——大量 Electron 应用只发布了 Windows 和 macOS 版本,Linux 用户要么等官方适配,要么自己折腾 Wine。Wine 兼容性参差不齐,字体渲染、输入法、文件关联经常出问题。
但 Electron 有一个结构性优势:应用逻辑打包在 ASAR 归档里,与底层 Electron 运行时(Chromium + Node.js)是分离的。这意味着只要拿到 ASAR 包,配上 Linux 版 Electron 运行时,就能让原本只有 Windows 版的应用在 deepin 上原生运行。下面一步步拆解怎么做。
Electron 应用的内部结构
先看一个典型 Windows 版 Electron 应用的目录布局:
MyApp/
├── MyApp.exe # 主入口,本质是 Electron shell
├── resources/
│ ├── app.asar # 应用逻辑(JS、HTML、CSS、图片等)
│ └── app.asar.unpacked/ # 无法打包进 asar 的外部文件
├── locales/ # Chromium 语言包
├── chrome_100_percent.pak # Chromium 资源
├── icudtl.dat # ICU 国际化数据
├── v8_context_snapshot.bin # V8 快照
├── libGLESv2.so # GPU 渲染库(Windows 版是 dll)
├── ...
关键发现:MyApp.exe 不过是个壳,真正的业务代码全在 resources/app.asar 里。asar 是一种类似 tar 的只读归档格式,Electron 启动时通过内置的 original-fs 模块读取它。
这就给了我们一条路:剥离 Windows 壳,换上 Linux 壳,内核不变。
提取 ASAR 包
第一步,从 Windows 版安装目录拿到 app.asar。如果你手上有安装包(.exe 或 .msi),可以用以下方式提取:
# 方式一:如果已有 7z,直接解压安装包
7z x MyApp-Setup-1.2.3.exe -o./myapp_extracted
# 方式二:msi 包用 msiextract(deepin/Debian 可装 msitools)
sudo apt install msitools
msiextract MyApp-Setup-1.2.3.msi -C ./myapp_extracted
提取后,resources/app.asar 就是核心资产。接下来确认它的内容:
# 安装 asar 工具(Node.js 自带 npm)
npm install -g @electron/asar
# 列出 asar 内的文件树
asar list resources/app.asar | head -30
# 如果想查看入口文件确认 package.json 里的 main 字段
asar extract-file resources/app.asar package.json ./extracted_package.json
cat extracted_package.json
package.json 中的 main 字段指向应用启动入口,通常是 main.js 或 index.js。确认这个值,后面启动命令要用。
换壳:配 Linux 版 Electron 运行时
现在需要一个与原应用 Electron 版本匹配的 Linux 运行时。版本号可以从 Windows 版的文件名或 package.json 中推断,也可以直接运行原 exe 用 process.versions.electron 查看(不过在 Linux 上跑 exe 就又回到 Wine 了,更实用的办法是看安装目录里的 LICENSE.electron.txt 或版本文件)。
假设原应用用的是 Electron 22.x,下载对应的 Linux release:
# 从 GitHub Electron Releases 下载(替换版本号)
ELECTRON_VER="22.3.27"
wget "https://github.com/electron/electron/releases/download/v${ELECTRON_VER}/electron-v${ELECTRON_VER}-linux-amd64.zip"
# 解压到运行时目录
mkdir -p ./electron-runtime
unzip electron-v${ELECTRON_VER}-linux-amd64.zip -d ./electron-runtime
deepin 基于 Debian,amd64 架构,所以选 linux-amd64。如果是 ARM 设备则换 linux-arm64。
组装与启动
把 ASAR 包放进 Linux 运行时的 resources/ 目录:
# 复制 asar 包到运行时目录
mkdir -p ./electron-runtime/resources
cp resources/app.asar ./electron-runtime/resources/
# 如果原包有 app.asar.unpacked 目录,也要一并复制
cp -r resources/app.asar.unpacked ./electron-runtime/resources/ 2>/dev/null || true
启动应用:
# 直接用 electron 可执行文件启动,指定 app 目录
./electron-runtime/electron ./electron-runtime/resources/app.asar
如果原应用 package.json 的 main 字段指向的是相对路径(比如 ./dist/main.js),asar 内部会自动解析,不需要额外处理。
做成 deepin 桌面应用
命令行启动不够优雅,接下来创建一个 .desktop 文件,让应用出现在 deepin 的启动器里:
# 创建启动脚本
cat > ./myapp-launch.sh << 'LAUNCH_EOF'
#!/bin/bash
# 根据实际路径修改
ELECTRON_PATH="/opt/myapp/electron"
ASAR_PATH="/opt/myapp/resources/app.asar"
# 禁用 GPU 加速(某些 deepin 环境下可避免渲染问题,按需开启)
# exec "$ELECTRON_PATH" "$ASAR_PATH" --disable-gpu
exec "$ELECTRON_PATH" "$ASAR_PATH"
LAUNCH_EOF
chmod +x ./myapp-launch.sh
# 创建 .desktop 文件
cat > ./MyApp.desktop << 'DESKTOP_EOF'
[Desktop Entry]
Name=MyApp
Comment=从 Windows 移植的 Electron 应用
Exec=/opt/myapp/myapp-launch.sh
Icon=/opt/myapp/resources/app-icon.png
Type=Application
Categories=Utility;
StartupNotify=true
DESKTOP_EOF
# 安装到系统应用目录
sudo cp MyApp.desktop /usr/share/applications/
sudo mkdir -p /opt/myapp
sudo cp -r ./electron-runtime/* /opt/myapp/
sudo cp ./myapp-launch.sh /opt/myapp/
图标可以从 asar 里提取,也可以用原 Windows 版的 .ico 转换:
# 从 asar 中找图标(常见路径)
asar extract-file resources/app.asar assets/icon.png /opt/myapp/resources/app-icon.png
# 如果只有 .ico,用 ImageMagick 转换
convert original.ico -resize 256x256 app-icon.png
需要注意的坑
版本匹配是关键。原应用用 Electron 20 开发,你配 Electron 28 的运行时,大概率能跑,但遇到 API 变更或 Node.js 内置模块差异就可能崩溃。尽量用同版本运行时,至少同大版本号。
原生模块会出问题。如果原应用用了 node-gyp 编译的原生 .node 模块(比如 better-sqlite3、keytar),Windows 版的 .dll 在 Linux 上无法加载。解决办法是重新编译这些模块的 Linux 版,然后替换进 asar:
# 解压整个 asar 以便修改
asar extract resources/app.asar ./app-extracted
# 在 app-extracted 目录下重新安装原生模块
cd app-extracted
npm rebuild better-sqlite3 # 需要对应版本的 Node.js headers
# 或
npm install better-sqlite3 # 全量重装
# 重新打包为 asar
cd ..
asar pack ./app-extracted ./electron-runtime/resources/app.asar
自动更新会冲突。很多 Electron 应用内置了 electron-updater,它会检查官方服务器并下载 Windows 版更新。移植后需要要么禁用自动更新,要么在 main.js 里拦截更新逻辑指向你自己的更新源。
文件路径差异。Windows 用反斜杠和盘符,Linux 用正斜杠。如果应用代码里硬编码了 C:\Users\... 这样的路径,在 Linux 上会找不到文件。asar 里的 JS 代码需要审查这类硬编码,用 path.join() 和 app.getPath() 替代。
移植检查清单
动手之前,跑一遍这个清单能省不少排查时间:
| 检查项 | 方法 | 风险等级 |
|---|---|---|
| Electron 版本号 | 看 package.json 或 LICENSE.electron.txt |
高——版本不匹配直接崩溃 |
是否有原生 .node 模块 |
asar list app.asar | grep '.node' |
高——不重编译无法加载 |
| 是否硬编码 Windows 路径 | asar extract 后搜索 C:\\ 或 \\Users |
中——运行时文件读写失败 |
| 自动更新机制 | 搜索 electron-updater 或 autoUpdater |
中——会覆盖你的移植版本 |
| 系统托盘 / 全局快捷键 | 看 BrowserWindow 和 globalShortcut 调用 |
低——deepin 桌面兼容性偶有问题 |
| 字体 / 渲染 | 首次启动观察界面 | 低——缺字体可装 fonts-noto-cjk |
这套方法的本质是利用 Electron "壳 + 内容"的分离架构,把平台相关的壳换掉,保留平台无关的内容。它不是万能的——重度依赖原生模块或 Windows 专有 API 的应用还是得改代码。但对于大量只做 Windows/macOS 发布、内部逻辑纯 JS 的 Electron 应用来说,这条路的投入产出比远优于 Wine 方案。