Skip to content

optimization.runtimeChunk详解、script-ext-html-webpack-plugin使用 #211

Open
@TieMuZhen

Description

@TieMuZhen

什么是运行时代码?

形如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的时候,runtimehash(b506e451)与0.01e47fe5.js(被分出的动态加载的代码)的hash(01e47fe5)不会改变mainhash(ebf457fa)会改变。
  • 当更改component.jsmainhash不会改变,runtimehash0.01e47fe5.js(动态加载的代码) 的hash改变

因为每一个chunkid基本都是基于内容 hash出来的,所以每次改动都会影响它,如果不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。

设置runtimeChunk之后,webpack就会生成一个个runtime~xxx.js的文件。然后每次更改所谓的运行时代码文件时,打包构建时app.jshash值是不会改变的。

如果每次项目更新都会更改app.jshash值,那么用户端浏览器每次都需要重新加载变化的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的依赖关系的文件。虽然每次构建后apphash没有改变,但是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请求

参考文献

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions