VUE发版白屏问题解决

表现:F12检查发现资源404错误,一般是app.{hash}.css,    app.{hash}.js    等文件404。
原因:发版后静态资源名称变了,但浏览器缓存了旧的index.html,引用的还是旧的资源url,资源404,所以白屏了。
问题解决思路:在index.html写JS去检查是否资源载入出错,如果是则自动刷新。

因为script标签是编译后插入的,没法用onerror事件去检查,因此另想方案。
既然是script标签,那document.getElementByTagName是可以取到的。注意这在调试模式下不可行,因为调试模式只有一个app.js。
function    checkUpgrade(){
var scriptEls = document.getElementsByTagName("script");
}
这样就可以取到script标签了,但是为什么只有一个?
这一个就是写的这段代码自身,因为浏览器js执行是单线程的,index.html刚解析和执行到这里,后面插入的script标签此时没有解析,因此我们需要判断一下延迟去处理。
if (scriptEls.length < 3) {
          return setTimeout(checkUpgrade, 1000);
}
接下来就是构造一个ajax请求去检测文件是否存在了,因为我们不需要单独再去下载一次文件,这里用head请求就好了。
遍历scriptEls,判断是否是我们要检测的目标
if (element.src && element.src.indexOf('./static/js/')) {}
如果是就执行检测
var httpRequest_ = createHttpRequest();
httpRequest_.open("HEAD", element.src, true);
httpRequest_.send(null);
可以写个函数来看返回的结果
function handleResponse(xhr, callback) {
          xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
              callback(xhr.status);
            }
          };
          xhr.onerror = function () {
          }
        }
好了,基本上表明清楚了。
var httpRequest_ = createHttpRequest();
httpRequest_.open("HEAD", element.src, true);
httpRequest_.send(null);
handleResponse(httpRequest_, function (status) {
                if (breakNext)
                  return;
                if (status == 404) {
                  //这是版本已经更新了,客户端缓存的旧版
                  //刷新就好了
                  fixedLoadingText = '正在刷新,请稍后';
                  breakNext = 1;
                  setTimeout(() => location.reload(), 3 * 1000);
                }
}
用breakNext是因为我们检测一个就好了,避免发很多不必要的请求
用fixedLoadingText是因为我做了一个加载动画,这个动画会在app.vue$mounted事件执行完毕后,也就是vue实例基本渲染好之后隐藏。

当然除了404,我们还可以做更多检测
if (status == 502 || status == 504 || status == 500) {
                  //这是后端无响应,提示正在升级
                  //10秒后继续检测是否升级完成
                  fixedLoadingText = '正在升级,请稍后';
                  breakNext = 1;
                  setTimeout(checkUpgrade, 5 * 1000);
                }
                else if (status == 0) {
                  //这是网络错误
                  // 一会儿再试
                  fixedLoadingText = '网络故障,正在重试';
                  breakNext = 1;
                  setTimeout(checkUpgrade, 10 * 1000);
                }

奉上全部代码
<!DOCTYPE html>
<html>

<head>
  <meta charset=utf-8>
  <meta name=viewport content="width=device-width,initial-scale=1">
  <link rel="shortcut icon" type=image/x-icon href=static/favicon.ico>
  <title>VueApplication</title>
  <style media=screen type=text/css>

    #appLoading {
      width: 100%;
      height: 100%;
      vertical-align: middle;
    }

    #appLoading p {
      background-color: white;
      border: 2px solid #ccc;
      text-align: center;
      position: absolute;
      display: block;
      width: 400px;
      font-size: 20px;
      padding: 20px;
      top: 50%;
      left: 50%;
      -webkit-transform: translateY(-50%) translateX(-50%);
      transform: translateY(-50%) translateX(-50%);
    }
  </style>
</head>

<body>
  <div id=appLoading><span></span></div>
  <div id=app style="display: none"></div>
  <script>
    (function () {

      var dot = 1;
      var dots = { 0: '', 1: '.', 2: '..', 3: '...' };
      var checkError = true;
      var loadingText = '载入中';
      var fixedLoadingText = '';
      var loadStart = new Date();
      var percent = 0;
      var breakNext = false;
      var vueAppLoad = function (loadEnd) {
        var loadElapsed = loadEnd - loadStart;
        console.log("loadElapsed", loadElapsed / 1000, 's');

        //关闭载入提示,显示app界面
        document.getElementById("appLoading").style.display = "none";
        document.getElementById("app").style.display = "block";
      }
      var updateLoadingText = function (loadingText) {
        document.getElementById("appLoading").innerHTML = '<p><span style="float:left">' + loadingText + '</span><span style="float:right">' + percent + '%</span></p>';
      }
      var itv =
        setInterval(() => {
          dot = dot == 4 ? 0 : dot;
          var loading = document.getElementById("appLoading");
          var loadEnd = new Date();
          if (percent == 100) {
            clearInterval(itv);
            checkError = false;
            vueAppLoad(loadEnd);
            return;
          }
          var loadSeconds = (loadEnd - loadStart) / 1000;
          if (!fixedLoadingText) {
            if (loadSeconds < 5) {
              percent = parseInt(loadSeconds * 5);
            }
            if (loadSeconds > 10) {
              loadingText = '请稍后'
              percent = 50;
            }
            if (loadSeconds > 20) {
              loadingText = '努力载入'
              percent = 60
            }
            if (loadSeconds > 30) {
              loadingText = '网有点慢'
              percent = 70;
            }
            if (loadSeconds > 40) {
              fixedLoadingText = '请检查网络'
              percent = 80;
            }
          }
          //检查app.vue事件创建的属性
          if (window.appCreated) {
            percent = 90;
          }
          if (window.appMounted) {
            percent = 100;
          }
          updateLoadingText((fixedLoadingText || loadingText) + dots[dot]);
          dot++;
        }, 450);
      function checkUpgrade() {
        //reset vars
        breakNext = false;
        function createHttpRequest() {
          if (window.ActiveXObject) {
            return new ActiveXObject("Microsoft.XMLHTTP");
          }
          else if (window.XMLHttpRequest) {
            return new XMLHttpRequest();
          }
        }
        function handleResponse(xhr, callback) {
          xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
              callback(xhr.status);
            }
          };
          xhr.onerror = function () {
          }
        }
        //版本更新后js和css的文件名称将更改,但因为客户端缓存了旧版的index.html页面
        //因此载入静态资源会404,表现为白屏
        //通过head请求去检测是否404来确认是否版本已更新,如果版本已更新则刷新页面
        var scriptEls = document.getElementsByTagName("script");
        if (scriptEls.length < 3) {
          return setTimeout(checkUpgrade, 1000);
        }
        for (let i = 0; i < scriptEls.length; i++) {
          if (breakNext)
            break;
          const element = scriptEls[i];
          if (element.src && element.src.indexOf('./static/js/')) {
            var httpRequest_ = createHttpRequest();
            try {
              httpRequest_.open("HEAD", element.src, true);
              httpRequest_.send(null);
              handleResponse(httpRequest_, function (status) {
                if (breakNext)
                  return;
                if (status == 404) {
                  //这是版本已经更新了,客户端缓存的旧版
                  //刷新就好了
                  fixedLoadingText = '正在刷新,请稍后';
                  breakNext = 1;
                  setTimeout(() => location.reload(), 3 * 1000);
                }
                else if (status == 502 || status == 504 || status == 500) {
                  //这是后端无响应,提示正在升级
                  //10秒后继续检测是否升级完成
                  fixedLoadingText = '正在升级,请稍后';
                  breakNext = 1;
                  setTimeout(checkUpgrade, 5 * 1000);
                }
                else if (status == 0) {
                  //这是网络错误
                  // 一会儿再试
                  fixedLoadingText = '网络故障,正在重试';
                  breakNext = 1;
                  setTimeout(checkUpgrade, 10 * 1000);
                }
                else {
                  //其他情况一般无需处理了
                }
              });
            }
            catch (ex) {
              console.log(ex);
            }
          }
        }
      }
      checkUpgrade();
    })();
  </script>
</body>
</html>

在app.vue的mounted事件中做个标记,这样index.html就可以用这个标记去检查vue是否渲染好了。
mounted() {
    window.appMounted = new Date(); //通知index.html
  },

  

posted @ 2021-12-11 12:29  道木先生  阅读(325)  评论(0编辑  收藏  举报