如果面试官问你webpack性能优化,你该怎么答?来看看这里。

500 阅读6分钟

最近面试了几家公司,其中有两家问到了webpack的性能优化方案。本人也就答出来了代码分割和懒加载,对于其他的一些优化方案不甚了解,所以回答得不满意。

于是在此总结了一下目前主要的性能优化方案。

温馨提示,本套方案基于webpack4。还有什么方案漏了的话,望补充,谢谢各位。

webpack性能优化

webpack性能优化主要分为两类,开发环境的性能优化和生产环境的性能优化。下面针对这两类来进行梳理。

开发环境性能优化

开发环境的性能优化主要是用来方便我们开发,节约我们的时间的,这主要是用在调试的阶段。主要会涉及到source-map的使用。

  • 优化打包构建速度
  • 优化调试功能

生产环境性能优化

生产环境的优化就比较多了,减小代码体积,减少首屏时间,优化构建速度,优化代码运行性,公共代码块提出等。

  • 优化打包构建速度
  • 优化代码运行的性能
  • 减小代码体积
  • 公共代码块提出

一、开发环境调试代码 source-map

sourcemap是一种技术,提供源代码到构建后代码的映射。(如果构建后代码出错了,通过映射关系,可以追踪到源代码错误,而不会只追踪到框架后面的)

source-map的配置非常简单,只需要在webpack中配置

{
    devtool: 'source-map'
}

配置了之后,再构建,就会生成一个对应的map文件,用于保存代码的映射关系。这些东西看不懂,也不需要看懂。

1621612515112.jpg

并且注意,devtool不止source-map这一个值。还有inline-source-map等值,构建之后表现的效果又有所不同。

{
    devtool: 'inline-source-map'
}

这样的配置会让构建后的文件夹中没有map文件。转而把之前生成的map文件直接加到对应js文件的后面。如下图所示:

2.jpg

这样做的好处就是,因为是内联的map文件,webpack少构建了资源,构建速度更快。

几种方式构建的效果如下图

4.jpg

这里要特地说一下, nosources-source-map,其可以让发生错误之后,点击错误信息追踪不到源码,可以隐藏源码信息。

例如代码中如果引入了一个没有的css文件ac.css

import './css/ac.css'

浏览器控制台报错如下:

5.jpg

但你点击错误信息,会提示你不能看到index.js内部的内容,

Could not load content for webpack:///./src/index.js (HTTP error: status code 404, net::ERR_UNKNOWN_URL_SCHEME)

于是,源码信息得到了隐藏。

总结

  1. 开发环境: source-map 和 inline-source-map

  2. 生产环境

    • 隐藏源码用 nosources-source-map。
    • 不隐藏源码 直接source-map或者inline-source-map。

二、 生产环境webpack缓存

webpack缓存可以通过给文件名添加hash值来实现,

hash: 每个文件都生成自己的hash

我们设置设置 output 的 filename 或者 MiniCssExtractPlugin 插件内的filename分别为

// 针对js文件
[name].[hash:10].js
// 针对css文件
[name].[hash:10].css

这样的配置,会让每个文件名都包含自己的hash值。

7.jpg

如果不去修改相应资源,打包后hash值不会改变。但是一旦有修改资源,就会触发hash值的变化。 这里我们修改了入口js文件之后再构建。

8.jpg

资源变化,反应到线上就是会重新请求对应的资源。

chunkhash: 每个代码块都生成一个hash

如果把上面的 [hash: 10] 改为 [chunkhash: 10],那么项目分为2个代码块,1.built 和 built。这两个代码会分别生成不同的hash值。

9.jpg

并且:

  • 改1.built的话,built不会改变。
  • 改built的话,1.built不会改变。

比如修改了入口文件index.js的代码,打包结果并不会影响到1.built.js的hash值。如下:

10.jpg

contenthash: 每个资源都会生成自己hash

这样做的好处是,如果js资源改变只生成js资源的hash 不会影响css资源,改谁谁变。

image.png

这个是webpack缓存的主要实现方式。

三、tree shaking

字面意思树摇,或者树震。 用来剔除无用的资源和那些没有用到的代码,减少打包体积。

条件:

  1. production模式打包,且用es6模块化
  2. 在package.json中实现
{
    "sideEffects": [
        "*.css",
        "*.less"
    ]
}
  • sideEffects: false 代表所有代码都没有副作用 都可以进行tree shaking,问题:css打包文件会消失,慎用。

  • sideEffects: [".css", ".less"],表示除了css和less文件,其他文件都要tree-shaking。

四、code split 代码分割

主要研究js代码的代码分割

1.根据入口文件代码分割

{
  // 多入口代码分割 可以进行多页面应用
  entry: {
    main: './src/js/index.js',
    test: './scr/js/test.js',
  }
}

2.optimization分割

webpack配置如下代码,主要用于将node_modules中的代码单独打包成1个chunk 最终输出 将别人的第三方的东西拿出来单独打包 而且会分析多入口文件中有没有公共的依赖,如果有,会打包成单独的一个chunk

optimization: {
  splitChunks: {
    chunks: 'all'
  }
}

3.import()函数代码分割

import(/* webpackChunkName: 'test' */'./index2').then(() => {
  console.log(333)
}).catch(() => {
  console.log(222)
})

会将index2单独打包。

五、懒加载 预加载

懒加载

一般用import()函数进行懒加载 事件中加载,then方法再处理逻辑

document.getElementById("btn").onclick = function() {
  // 懒加载
  import(/* webpackChunkName: 'test' */'./test').then((mul) => {
    console.log(mul(4, 5))
  })
}

绑定事件中用import()函数,会在事件触发时才加载相应资源。

预加载

webpackPrefetch: true 预加载 在import函数中配置webpackPrefetch,会对资源进行预加载,提高事件触发的时候的效率。

document.getElementById("btn").onclick = function() {
  // 懒加载
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then((mul) => {
    console.log(mul(4, 5))
  })
}

六、PWA 渐进式网络开发

利用workbox webpack中利用workbox-webpack-plugin

const workboxWebpackPlugin = require('workbox-webpack-plugin')

new workboxWebpackPlugin.GenerateSW({
  clientsClaim: true, // 帮助serviceworker快速启动 删除旧的serviceworker
  skipWaiting: true // 跳过等待
})

判断serviceworker是否可用

// 在项目入口
if ('serviceworker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceworker.register('/service-worker.js').then(() => {
      console.log('注册成功');
    }).catch(
      console.log('注册失败');
    )
  })
}

七、多进程打包

webpack4 用 thread-loader

八、externals 防止某些依赖打包进我们的项目

比如jquery 我们需要用cdn 就用webpack的externals属性来让其不打包进项目

{
  externals: {
    jquery: 'jQuery'
  }
}

九、oneof

作用:提升构建速度,避免每个文件都被所有loader过一遍,因为任何一个文件,构建过程中,在遇到第一个与之对应的loader后,不会再往下进行。

正常来讲,所有文件在执行的时候,都要将loader中的rules过一遍,如果符合,就被对应loader处理,不符合则直接过。这样对性能不好,为了解决这个问题,使用oneof。

oneOf里面的loader只匹配一个。不能有两个配置处理同一种类型的文件。

oneOf: [
          {
            test: /\.css$/,
            use: [......]
          },
          {
            test: /\.less$/,
            use: [......]
          }
        ]

配置loader为oneof,因为loader解析都是从下往上来的,一个css文件也要先经过less-loader解析,这就会降低构建速率。如果配置oneof,css文件就会只经过css-loader处理。