webpack性能优化


webpack性能优化

1. 代码分离

Webpack中常用的代码分离有三种:

1.1 入口起点

使用entry配置手动分离代码;也就是多入口文件来实现代码分离;

注意:多入口起点的话就要注意重复引入,导致体积过大的问题,可配置共享解决此问题

module.exports = {
  mode: 'development',
  devtool: false,
  // entry: './src/index.js',
  entry: {
    index: {
      import: './src/index.js',
      dependOn: 'shared' //可共享
    },
    main: {
      import: './src/main.js',
      dependOn: 'shared' //可共享
    },
    shared: ['axios'] //配置共享的模块
  },
  output: {
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: '[name]-bundle.js', //[name]也就是entry对应的名称
    clean: true //每次打包的前先清理之前的打包文件
  },
}

1.2 自定义分包

另外一种分包的模式是splitChunk,它底层是使用SplitChunksPlugin来实现的:
 因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件;
 只需要提供SplitChunksPlugin相关的配置信息即可;

 // 优化配置
  optimization: {
    // 设置生成的chunkId的算法
    // development: named
    // production: deterministic(确定性)
    // 早期webpack4中使用: natural
    chunkIds: 'deterministic',
    // runtime的代码是否抽取到单独的包中(早Vue2脚手架中)
    runtimeChunk: {
      name: "runtime"
    },
    // 分包插件: SplitChunksPlugin
    splitChunks: {
      chunks: "all",//包括异步引入和同步引入,而async只仅仅针对异步的
      // 当一个包大于指定的大小时, 继续进行拆包
       maxSize: 20000,//当分出来的包又大于20000,则继续拆包
      // // 将包拆分成不小于minSize的包
      minSize: 10,//最小的包体积

      // 自己对需要进行拆包的内容进行分包
      cacheGroups: {
        utils: {
          test: /utils/,
          filename: "[id]_[hash:6]_utils.js" //hash是哈希值
        },
        vendors: {
          // /node_modules/
          // window上面 /\
          // mac上面 /
          test: /[\\/]node_modules[\\/]/,
          filename: "[id]_vendors.js"
        }
      }
    },
    // 代码优化: TerserPlugin => 让代码更加简单 => Terser
    minimizer: [
      // JS代码简化
      new TerserPlugin({
        extractComments: false
      })
      // CSS代码简化
    ]
  },

1.3 动态导入

通过模块的内联函数调用来分离代码;(动态路由)

1.3.1 两种实现动态导入的方式:

第一种,使用ECMAScript中的 import() 语法来完成,也是目前推荐的方式;
第二种,使用webpack遗留的 require.ensure,目前已经不推荐使用;

比如我们有一个模块 bar.js:
 该模块我们希望在代码运行过程中来加载它(比如判断一个条件成立时加载);
 因为我们并不确定这个模块中的代码一定会用到,所以最好拆分成一个独立的js文件;
 这样可以保证不用到该内容时,浏览器不需要加载和处理该文件的js代码;
 这个时候我们就可以使用动态导入;

1.3.2 动态导入的文件命名

[name]是文件路径各个层级组合而成的名称,默认是有点丑的

  output: {
    clean: true,
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: '[name]-bundle.js',
    // 单独针对分包的文件进行命名,[name]是路径名称
    chunkFilename: '[name]_chunk.js'
  },

引入的时候命名;以通过magic comments(魔法注释)的方式

import(/* webpackChunkName: "about" */'./router/about').then(res => {
    res.about()
    res.default()
  })

2. 预加载preload和预获取prefetch

2.1 区别

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。

2.2 如何使用?

预加载和预获取不是在配置文件配置的,是在代码引入的时候配置的;如下

btn2.onclick = function() {
  import(
    /* webpackChunkName: "category" */
    /* webpackPrefetch: true */
    './router/category')
}

3. cdn

3.1 配置

  output: {
    clean: true,
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: '[name]-bundle.js',
    // 单独针对分包的文件进行命名
    chunkFilename: '[name]_chunk.js',
    // publicPath: 'http://coderwhycdn.com/'
  },

3.2 html中使用

 <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.2.0/axios.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>

3.2 排除打包

因为使用的是cnd,则排除打包

  // 排除某些包不需要进行打包
  externals: {
    react: "React",
    // key属性名: 排除的框架的名称
    // value值: 从CDN地址请求下来的js中提供对应的名称
    axios: "axios"
  },

3.3 推荐较为知名的cdn服务器

 国际上使用比较多的是unpkg、JSDelivr、cdnjs;
 国内也有一个比较好用的CDN是bootcdn;

4. 提取css

如果用的是css-loader和style-loder,那么css代码是集成到js文件里面的,不是单独的css;需要做如下的配置;

4.1 插件安装

npm install mini-css-extract-plugin -D

4.2 使用

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const { ProvidePlugin } = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  mode: 'development',
  devtool: false,
  // entry: './src/index.js',
  entry: './src/main.js',
  output: {
    clean: true,
    path: path.resolve(__dirname, './build'),
    // placeholder
    filename: 'js/[name]-bundle.js',
    // 单独针对分包的文件进行命名
    chunkFilename: 'js/[name]_chunk.js',
    // publicPath: 'http://coderwhycdn.com/'
  },
  resolve: {
    extensions: ['.js', '.json', '.wasm', '.jsx', '.ts']
  },
  devServer: {
    static: ['public', 'content'],
    port: 3000,
    compress: true,
    proxy: {
      '/api': {
        target: 'http://localhost:9000',
        pathRewrite: {
          '^/api': ''
        },
        changeOrigin: true
      }
    },
    historyApiFallback: true
  },
  // 优化配置
  optimization: {
    // 设置生成的chunkId的算法
    // development: named
    // production: deterministic(确定性)
    // webpack4中使用: natural
    chunkIds: 'deterministic',
    // runtime的代码是否抽取到单独的包中(早Vue2脚手架中)
    runtimeChunk: {
      name: "runtime"
    },
    // 分包插件: SplitChunksPlugin
    splitChunks: {
      chunks: "all",
      // 当一个包大于指定的大小时, 继续进行拆包
      // maxSize: 20000,
      // // 将包拆分成不小于minSize的包
      // minSize: 10000,
      minSize: 10,

      // 自己对需要进行拆包的内容进行分包
      cacheGroups: {
        utils: {
          test: /utils/,
          filename: "js/[id]_utils.js"
        },
        vendors: {
          // /node_modules/
          // window上面 /\
          // mac上面 /
          test: /[\\/]node_modules[\\/]/,
          filename: "js/[id]_vendors.js"
        }
      }
    },
    // 代码优化: TerserPlugin => 让代码更加简单 => Terser
    minimizer: [
      // JS代码简化
      new TerserPlugin({
        extractComments: false
      })
      // CSS代码简化
    ]
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: "babel-loader",
        }
      },
      {
        test: /\.ts$/,
        use: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: [
          // 'style-loader', 开发阶段
          MiniCssExtractPlugin.loader, // 生产阶段
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    }),
    // 完成css的提取
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
      chunkFilename: 'css/[name]_chunk.css'
    })
  ]
}

5. js压缩丑化代码Terser(减少体积)

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

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

相关文档
https://github.com/terser/terser#compress-options
https://github.com/terser/terser#mangle-options

5.1 安装

npm install terser -D

5.2 使用

首先,我们需要打开minimize,让其对我们的代码进行压缩(默认production模式下已经打开了)

其次,我们可以在minimizer创建一个TerserPlugin:

  • extractComments:默认值为true,表示会将注释抽取到一个单独的文件中;
    ✓ 在开发中,我们不希望保留这个注释时,可以设置为false;
  • parallel:使用多进程并发运行提高构建的速度,默认值是true
    ✓ 并发运行的默认数量: os.cpus().length - 1;
    ✓ 我们也可以设置自己的个数,但是使用默认值即可;
  • terserOptions:设置我们的terser相关的配置
    ✓ compress:设置压缩相关的选项;
    ✓ mangle:设置丑化相关的选项,可以直接设置为true;
    ✓ toplevel:顶层变量是否进行转换;
    ✓ keep_classnames:保留类的名称;
    ✓ keep_fnames:保留函数的名称;
   minimize: true,
    // 代码优化: TerserPlugin => 让代码更加简单 => Terser
    minimizer: [
      // JS压缩的插件: TerserPlugin
      new TerserPlugin({
        extractComments: false,
        terserOptions: {
          compress: {
            arguments: true,
            unused: true
          },
          mangle: true,
          // toplevel: false
          keep_fnames: true
        }
      }),
      // CSS压缩的插件: CSSMinimizerPlugin
      new CSSMinimizerPlugin({
        // parallel: true
      })
    ]

6. css压缩

官方对应章节

css-minimizer-webpack-plugin
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
 new CSSMinimizerPlugin({
        // parallel: true
      })

7. css tree Shaking

用来清除未使用的css样式

npm install purgecss-webpack-plugin -D

const glob = require('glob')
const { PurgeCSSPlugin } = require('purgecss-webpack-plugin')
  plugins: [
    // 完成css的提取
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
      chunkFilename: 'css/[name]_chunk.css'
    }),
    // 对CSS进行TreeShaking
    new PurgeCSSPlugin({
      paths: glob.sync(`${path.resolve(__dirname, '../src')}/**/*`, { nodir: true }),
      safelist: function() {
        return {
          standard: ["body"]
        }
      }
    })
  ]

8 文件压缩gzip

npm install compression-webpack-plugin -D
 plugins: [
    // 完成css的提取
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
      chunkFilename: 'css/[name]_chunk.css'
    }),
    // 对CSS进行TreeShaking
    // new PurgeCSSPlugin({
    //   paths: glob.sync(`${path.resolve(__dirname, '../src')}/**/*`, { nodir: true }),
    //   safelist: function() {
    //     return {
    //       standard: ["body"]
    //     }
    //   }
    // }),
    // 作用域提升
    new webpack.optimize.ModuleConcatenationPlugin(),
    // 对打包后的文件(js/css)进行压缩
    new CompressionPlugin({
      test: /\.(js|css)$/,
      algorithm: 'gzip'
    })
  ]

版权声明:本文为weixin_42424283原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。