场景复现:
页面打开不操作,前端项目代码更新重新部署后,页面不刷新,操作页面(点击打开弹窗、切换菜单等),页面没有反应,控制台报错 Uncaught SyntaxError: Unexpected token <。这个问题偶现,只有在项目重新部署后会出现,页面刷新后就恢复正常
问题分析:
控制台报错可以看出,报错信息来自script,
51.chunk.866cbe17.js、163.chunk.866cbe17.js这两个js文件在Unexpected的地方出现了尖括号<。这两个js文件是webpack打包后的js文件,在本地调试是无法复现这个问题的。组件资源是动态加载的,在head标签里我们可以看到已经加载的很多js/css文件,在用户操作界面触发加载新组件时,该组件的js也会以script标签的形式动态添加到head标签里,如下图:
这就是问题所在,在前端项目未更新之前打开的页面,在前端项目更新后,hash码更新导致js请求路径改变,而页面依然以之前的路径请求js资源,必然会请求不到。如果资源请求不到一般会报404问题,但是服务器配置了404页面,对于请求不到的资源会返回一个404页面,在script标签里解析html文件,就会报错Unexpected token <。
解决思路:
问题的根源是服务器js文件更新了,页面还在请求以前的js文件。可以保留之前webpack打包的文件,但是时间久了文件体积会积累到很大,而且从产品角度更希望用户访问新的资源。所以最好的解决方式是在报错时给用户提示,用户点击确认后刷新页面。前端如何能catch到这种错误?
目前还没找到catch这种错误的方法,但是,可以模拟这种错误的出现。
js文件是以script标签的形式动态添加到head标签里的,可以给head绑定DOMNodeInserted这个事件在有子元素插入的时候触发,可以在回调里拿到插入的标签名以及标签的属性包括src。这样在所有js资源加载时我们都可以在回调事件里拿到资源路径,然后在创建一个请求去请求该资源,代码如下:
const head = document.getElementsByTagName('head')[0] head.addEventListener('DOMNodeInserted', e => { // 获取标签名 const type = e.target.tagName // 获取资源路径 const url = e.target.src if (type === 'SCRIPT' && url) { // 创建请求,如果需要低版本浏览器兼容的,请注意 let xhr = new XMLHttpRequest() xhr.open('get', url) xhr.onload = () => { const text = xhr.responseText if (text.indexOf('<') === 0) { this.$modal.info({ title: '检测到有新的版本发布,需要刷新页面以访问最新内容', width: 350, okText: '确定', onOk() { location.reload() } }) } } xhr.send() } })
对于报错的js文件,我们会在xhr.responseText获取到一个以尖括号开头的html,这时候我们就可以知道当前资源路径失效,就可以在此时做一些处理
总结:
操作页面时报错Unexpected token <,刷新后页面正常,这是因为页面请求的js文件资源找不到,服务器返回一个html,在script标签里解析这段html内容,就会报错Unexpected token <。js文件资源找不到是因为代码更新重新打包,导致js文件路径中的hash码改变造成的。
PS:
如果是一打开页面就报错Unexpected token <,那应该是路径配置问题,检查一下webpack的publicPath配置,这种场景跟上面提到的不是同一个问题。