场景:

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

 问题原因:

在前端项目未更新之前打开的页面,在前端项目更新后,hash码更新导致js请求路径改变,而页面依然以之前的路径请求js资源,必然会请求不到。如果资源请求不到一般会报404问题,但是服务器配置了404页面,对于请求不到的资源会返回一个404页面,在script标签里解析html文件,就会报错Unexpected token <

解决:

报错时给用户提示,用户点击确认后刷新页面;目前还没找到catch这种错误的方法,但是,可以模拟这种错误的出现。js文件是以script标签的形式动态添加到head标签里的,可以给head绑定DOMNodeInserted这个事件在有子元素插入的时候触发,可以在回调里拿到插入的标签名以及标签的属性包括src。这样在所有js资源加载时我们都可以在回调事件里拿到资源路径,然后在创建一个请求去请求该资源

代码:

在app.vue中,添加如下代码。对于报错的js文件,我们会在xhr.responseText获取到一个以尖括号开头的html,这时候我们就可以知道当前资源路径失效,就可以在此时做一些处理

<script>
export default {
  name: "App",
  created() {
    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.$confirm("检测到新版本已发布,刷新后加载最新内容!",'提示', {
              confirmButtonText: '刷新',
              type: 'error',
              closeOnClickModal: false,
              closeOnPressEscape:false,
              closeOnHashChange:false,
              showCancelButton: false,
              showClose: false
            }).then(res => {
              window.location.reload(true);
            })
          }
        }
        xhr.send();
      }
    })
  }
};
</script>

问题:使用如上代码可以解决Uncaught SyntaxError: Unexpected token <问题,但随之控制台出现下面错误;

DOMNodeInserted 是一个已经被废弃的 Mutation Events 接口,用于监听DOM树中节点的插入事件;推荐使用 MutationObserver 接口,MutationObserver性能更优

 使用 MutationObserver 重写代码:

<script>
export default {
  name: "App",
  created() {
    const head = document.getElementsByTagName('head')[0];
    // 定义DOM树变化后的回调
    const callBack = (mutationsList, observer) => {
      for (const mutation of mutationsList) {
        if (mutation.type === 'childList' && mutation.addedNodes && mutation.addedNodes[0] && mutation.addedNodes[0].tagName === "SCRIPT") {
          const url = mutation.addedNodes[0].src;
          console.log(url);
          if (url) {
            let xhr = new XMLHttpRequest();
            xhr.open('get', url);
            xhr.onload = () => {
              const text = xhr.responseText;
              // console.log(text);
              if (text.indexOf('<') === 0) {
                this.$confirm("检测到新版本已发布,刷新后加载最新内容!",'提示', {
                  confirmButtonText: '刷新',
                  type: 'error',
                  closeOnClickModal: false,
                  closeOnPressEscape:false,
                  closeOnHashChange:false,
                  showCancelButton: false,
                  showClose: false
                }).then(res => {
                  window.location.reload(true);
                })
                return false;
              }
            }
            xhr.send();
          }
        }
      }
    }
    // 配置项
    const config = {attributes: false, childList: true, subtree: false};
    // observer观察器
    const observer = new MutationObserver(callBack);
    // 开始观察
    observer.observe(head, config);
    // 停止观察
    // observer.disconnect();
  }
};
</script>

参考:

https://blog.csdn.net/FortheOne/article/details/123073336

https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver