问题描述:
最近做一个软件,用better-sqlite3存储数据用vue3做前端,用IPC进程间通信模拟ajax请求实现前后端分离。
后端简单模仿MVC框架写了一遍,后端在node环境下测试没有问题,但是打包进electron项目中就出现了以下问题
INFO Launching Electron...
App threw an error during load
TypeError: Cannot read property 'indexOf' of undefined
at Function.getFileName (webpack:///./node_modules/bindings/bindings.js?:179:16)
at bindings (webpack:///./node_modules/bindings/bindings.js?:82:48)
at new Database (webpack:///./node_modules/better-sqlite3/lib/database.js?:48:119)
at DBUtils.createDatabase (webpack:///./src/backend/utils/DBUtils.js?:37:25)
at new DBUtils (webpack:///./src/backend/utils/DBUtils.js?:15:18)
at new PropertyDao (webpack:///./src/backend/dao/PropertyDao.js?:10:29)
at new InfoPairService (webpack:///./src/backend/service/InfoPairService.js?:27:32)
at new ArchivesController (webpack:///./src/backend/controller/ArchivesController.js?:12:26)
at eval (webpack:///./src/backend/server-routes/Routes.js?:8:28)
at Module../src/backend/server-routes/Routes.js (D:\Code\political_assistant\merge\dist_electron\index.js:2076:1)
原因是使用DBUtils中使用better-sqlite3的new Database方法打开sqlite数据库文件时,调用了bindings库中的getFileName方法,这个方法返回了Undefined。
打开bindings.js这个文件找到此方法:
exports.getFileName = function getFileName(calling_file) {
var origPST = Error.prepareStackTrace,
origSTL = Error.stackTraceLimit,
dummy = {},
fileName;
Error.stackTraceLimit = 10;
Error.prepareStackTrace = function(e, st) {
for (var i = 0, l = st.length; i < l; i++) {
fileName = st[i].getFileName();
if (fileName !== __filename) {
if (calling_file) {
if (fileName !== calling_file) {
return;
}
} else {
return;
}
}
}
};
// run the 'prepareStackTrace' function above
Error.captureStackTrace(dummy);
dummy.stack;
// cleanup
Error.prepareStackTrace = origPST;
Error.stackTraceLimit = origSTL;
// handle filename that starts with "file://"
var fileSchema = 'file://';
if (fileName.indexOf(fileSchema) === 0) {
fileName = fileURLToPath(fileName);
}
return fileName;
};
从上面的代码发现,报错中的indexOf出现在
// handle filename that starts with "file://"
var fileSchema = 'file://';
if (fileName.indexOf(fileSchema) === 0) {
fileName = fileURLToPath(fileName);
}
这一部分,fileName的值为undefined原因在于上面的方法
Error.prepareStackTrace = function(e, st) {
for (var i = 0, l = st.length; i < l; i++) {
fileName = st[i].getFileName();
if (fileName !== __filename) {
if (calling_file) {
if (fileName !== calling_file) {
return;
}
} else {
return;
}
}
}
};
st[i].getFileName()返回了undefined
解决方法:
到bindings包的github仓库中发现有大佬已经踩过这个坑,并且提交了解决这个BUG的pull request,但是原作者似乎不再维护了,没有把修复bug的pull request合并进去。
大佬的PR地址:
Fix for using bindings.js with eval by pverscha · Pull Request #66 · TooTallNate/node-bindings · GitHub
进大佬的PR把修复了bug的bindings.js下载下来替换自己项目中的node_modules/bindings/bindings.js文件
再次运行项目,问题解决!
问题复盘:
better-sqlite3需要被编译成.node二进制文件来执行。但是编译后生成的.node文件在不同的环境下会输出到不同的目录,导致在加载.node文件时找不到对应目录。
这个时候使用node-bindings可以自动从包目录出发遍历整个目录来查找编译后的node文件。
但是node-bindings使用chrome V8的Error API获取程序调用栈,来找到调用bindings的函数所在目录。
然而webpack打包后,webpack似乎使用eval方式调用了我的代码,一直调用到better-sqlite3和bindings.js,而bindings存在被eval调用后getFileName返回undefined的BUG,引发了这个BUG。
具体为啥被eval方式调用后出现这个BUG,还没搞清楚。
事实上这个BUG已经被大佬修复,但是原作者似乎弃坑了,没有把修复的PR合并到主线。
参考文章:
关于electron+vue开发:
最简洁Vue+Electron项目搭建教程 - 知乎
Electron + Vue3 + TS 实战 - 掘金
关于进程间通信模拟B/S架构:
关于 Electron 进程间通信的一个小小实践 - 掘金
关于问题定位:
https://github.com/TooTallNate/node-bindings/issues/29
getFileName cannot read property of undefined · Issue #54 · TooTallNate/node-bindings · GitHub
Cannot read property 'indexOf' of undefined at Function.getFileName · Issue #76 · TooTallNate/node-bindings · GitHub
关于问题解决:
Fix for using bindings.js with eval by pverscha · Pull Request #66 · TooTallNate/node-bindings · GitHub
关于node-bindings中使用的chrome V8 的 Error API:
Error 错误 | Node.js API 文档
Stack trace API · V8