项目背景
最近针对公司的一款Electron软件,对图片处理相关功能进行统一优化,又之前的canvas实现改成了sharp。在应用的核心功能中,大量使用了图片处理能力,包括:
- 图片缩放:将图片调整到指定尺寸
- 图片旋转:支持图片旋转操作
- 图片裁剪:按百分比裁剪图片区域
- 图片合并:前景图和背景图的合成处理
技术选型
项目选择了 Sharp 作为图片处理的核心库,原因如下:
- 高性能:基于 libvips 的 C++ 实现,性能远超基于 Canvas 的方案
- 功能丰富:支持多种图片格式和操作
- API 简洁:提供友好的 Node.js API
Sharp 在项目中的使用示例:
typescript
// 图片缩放
const imageScale = (inputImage: string, w: number, h: number, outImage: string) => {
return sharp(inputImage).resize(w, h, { fit: 'fill' }).toFile(outImage)
}
// 图片旋转
const imageRotate = (imagePath: string, outPath: string) => {
return sharp(imagePath).rotate(90).toFile(outPath)
}
问题描述
问题现象
在 macOS ARM 芯片(M系列)机器上构建的 Electron 应用包,分发到 macOS Intel 芯片机器上运行时,应用启动失败,出现以下错误:
vbnet
Error: Could not load the "sharp" module using the darwin-x64 runtime

错误分析
从错误信息可以看出:
-
核心错误 :
Could not load the "sharp" module using the darwin-x64 runtime- 应用尝试加载
darwin-x64(Intel 架构)的 sharp 模块失败 - 说明应用包中缺少 Intel 架构的二进制文件
- 应用尝试加载
-
错误位置:错误发生在应用启动时,sharp 模块加载阶段
- 路径:
app.asar/node_modules/sharp/lib/sharp.js:121 - 说明 sharp 模块已经被打包,但缺少对应架构的二进制文件
- 路径:
-
平台信息:
- 构建平台:macOS ARM64 (darwin-arm64)
- 运行平台:macOS Intel (darwin-x64)
- 架构不匹配导致运行时失败
原因分析
根本原因
Sharp 是一个原生 Node.js 模块,它依赖于平台特定的二进制文件。Sharp 的架构如下:
scss
sharp (主模块)
├── @img/sharp-darwin-arm64 (ARM 架构二进制)
├── @img/sharp-darwin-x64 (Intel 架构二进制)
├── @img/sharp-libvips-darwin-arm64 (ARM 架构依赖库)
└── @img/sharp-libvips-darwin-x64 (Intel 架构依赖库)
问题根源:
- npm 的平台检查机制 :当在 ARM 机器上运行
npm install时,npm 会检查包的os和cpu字段,只安装当前平台匹配的包 - 默认行为 :npm 默认只安装
darwin-arm64架构的包,不会安装darwin-x64架构的包 - 打包结果:构建时只包含了 ARM 架构的二进制文件,导致在 Intel 机器上无法运行
验证过程
为了确认问题,进行了以下验证:
解包查看
解压构建后的应用包,检查 node_modules 目录:
bash
npm install -g asar
asar extract app.asar ./

从解包结果可以看到,node_modules/@img/ 目录下只有:
sharp-darwin-arm64sharp-libvips-darwin-arm64
缺少:
sharp-darwin-x64sharp-libvips-darwin-x64
这证实了问题:应用包中只包含了 ARM 架构的二进制文件。
检查 npm 包信息
sharp包很常用,我想大概其他人应该也会遇到类型问题,npm如此强大的库也应该有类型的处理包,于是在npm仓库搜索了一下electron sharp相关发现没有:

Electron Universal Binary 构建
虽然 Electron Builder 支持构建 Universal Binary(通用二进制),配置如下:
javascript
mac: {
target: [
{target: 'mas', arch: ['universal']}
]
}
但是,Universal Binary 只对 Electron 主程序和系统库有效 ,对于 node_modules 中的原生模块(如 Sharp),需要手动确保包含所有架构的二进制文件。
解决方案
解决思路
核心思路:在构建前,确保 Sharp 模块包含所有目标架构的二进制文件
实现方式:
- 创建构建前钩子脚本,检查并安装缺失的架构二进制文件
- 使用
npm install --force强制安装跨平台包 - 在 electron-builder 的
beforePack钩子中自动执行
实现方案
1 创建构建前检查脚本
创建 build/scripts/ensure-sharp-universal.js 脚本:
javascript
function ensureSharpUniversal() {
console.log('开始检查 sharp 模块的架构支持...')
// 检查必需的包是否存在
const requiredPackages = [
'@img/sharp-darwin-arm64',
'@img/sharp-darwin-x64',
'@img/sharp-libvips-darwin-arm64',
'@img/sharp-libvips-darwin-x64'
]
// 安装缺失的包
for (const pkg of requiredPackages) {
if (!checkModuleExists(pkg)) {
installPackage(pkg)
}
}
// 验证所有包都已安装
verifyAllPackages()
}
// 安装包(使用 --force 绕过平台检查)
function installPackage(packageName) {
execSync(`npm install --no-save --force --ignore-scripts ${packageName}`, {
stdio: 'inherit',
cwd: PROJECT_ROOT,
env: env
})
}
2 配置 electron-builder 钩子
为了方便操作,在electron macOS 构建配置文件中添加 beforePack 钩子,无需额外操作一键完成资源包下载:
javascript
// build/config/builder.mas.config.js
module.exports = {
// ... 其他配置
beforePack: async (context) => {
// 确保 sharp 模块包含两种架构的二进制文件
const { ensureSharpUniversal } = require('../scripts/ensure-sharp-universal')
ensureSharpUniversal()
},
// ... 其他配置
}
3 关键配置说明
1. asarUnpack 配置
确保 Sharp 模块不被压缩到 asar 中,保持原生二进制文件的可访问性:
javascript
asarUnpack: [
'**/node_modules/sharp/**/*',
'**/node_modules/@img/**/*',
// ... 其他原生模块
]
2. 强制安装跨平台包
这里有个坑,npm 默认不允许在 ARM 机器上安装 x64 包,所以要使用 --force 和 --ignore-scripts 标志强制安装:
--force:强制 npm 忽略平台检查--ignore-scripts:跳过安装脚本中的平台验证--no-save:不更新 package.json,避免污染依赖
javascript
npm install --no-save --force --ignore-scripts @img/sharp-darwin-x64
解决方案流程图
以下是完整的解决方案流程图:
使用方法
1 自动执行
运行任何 macOS 构建命令时,脚本会自动执行:
bash
# 开发环境构建
npm run build:mas:dev
# PKG 格式构建
npm run build:pkg
2 手动执行
也可以手动运行检查和修复:
bash
npm run ensure-sharp-universal
验证结果
修复后,验证 node_modules/@img/ 目录:

所有必需的架构二进制文件都已存在,应用包可以在两种架构的 macOS 上正常运行。
技术要点总结
关键知识点
- 原生模块的架构依赖:原生 Node.js 模块需要为每个目标平台/架构编译二进制文件
- npm 的平台检查:npm 默认只安装当前平台匹配的包,需要特殊处理才能安装跨平台包
- Electron Universal Binary:只对 Electron 主程序有效,node_modules 中的原生模块需要手动处理
- asarUnpack 配置:原生模块必须从 asar 中解包,保持二进制文件的可访问性
最佳实践
- 构建前检查 :使用
beforePack钩子确保所有必需的架构文件都存在 - 强制安装 :使用
--force和--ignore-scripts绕过平台检查 - 自动化:将检查流程集成到构建流程中,避免手动操作
适用场景
本解决方案适用于所有使用原生 Node.js 模块的 Electron 应用,包括:
- Sharp(图片处理)
- node-sqlite3(数据库)
- fsevents(文件系统监控)
- 其他包含平台特定二进制文件的模块
总结
通过创建构建前检查脚本,使用 npm install --force 强制安装跨平台包,成功解决了在 ARM 机器上构建的 Electron 应用在 Intel 机器上无法运行的问题。
- 自动化处理,无需手动干预
- 支持所有 macOS 构建配置
- 可扩展到其他原生模块
- 在 ARM 机器上构建的包可以在 Intel 机器上正常运行
- 构建流程更加可靠和自动化
- 解决了跨平台兼容性问题
下面是自动处理完整代码,有遇到类型问题的同学可以参考
javascript
// ensure-sharp-universal.js
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
// 获取项目根目录(从脚本位置向上两级,或从当前工作目录查找 package.json)
function getProjectRoot() {
// 尝试从脚本位置向上查找
let currentDir = __dirname
while (currentDir !== path.dirname(currentDir)) {
if (fs.existsSync(path.join(currentDir, 'package.json'))) {
return currentDir
}
currentDir = path.dirname(currentDir)
}
// 如果找不到,尝试从当前工作目录查找
const cwd = process.cwd()
if (fs.existsSync(path.join(cwd, 'package.json'))) {
return cwd
}
// 默认使用脚本位置向上两级
return path.join(__dirname, '../..')
}
const PROJECT_ROOT = getProjectRoot()
const SHARP_MODULE_PATH = path.join(PROJECT_ROOT, 'node_modules/sharp')
const SHARP_ARM64_PATH = path.join(PROJECT_ROOT, 'node_modules/@img/sharp-darwin-arm64')
const SHARP_X64_PATH = path.join(PROJECT_ROOT, 'node_modules/@img/sharp-darwin-x64')
const SHARP_LIBVIPS_ARM64_PATH = path.join(PROJECT_ROOT, 'node_modules/@img/sharp-libvips-darwin-arm64')
const SHARP_LIBVIPS_X64_PATH = path.join(PROJECT_ROOT, 'node_modules/@img/sharp-libvips-darwin-x64')
function checkModuleExists(modulePath) {
return fs.existsSync(modulePath)
}
function installSharpArchitecture(arch) {
const platform = 'darwin'
const sharpPackage = `@img/sharp-${platform}-${arch}`
const libvipsPackage = `@img/sharp-libvips-${platform}-${arch}`
console.log(`正在安装 ${sharpPackage}...`)
// 设置环境变量来绕过 npm 的平台检查
const env = {
...process.env,
// 强制 npm 忽略平台检查
npm_config_force: 'true',
// 设置目标架构(虽然不会真正改变当前架构,但可以帮助某些包管理器)
npm_config_target_arch: arch === 'x64' ? 'x64' : 'arm64',
npm_config_target_platform: platform
}
try {
// 使用 --force 和 --ignore-scripts 来强制安装跨平台包
// --ignore-scripts 可以避免安装脚本中的平台检查
execSync(`npm install --no-save --force --ignore-scripts ${sharpPackage} ${libvipsPackage}`, {
stdio: 'inherit',
cwd: PROJECT_ROOT,
env: env
})
console.log(`✓ ${sharpPackage} 安装成功`)
} catch (error) {
// TODO: 如果上面的方法失败,可以尝试使用 npm pack + 手动安装,这里暂未实现
console.error(`✗ 安装 ${sharpPackage} 失败:`, error.message)
}
}
function ensureSharpUniversal() {
console.log('开始检查 sharp 模块的架构支持...')
// 检查 sharp 主模块是否存在
if (!checkModuleExists(SHARP_MODULE_PATH)) {
console.error('错误: sharp 模块未找到,请先运行 npm install')
process.exit(1)
}
let needInstall = false
// 检查 ARM64 架构
if (!checkModuleExists(SHARP_ARM64_PATH)) {
console.log('警告: 未找到 @img/sharp-darwin-arm64,需要安装')
needInstall = true
}
if (!checkModuleExists(SHARP_LIBVIPS_ARM64_PATH)) {
console.log('警告: 未找到 @img/sharp-libvips-darwin-arm64,需要安装')
needInstall = true
}
// 检查 X64 架构
if (!checkModuleExists(SHARP_X64_PATH)) {
console.log('警告: 未找到 @img/sharp-darwin-x64,需要安装')
needInstall = true
}
if (!checkModuleExists(SHARP_LIBVIPS_X64_PATH)) {
console.log('警告: 未找到 @img/sharp-libvips-darwin-x64,需要安装')
needInstall = true
}
if (needInstall) {
console.log('\n正在安装缺失的架构二进制文件...')
// 安装 ARM64 架构(如果缺失)
if (!checkModuleExists(SHARP_ARM64_PATH) || !checkModuleExists(SHARP_LIBVIPS_ARM64_PATH)) {
installSharpArchitecture('arm64')
}
// 安装 X64 架构(如果缺失)
if (!checkModuleExists(SHARP_X64_PATH) || !checkModuleExists(SHARP_LIBVIPS_X64_PATH)) {
installSharpArchitecture('x64')
}
console.log('\n✓ 所有必需的架构二进制文件已安装')
} else {
console.log('✓ sharp 模块已包含所有必需的架构二进制文件')
}
// 最终验证
const allExists =
checkModuleExists(SHARP_ARM64_PATH) &&
checkModuleExists(SHARP_X64_PATH) &&
checkModuleExists(SHARP_LIBVIPS_ARM64_PATH) &&
checkModuleExists(SHARP_LIBVIPS_X64_PATH)
if (allExists) {
console.log('\n✓ 验证通过: sharp 模块支持 macOS Universal (arm64 + x64)')
return true
} else {
console.error('\n✗ 验证失败: 某些架构的二进制文件仍然缺失')
process.exit(1)
}
}
// 如果直接运行此脚本
if (require.main === module) {
ensureSharpUniversal()
}
module.exports = { ensureSharpUniversal }