webpack优化

659 阅读4分钟

提取CSS

因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来加载

npm install mini-css-extract-plugin --save-dev

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  mode: 'development',
  devtool: false,
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
+    publicPath: '/'
  },
  module: {
    rules: [
      { test: /.txt$/, use: 'raw-loader' },
+      { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
+      { test: /.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
+      { test: /.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
       {
        test: /.(jpg|png|gif|bmp|svg)$/,
        type:'asset/resource',
        generator:{
          filename:'images/[hash][ext]'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './src/index.html' }),
+   new MiniCssExtractPlugin({
+      filename: '[name].css'
+   })
  ]
};

可以在调用MiniCssExtractPlugin的时候指定将css放在哪个目录下:

+   new MiniCssExtractPlugin({
+      filename: 'style/[name].css'
+   })

图片优化

webpack5以后,对于文件的处理,使用type: 'asset/resource',而不再使用url-loader、file-loader

  module: {
    rules: [
      {
        test: /.(jpg|png|gif|bmp|svg)$/,
        type:'asset/resource',
        generator:{
          filename:'images/[hash][ext]'
        }
      }

\

  module: {
    rules: [
      {
        test: /.(jpg|png|gif|bmp|svg)$/,
        type:'asset', //必定会输出一个文件
        parser: {
          //根据这个条件做选择,如果小于maxSize的话就变成base64字符串,如果大于的就拷贝文件并返回新的地址
        	dataUrlCondition: {
          	maxSize: 4 * 1024
          }
        },
        generator:{
          filename:'images/[hash][ext]'
        }
      }

压缩HTML、css、js

js压缩:之前有UglifyJsPlugin等插件,但UglifyJsPlugin不支持ES6,因此现在用TerserPlugin更多

css压缩:OptimizeCssAssetsWebpackPlugin,可能会和webpack5不兼容,因此需要改用CssMinimizerPlugin

HTML压缩:HtmlWebpackPlugin里直接配置

图片不推荐用webpack压缩

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
+  mode: 'none',
  devtool: false,
  entry: './src/index.js',
+  optimization: {
+    minimize: true,
+    minimizer: [
+      new TerserPlugin(),
+    ],
+  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/',
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    compress: true,
    port: 8080,
    open: true,
  },
  module: {
    rules: [
      {
        test: /.jsx?$/,
        loader: 'eslint-loader',
        enforce: 'pre',
        options: { fix: true },
        exclude: /node_modules/,
      },
      // ...
      {
        test: /.(jpg|png|gif|bmp|svg)$/,
        type:'asset/resource',
        generator:{
          filename:'images/[hash][ext]'
        }
      },
      {
        test: /.html$/,
        loader: 'html-loader',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html',
+     minify: {  
+        collapseWhitespace: true,
+        removeComments: true
      }
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    }),
+    new OptimizeCssAssetsWebpackPlugin(),
  ],
};

clean-webpack-plugin可以每次在执行build之前将原来打包的dist目录清除掉

purgecss-webpack-plugin

  • 可以去除未使用的 css,一般与 glob、glob-all 配合使用
  • 必须和mini-css-extract-plugin配合使用
  • paths路径是绝对路径

npm i purgecss-webpack-plugin mini-css-extract-plugin css-loader glob -D

const path = require("path");
+const glob = require("glob");
+const PurgecssPlugin = require("purgecss-webpack-plugin");
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const PATHS = {
  src: path.join(__dirname, 'src')
}
module.exports = {
  mode: "development",
  entry: "./src/index.js",
  module: {
    rules: [
      {
        test: /.js/,
        include: path.resolve(__dirname, "src"),
        use: [
          {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-env", "@babel/preset-react"],
            },
          },
        ],
      },
+      {
+        test: /.css$/,
+        include: path.resolve(__dirname, "src"),
+        exclude: /node_modules/,
+        use: [
+          {
+            loader: MiniCssExtractPlugin.loader,
+          },
+          "css-loader",
+        ],
+      },
    ],
  },
  plugins: [
+    new MiniCssExtractPlugin({
+      filename: "[name].css",
+    }),
+    new PurgecssPlugin({
+      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
+    })
  ],
};

CDN

  • HTML放在自己的服务器上,不缓存,关闭服务器缓存,每次访问服务器都可以获取最新的资源

  • 里面的静态文件js css image都指向CDN的地址

  • js css image都放在CDN上,并且文件名带上hash值,hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。

  • 为了可以并行加载,需要把不同类型的文件和不同的文件放在不同的CDN服务器上

  • 为了避免同一时间对同一个域名请求数并发的限制,不同的资源放在不同的域名服务器进行并行加载,但

多个域名后会增加域名解析时间

  • dns-prefetch DNS预解析,可以通过在 HTML HEAD 标签中 加入去预解析域名,以降低域名解析带来的延迟

三种hash值

  • hash:代表整个项目,每次webpack构建时生成一个唯一的hash值,即使css没动过,只动了js,打包后这个hash依然会变,同样的,公共代码vendor虽然没有改变,但它的hash也会变

vendor.4305adaee27b2a3244e5.js

main.4305adaee27b2a3244e5.js

style/main.4305adaee27b2a3244e5.css

  • chunkhash

每个入口都有自己的chunkhash

如果本入口对应的文件发生改变,chunkhash会改变,如果没有改变,chunkhash会保持不变

但是对于css来说,我们通常都是在js文件中通过import './index.css'这种方式引入的,如果我们改了js文件,css文件的chunkhash也会跟着改,这时就要用到contenthash:

        new MiniCssExtractPlugin({
            filename: 'style/[name].[contenthash].css'
        }),

treeshaking

webpack4的tree-shaking原理是找import进来的变量在模块中是否出现过

webpack5可以进行各作用域之间的关系进行优化

tree-shaking 必须是esm模块规范才会生效

ModuleConcatenationPlugin

hello.js:

export default 'hello';

entry.js:

import hello from './hello';
console.log(hello);

hello模块只导出了一个常量,然后在entry中直接被引入,经过ModuleConcatenationPlugin处理后,hello.js并不会单独作为一个模块放到module中,而是直接将'hello',放到entry模块里:

(() => {
	"use strict";
  var exports = {};
  /*
  ****./src/entry1.js + 1 modules ****
  */
  ;// CONCATATION_MODULE: ./src/hello.js
  const hello = ("hello");
  ;// CONCATATION_MODULE: ./src/index.js
  console.log(hello);
})()

babel-loader增加缓存

            {
                test: /.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'thread-loader',
                        options: {
                            workers: 3
                        }
                    },
                    {
                        loader: 'babel-loader',
                        options: {//babel编译 后把结果缓存起来,下次编译 的时候可以复用上次的结果
                            cacheDirectory: true
                        }
                    }
                ]
            },

babel-loader自带缓存,所以可以通过配置参数来实现,但有些loader不带缓存,就需要配置额外的cache-loader来实现

假设babel-loader没有缓存,我们就可以进行如下配置:

npm install cache-loader -D

            {
                test: /.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: 'cache-loader'
                    },
                    {
                        loader: 'babel-loader',
                        options: {//babel编译 后把结果缓存起来,下次编译 的时候可以复用上次的结果
                            cacheDirectory: true
                        }
                    }
                ]
            },

但在webpack5中,已经不需要这个loader了,直接写在webpack配置里即可:

module.exports = {
    mode: 'development',
    devtool: false,
    entry: './src/index.js',
  	cache: {
    	type: 'filesystem', // memory|filesystem
      cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack')
    }

可以看到我们配置的cache.type是filesystem,即在磁盘持久缓存

这个缓存的配置会缓存生成的webpack模块和chunk,来改善构建速度

webpack5中默认开启cache.type为'memory'的缓存

moduleId和chunkId的优化

module:每一个文件都可以看成是一个module

chunk:webpack打包最终生成的代码块,代码块会生成文件,一个文件对应一个chunk

在webpack5以前,没有从entry打包的chunk文件,都会以1 2 3的文件命名方式输出:

例如,下面这些以import动态加载进来的模块:

index.js:

import('./one')
import('./two')
import('./three')

这样打包后会生成chunk.0.js chunk.1.js chunk.2.js 3个包

如果在某次更改代码时,我们去掉了import('./two')

那会生成chunk.0.js chunk.1.js两个包,而这一次生成的chunk.1.js和上一次的chunk.1.js是不一样的,因此,我们删除掉的import('./two'),可能会导致缓存失败

在webpack5中,做了一些优化,这些模块名默认会变成以路径区分:

例如:src_chunks_one_js.main

我们也可以手动配置这个名字的生成规则

这个名字生成的规则是可以在optimize里面配置的:

module.exports = {
    mode: 'development',
    devtool: false,
    entry: './src/index.js',
    optimization: {
        moduleIds: 'named', // 
        chunkIds: 'named',

其中deterministic在生产环境中用的较多

sideEffect副作用

package.json中可以配置sideEffect的值:

{
  "name": "9.optimize",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "sideEffects": [
    "*.css"
  ],

配置值为["*.css"],代表不干掉css文件