webpack指南(优化篇)——webpack项目优化

系列文章目录

webpack指南(基础篇)——手把手教你配置webpack


文章目录

  • 系列文章目录
  • 前言
  • 一、概述
  • 二、传输性能优化
    • 1.代码压缩
      • 1.1 css压缩
      • 1.2 js压缩
      • 1.3 进一步压缩js
      • 1.4 压缩html
      • 1.5图片压缩
    • 2.摇树优化(Tree Shaking)
    • 3.作用域提升
    • 4.抽取公共模块(splitChunks)
    • 5.按需加载
    • 6.优化按需加载
    • 7.利用浏览器缓存
    • 8.切换为cdn资源
      • 1.打包分析
      • 2.切换为cdn
  • 三、构建过程优化
      • 1.忽略对第三方包的解析
      • 2.忽略第三方包的指定目录
      • 3.并行打包


前言

面试的时候总是会问用webpack对项目做过哪些优化,今天我们就来详细列举一下常用的优化手段;
前面我们利用webpack构建了一个项目,今天我们在原项目基础上进行优化,还没有观看的小伙伴可以移步看一下。

一、概述

关于webpack常见的性能优化,我们可以从两个方面去着手考虑:
1.传输性能优化;通过优化代码体积等手段提高资源传输速度,达到优化目的;
2.构建过程优化;主要是提高webpack的打包速度;


二、传输性能优化

1.代码压缩

进行代码压缩,减少资源包体积

1.1 css压缩

CssMinimizerWebpackPlugin这个插件使用 cssnano 优化和压缩 CSS

安装

 yarn add css-minimizer-webpack-plugin -D

使用

// 引入
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// 配置
  //  webpack内置的优化配置项
  optimization: {
    // 为true时 告知webpack 使用配置的插件压缩 bundle。生产环境默认开启
    minimize: true,
    // 配置优化插件
    minimizer: [
         new CssMinimizerPlugin(),
    ],
  },

效果
配置前:
webpack指南(优化篇)——webpack项目优化_第1张图片

配置后:配置后

1.2 js压缩

webpack自带的配置默认开启js压缩,我们保留原来配置即可

    minimizer: [
      // 使用 `...` 语法来扩展现有的 minimizer,保留原webpack的优化措施
      //   `...`,
      new CssMinimizerPlugin(),
    ],

开启前:
webpack指南(优化篇)——webpack项目优化_第2张图片
开启后:
webpack指南(优化篇)——webpack项目优化_第3张图片

1.3 进一步压缩js

有时候默认的配置无法满足我们的需求,我们需要自定义一些压缩规则,这时候就需要使用terser-webpack-plugin自己配置了;

安装

yarn add terser-webpack-plugin -D

使用

const TerserPlugin = require('terser-webpack-plugin');
devtool: 'source-map',
  //  webpack内置的优化配置项
  optimization: {
    // 为true时 告知webpack 使用配置的插件压缩 bundle。生产环境默认开启
    minimize: true,
    // 配置优化插件
    minimizer: [
      // 使用 `...` 语法来扩展现有的 minimizer,保留原webpack的优化措施
      //   `...`,
      new CssMinimizerPlugin(),
      new TerserPlugin({
        // 匹配要压缩的文件
        test: /\.js(\?.*)?$/i,
        // 配置参数
        terserOptions: {
          format: {
            // 删除注释
            comments: false,
          },
          compress: {
            // 移除所有console相关代码;
            drop_console: true,
            // 移除自动断点功能;
            drop_debugger: true,
            // 配置移除指定的指令,如console.log,alert
            pure_funcs: ['console.log', 'console.error'],
          },
        },
        // 是否抽离注释
        extractComments: false,
      }),
    ],
  },

压缩后
在这里插入图片描述

值得注意的是,开启TerserPlugin 优化必须配置devtool为terse-webpack-plugin支持的选项:source-map,inline-source-map!!否则不生效这个配置项的作用我们随后再介绍;

1.4 压缩html

我们通过修改html-webpack-plugin的配置来实现压缩

    new HtmlWebpackPlugin({
      template: './index.html',
      minify: {
        // 移除空格
        collapseWhitespace: true,
        // 移除注释
        removeComments: true,
      },
    }),

压缩前:
webpack指南(优化篇)——webpack项目优化_第4张图片
压缩后:
在这里插入图片描述

1.5图片压缩

image-webpack-loader基于webpack的图片压缩工具,压缩方案采用 imagemin ,代码实现比较全面,支持 Minify PNG, JPEG, GIF, SVG and WEBP images 等,支持各个版本 webpack,github start 1625

安装

cnpm install --save-dev image-webpack-loader file-loader

使用

      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        // type: 'asset',
        use: [
          'file-loader',
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: {
                progressive: true,
              },
              optipng: {
                enabled: false,
              },
              pngquant: {
                quality: [0.5, 0.65],
                speed: 4,
              },
              gifsicle: {
                interlaced: false,
              },
              // the webp option will enable WEBP
              webp: {
                quality: 75,
              },
            },
          },
        ],
      },

效果:5kb压缩至2kb
在这里插入图片描述
在这里插入图片描述
踩坑:
enn…一言难尽
1.安装loader必须用cnpm,别不信邪…
2.不是所有的图片都能压缩成功,目前没有找出规律,所以谨慎使用吧。

ok,关于代码压缩就先介绍到这里;更多参数配置都可以移步npm查看;

2.摇树优化(Tree Shaking)

简单来说就像摇晃一颗树一样,把没有根的叶子晃下来;在代码里就是移除没有被引用的代码;

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export

演示
我们新建个模块文件module.js,导出module1和module2代码如下:

export function module1() {
  console.log('module1');
}
export function module2() {
  console.log('module2');
}

在index.js中只引入module1

import { module1 } from './utils/module';
module1();

打包后可以看到,module2函数也被打包在内
在这里插入图片描述
解决
在optimization中配置usedExports: true;

 // 检查代码是否使用
 usedExports: true,

打包后module2被排除
在这里插入图片描述
但是这样也是有问题的,因为usedExports 依赖于 terser 去自动检测语句中的副作用,他的策略比较保守,有时候我们需要告诉插件我们的这个代码没有副作用,可以全部优化,这个时候我们需要对代码进行标记,让terser 强制优化;

使用sideEffects标记代码
可以通过 package.json 的 sideEffects声明哪些文件可以被优化;fasle表示无副作用,可以优化,true表示谨慎对待,也可以提供一个数组;

演示
在全局对象上挂一个方法,terser 不会优化此方法

window.onload = function () {
  console.log(123);
};

在这里插入图片描述
现在我们声明我们的代码没有副作用,可以优化;

// 所有代码都可以优化
"sideEffects": false

可以看到,确实没有了onload,但是同时也发现项目中的css和静态资源也没有了
在这里插入图片描述
在这里插入图片描述
sideEffects使用数组,告诉插件数组内的是有副作用的,不能优化,其他的无副作用,可以优化;

  "sideEffects": [
    "*.css",
    "*.jpg"
  ]

webpack指南(优化篇)——webpack项目优化_第5张图片
sideEffects原理
webpack 能将标记为 side-effects-free 的包由 import {a} from xx 转换为 import {a} from ‘xx/a’,是根据你的引入关系进行判断的,从而自动修剪掉没必要要的 import,如果写一个方法没有被导出的话,是无法被优化的;
如下图,onscroll打包有依旧存在
webpack指南(优化篇)——webpack项目优化_第6张图片
在这里插入图片描述

3.作用域提升

将具有引用关系的代码尽可能的合并,减少代码量;

// optimization中插入如下
concatenateModules: true,

4.抽取公共模块(splitChunks)

SplitChunks插件是webpack中用来提取或分离代码的插件,主要作用是提取公共代码,减少代码被重复打包,拆分过大的js文件,合并零散的js文件

webpack 将根据以下条件自动拆分 chunks:

  • 新的 chunk 可以被共享,或者模块来自于 node_modules 文件夹
  • 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
  • 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
  • 当加载初始化页面时,并发请求的最大数量小于或等于 30
    我们可以自己去修改一些配置项,常用配置如下:
    //   分包
    splitChunks: {
      // 选取使用哪些chunks进行优化
      chunks: 'all',
      //   被引用次数超过该阈值的模块才会被拆包处理
      minChunks: 2,
      //   打包后的分包数量
      maxInitialRequests: 10,
      maxAsyncRequests: 10,
      //   生成 chunk 的最小/大体积(kb)
      minSize: 2000,
      maxSize: 200000,
      // 为不同的包配置不同的策略,缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项
      // 当不配置cacheGroups时,内部会存在vendors和default默认配置,我们也可以通过手动更改默认值。
      cacheGroups: {
        // 自定义的缓存组名称
        common: {
          name: 'common',
          //   始终为此缓存组创建分包
          enforce: true,
        },
      },
    },

5.按需加载

对于一些不需要一打开页面就展示的资源,我们可以使用按需加载的方式,减少起始页面压力;

标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入
import()返回一个promise对象;

import('/modules/my-module.js')
  .then((module) => {
    // Do something with the module.
  });

6.优化按需加载

有时候模块包比较大,用户操作后再进行加载会有一定的延时,影响用户体验,我们可以配合webpack的魔法注释,使用预加载或预拉取进行优化;

使用prefetch或preload标签预加载模块;prefetch标签可以告诉浏览器在后台加载指定的资源,以便在未来某个时刻使用。preload标签则可以告诉浏览器在当前页面加载时立即加载指定的资源。通过使用这两种标签,可以在浏览器闲置时提前加载一些模块,从而提高程序响应速度。

// 预加载
import(/*webpackPrefetch:true*/ '@/assets/01.png');
// 预拉取
import(/*webpackPreload:true*/ '@/assets/01.png');

7.利用浏览器缓存

浏览器回对加载资源默认进行缓存,当加载资源名称没有改变时,会从缓存数据库中读取;我们可以配置打包后没有修改的文件hash值不变,使浏览器从缓存中读取文件;

// 修改output中filename属性,contenthash代表文件内容改变时更新hash
filename: '[name].[contenthash].js',

8.切换为cdn资源

1.打包分析

// 安装webpack-bundle-analyzer
yarn add -D webpack-bundle-analyzer
// 引入
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
// 使用
new BundleAnalyzerPlugin(),

执行命令后发现:jquery占了很大体积,我们尝试把它切换为cdn资源
webpack指南(优化篇)——webpack项目优化_第7张图片

2.切换为cdn

// 配置排除打包
  externals: {
    // 排除打包 npm包名:window.对象名
    jquery: 'jQuery',
  },

效果:jquery确实没有了,包小了很多,但同时,用到jquery的功能也失效了,我们需要在页面中将jquery的引入换为cdn
webpack指南(优化篇)——webpack项目优化_第8张图片

// index.html中引入
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

三、构建过程优化

随着项目体积越来越大,打包时耗费时间也越来越多,我们可以通过一些配置去优化这个过程;

1.忽略对第三方包的解析

webpack打包过程会先解析再打包,对于比较成熟的第三方包我们可以忽略解析这个过程l;

// module中配置noParse
 noParse: /jquery/,

2.忽略第三方包的指定目录

对于多语言组件库如moment,我们用不到的语言包就可以排除;使用方法如下:

let Webpack = require('webpack');
plugins:[
	new Webpack.IgnorePlugin(/\.\/locale/,/moment/),//moment这个库中,如果引用了./locale/目录的内容,就忽略掉,不会打包进去
]

这时候locale目录都会被忽略,我们需要手动引入下中文包:

import 'moment/locale/zh-cn';
moment.locale('zh-cn');

3.并行打包

顾名思义,开启多线程打包,使用happypack

// 下载
yarn add happypack -D
// 使用
// 多线程打包
const happypack = require('happypack');
const os = require('os');
// 创建进程池
const happyThreadPool = happypack.ThreadPool({
  // 获取电脑cpu数,作为线程数
  size: os.cpus().length,
});

// 配置plugin,以babel-loader为例
    new HappyPack({
      // 进程名称,每个线程独一无二
      id: 'happypack1',
      //   当前线程要使用的loader
      loaders: [
        {
          loader: 'babel-loader',
          options: {
            // 降级es6
            presets: ['@babel/preset-env'],
            //   修改辅助代码引入
            plugins: ['@babel/plugin-transform-runtime'],
          },
        },
      ],
      threadPool: happyThreadPool,
    }),
 // 改写js babel-loader关联到进程
   {
        test: /\.js$/i,
        // 排除node_modules下的文件
        exclude: /node_modules/,
        // 用url的形式把规则和线程关联起来
        use: 'happypack/loader?id=happypack1',
        // use: {
        //   loader: 'babel-loader',
        //   options: {
        //     // 降级es6
        //     presets: ['@babel/preset-env'],
        //     //   修改辅助代码引入
        //     plugins: ['@babel/plugin-transform-runtime'],
        //   },
        // },
      },
 

happypack目前并不支持所有的loader,有一些兼容性问题,使用需注意

ok,关于webpack打包优化就介绍到这里,码字不易,欢迎三连;

你可能感兴趣的:(前端学习笔记,webpack,javascript,前端)