pnpm安装Electron的坑,你遇到了吗?

MacOS系统里,使用pnpm安装Electron后,正常使用没有问题。但是,如果删除了node_modules,重新安装后,启动应用时会报错:

bash 复制代码
jw my-electron-app % pnpm start

> [email protected] start /Users/jw/wk/github/electron/my-electron-app
> electron .

dyld[38219]: Library not loaded: @rpath/Electron Framework.framework/Electron Framework
  Referenced from: <4C4C4421-5555-3144-A154-D5199CCDD1BE> /Users/jw/wk/github/electron/my-electron-app/node_modules/.pnpm/[email protected]/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron
  Reason: tried: '/Users/jw/wk/github/electron/my-electron-app/node_modules/.pnpm/[email protected]/node_modules/electron/dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (no such file), '/Users/jw/wk/github/electron/my-electron-app/node_modules/.pnpm/[email protected]/node_modules/electron/dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (no such file), '/Library/Frameworks/Electron Framework.framework/Electron Framework' (no such file), '/System/Library/Frameworks/Electron Framework.framework/Electron Framework' (no such file, not in dyld cache)
/Users/jw/wk/github/electron/my-electron-app/node_modules/.pnpm/[email protected]/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron exited with signal SIGABRT
 ELIFECYCLE  Command failed with exit code 1.

排查

我们进入到报错的文件的上级目录,看到确实是没有这个文件Electron Framework,只有一个文件夹Versions

bash 复制代码
jw my-electron-app % cd "/Users/jw/wk/github/electron/my-electron-app/node_modules/.pnpm/[email protected]/node_modules/electron/dist/Electron.app/Contents/Frameworks/Electron Framework.framework/"
jw Electron Framework.framework % ls
Versions

从错误提示上看,应该是Electron Framework这个文件在安装时没有复制过来导致的,使用npmyarn安装并不会出现这个问题,锅显然是pnpm的。

我们知道,pnpm有别于npmyarn的核心卖点是,使用硬链接和符号链接的方式来管理node_modules目录,这种方式可以避免重复下载和存储相同的包,从而节省磁盘空间和提高安装速度。这个错误信息必然与这种链接方式有关。

我们在正常的版本中找到这个目录node_modules/.pnpm/[email protected]/node_modules/electron/dist/Electron.app,看下里面有什么内容:

bash 复制代码
├── Electron Framework
├── Helpers
│   └── chrome_crashpad_handler
├── Libraries
│   ├── libEGL.dylib
│   ├── libGLESv2.dylib
│   ├── libffmpeg.dylib
│   ├── libvk_swiftshader.dylib
│   └── vk_swiftshader_icd.json
├── Resources
└── Versions

177 directories, 207 files

MacOS上看,Electron Framework是个可执行文件:

遇事不决问谷歌,很快找到了这条有关联的信息,是pnpm的GitHub的issue

这个问题在2023年10月就已经被提出了,顺便还给了一个临时解决方案:

也就是在preparehook里,先删除这个目录,再重新执行一次Electroninstall脚本:

json 复制代码
"scripts": {
	"prepare": "rimraf node_modules/electron/dist && node node_modules/electron/install.js"
}

由于只有Mac有这个问题,所以我写了一个简单的脚本install.cjs

javascript 复制代码
const os = require("os");
const fs = require("fs");

function main() {
  if (os.platform() !== "darwin") {
    return;
  }
  console.info("rm -rf node_modules/electron/dist");
  fs.rmSync("node_modules/electron/dist", { force: true, recursive: true });
  console.info("node node_modules/electron/install.js");
  require("electron/install.js");
}

main();

将上述script修改为:

json 复制代码
{
  "prepare": "node build/install.cjs"
}

以上是我在2024年1月发现这个问题的处理。

进展

时间进展到4月,我发现这个issue被关闭了:

这位大佬Zoltan Kochan是谁呢?到pnpm的贡献榜里一看:

排在第一位,哦,是pnpm之父,失敬:

另一个方案

言归正传。

大佬在2月24日提出:『我认为这个问题的临时解决方法是将side-effects-cache设置为 false』。

我试了下,修改.npmrc

bash 复制代码
side-effects-cache = false

果然生效。

这个配置是什么呢?它是pnpm的特有的配置项,在官方文档里是这样写的:

也就是说,side-effects-cache用于配置是否缓存包的安装副作用。

当你使用pnpm安装一个包时,pnpm会在node_modules目录下创建一个符号链接,指向一个全局的包存储位置。这样,如果多个项目使用同一个包,那么这个包只需要在全局存储位置存储一次,而不是在每个项目的node_modules目录下都存储一份。

然而,有些包在安装过程中会有副作用,比如编译本地代码或者生成某些文件。这些副作用通常是项目特定的,不能在多个项目之间共享。

这就是side-effects-cache配置项的作用。如果设置为truepnpm会在每个项目的node_modules目录下缓存这些副作用。这样,即使这个包在全局存储位置已经存在,pnpm也会重新执行这个包的安装过程,以生成这些副作用。

如果设置为falsepnpm则不会缓存这些副作用。这意味着,如果一个包在全局存储位置已经存在,pnpm就不会重新执行这个包的安装过程,即使这个过程可能会生成一些项目特定的副作用。

Bug修复解析

紧接着,这个Bug就被大佬修复了:

翻译过来是:

符号链接被解析为其真实路径并上传为真实文件。它们不会占用更多空间,因为硬链接最终指向同一文件。这可能令人惊讶,因为在首次安装时,用户会看到符号链接,但在后续安装中,软件包将使用"真实文件",因为它们将被链接到存储库(来自副作用缓存)。我认为理想情况下,在从副作用缓存链接软件包时,我们应该恢复符号链接,但这将需要对软件包索引文件进行破坏性更改,因为目前仅支持"真实文件"条目。

已经合并到main分支和v9 beta版本上了:

再看发版日志里,在v8.15.4里果然有了这条信息:

我们使用corepack切换pnpmv8.15.4

bash 复制代码
jw my-electron-app % corepack use [email protected]
Installing [email protected] in the project...

Lockfile is up to date, resolution step is skipped
Packages: +78
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 78, reused 78, downloaded 0, added 78, done

devDependencies:
+ electron 27.0.4

Done in 540ms

这时发现pnpm start果然正常了。

下来,我们简单看下这个Bug的修复逻辑

大佬只是将代码顺序调整了一下,额外添加了一句stat.isDirectory的处理。

你可能会产生疑惑,什么情况下,在33fs.readdir判断过不是文件夹,到了47行又使用fs.stat判断是个文件夹的呢?

答案是pnpm的核心------符号链接。

我们做个测试,方便理解。随便找一个工程,使用ln -s创建一个文件夹的符号链接:

bash 复制代码
mkdir test
cd test
ln -s  ../node_modules aa

在IDE里能看到创建成功了:

再写段JS代码:

javascript 复制代码
const fs = require("fs");
const path = require("path");

const dir = "./test";
fs.readdirSync(dir, {
  withFileTypes: true,
}).forEach((file) => {
  console.log(
    "file:",
    file.name,
    "isDir:",
    file.isDirectory(),
    "isSymLink:",
    file.isSymbolicLink()
  );
  const fullPath = path.join(dir, file.name);
  let stat;
  try {
    stat = fs.statSync(fullPath);
    console.log("isDir:", stat.isDirectory());
  } catch (err) {
    if (err.code !== "ENOENT") {
      throw err;
    }
  }
});

// file: aa isDir: false isSymLink: true
// isDir: true

从打印结果就能看出,在fs.readdir里,如果返回的这个Dirent对象表示的是一个指向目录的符号链接,isDirectory方法会返回false,因为符号链接本身并不是目录。而fs.stat会"解引用"符号链接,也就是说,它会返回符号链接指向的目录或文件的状态,而不是符号链接本身的状态。

大佬的这次修改,考虑了文件夹为符号链接的情况,又考虑到file为符号链接(file.isSymbolicLink,原来判断是file.isFile)的情况,完美解决了这个Bug。

总结

MacOS中使用pnpm二次安装Electron后,可能会出现node_modules里缺失了二进制文件的情况,这是pnpm符号链接引发的一个Bug,不确定是具体哪个版本出的问题(8.0.0是好的),所以请大家升级到最新版本(v8.15.4以上),或者v9 beta版本。

相关推荐
zhougl99629 分钟前
html处理Base文件流
linux·前端·html
花花鱼33 分钟前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_36 分钟前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷6 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript