乐趣区

webpacklazy-loading和prefetch

1、lazy loading

我们通过一个示例来说明什么是懒加载。

// a,js
import './common'
console.log('A')
export default 'A'

// b.js
import './common'
console.log('B')
export default 'B'

// common.js
console.log('公共模块')
export default 'common'

// 异步代码
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {console.log(a)
})

import(/* webpackChunkName: 'b'*/ './b').then(function(b) {console.log(b)
})

document.addEventListener('click', () => {
  // 异步加载 lodash 模块
  import('lodash').then(({default: _}) => {console.log(_.join(['3', '4']))
  })
})

目录结构:

配置webpack.config.js

const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  entry: {main: './lazyloading/index.js'},
  output: {path: path.resolve(__dirname, 'build'), // 打包文件的输出目录
    filename: '[name].bundle.js', // 代码打包后的文件名
    publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址
    chunkFilename: '[name].js' // 代码拆分后的文件名
  },
  // 拆分代码配置项
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        lodash: {
          name: 'lodash',
          test: /[\\/]node_modules[\\/]/,
          priority: 10
        },
        common: {
          name: 'common',
          minSize: 0, // 表示在压缩前的最小模块大小, 默认值是 30kb,如果没设置为 0,common 模块就不会抽离为公共模块,因为原始大小小于 30kb
          minChunks: 2, // 最小公用次数
          priority: 5, // 优先级
          reuseExistingChunk: true // 公共模块必开启
        },
        vendors: {test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  },
  plugins: [new CleanWebpackPlugin(), // 会删除上次构建的文件,然后重新构建
    new BundleAnalyzerPlugin()]
}

我们打包执行 index.html,当点击页面的时候,异步加载lodash 并输出内容,演示如下:

第一次进入页面时并没有加载 lodash 模块,当我点击页面时,浏览器再去加载lodash,并执行输出,这就是所谓的懒加载。

其实懒加载就是通过 import 去异步加载一个模块,至于什么时候去加载,这个要根据业务逻辑来写,比如弹窗组件、模态框组件等等,都是点击按钮之后才出现。

懒加载能够加快页面的加载速度,如果你把详情页、弹窗等页面全部打包放在一个 js 中,当用户只是访问首页,只需要首页的代码,不需要其他页面的代码,加入多余的代码只会使得加载时间变长。

import 后面返回的是一个 then,说明这是一个 promise 类型,一些低端的浏览器不支持 promise,比如 IE,如果要使用这种异步的代码,就要使用 babel 以及 babel-polyfill 来做转换,因为我使用的是高版本的 chrome 浏览器,对 ES6 语法是支持的,所以我并没有安装 babel 也能使用。

现在我们再列举一个示例证实懒加载在性能方面的优化

更改index.js

// 异步代码
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {console.log(a)
})

import(/* webpackChunkName: 'b'*/ './b').then(function(b) {console.log(b)
})

document.addEventListener('click', () => {var element = document.createElement('div')
  element.innerHTML = "hello world"
  document.body.appendChild(element)
})

重新打包,并打开 index.html,打开浏览器控制台,按 ctrl + shift + p ,输入 coverage
就能看到当前页面加载的 js 代码未使用率,红色区域表示未被使用的代码段


这里一开始红色区域的代码未被使用,当我点击了页面后,变成绿色,页面出现了 Hello World,说明代码被使用了

页面刚加载的时候,异步的代码根本就不会执行,但是我们却把它下载下来,实际上就会浪费页面执行性能,webpack 就希望 像这样交互的功能,应该把它放到一个异步加载的模块去写

新建一个 click.js 文件

function handleClick() {var element = document.createElement('div')
  element.innerHTML = 'hello world'
  document.body.appendChild(element)
}

export default handleClick

并且将 index.js 文件改为异步的加载模块:

document.addEventListener('click', () => {import('./click.js').then(({default: fn}) => {fn()
  })
})

重新打包,使用 coverage 分析

当加载页面的时候,没有加载业务逻辑,当点击网页的时候,才会加载 0.js 模块,也就是我们在 index.js 中异步引入的 click.js

click.js 文件越大时,懒加载对性能的提升越加明显

所以想写出高性能的代码,不仅仅考虑缓存,更要考虑到代码的使用率

所以 webpack 在打包过程中,是希望我们多写这种异步的代码,才能提升网站的性能,这也是为什么 webpacksplitChunks 中的 chunks 默认是 async

2、prefetch

异步能提高你网页打开的性能,而同步代码是增加一个缓存,对性能的提升是非常有限的,因为缓存一般是 第二次打开网页或者刷新页面 的时候,缓存很有用,但是网页的性能一般是用户 第一次打开网页,看首屏的时候。

当然,这也会出现另一个问题,就是当用户点击的时候,才去加载业务模块,如果业务模块比较大的时候,用户点击后并没有立马看到效果,而是要等待几秒,这样体验上也不好,怎么去解决这种问题

一:如果访问首页的时候,不需要加载详情页的逻辑,等用户首页加载完了以后,页面展示出来了,页面的带宽被释放出来了,网络空闲了,再「偷偷」的去加载详情页的内容,而不是等用户去点击的时候再去加载

这个解决方案就是依赖 webpack 的 Prefetching/Preloading 特性

修改index.js

document.addEventListener('click', () => {import(/* webpackPrefetch: true */ './click.js').then(({default: fn}) => {fn()
  })
})

webpackPrefetch: true 会等你主要的 JS 都加载完了之后,网络带宽空闲的时候,它就会预先帮你加载好

重新打包后刷新页面,注意看 Network

可以看到还没有点击页面时,当主要的 js 都加载完后,0.js(也就是 click.js)在网络空闲时就加载了。点击页面时,第二次加载就利用缓存中的0.js,发现加载时间由29ms 缩减到4ms,可想而知预加载对页面性能提升的重要性。

这里我们使用的是 webpackPrefetch,还有一种是 webpackPreload

区别:

与 prefetch 相比,Preload 指令有很多 不同之处

Prefetch会等待核心代码加载完之后,有空闲之后再去加载。Preload 会和核心的代码并行加载,还是不推荐

总结

针对优化,不仅仅是局限于缓存,缓存能带来的代码性能提升是非常有限的,而是如何让代码的 使用率 最高,有一些交互后才用的代码,可以写到异步组件里面去,通过懒加载的形式,去把代码逻辑加载进来,这样会使得页面访问速度变的更快,如果你觉得懒加载会影响用户体验,可以使用 Prefetch 这种方式来预加载,不过在某些游览器 不兼容 ,会有兼容性的问题,重点不是在 Prefetch 怎么去用,而是在做前端代码性能优化的时候, 缓存不是最重要的点,最重要的是代码使用的覆盖率上(coverage)

退出移动版