背景
最近在做公司的一个前端监控项目,有一个需求是需要捕获前端错误并上报。这个需求其实很简单,使用 window.onerror
或者 window.addEventListener('error', ...)
就可以捕获异常数据,MDN 的 这篇文章 讲述了具体的用法,AlloyTeam 的 这篇文章 也讲了一些例子,以及针对触发了同源策略的应对方案。
但是当我开始写自己的库时,才发现这个需求并不是那么一帆风顺。我在我们的前端项目里引入了自己写的库,然后故意触发一个语法错误:
new ErrorWatcher(reporter);
// 下面一句会触发 Reference Error
asd;
然并卵,我获取到的错误信息只有 Script Error
,就和被同源策略拦下来了一模一样。我再三确认了编译出的 bundle.js
跟 index.html
是同源的,并且源代码中没有任何诡异的写法。
我新写了一个空页面,尝试复现这个问题:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Normal Bundle</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
window.addEventListener('error', evt => {
console.log(evt);
});
asd;
双击 index.html
,确实是只有一个 Script Error
。在高兴之余,突然想起来 file:
协议本来就不属于同源策略,于是祭出了万能的 serve -o
,浏览器自动打开了 http://localhost:5000/
。
然后顺利的得到了 message
、filename
等信息。我特么……
解决方案
仔细查看 Console 中的日志,发现右边显示的文件地址是 webpack:
协议的,不知道是否与这个有关,于是将 Source Map 删除,发现可以正常获取信息了……Source Map 会影响运行结果?不管你信不信,反正我是不信。
于是去看了一眼编译之后的代码,发现 Dev 环境下的 bundle.js
使用了 eval-source-map
,整个文件全都是通过 eval
来执行代码的。eval
可以改变内部代码的上下文,但是能不能改变内部代码的所属文件呢?于是我把自己测试用的 bundle.js
稍微改了改:
eval(`
window.addEventListener('error', evt => {
console.log(evt);
});
asd;
`);
然后 Script Error
就出现了。虽然不知道为什么,但似乎跟 eval
的关系非常大,于是我将之前项目 webpack.config.js
中的 devtool
从 eval-source-map
改成了 source-map
,问题解决了。
探索
我开始寻找为什么,因为之前搜 js script error
的结果均是清一色的同源策略相关的文章,这次知道了跟 eval
有关,于是就在谷歌中搜索 js eval onerror
,发现了 Webpack 的一个 Issue,里面的链接导向了 Chromium 的这个 Issue。这是 2017 年九月份就有人提出的 Bug,但至今没有被修复。虽然该 Issue 的状态是 Assigned,但是自从被分出去之后就无人问津了。
既然这是个 Bug,那么探索就结束了。得出的一个结论:以后尽量不要用 eval
,即使 Source Map 也不用。