场景复现:

        页面打开不操作,前端项目代码更新重新部署后,页面不刷新,操作页面(点击打开弹窗、切换菜单等),页面没有反应,控制台报错 Uncaught SyntaxError: Unexpected token <。这个问题偶现,只有在项目重新部署后会出现,页面刷新后就恢复正常

问题分析:

控制台报错可以看出,报错信息来自script,

报错信息很简单,51.chunk.866cbe17.js、163.chunk.866cbe17.js这两个js文件在Unexpected的地方出现了尖括号<。这两个js文件是webpack打包后的js文件,在本地调试是无法复现这个问题的。组件资源是动态加载的,在head标签里我们可以看到已经加载的很多js/css文件,在用户操作界面触发加载新组件时,该组件的js也会以script标签的形式动态添加到head标签里,如下图:

图1

图2

 图1是页面刚打开时,head标签里的资源,图2是页面点击操作后加载了新的组件资源。

报错后刷新页面,重复原来的操作,发现js文件后面的hash码变了:

 这就是问题所在,在前端项目未更新之前打开的页面,在前端项目更新后,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配置,这种场景跟上面提到的不是同一个问题。

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐