使用Vue CLI,服务端构造index.html,如何引入hash之后的JS、CSS等资源文件?

43 篇文章 1 订阅
1 篇文章 0 订阅

目录

一、前端打包构造index.html

二、服务器端构造index.html,有什么问题呢?

1. 不生成 index

2. 生成 index.html

三、问题解决,webpack-manifest-plugin && assets-webpack-plugin

1. 获取打包产物资源 json 文件

2. 读取 json 文件,index.html动态引入资源

总结


一、前端打包构造index.html

一般情况下,当我们运行 vue-cli-service build 时,public/index.html 文件是一个会被 html-webpack-plugin 处理的模板。在构建过程中,资源链接会被自动注入。另外,Vue CLI 也会自动注入 resource hint (preload/prefetch、manifest 和图标链接 (当用到 PWA 插件时) 以及构建过程中处理的 JavaScript 和 CSS 文件的资源链接。

 资源包带有hash,打包后如下:

index.html:

部署时直接服务器访问这个index.html就可以正常访问了。

这样的好处在于:

  • 能实现高效率的缓存控制
  • 通过这个被注入资源链接的 index.html,能很好的进行 code-splitting (代码分段)
  • 可以在现代模式下工作

二、服务器端构造index.html,有什么问题呢?

如果是服务器端构造的视图模板,肯定需要引入css和js等相关资源文件,我们有两种方法解决问题

1. 不生成 index

最终你可能会使用这样的一个index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
  <meta name="renderer" content="webkit" />
  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
  <link rel="icon" href="XXXXXX/favicon.ico" />
  <title>XXXXXX</title>
  <link href="XXXXXX/css/chunk-libs.css" rel="stylesheet" />
  <link href="XXXXXX/css/app.css" rel="stylesheet" />
</head>
<body>
<div id="app"></div>
<script src="XXXXXX/js/runtime.js"></script>
<script src="XXXXXX/js/chunk-elementUI.js"></script>
<script src="XXXXXX/js/chunk-libs.js"></script>
<script src="XXXXXX/js/app.js"></script>
</body>
</html>

2. 生成 index.html

但这样的话,生成的 index.html 内容是动态多变的(文件增减、文件名变化、hash变化)。 服务器端压根不知道 splitChunks 后会分成多少个chunk块,也不知道hash之后的JS文件名字和CSS名字,所以引入资源成为了一个棘手的问题。

三、问题解决,webpack-manifest-plugin && assets-webpack-plugin

(两个插件效果一致,都是生成编译结果的资源单,只是资源单的数据结构不一致而已)

使用生成 index.html方式,解决资源引入问题

1. 获取打包产物资源 json 文件

你可以先读一下webpack中文官网 manifest

通过 WebpackManifestPlugin 插件,可以将 manifest 数据提取为一个容易使用的 json 文件。

npm install webpack-nano webpack-manifest-plugin --save-dev
// webpack.config.js

const { WebpackManifestPlugin } = require('webpack-manifest-plugin');

module.exports = {
	// an example entry definition
	entry: [ 'app.js'	],
  ...
  plugins: [
    new WebpackManifestPlugin()
  ]
};
// vue.config.js

const { WebpackManifestPlugin } = require('webpack-manifest-plugin')

module.exports = {
  configureWebpack: {
    plugins: [
      new WebpackManifestPlugin()
    ]
  }
}

使用默认选项上面的示例将创建一个“清单”。json文件在生成的输出目录中。manfist文件将包含源文件名到相应构建输出文件的映射。如:

{
  "dist/batman.js": "dist/batman.1234567890.js",
  "dist/joker.js": "dist/joker.0987654321.js"
}

按照官网的示例,你可以成功得到这个json资源清单。

但是这里我用了assets-webpack-plugin 来得到了这个json资源清单。使用如下:

// vue.config.js

const AssetsPlugin = require('assets-webpack-plugin')

module.exports = {
  configureWebpack: {
    plugins: [
      new AssetsPlugin({
        filename: 'assets.js', // 文件的名称
        path: path.join(__dirname, 'dist'), // 输出目录,默认为项目的最高级别目录
        prettyPrint: true, // 格式化
        // 自定义
        processOutput: function(assets) {
          return 'window.ASSETS_FILE_MAP = ' + JSON.stringify(assets)
        }
      })
    ]
  }
}

这样你会在你的打包输出目录dist得到一个assets.js:

2. 读取 json 文件,index.html动态引入资源

问题又来了,得到这个资源清单,我们改怎么使用呢?

前端打包构造的index.html格式化后是这样的:

服务端构造的index.html可能是这样的:

所以我们要在服务端构造的index.html中,在script内联脚本中动态引入这些 js 和 css 资源,以及那些chunk块,完整代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
  <meta name="renderer" content="webkit" />
  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
  <link rel="icon" href="https://xxxxxxxxx/favicon.ico" />
  <title>demo</title>
</head>
<body>
<div id="app"></div>
<script crossorigin="anonymous" src="https://xxxxxxxxx/tlog/tlog.min.js"></script>
<script src="https://xxxxxxxxx/assets.js"></script>
<script>
  // 获取每个chunk
  const getChunks = (chunkCommonsJosn, josnArr, isCss) => {
    let chunks = {};
    let chunks1 = {};

    const lastIndex = chunkCommonsJosn.lastIndexOf('/');
    const str = chunkCommonsJosn.slice(lastIndex + 1, -3);
    const arr = str.split('.');
    chunks[arr[0]] = arr[1];
    if (isCss) {
      chunks1[arr[0]] = 1;
    }
    for(let i = 0; i < josnArr.length; i++) {
      const item = josnArr[i];
      const lastIndex = item.lastIndexOf('/');
      const str = item.slice(lastIndex + 1, -3);
      const arr = str.split('.');
      if (arr[1] === 'worker') {
        continue
      }
      chunks[arr[0]] = arr[1];
      if (isCss) {
        chunks1[arr[0]] = 1;
      }
    }
    return { chunks, chunks1 };
  }
  window.onload = function () {
    let version = 'v5'; // 定义输出的目录
    const json = window.ASSETS_FILE_MAP;
    
    // 获取数据
    const appJosn = json.app;
    const chunkCommonsJosn = json['chunk-commons'];
    const chunkElementUIJosn = json['chunk-elementUI'];
    const chunkLibsJosn = json['chunk-libs'];
    const jsJosn = json[''] && json[''].js;
    const cssJosn = json[''] && json[''].css;

    const chunkCommonsJs = getChunks(chunkCommonsJosn.js, jsJosn)
    const chunkCommonsCss = getChunks(chunkCommonsJosn.css, cssJosn, true)

    // 创建元素
    let appLink = document.createElement('link');
    let chunkLibsLink = document.createElement('link');
    let appScript = document.createElement('script');
    let chunkElementUIScript = document.createElement('script');
    let chunkLibsScript = document.createElement('script');

    // 元素属性编辑
    appLink.href = appJosn.css;
    chunkLibsLink.href = chunkLibsJosn.css;
    appLink.rel = 'stylesheet';
    chunkLibsLink.rel = 'stylesheet';
    appScript.src = appJosn.js;
    chunkElementUIScript.src = chunkElementUIJosn.js;
    chunkLibsScript.src = chunkLibsJosn.js;
    appScript.crossorigin = 'anonymous';
    chunkElementUIScript.crossorigin = 'anonymous';
    chunkLibsScript.crossorigin = 'anonymous';

    // 把元素追加到父级元素中
    document.head.appendChild(appLink);
    document.head.appendChild(chunkLibsLink);
    document.body.appendChild(appScript);
    document.body.appendChild(appScript);
    document.body.appendChild(chunkElementUIScript);
    document.body.appendChild(chunkLibsScript);

    (function(c) {
      function e(e) {
        for (var d, u, a = e[0], f = e[1], b = e[2], t = 0, r = []; t < a.length; t++) u = a[t],
        h[u] && r.push(h[u][0]),
          h[u] = 0;
        for (d in f) Object.prototype.hasOwnProperty.call(f, d) && (c[d] = f[d]);
        o && o(e);
        while (r.length) r.shift()();
        return k.push.apply(k, b || []),
          n()
      }
      function n() {
        for (var c, e = 0; e < k.length; e++) {
          for (var n = k[e], d = !0, u = 1; u < n.length; u++) {
            var a = n[u];
            0 !== h[a] && (d = !1)
          }
          d && (k.splice(e--, 1), c = f(f.s = n[0]))
        }
        return c
      }
      var d = {},
        u = {
          runtime: 0
        },
        h = {
          runtime: 0
        },
        k = [];
      function a(c) {
        return f.p + version + "/js/" + ({
          "chunk-commons": "chunk-commons"
        } [c] || c) + "." +  chunkCommonsJs.chunks [c] + ".js"
      }
      function f(e) {
        if (d[e]) return d[e].exports;
        var n = d[e] = {
          i: e,
          l: !1,
          exports: {}
        };
        return c[e].call(n.exports, n, n.exports, f),
          n.l = !0,
          n.exports
      }
      f.e = function(c) {
        var e = [],
          n = chunkCommonsCss.chunks1;
        u[c] ? e.push(u[c]) : 0 !== u[c] && n[c] && e.push(u[c] = new Promise((function(e, n) {
          for (var d = version + "/css/" + ({
            "chunk-commons": "chunk-commons"
          } [c] || c) + "." + chunkCommonsCss.chunks [c] + ".css", h = f.p + d, k = document.getElementsByTagName("link"), a = 0; a < k.length; a++) {
            var b = k[a],
              t = b.getAttribute("data-href") || b.getAttribute("href");
            if ("stylesheet" === b.rel && (t === d || t === h)) return e()
          }
          var r = document.getElementsByTagName("style");
          for (a = 0; a < r.length; a++) {
            b = r[a],
              t = b.getAttribute("data-href");
            if (t === d || t === h) return e()
          }
          var o = document.createElement("link");
          o.rel = "stylesheet",
            o.type = "text/css",
            o.onload = e,
            o.onerror = function(e) {
              var d = e && e.target && e.target.src || h,
                k = new Error("Loading CSS chunk " + c + " failed.\n(" + d + ")");
              k.request = d,
                delete u[c],
                o.parentNode.removeChild(o),
                n(k)
            },
            o.href = h;
          var i = document.getElementsByTagName("head")[0];
          i.appendChild(o)
        })).then((function() {
          u[c] = 0
        })));
        var d = h[c];
        if (0 !== d) if (d) e.push(d[2]);
        else {
          var k = new Promise((function(e, n) {
            d = h[c] = [e, n]
          }));
          e.push(d[2] = k);
          var b, t = document.createElement("script");
          t.charset = "utf-8",
            t.timeout = 120,
          f.nc && t.setAttribute("nonce", f.nc),
            t.src = a(c),
            b = function(e) {
              t.onerror = t.onload = null,
                clearTimeout(r);
              var n = h[c];
              if (0 !== n) {
                if (n) {
                  var d = e && ("load" === e.type ? "missing": e.type),
                    u = e && e.target && e.target.src,
                    k = new Error("Loading chunk " + c + " failed.\n(" + d + ": " + u + ")");
                  k.type = d,
                    k.request = u,
                    n[1](k)
                }
                h[c] = void 0
              }
            };
          var r = setTimeout((function() {
            b({
              type: "timeout",
              target: t
            })
          }), 12e4);
          t.onerror = t.onload = b,
            document.head.appendChild(t)
        }
        return Promise.all(e)
      },
        f.m = c,
        f.c = d,
        f.d = function(c, e, n) {
          f.o(c, e) || Object.defineProperty(c, e, {
            enumerable: !0,
            get: n
          })
        },
        f.r = function(c) {
          "undefined" !== typeof Symbol && Symbol.toStringTag && Object.defineProperty(c, Symbol.toStringTag, {
            value: "Module"
          }),
            Object.defineProperty(c, "__esModule", {
              value: !0
            })
        },
        f.t = function(c, e) {
          if (1 & e && (c = f(c)), 8 & e) return c;
          if (4 & e && "object" === typeof c && c && c.__esModule) return c;
          var n = Object.create(null);
          if (f.r(n), Object.defineProperty(n, "default", {
            enumerable: !0,
            value: c
          }), 2 & e && "string" != typeof c) for (var d in c) f.d(n, d,
            function(e) {
              return c[e]
            }.bind(null, d));
          return n
        },
        f.n = function(c) {
          var e = c && c.__esModule ?
            function() {
              return c["default"]
            }: function() {
              return c
            };
          return f.d(e, "a", e),
            e
        },
        f.o = function(c, e) {
          return Object.prototype.hasOwnProperty.call(c, e)
        },
        f.p = "https://xxxxxxxxxxxxx/" + version + '/',
        f.oe = function(c) {
          throw console.error(c),
            c
        };
      var b = window["webpackJsonp"] = window["webpackJsonp"] || [],
        t = b.push.bind(b);
      b.push = e,
        b = b.slice();
      for (var r = 0; r < b.length; r++) e(b[r]);
      var o = t;
      n()
    })([]);
  }
</script>
</body>
</html>

部署,亲测有效无bug!!

总结

  1. 服务端构造index.html,先使用 webpack-manifest-plugin 或 assets-webpack-plugin 生成一个json资源清单
  2. 读取json文件,动态创建<link />、<script />和加载 chunks 文件

 如果有用,点个赞吧(*^▽^*) 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__畫戟__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值