目录
三、问题解决,webpack-manifest-plugin && assets-webpack-plugin
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!!
总结
- 服务端构造index.html,先使用 webpack-manifest-plugin 或 assets-webpack-plugin 生成一个json资源清单
- 读取json文件,动态创建<link />、<script />和加载 chunks 文件
如果有用,点个赞吧(*^▽^*)