背景

我们的项目是后台管理系统,框架是Vue,构建工具是Webpack

问题

每一次修改代码后打包发布到生产环境后,用户需要手动刷新才能获取到最新的代码。

原因

Webpack打包之后会根据文件内容生成一个hash值,然后再按照[name].[hash].js的格式生成文件名;

然后再根据文件的路由对应关系生成一个runtime.[hash].js的文件,这个文件会监控浏览器的路由变化去服务器加载对应的js文件。

runtime.[hash].js文件是放在index.html中,在开始进入系统时已经加载。

所以当服务器重新发版之后,生成的文件会出现hash值改变,这时候当用户跳转路由时,请求加载的文件名在服务器上已经找不到了,就会报错:Loading chunk [name].[hash].js failed

方案

思路:既然能监控到加载js的错误信息,那么就可以在处理错误信息的代码中加入刷新页面的功能,让页面自动去刷新页面,然后再获取最新的runtime.[hash].js文件,用户再次点击时就可以正常访问了。

按照思路,分为以下2步:

  1. 找到处理错误信息的代码,加入刷新页面逻辑

    经过几番查找,处理错误信息的代码在runtime.[hash].js中,如下:

    var u = new Error;
        d = function(a) {
            o.onerror = o.onload = null,
            clearTimeout(b);
            var c = r[e];
            if (0 !== c) {
                if (c) {
                  var t = a && ("load" === a.type ? "missing" : a.type), 
                      f = a && a.target && a.target.src;
                  u.message = "Loading chunk " + e + " failed.\n(" + t + ": " + f + ")",
                  u.type = t,
                  u.request = f,
                  c[1](u)
                }
                r[e] = void 0
            }
        }
    

    难点在于,runtime.[hash].js是由webpack单独生成的,我们不能通过项目中的代码来修改,而且暂时没有找到通过配置的方式来修改。

    目前的临时解决方案:

    通过Loading chunk关键字在webpack模块中找到了对应的文件,文件地址:webpack/lib/web/JsonpMainTemplatePlugin.js,找到处理错误代码,并添加刷新页面代码:

    Template.indent([
        "if(chunk) {",
        Template.indent([
            "var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
            "var realSrc = event && event.target && event.target.src;",
            "error.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
            "error.type = errorType;",
            "error.request = realSrc;",
            "chunk[1](error);"
        ]),
        "}",
        "location.reload(ture)", // 在此处添加刷新页面代码
        "installedChunks[chunkId] = undefined;"
    ]),
    

    重新执行npm run build命令就可以了

    问题的来了,自动布署时会重新拉取webpack包,还是不能生效。

    解决方案就是自己Fork webpack项目,修改以上代码后,再自己发布一个npm包,

    然后将项目中的webpack引用名改成自己的npm包名,这样就可以了,缺点就是后续的升级比较麻烦。

  2. 刷新页面时,取消缓存,获取最新的文件

    自动刷新解决了,发现浏览器并没从服务器上拉取文件,原因就是浏览器缓存了。

    解决方案:在文件请求路径添加参数,参数值设置为hash值,比如:

    <script src="/static/js/runtime.68e22691.js?t=68e22691">
    <link href="/static/css/vendors.bbf38da36d1cd1d96a5e.css?t=cd1d96a5e"  rel="stylesheet">
    

    怎么配置呢?

    找到webpackoutput,设置成如下:

    output: {
        path: OUTPUT_PATH,
        publicPath: '/',
        filename: 'static/js/[name].[chunkhash:8].js?t=[chunkhash:8]',
        chunkFilename: 'static/js/[name].[chunkhash:8].js?t=[chunkhash:8]'
      }
    

优化

按照目前的解决方案,确实可以实现不再需要手动刷新获取最新的资源,但是有个问题就是刷新之后并不会跳转到用户点击的页面,还是停留在当前页面,需要再点击一次才到跳转。

所以我们可以在刷新之前,弹出一个确认框,告知用户服务器发版或升级了,需要刷新页面才能获取最新的资源。用户点击确认之后再进行刷新操作。

Logo

前往低代码交流专区

更多推荐