runtimeChunk,直观翻译是运行时的chunk文件,其作用是啥呢,通过调研了解了一波,在此记录下。
何为运行时代码?
形如import('abc').then(res=>{})
这种异步加载的代码,在webpack中即为运行时代码。
在VueCli工程中常见的异步加载路由即为runtime代码。
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
// component: About
}
搭建工程测试功效
1、搭建简单的vue项目,使用vuecli新建一个只需要router的项目,脚手架默认路由配置了一个异步加载的about路由,如上图所示
2、不设置runtimeChunk时,查看打包文件,此时不需要做任何操作,因为其默认是false,直接yarn build,此时生成的主代码文件的hash值为7d50fa23
。
3、接着改变about.vue文件的内容,再次build,查看打包结果,发现app文件的hash值发生了变化。
发现app文件的hash值发生了变化。
设置runtimeChunk是将包含
chunks 映射关系
的 list单独从 app.js里提取出来,因为每一个 chunk 的 id 基本都是基于内容 hash 出来的,所以每次改动都会影响它,如果不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。设置runtimeChunk之后,webpack就会生成一个个runtime~xxx.js的文件。
然后每次更改所谓的运行时代码文件时,打包构建时app.js的hash值是不会改变的。如果每次项目更新都会更改app.js的hash值,那么用户端浏览器每次都需要重新加载变化的app.js,如果项目大切优化分包没做好的话会导致第一次加载很耗时,导致用户体验变差。现在设置了runtimeChunk,就解决了这样的问题。所以这样做的目的是避免文件的频繁变更导致浏览器缓存失效,所以其是更好的利用缓存。提升用户体验。
4、新建vue.config.js,配置runtimeChunk,第一次打包,然后修改about,在打包一次,查看2次打包之后app文件的hash值的变化。
// vue.config.js
module.exports = {
productionSourceMap: false,
configureWebpack: {
runtimeChunk: true
}
}
通过截图看到2次打包生成的app文件的hash值没有改变。和上面说的作用一致。
你以为这就完了?
1、查看下runtime~xxx.js文件内容:
function a(e){return i.p+"js/"+({about:"about"}[e]||e)+"."+{about:"3cc6fa76"}[e]+".js"}f
发现文件很小,且就是加载chunk的依赖关系的文件。虽然每次构建后app的hash没有改变,但是runtime~xxx.js会变啊。每次重新构建上线后,浏览器每次都需要重新请求它,它的 http 耗时远大于它的执行时间了,所以建议不要将它单独拆包,而是将它内联到我们的 index.html 之中。这边我们使用script-ext-html-webpack-plugin来实现。(也可使用html-webpack-inline-source-plugin,其不会删除runtime文件。)
// vue.config.js
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
module.exports = {
productionSourceMap: false,
configureWebpack: {
optimization: {
runtimeChunk: true
},
plugins: [
new ScriptExtHtmlWebpackPlugin({
inline: /runtime~.+\.js$/ //正则匹配runtime文件名
})
]
},
chainWebpack: config => {
config.plugin('preload')
.tap(args => {
args[0].fileBlacklist.push(/runtime~.+\.js$/) //正则匹配runtime文件名,去除该文件的preload
return args
})
}
}
重新打包,查看index.html文件
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=utf-8>
<meta http-equiv=X-UA-Compatible content="IE=edge">
<meta name=viewport content="width=device-width,initial-scale=1">
<link rel=icon href=/favicon.ico>
<title>runtime-chunk</title>
<link href=/js/about.cccc71df.js rel=prefetch>
<link href=/css/app.b087a504.css rel=preload as=style>
<link href=/js/app.9f1ba6f7.js rel=preload as=script>
<link href=/css/app.b087a504.css rel=stylesheet>
</head>
<body><noscript><strong>We're sorry but runtime-chunk doesn't work properly without JavaScript enabled. Please enable it
to continue.</strong></noscript>
<div id=app></div>
<script>(function (e) { function r(r) { for (var n, a, i = r[0], c = r[1], l = r[2], f = 0, s = []; f < i.length; f++)a = i[f], Object.prototype.hasOwnProperty.call(o, a) && o[a] && s.push(o[a][0]), o[a] = 0; for (n in c) Object.prototype.hasOwnProperty.call(c, n) && (e[n] = c[n]); p && p(r); while (s.length) s.shift()(); return u.push.apply(u, l || []), t() } function t() { for (var e, r = 0; r < u.length; r++) { for (var t = u[r], n = !0, a = 1; a < t.length; a++) { var c = t[a]; 0 !== o[c] && (n = !1) } n && (u.splice(r--, 1), e = i(i.s = t[0])) } return e } var n = {}, o = { "runtime~app": 0 }, u = []; function a(e) { return i.p + "js/" + ({ about: "about" }[e] || e) + "." + { about: "cccc71df" }[e] + ".js" } function i(r) { if (n[r]) return n[r].exports; var t = n[r] = { i: r, l: !1, exports: {} }; return e[r].call(t.exports, t, t.exports, i), t.l = !0, t.exports } i.e = function (e) { var r = [], t = o[e]; if (0 !== t) if (t) r.push(t[2]); else { var n = new Promise((function (r, n) { t = o[e] = [r, n] })); r.push(t[2] = n); var u, c = document.createElement("script"); c.charset = "utf-8", c.timeout = 120, i.nc && c.setAttribute("nonce", i.nc), c.src = a(e); var l = new Error; u = function (r) { c.onerror = c.onload = null, clearTimeout(f); var t = o[e]; if (0 !== t) { if (t) { var n = r && ("load" === r.type ? "missing" : r.type), u = r && r.target && r.target.src; l.message = "Loading chunk " + e + " failed.\n(" + n + ": " + u + ")", l.name = "ChunkLoadError", l.type = n, l.request = u, t[1](l) } o[e] = void 0 } }; var f = setTimeout((function () { u({ type: "timeout", target: c }) }), 12e4); c.onerror = c.onload = u, document.head.appendChild(c) } return Promise.all(r) }, i.m = e, i.c = n, i.d = function (e, r, t) { i.o(e, r) || Object.defineProperty(e, r, { enumerable: !0, get: t }) }, i.r = function (e) { "undefined" !== typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, i.t = function (e, r) { if (1 & r && (e = i(e)), 8 & r) return e; if (4 & r && "object" === typeof e && e && e.__esModule) return e; var t = Object.create(null); if (i.r(t), Object.defineProperty(t, "default", { enumerable: !0, value: e }), 2 & r && "string" != typeof e) for (var n in e) i.d(t, n, function (r) { return e[r] }.bind(null, n)); return t }, i.n = function (e) { var r = e && e.__esModule ? function () { return e["default"] } : function () { return e }; return i.d(r, "a", r), r }, i.o = function (e, r) { return Object.prototype.hasOwnProperty.call(e, r) }, i.p = "/", i.oe = function (e) { throw console.error(e), e }; var c = window["webpackJsonp"] = window["webpackJsonp"] || [], l = c.push.bind(c); c.push = r, c = c.slice(); for (var f = 0; f < c.length; f++)r(c[f]); var p = l; t() })([]);</script>
<script src=/js/chunk-vendors.1e5c55d3.js></script>
<script src=/js/app.9f1ba6f7.js></script>
</body>
</html>
index.html中已经没有对runtime~xxx.js的引用了,而是直接将其代码写入到了index.html中,故不会在请求文件,减少http请求。
总结
runtimeChunk作用是为了线上更新版本时,充分利用浏览器缓存,使用户感知的影响到最低。