Description
什么是运行时代码?
形如import('abc').then(res=>{})
这种异步加载的代码,在webpack
中即为运行时代码。
在VueCli工程中常见的异步加载路由
即为runtime
代码。
{
path: '/about',
name: 'About',
// 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')
}
搭建工程测试功效
假设一个使用动态导入的情况(使用import()
),在app.js动态导入component.js
const app = () =>import('./component').then();
build
之后,产生3个包。
0.01e47fe5.js
main.ebf457fa.js
runtime.b506e451.js
其中runtime
,用于管理被分出来的包。下面就是一个runtimeChunk
的截图,可以看到chunkId
这些东西。
function jsonpScriptSrc(chunkId) {
/******/ return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + "." + {"0":"01e47fe5"}[chunkId] + ".bundle.js"
/******/ }
如果采用这种分包策略
- 当更改
app
的时候,runtime
的hash
(b506e451
)与0.01e47fe5.js
(被分出的动态加载的代码)的hash
(01e47fe5
)不会改变,main
的hash
(ebf457fa
)会改变。 - 当更改
component.js
,main
的hash
不会改变,runtime
的hash
与0.01e47fe5.js
(动态加载的代码) 的hash
会改变。
因为每一个chunk
的id
基本都是基于内容 hash
出来的,所以每次改动都会影响它,如果不将它提取出来的话,等于app.js
每次都会改变。缓存就失效了。
设置runtimeChunk
之后,webpack就会生成一个个runtime~xxx.js
的文件。然后每次更改所谓的运行时代码文件时,打包构建时app.js
的hash
值是不会改变的。
如果每次项目更新都会更改app.js
的hash
值,那么用户端浏览器每次都需要重新加载变化的app.js
,如果项目大切优化分包没做好的话会导致第一次加载很耗时,导致用户体验变差。现在设置了runtimeChunk
,就解决了这样的问题。
script-ext-html-webpack-plugin使用
查看下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
请求。