• home > webfront > SGML > html5 >

    Preload与Prefetch的区别以及webpack项目中如何优化

    Author:zhoulujun Date:

    preload 是一个声明式 fetch,可以强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源。​webpack code-splitting 搭配preload优化单页面应用,可以大幅提示用户的网卡请求等待感知

    在网络请求中,我们在使用到某些资源比如:图片,JS,CSS 等等,在执行之前总需要等待资源的下载,如果我们能做到预先加载资源,那在资源执行的时候就不必等待网络的开销。Preload/Prefetch可以让浏览器提预加载资源。

    preload 与prefetch 的区别

    • preload 是一个声明式 fetch,可以强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源

      preload 顾名思义就是一种预加载的方式,它通过声明向浏览器声明一个需要提交加载的资源,当资源真正被使用的时候立即执行,就无需等待网络的消耗。

    • prefetch 告诉浏览器这个资源将来可能需要,但是什么时间加载这个资源是由浏览器来决定的。

      若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源。

    preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源,而 prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。所以建议:对于当前页面很有必要的资源使用 preload,对于可能在将来的页面中使用的资源使用 prefetch

    使用方法:

    通过 Link 标签进行创建:

    <link rel="preload" href="/path/to/style.css" as="style">

    在 HTTP 响应头中加上 preload 字段:

    Link: <https://example.com/other/styles.css>; rel=preload; as=style

    这种方式比通过 Link 方式加载资源方式更快,请求在返回还没到解析页面的时候就已经开始预加载资源了

    prefetch 预判加载与preload 使用方法是一样的

    preload/prefetch的缓存行为

    当资源被 preload 或者 prefetch 后,会从网络堆栈传输到 HTTP 缓存并进入渲染器的内存缓存。 

    • 如果资源可以被缓存(例如,存在有效的 cache-control 和 max-age),它将存储在 HTTP 缓存中,可用于当前和未来的会话。 

    • 如果资源不可缓存,则不会将其存储在 HTTP 缓存中。 相反,它会被缓存到内存缓存中并保持不变直到它被使用。

    用 “preload” 和 “prefetch” 情况下,如果资源不能被缓存,那么都有可能浪费一部分带宽,在移动端请慎用

    preload/prefetch的副作用

    正确使用 preload/prefetch 不会造成二次下载,也就说:当页面上使用到这个资源时候 preload 资源还没下载完,这时候不会造成二次下载,会等待第一次下载并执行脚本。

    对于 preload 来说,一旦页面关闭了,它就会立即停止 preload 获取资源,而对于 prefetch 资源,即使页面关闭,prefetch 发起的请求仍会进行不会中断

    所以,不要将 preload 和 prefetch 进行混用,它们分别适用于不同的场景,对于同一个资源同时使用 preload 和 prefetch 会造成二次的下载

    preload 字体不带 crossorigin 也将会二次获取确保你对 preload 的字体添加 crossorigin 属性,否则他会被下载两次,这个请求使用匿名的跨域模式。这个建议也适用于字体文件在相同域名下,也适用于其他域名的获取(比如说默认的异步获取)。

    preload 和 prefetch 的优先级、

    preload 用 “as” 或者用 “type” 属性来表示他们请求资源的优先级(比如说 preload 使用 as=”style” 属性将获得最高的优先级)。没有 “as” 属性的将被看作异步请求,“Early”意味着在所有未被预加载的图片请求之前被请求(“late”意味着之后)

    例如,preload as =“style”将获得最高优先级,而as =“script”将获得低优先级或中优先级。 这些资源也遵循相同的CSP策略(例如脚本受 script-src 约束)。


    下面是在 Blink 内核的 Chrome 46 及更高版本中不同资源的加载优先级情况著作权归作者所有。

    2019-0411-06.png

    从图中可以看出:(以 Blink 为例)

    1. HTML/CSS 资源,其优先级是最高的

    2. font 字体资源,优先级分别为 Highest/High

    3. 图片资源,如果出现在视口中,则优先级为 High,否则为 Low

    而 script 脚本资源就比较特殊,优先级不一,脚本根据它们在文件中的位置是否异步、延迟或阻塞获得不同的优先级:

    1. 网络在第一个图片资源之前阻塞的脚本在网络优先级中是 High

    2. 网络在第一个图片资源之后阻塞的脚本在网络优先级中是 Medium

    3. 异步/延迟/插入的脚本(无论在什么位置)在网络优先级中是 Low

    当页面 preload 已经在 Service Worker 缓存及 HTTP 缓存中的资源时会发生什么?

    这各情况来说是比较少的,但通常来说,会是比较好的情况 —— 如果资源没有超出 HTTP 缓存时间或者 Service Worker 没有主动重新发起请求,那么浏览器就不会再去请求这个资源了。


    如果资源在 HTTP 缓存中(在SW缓存和网络之间),那么 preload 会从相同的资源中获得缓存命中。

    使用 preload 或 prefetch,可能会浪费用户的带宽,特别是在资源没有缓存的情况下

    没有用到的 preload 资源在 Chrome 的 console 里会在 onload 事件 3s 后发生警告。

    preload/prefetch与 async/defer 加载方式对比

    在《再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载》里面提过:

    h5时代,script添加defer或asyn两个属性(html4.0中定义了defer;html5.0中定义了async)

    • 如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。即:整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。defer不会改变script中代码执行顺序

    • 如果 script 标签中包含 async,则 HTML 文档构建不受影响,不需要等待 async-script 执行。但是,async-script 加载完成后,就会立即执行!如果页面还是没有解析完成,就会停下来(阻塞页面)等此脚本执行完毕再继续解析。async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。而且:多个 async-script 的执行顺序是不确定的

    在资源加载适配方面

    • async/defer只作用于脚本文件,对于样式、图片等资源就无能为力了

    •  preload/prefetch 对脚本、图片、样式都支持,如:audio、document、embed、fetch、font、image、object、script、style、track、worker、video(浏览器暂未实现)

    在资源加载后处理方面

    • async/defer必定执行加载的脚本

    • preload/prefetch只下载资源并不执行,待真正使用到才会执行文件

    建议:对于页面上主/首屏脚本,可以直接使用 defer 加载,而对于非首屏脚本/其它资源,可以采用 preload/prefeth 来进行加载

    webpack优化之preload和prefetch

    单页面应用由于页面过多,可能会导致代码体积过大,从而使得首页打开速度过慢。所以切分代码,优化首屏打开速度尤为重要

    但是所有的技术手段都不是完美的。当我们切割代码后,首屏的js文件体积减少了好多。但是也有一个突出的问题:

    那就是当跳转其他页面的时候,需要下载相应页面的js文件,这就导致体验极其不好,每一次点击访问新页面都要等待js文件下载,然后再去请求接口获取数据。频繁出现loading动画的体验真的不好

    所以如果我们在进入首页后,在浏览器的空闲时间提前下好用户可能会点击页面的js文件,这样首屏的js文件大小得到了控制,而且再点击新页面的时候,相关的js文件已经下载好了,就不再会出现长久的loading动画。

    一般可通过 preload-nginx-webpack-plugin插件实现(具体直接点击链接看官方说明就够了)

    plugins: [
      new HtmlWebpackPlugin(),
      new PreloadWebpackPlugin({
        rel: 'preload',
        include: ['home']
      })]

    但是,这个配置的细粒度不过。此方面可以通过webpack注释去控制(全部通过注释,手写太烦躁咯)

    注意:只有webpack 4版本才支持prefetch功能

    动态引入js文件,实现code-splitting,减少首屏打开时间

    按引入情况加载,只需添加注释即可

    • 代码分割注释:/*webpackChunkName: 'mp-supports'*/

    • prefetch注释:/* webpackPrefetch: true */

    更多的,可以查看 webpack 注释黑魔法:https://webpack.js.org/api/module-methods/#magic-comments

    使用案例

    const { default: lodash } = await import(/* webpackChunkName: "lodash" */ /* webpackPrefetch: true */ 'lodash');
    // Multiple possible targets
    import(
      /* webpackInclude: /\.json$/ */
      /* webpackExclude: /\.noimport\.json$/ */
      /* webpackChunkName: "my-chunk-name" */
      /* webpackMode: "lazy" */
      /* webpackPrefetch: true */
      /* webpackPreload: true */
      `./locale/${language}`
    );

    原来还可以叠罗汉的

    prefetch 配合 Vue 中的路由懒加载代码分割更好用

    Vue项目

    路由

    export default {
      path: '/list/',
      name: 'list',
      component: () => import(/* webpackChunkName: "products" */'@/views/list/index.vue'),
      children: [
        {
          path: 'detail/:id',
          name: 'detail',
          component: () => import(/* webpackPrefetch: true */ /* webpackChunkName: "products" */ '@/views/list/detail.vue'),
          props: route => ({
            osId: route.params.os_id,
          }),
          meta: {
            keepAlive: true,
          },
        }],
    };

    这样就可以根据工程结构,更好的控制打包与预加载。react 项目同理。

    react项目

    // 代码分割后的react组件
    const Demo = asyncComponent(() => import(
     /* webpackPrefetch: true */
     /*webpackChunkName: 'mp-supports'*/
      './views/Brand'
    ))
    
    // 路由引入
    <Route path="/" component={App}>
        <Route path="/brand" component={Demo} />
     </Route>

    组件内加装

    / 在接口取的数据后,进行prefetch
    componentDidUpdate({ topics }) {
      if( topics.length === 0 && this.props.topics.length > 0 ) {
       // 实行prefetch
        import(
            /* webpackPrefetch: true */
            /*webpackChunkName: 'topic'*/
            "../topic"
          )
      }
    }

    vue用法差不多


    参考文章:




    转载本站文章《Preload与Prefetch的区别以及webpack项目中如何优化》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/html5/2020_0702_8505.html