webpack5优化配置

271 阅读7分钟

一、定位错误信息 SourceMap

1、SourceMap是什么

SourceMap是源代码映射,使源代码和构建后代码可以一一映射。

它会生成一个xxx.map文件,里面包含源代码和构建后代码每一行和每一列的映射关系。当构建后的代码出错了,会通过xxx.map文件找到源代码出错的位置,从而让浏览器可以提示源代码中出错的位置

2、为什么需要配置SourceMap

在浏览器中运行的代码是经过webpack编译的,大概这个样子:

image.png

可以看到,所有的js和css都合并成了main.js,并且多了很多其他代码和注释。此时,如果项目中有一行代码运行出错了,很难定位到错误的位置。

例如,我现在故意将sum方法写错,控制台上会报错,但是点(main.js662)进到源代码后,没有明确的告知是哪里报错了,点(sum.js:11:8)进入源代码后,稍微好点,定位sum.js文件中有报错,但是如果sum.js中有一千行代码,这还是找不到错误的是哪一行。

a.gif

3、开发模式下使用

配置文件(webpack.dev.js):

  devtool: 'cheap-module-source-map' // 只包含行映射,没有列映射,打包速度快

重启:npm start

可以看到,配置devtool后,当我们写的代码里有错误时,webpack编译后可以精确定位到错误的文件和位置: a.gif

4、生产模式下使用

配置文件(webpack.prod.js):

  devtool: 'source-map' // 包含行/列映射,打包速度慢

编译:npm run prod

可以看到,在编译后,生成了main.js.map,这个文件就是将编译后的代码和源代码的关系一一映射了,咱们运行index.html可以看到报错信息,会指出源代码中出错的位置:

image.png

二、提升打包构建速度

1、热模块替换HotModuleReplacement

概念:

在开发模式下,当我们改动一个文件时,只需要对这一个模块进行更新,而不需要加载整个页面。这样一来,会大大提高编译的速度

使用: 配置文件(webpack.dev.js):只需要在开发模式下配置

  devServer: {
    host: 'localhost',
    port: 3000,
    hot: true // 默认为true,开启状态
  }

当hot为false时,修改任何一个文件,都会触发浏览器的刷新: 动图.gif

当hot为true时(默认状态),修改css文件时,可以看到,只更新了这一个文件: 动图.gif

但此时,如果改变js文件,还是会触发浏览器刷新,需要在main.js中设置:

// 判断是否支持HMR功能
if (module.hot) {
  module.hot.accept('./js/sum.js', str => {
    console.log('HMR>>>>>>>>>' + str + '发生改变')
  })
}

可以看到,当sum.js发生改变时,只会加载这一个文件,而count.js改变时,会触发浏览器的更新: 动图.gif

实际开发中不会这样一个个引入js文件,可以使用vue-loader,react-hot-loader来解决。

可以在webpack-reactcli配置中搜一下react-refresh/babelReactRefreshWebpackPlugin,这是用来激活js的hmr

2、oneOf

在编译时,每个文件都会经过所有的loader,然后通过正则匹配到对应的loader处理。比如说一个less文件,会从rules中每个对象都进行一遍正则匹配,这样效率会比较低下,oneOf意思是一个文件只需要匹配到loader,就不再往下匹配

image.png

配置(开发和生产一样):将rules中的对象搬到oneOf这个数组中

  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/, // 匹配 .css 结尾的文件
            use: ['style-loader', 'css-loader'] // 执行顺序:从右到左
          },
          {
            test: /\.less$/,
            use: ['style-loader', 'css-loader', 'less-loader']
          },
          {
            test: /\.s[ac]ss$/,
            use: ['style-loader', 'css-loader', 'sass-loader']
          },
          {
            test: /\.styl$/,
            use: ['style-loader', 'css-loader', 'stylus-loader']
          },
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: 'asset',
            parser: {
              dataUrlCondition: {
                maxSize: 130 * 1024 // 小于130kb的图片会被base64处理
              }
            },
            generator: {
              filename: 'static/imgs/[hash:8][ext][query]'
            }
          },
          {
            test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
            type: 'asset/resource',
            generator: {
              filename: 'static/media/[hash:8][ext][query]'
            }
          },
          {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules
            loader: 'babel-loader'
          }
        ]
      }
    ]
  }

3、include/exclude

  • include:表示只处理某些文件
  • exclude:表示除了某些文件外,其他文件都处理

在开发中,经常要下载一些第三方的插件,这些文件都下载到node_modules目录中了,这些文件是不需要webpack编译可以直接使用的,所以在使用webpack对js文件进行处理时,需要排除node_modules目录

配置:(开发和生产一样)

          {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules
            loader: 'babel-loader'
          }

或者:

          {
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules
            include: path.resolve(__dirname, '../src'),
            loader: 'babel-loader'
          }

eslint排除node_modules的配置:

    new ESLineWebpackPlugin({
      context: path.resolve(__dirname, '../src'), // 检测src目录下的文件
      exclude: 'node_modules' // exclude默认值是node_modules
    })

4、cache

每次打包时,都要经过eslint检查和babel编译,我们可以缓存之前的eslint和babel,这样在第二次及往后的打包速度就会快些,cache就是来干这件事的

配置:(主要是生产环境)

          {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules
            loader: 'babel-loader',
            options: {
              cacheDirectory: true, // 开启babel缓存
              cacheCompression: false // 关闭缓存文件压缩,因为压缩需要时间
            }
          }
          
          
    new ESLineWebpackPlugin({
      context: path.resolve(__dirname, '../src'), // 检测src目录下的文件
      exclude: 'node_modules',
      cache: true, // 开启eslint缓存
      cacheLocation: path.resolve(
        __dirname,
        '../node_modules/.cache/eslintCache'
      ) // 设置缓存目录
    })

再次执行npm run prod时,会发现.cache目录下多了一个目录babel-loader和一个文件eslintCache: image.png

五、thread-loader多进程打包

当项目体积越来越大时,打包的速度会随之变慢,想要继续提升打包速度可以开启多进程进行打包,thread-loader就是用来开启多进程进行打包的,但是要注意,开启进程本身是有额外的开销的,每个进程的开启大约有600ms

使用:(开发和生产一样)

1、下载包

npm i thread-loader -D

2、配置文件

webpack处理js文件主要针对eslint、babel、terser(开启生产模式,webpack5会自动对js进行压缩,就是通过terser处理的)

const os = require('os')

...

const TerserPlugin = require('terser-webpack-plugin') // 内置插件,不需要下载
const { length: threads } = os.cpus()

...

          // 1、babel处理
          {
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules
            include: path.resolve(__dirname, '../src'),
            use: [
              {
                loader: 'thread-loader', // 开启多进程
                options: { workers: threads } // 设置进程数
              },
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true, // 开启babel缓存
                  cacheCompression: false // 关闭缓存文件压缩,因为压缩需要时间
                }
              }
            ]
          }
      
...
      
    // 2、eslint处理  
    new ESLineWebpackPlugin({
      context: path.resolve(__dirname, '../src'), // 检测src目录下的文件
      threads // 开启多进程,并设置进程数(.cache目录下不生成eslintCache文件了。。。)
    }),
    // 3、terser处理
    new TerserPlugin({ parallel: threads }) // 开启多进程,并设置进程数

webpack5推荐将压缩代码的配置从plugins中搬到optimization.minimizer中:

  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin(), // css压缩,仅生产环境需要
      new TerserPlugin({ parallel: threads }) // webpack5会默认添加new TerserPlugin(),由于我们需要添加配置项,所以这里手动写上
    ]
  }

三、减少代码体积

1、tree shaking

在开发时,定义或引入第三方工具库,没有特殊处理的话打包时会引入整个库,实际上可能只用上极少的功能,这样就造成了打包后的臃肿。

tree shaking就是将没有使用到的js代码,不用打包进来。

webpack已经默认开启了这个功能,不需要额外配置

举例说明:

  1. src/js/treeShaking.js:
export const a = () => {
  console.log('aaa')
}

export const b = () => {
  console.log('bbb')
}

exports.c = function () {
  console.log('ccc')
}
  1. main.js:
import { a } from './js/treeShaking'
a()
  1. 执行打包命令

npm run prod

  1. 效果:可以看到,当使用es6的模块化时,只有当你使用了a函数,才会被打包,b函数就没有被打包;当使用commonJS的模块化时,树摇的功能就失效了,不管有没有使用到c函数,c函数都会被打包

image.png

2、@babel/plugin-transform-runtime减少代码体积

babel为每个被编译的文件都插入辅助代码,使得代码体积变大。

babel对一些公共方法使用了辅助代码,比如_extend,假如有10个文件都使用了_extend,那么_extend就会被定义了10次。

可以将_extend作为一个独立模块,避免重复的定义。

@babel/plugin-transform-runtime禁用了babel自动对每个文件runtime注入,而是使用这个库里的辅助代码,避免了重复的定义。

怎么用:

  1. 下载包
npm i @babel/plugin-transform-runtime -D
  1. 配置(开发和生产一样)
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true, // 开启babel缓存
                  cacheCompression: false, // 关闭缓存文件压缩,因为压缩需要时间
                  plugins: ['@babel/plugin-transform-runtime'] // 减少代码体积
                }
              }

3、image minimizer

针对项目中引用的本地图片,可以使用webpack对其进行压缩,压缩可以是无损压缩,也可以是有损压缩,减少打包后的体积

使用:

  1. 下载包:
npm i image-minimizer-webpack-plugin imagemin -D

无损压缩:

cnpm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D

有损压缩:

cnpm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
  1. 配置:以无损压缩为例

webpack.prod.js

const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')

...

optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin(), // css压缩
      // new TerserPlugin({ parallel: threads })
      // 压缩图片
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              [
                'svgo',
                {
                  plugins: [
                    'preset-default',
                    'prefixIds',
                    {
                      name: 'sortAttrs',
                      params: { xmlnsOrder: 'alphabetical' }
                    }
                  ]
                }
              ]
            ]
          }
        }
      })
    ]
  }
  1. 运行命令

npm run prod

可以看到,压缩后,图片体积变小了: image.png

  1. 注意

无损压缩那几个包挺难下,这里我使用cnpm安装好了,还有可能在打包时报错:

Error: Error with 'src\images\1.jpeg': '"C:\Users\86176\Desktop\webpack\webpack_code\node_modules\jpegtran-bin\vendor\jpegtran.exe"'
Error with 'src\images\3.gif': spawn C:\Users\86176\Desktop\webpack\webpack_code\node_modules\optipng-bin\vendor\optipng.exe ENOENT

这是因为在下载包时,有的包没下载下来

需要手动地将两个文件复制到node_modules中:

  • 将jpegtran.exe复制到node_modules\jpegtran-bin\vendor目录下
  • 将optipng.exe复制到node_modules\optipng-bin\vendor 目录下
  • 地址

四、优化代码运行性能

1、code split代码分割,按需加载

代码分割是什么

以上,在webpack配置文件中入口文件一直都是这样写的:entry: './src/main.js',这是只有一个入口文件的写法,通常入口文件都叫main.js,打包输出的通常也只有一个文件,一般和入口文件保持同名,也叫main.js。

但有的时候,我们希望在渲染首页的时候,就只加载首页的js文件,所以需要将打包生成的文件进行代码分割,生成多个js文件,渲染当前页面时只加载当前页面所需js文件,这样一来,就可以实现按需加载。

如何实现代码分割

多入口

  1. 下载包
npm i webpack webpack-cli html-webpack-plugin -D
  1. 文件目录
├── public
├── src
|   ├── app.js
|   └── main.js
├── package.json
└── webpack.config.js

public/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.0" />
    <title>代码分割-多入口</title>
  </head>
  <body>
    <h1>代码分割-多入口</h1>
  </body>
</html>

src/app.js

console.log('----app')

src/main.js

console.log('----main')

webpack.confing.js

const path = require('path')
const HtmlWebplacePlugin = require('html-webpack-plugin')

module.exports = {
  entry: {
    main: './src/main.js',
    app: './src/app.js'
  },
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'js/[name].js', // 使用chunk作为bundle的文件名。chunk:entry中引入的文件就叫chunk,输出到dist中的文件就叫bundle
    clean: true
  },
  plugins: [new HtmlWebplacePlugin({ template: './public/index.html' })],
  mode: 'production'
}

  1. 执行命令

npx webpack

可以看到,配置了几个入口,就可以输出几个js文件: image.png

提取重复代码

如果入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,所以需要将这一部分代码抽取出来,生成一个js文件,在其他文件中引入就好。

  1. 修改文件和配置

src/math.js

export const sum = (...args) => args.reduce((a, b) => a + b, 0)

src/app.js

import { sum } from './math'

console.log('----app', sum(1, 2, 3))

src/main.js

import { sum } from './math'

console.log('----main', sum(1, 2, 3, 4))

webpack.config.js

  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: 'all', // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      // cacheGroups: { // 组,哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重(越大越高)
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
      // 修改配置
      cacheGroups: {
        // 组,哪些模块要打包到一个组
        // defaultVendors: { // 组名
        //   test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
        //   priority: -10, // 权重(越大越高)
        //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
        // },
        default: {
          // 其他没有写的配置会使用上面的默认值
          minSize: 0, // 我们定义的文件(math.js)体积太小了,所以要改打包的最小文件体积
          minChunks: 2, // math.js中的sum方法至少得在不同的入口文件中被引入和使用2次才会被抽离出来
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
  1. 执行命令

npx webpack

可以看到,当app.js和main.js中都使用sum方法后,math.js中的sum方法会被单独打包出来,叫做52.jsimage.png

动态导入实现按需加载

上面的math.js中的sum方法被app.js和main.js引入后会抽离出来,打包成52.js,但是52.js是在页面打开后就立即被载入了:

image.png

我们希望当我用到其时再加载其,这个时候就需要用到动态导入了

public/index.html

    <button id="btn">点击我载入sum方法</button>

src/app.js

console.log('----app被载入')

src/main.js

console.log('----main被载入')
const btn = document.querySelector('#btn')

btn.addEventListener('click', () => {
  // import动态导入语法,只在这里引入一次,也会进行代码分割
  import('./math.js').then(({ sum }) => {
    console.log('----main', sum(1, 2))
  })
})

执行npx webpack,打开dist/index.html

可以看到,52.js是在点击按钮后被加载进来的: 动图.gif

单入口

实际开发中都是单入口的模式,拿生产环境来测试:

public/index.html

    <button id="btn">动态加载</button>

main.js

document.querySelector('#btn').onclick = function () {
  import(/* webpackChunkName: 'sum' */ './js/sum').then(res => {
    console.log('动态加载', res.default(1, 2, 3))
  })
}

.eslintrc.js(需要支持动态导入的语法)

  parserOptions: {
    ecmaVersion: 11 // ES 语法版本
  }

webpack.prod.js

  entry: './src/main.js',
  
  ...
  
  optimization: {
    ...
    
    splitChunks: { chunks: 'all' }
  }

执行打包命令:npm run prod,使用import动态引入的sum.js会被打包成258.main.js:

image.png

此时,页面加载时,还没有用到sum.js,就没有加载,当点击按钮时,动态载入:

动图.gif

更新配置

使用单入口+代码分割+动态导入的方式来进行配置

webpack.prod.js

  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: 'static/js/[name].js', // 使用chunk的名字命名bundle的名字
    chunkFilename: 'static/js/[name].chunk.js', // 动态导入输出资源命名方式
    assetModuleFilename: 'static/media/[name].[hash][ext]', // 图片、字体等资源命名方式
    clean: true
  }
  
  
          // rules中对于图片、字体等资源的文件名配置可以注释掉
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: 'asset',
            parser: {
              dataUrlCondition: {
                maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
            }
            // generator: {
            //   filename: 'static/imgs/[hash:8][ext][query]'
            // }
          },
          {
            test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
            type: 'asset/resource'
            // generator: {
            //   filename: 'static/media/[hash:8][ext][query]'
            // }
          }
          
  optimization: {
    ...
    
    splitChunks: { chunks: 'all' }
  }
  

image.png

2、preload/prefetch

前面介绍了在【单入口】情况下进行代码分割,就是在optimization中设置splitChunks,再使用import动态导入语法,这样在打包时,会将sum方法打包成一个单独的js文件,这样便可以实现在用到sum方法时才加载sum,这个就是懒加载。

但是万一sum这个资源特别的大,用户在点击按钮时才进行加载,可能会造成卡顿,我们希望在浏览器空闲时间,默默地加载后续所需要的资源,就需要用到preload和prefetch

是什么优先级兼容性使用场景
preload告诉浏览器立即加载资源优先级高兼容性稍好只能加载当前页面所需资源,当前页面优先级高的资源用preload加载
prefetch告诉浏览器在空闲时间加载资源优先级低兼容性低可以加载当前页面资源,也可以加载下一个页面资源

prefetch可以加载下一个页面的资源,从特点来看,更适合实际使用,但它的兼容性更低,所以一般使用preload。

webpack5依赖@vue/preload-webpack-plugin,但实际我使用时没看出来效果,后面再看

3、contenthash和runtime结合使用

浏览器的缓存:以上的proload和prefetch都是利用了浏览器的缓存,但是当前后输出的文件名一样时,比如都叫main.js,可能会因为文件名没有变化导致浏览器直接读取缓存了,为了避免这种情况,从文件名入手,确保更新前后文件名发生改变。

更改文件名的几种方式:

  1. fullhash(webpack4是hash):每次修改任何一个文件,所有的hash值都改变,这意味着,一旦修改了任一文件,整个项目的缓存都将失效

a.gif

  1. chunkhash:根据不同的入口文件进行依赖文件解析、构建对应的chunk,生成对应的hash值。

  2. contenthash:根据文件内容生成hash值,只有文件内容变化了,hash值才会变化,所有文件的hash值是独享且不同的

a.gif

所以我们一般使用contenthash,但是这样还是不行,因为我改动sum.js后再进行打包,sum.js文件的hash值发生变化,这是很正常的,但是main.js的hash值也发生变化了,这就不合理了,这会导致main.js的缓存失败。

原因:main.js中引入了sum.js,当sum.js的hash值变化了,在main.js中的引入的代码也会跟着变,所以导致main.js也发生变化了,所以main.js的hash值跟着变

解决办法:将hash值单独保管在一个runtime文件中,最终会输出main.js、sum.js、runtime.js,此时当sum.js变化时,只会影响到sum.js和runtime.js,main.js不变

webpack.prod.js

  optimization: {
    ...
    
    // 提取runtime文件
    runtimeChunk: { name: (entrypoint) => `runtime~${entrypoint.name}` }
  }

可以看到,当我只修改sum.js时,main.js的hash没有变化: a.gif

总结:contenthash可以保证在文件内容发生变化时,缓存失效。runtime可以保证当前模块的变化只会导致当前文件的hash发生变化,不影响其他文件的hash。

4、core-js

用来兼容promise、async、includes等ES6后面出来的语法

5、pwa

开发web项目,一旦离线就没法访问了。pwa是一种可以提供类似于native app(原生应用程序)体验的web app的技术,在离线时可以继续运行,内部通过service workers技术实现。

使用:

  1. 下载包
npm i workbox-webpack-plugin -D
  1. webpack.prod.js
const WorkboxPlugin = require('workbox-webpack-plugin');


    new WorkboxPlugin.GenerateSW({
      // 这些选项帮助快速启用 ServiceWorkers
      // 不允许遗留任何“旧的” ServiceWorkers
      clientsClaim: true,
      skipWaiting: true
    })
  1. main.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then(registration => {
        console.log('SW registered: ', registration);
      })
      .catch(registrationError => {
        console.log('SW registration failed: ', registrationError);
      });
  });
}
  1. 打包 npm run prod

  2. 打开dist/index.html

  • 如果直接打开,控制台会出现SW registration failed: ... image.png 此时,请求service-worker.js文件的路径是http://127.0.0.1:5500/service-workers.js,而实际路径应该为http://127.0.0.1:5500/dist/service-workers.js image.png

  • 解决路径问题,安装npm i serve -g,执行server dist,浏览器访问http://localhost:3000/ image.png 离线也能访问: image.png

五、总结

从4个角度对webpack和代码进行优化:

1、定位错误信息

  1. 使用SourceMap让开发或线上代码的报错信息更加准确

2、提升webpack打包构建速度

  1. 使用hmr,在开发时,只重新编译发生变化的代码,不变的代码使用缓存,从而使热更新速度更快
  2. 使用oneOf,可以让源文件一旦被某个loader处理了,就不会继续遍历了,减少无效的loader匹配
  3. 使用include/exclude,排除或只检测某些文件,比如讲node_modules排除掉
  4. 使用cache,对eslint和babel处理的结果进行缓存,让第二次及以后的打包速度更快
  5. 使用thread-loader,开启多进程打包,但是进程的开启是有很大开销的,慎用

3、减少代码体积

  1. 使用tree shaking,将用到的js代码打包,没有用到的js代码不用打包进来,这个是默认开启了的,但是需要使用es6的模块化语法
  2. 使用@babel/plugin-transform-runtime,将babel的辅助代码独立出来,不用在每个文件中都生成辅助代码
  3. 使用image minimizer,对本地图片进行压缩

4、优化代码运行性能

  1. 使用code split,对代码进行分割(splitChunks),使单个js文件体积更小,结合import动态导入,可以实现按需加载
  2. 使用preload/prefetch,利用浏览器空闲时间,提前加载需要用到的资源。preload只能加载当前页面的资源,prefetch可以加载下一个页面所需资源,但是兼容性更差
  3. 使用contenthash和runtime,在利用network cache时,如果文件名都叫main.js时,可能会直接读取缓存。contenthash可以确保当文件内容发生变化时,文件名会发生变化,从而缓存失效;runtime通过runtimeChunk配置,可以将hash值抽离出来(runtime.js),可以确保当某个文件(如sum.js)发生变化时,打包时只会更新sum.xxx.js和runtime.xxx.js,而不会影响到其他文件,这样其他文件便可以读取缓存
  4. 使用core-js,对高版本的ES语法做兼容性处理
  5. 使用pwa,在离线(network offline)状态下,也可以访问