性能优化(二)

280 阅读3分钟

terser

Terser是一个JavaScript的解释(Parser)、Mangler(绞肉机)/Compressor(压缩机)的工具集

Terser可以帮助我们压缩、丑化我们的代码,让我们的bundle变得更小

terser和postcss和babel一样都是独立的工具,即可以独立使用,也可以结合webpack等构建工具一起使用

npm install terser -g

在webpack中有一个minimizer属性,在production模式下,默认就是使用TerserPlugin来处理我们的代码的

如果我们对默认的配置不满意,也可以自己来创建TerserPlugin的实例,并且覆盖相关的配置

optimization: {
  // 开启minimize后,webpack才会依次去调用minimizer中的插件进行使用
  // production模式下 minimize的默认值即为true,可以不单独设置
  minimize: true,
    minimizer: [
      new TerserPlugin({
        // 不抽取代码
        extractComments: false,
        // 使用多进程并发运行提高构建的速度,默认值是true
        // 并发运行的默认数量: os.cpus().length - 1
        parallel: true,
        // terser的配置项
        // 具体配置文档 https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
        // terserOptions: {}
      })
    ]
  }
}

压缩css

CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等

CSS的压缩我们可以使用另外一个插件:css-minimizer-webpack-plugin

css-minimizer-webpack-plugin是使用cssnano工具来优化、压缩CSS

npm install css-minimizer-webpack-plugin -D
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')

optimization: {
  minimize: true,
    
  // 普通插件配置在plugins 选项下
  // 压缩插件配置在minimizer 选项下
  minimizer: [
    new TerserPlugin({
      extractComments: false,
    }),

    new CssMinimizerPlugin()
  ]
}

配置分离

将项目配置文件调整为

config # 位于项目根目录下 --- 用于存放项目相关的配置文件
├── common.config.js # 通用配置文件
├── prod.config.js # 生成环境配置文件
└── sit.config.js # 开发环境配置文件
// 调整package.json下的script命令值
"build": "webpack --config ./config/common.config.js --env prod",
"serve": "webpack serve  --config ./config/common.config.js --env sit"
// common.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// ps: 不是默认导出,是具名导出
const { merge } = require('webpack-merge')
const devConfig = require('./dev.config')
const prodConfig = require('./prod.config')

const getConfig = function(env) {
  return {
    // entry是基于context的
    // 即path.resolve(context的值, entry的值)
    // context的值是项目根目录
    entry: './src/main.js',
    output: {
      // 因为配置文件放置到了config下
      // 所以为了让构建后的内容存放到项目根目录
      // 需要将build修改为../build
      path: path.resolve(__dirname, '../build'),
      filename: 'js/[name]-[chunkhash]-bundle.js',
      chunkFilename: 'js/[name]-[chunkhash]_chunk.js',
      clean: true
    },
    resolve: {
      extensions: ['.js', '.json', '.jsx', '.ts']
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            env.prod ? MiniCssExtractPlugin.loader : 'style-loader',
            'css-loader'
          ]
        }
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: 'index.html'
      })
    ]
  }
}

// 可以直接导出一个函数
// 这个函数需要返回一个配置对象
// 参数是命令行运行时候传入的配置对象
module.exports = function(env) {
  const config = env.prod ? prodConfig : devConfig

  return merge(getConfig(env), config)
}
// dev.config.js

const path = require('path')
const WebpackDevServer = require('webpack-dev-server')

module.exports = {
  mode: 'development',
  devServer: {
    port: 3000,
    host: WebpackDevServer.internalIPSync('v4'),
    compress: true,
    static: ['public'],
    historyApiFallback: true,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        pathRewrite: {
          '^/api': ''
        },
        changeOrigin: true
      }
    }
  }
}
// prod.config.js

const path = require('path')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')


module.exports = {
  mode: 'production',
  optimization: {
    runtimeChunk: {
      name: 'runtime'
    },
    splitChunks: {
      chunks: 'all',
      minSize: 10,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          filename: 'js/[name]_[chunkhash].js'
        },
        math: {
          test: /[\\/]math[\\/]/,
          filename: 'js/[name]_[chunkhash].js'
        }
      }
    },
    minimize: true,
    // 普通插件配置在plugins 选项下
    // 压缩插件配置在minimizer 选项下
    minimizer: [
      new TerserPlugin({
        extractComments: false,
      }),

      new CssMinimizerPlugin()
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name]_[chunkhash].css',
      chunkFilename: 'css/[name]_[chunkhash]_chunk.css'
    })
  ]
}

Tree Shaking

Tree Shaking是一个术语,最早起源于LISP,在计算机中表示消除死代码(dead_code)

只有纯函数才可以被tree shaking,一个非纯函数,即使没有被使用,也无法被tree shaking

对JavaScript进行Tree Shaking是源自打包工具rollup

Tree Shaking依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)来确定哪些代码可以被tree shaking

在webpack中有两种实现tree shaking的方案

  1. usedExports: 通过标记某些函数是否被使用,之后通过Terser来进行优化
  2. sideEffects: 跳过整个模块/文件,直接查看该文件是否有副作用

usedExports

在usedExports设置为true时,会有一段注释: unused harmony export mul

这是段魔法注释,用于告知Terser在优化时,可以删除掉这段代码

 optimization: {
   // 该属性在production模式下会被自动设置为true
   // 所以一般不需要手动配置
   usedExports: true
 }

sideEffects

sideEffects 是一个 webpack 特有的选项,它可以用来告诉 webpack 在做代码分割时,哪些模块是有副作用(side effect)的

默认情况下,webpack会认为所有的模块都是副作用模块

// package.json
// 值为false的时候,表示所有模块都是纯模块
// 此时即使该模块有副作用,也会被移除
// 也可以指定某些特定的模块是纯模块
"sideEffects": [
  "*.css"
]

Scope Hoisting

在开发模式下,webpack会降每一个模块中的内容放在一个IIFE中

无论是从最开始的代码运行,还是加载一个模块,都需要执行一系列的函数

作用域提升是指Webpack 会尽量把所有模块的代码打包到一个函数中,然后把这个函数放在最顶层

从而让代码在运行时更快,并且打出来的包也更小

在production模式下,默认这个模块就会启用

在development模式下,我们需要自己来打开该模块

const webpack = require('webpack')

plugins: [
   new webpack.optimize.ModuleConcatenationPlugin()
]

http 压缩

HTTP压缩是一种内置在 服务器 和 客户端 之间的,以改进传输速度和带宽利用率的方式

  1. 将已经压缩的文件部署在服务器上
  2. 浏览器在请求的时候会通过Accept-Encoding来告知浏览器支持哪些压缩格式
  3. 服务器会将浏览支持的对应压缩格式文件进行返回,并通过Content-Encoding来告知浏览器返回的是那种压缩格式
  4. 浏览器会自动对对应的资源进行解压,并进行解析

目前常见的压缩格式

对http内容进行压缩的压缩算法和普通的文件压缩算法不同

http内容的压缩算法都是单文件压缩算法,一次只能压缩一个文件

格式说明
compressUNIX的“compress”程序的方法
历史性原因,已被淘汰
deflate基于deflate算法(定义于RFC 1951)的压缩,使用zlib数据格式封装
gzipGNU zip格式(定义于RFC 1952),是目前使用比较广泛的压缩算法
bz一种新的开源压缩算法,专为HTTP内容的编码而设计
# 使用compression-webpack-plugin对文件进行压缩
# 打包后会存在原文件和打包后的文件
# 如果浏览器支持压缩格式,就传输压缩格式
# 如果浏览器不支持压缩格式,就传输原始文件
npm install compression-webpack-plugin -D
const CompressionPlugin = require("compression-webpack-plugin")

module.exports = {
  plugins: [
    // CompressionPlugin在配置中存在一个默认的压缩比
    // 如果文件压缩后,压缩比小于配置中的压缩比
    // 那么就表示文件压缩的意义并不大,因此就不会对其进行压缩
    new CompressionPlugin({
      // 对那些文件进行压缩
      test: /\.(js|css)$/,
      // 采用那种压缩算法
      algorithm: 'gzip'
    })
  ],
};

html压缩

plugins: [
  // 在生产环境,会自动使用HtmlWebpackPlugin对html模板进行压缩
  // 配置项 template --- 模板
  // 配置项 inject --- true、 false 、body、head
  // cache --- 默认值为true --- 只有当文件改变时,才会生成新的文件
  // minify --- 默认会使用一个插件html-minifier-terser
  //        --- 可以对html的压缩进行自定义配置
  new HtmlWebpackPlugin({
    template: 'index.html'
  })
]

打包分析

时间分析

如果我们需要查看每个包的构建时间,来分析那个模块或那个loader的构建时间比较长

可以使用speed-measure-webpack-plugin

#  speed-measure-webpack-plugin 和 mini-css-extract-plugin 冲突 不能一起使用
npm install speed-measure-webpack-plugin -D
const speedMeasurePlugin = require('speed-measure-webpack-plugin')
const SMP = new speedMeasurePlugin()

module.exports = function(env) {
  const config = env.prod ? prodConfig : devConfig
  const finalConfig = merge(getConfig(env), config)
  
  // SMP.wrap会对配置进行拦截,添加分析功能后返回一个新的配置对象
  return SMP.wrap(finalConfig)
}

文件分析

stats.json

"scripts": {
# --profile --- 进行文件分析
# --json=<文件名> --- 将分析结果输出到那个文件中
"build": "webpack --config ./config/common.config.js --env prod --profile --json=stats.json",
"serve": "webpack serve  --config ./config/common.config.js --env sit"
}
  1. 去github clone webpack/analyse
  2. 安装依赖并通过 npm run dev运行项目
  3. 将json文件传达到运行网页中,即可得到分析结果

webpack-bundle-analyzer

我们可以通过webpack-bundle-analyzer来非常直观查看包大小

npm install webpack-bundle-analyzer -D
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

plugins: [
  new HtmlWebpackPlugin({
    template: 'index.html'
  }),
  new BundleAnalyzerPlugin()
]