webpack 配置知其然知其所以然(持续更新...)

2,284 阅读3分钟

webpack核心模块

  • mode 环境配置,production、development、none三个值,默认production
  • entry 打包入口文件
  • output 打包出口文件
  • loader 文件解析
  • plugins 插件配置
module.exports = {
  mode: 'production'                   // 环境配置,production、development、none三个值,默认production
  entry: './src/index.js',             // 打包入口文件
  output: {                            // 打包出口文件
    filename: '[name]_[hash].js',
    path: path.join(__dirname, './dist')
  },
  module: {                            // 文件loader配置
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: []                         // 插件配置                    
}

loader example

js | jsx

npm install babel-loader @babel/core @babel/preset-env @babel/preset-react --save-dev

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        options: {
          loader: "babel-loader",
          presets: [
            "@babel/preset-env", // es6+ => es5
            "@babel/preset-react", // jsx
          ],
        },
      },
    ],
  },
};

css | less

npm install style-loader css-loader less-loadr less --save-dev

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader"], // loader 从右往左执行
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"], // loader 从右往左执行
      },
    ],
  },
};

watch

原理:轮询判断文件最后编辑时间是否变化,某个文件发生了变化,并不会立即告诉监听者,而生先缓存起来,等 aggregateTimeout 设置时间到了再执行

两种设置方式:

1、npm scripts 带 --watch 参数

"watch" : "webpack --watch"

2、webpack 配置

module.exports = {
  watch: true,
  // watch设置true才有效
  watchOptions: {
    // 默认为空,不监听文件
    ignored: /node_modules/,
    // 监听到变化后会等300ms再执行
    aggregateTimeout: 300,
    // 轮询监听文件,默认每秒轮询次数1000
    poll: 1000,
  },
};

热更新 HMR

热更新:使用 webpack-dev-server, WDS 不刷新浏览器,不输出文件,而是放在内存中;使用 webpack.HotModuleRepleacementPlugin 插件刷新浏览器

  1. webpack 配置
const webpack = require("webpack");

module.exports = {
  plugins: [new webpack.HotModuleRepleacementPlugin()],
};
  1. npm scripts 配置

webpack 4+

// package.json
{
  "scripts": {
    "dev": "webpack-dev-server --open --mode=development"
  }
}

webpack 5+

// package.json
{
  "scripts": {
    "dev": "webpack server --open --mode=development"
  }
}

热更新原理

热更新有两个阶段:

  1. 启动阶段
  2. 文件改变更新阶段
  • Webpack Compile :将 js 编译成对应Bundle文件
  • HMR Server: 将热更新的文件输出给 HMR Runtime
  • Bundle Server: 提供文件在浏览器访问的服务
  • HMR Runtime: 会被注入到浏览器,更新文件的变化

文件指纹

hash、 chunkhash、 contenthash

  1. hash:和整个项目构建相关,项目相关文件发生变化就会改变

  2. chunkhash: 和 webpack 入口 entr 入口文件有关,不同的 entry 会生成不同的 chunkhash 值

  3. contenthash: 根据文件内容有关,只有文件改变才会改变

js 文件指纹设置

const path = require("path");

module.exports = {
  output: {
    filename: "[name]_[chunkhash:8].js",
    path: path.join(__dirname, "./dist"),
  },
};

css 文件指纹设置

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: `[name]_[contenthash:8].css`,
    }),
  ],
};

图片、文件指纹设置

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpeg|jpg|gif)$/,
        use: {
          loader: "file-loader",
          options: {
            name: "[name]_[hash:8].[ext]",
          },
        },
      },
    ],
  },
};

设置 file-loade 的 name,使用 [hash]

文件相关占位符含义

占位符名称含义
[ext]文件后缀名
[name]文件名
[path]文件相对路径
[folder]文件所在的文件夹
[contenthash]文件内容 hash, 默认 md5 生成
[hash]文件内容 hash, 默认 md5 生成

html、css、javascript 代码压缩

html

npm install html-webpack-plugin --save-dev

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      filename: "index.html",
    }),
  ],
};

css

webpack 4.+版本

npm install optimize-css-assets-webpack-plugin cssnano --save-dev

const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const cssnano = require('cssnano');

module.exports = {
  plugins: [
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/,
      cssProcessor: cssnano,
    }),
  ];
}

webpack 5.+版本

npm install css-minimizer-webpack-plugin --save-dev

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin({
        parallel: true, // 启用/禁用多进程并发执行
      }),
    ],
  },
};

javascript

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true, // 启用/禁用多进程并发执行
      }),
    ],
  },
};

mode: production 是开启 js 压缩

清楚构建目录

npm install clean-webpack-plugin --save-dev

const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  plugins: [new CleanWebpackPlugin()],
};

webpack 5+ 使用 output.clean 设置

module.exports = {
  output: {
    clean: true,
  },
};

css3 自动添加厂商前缀

npm install postcss-loader autoprefixer -save-dev

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [["autoprefixer"]],
              },
            },
          },
        ],
      },
    ],
  },
};

抽取公共资源

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: "vendors",
          test: /(react|teact-dom)/,
          chunks: "all",
        },
      },
    },
  },
};

Tree Shaking 原理和分析

分析 代码中以下情况的代码会被 Tree Shaking

  1. 代码不会被执行,不可到达
  2. 代码执行的结果不会被使用
  3. 代码只会影响死变量(只读不写)

原理 利用 es6 模块特点:

  1. 只能作为顶层语句的出现
  2. import 的模块名只能是字符串常量
  3. import binding 是 immutable 的

代码擦除:ugify 阶段删除无用代码

Scope Hoisting 原理和分析

分析 webpack 构建后对每个模块会包裹,增加构建后代码体积;构建后的代码存在大量闭包,运行时创建函数作用域,内存开销变得

原理 构建时将模块引用按照引用顺序放在一个函数作用域里,然后适当的重命名防止变量名冲突

通过 Scope Hoisting 可以减少函数声明代码和内存开发

webpack 4+版本默认已经集成,设置 mode: production, webpack 3 版本设置: new webpack.optimize.ModuleConcatenationPlugin()

const webpack = require("webpack");

module.exports = {
  plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
};

代码分割和动态 import

npm install @babel/plugin-syntax-dynamic-import --save-dev

// .babbelrc
{
  "presets": [],
  "plugins": [
    "@babel/plugin-syntax-dynamic-import"
  ];
}

构建速度和体积优化策略

构建速度

分析 loader 和 plugin 耗时

npm install speed-measure-webpack-plugin --save-dev

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap({
  // webpackConfig
});

优化策略

  1. 多进程、多实例构建

推荐方案

npm install thread-loader --save-dev

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: "thread-loader",
            options: [
              workders: true
            ],
          },
          'babel-loader'
        ],
      },
    ],
  },
};

可选方案

npm install happypack --save-dev

const Happypack = require("happypack");

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ["happypack/loader"],
      },
    ],
  },
  plugins: [
    new Happypack({
      loaders: ["babel-loader"],
    }),
  ],
};
  1. 预编译

使用 Dllplugin 分离基础包

const webpack = require('path');
const path = require('path');

module.exports = {
  entry: {
      library: [
          'react',
          'react-dom' //分离的基础包
      ]
  },
  output: {
      filename: '[name]_[chunkhash:8].dll.js',
      path: path.join(__dirname, './library'),
      library: '[name]'
  },
  plugins: [
    new new webpackDllplugin({
       name: '[name]',
      path: path.join(__dirname, 'manifest.json')
    })
  ]
}

DllReferencePlugin 引入 manifest.json

module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      manifest: require(/* 预编译基础包路径 */),
    }),
  ],
};

缓存

  1. babel缓存 babel-loader?cacheDirectory=true
  2. 代码压缩缓存 zerser-webpack-plguin
  3. 模块缓存 hard-source-webpack-plugin

babel-loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        user: [{
          loader: 'babel-loader',
          options: {
            cacheDirectory: true
          }
        }]
      }
    ]

  }
}

terser-webpack-plugin

hard-source-webpack-plugin

缩小构建目标

构建体积

包大小分析

npm install webpack-bundle-analyzer --save-dev

const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");

module.exports = {
  plugins: [new BundleAnalyzerPlugin()],
};

优化策略

  1. 多进程并行压缩代码

npm install terser-webpack-plugin css-minimizer-webpack-plugin --save-dev

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin({
        parallel: true, // 启用多进程并行压缩代码
      }),
      new TerserPlugin({
        parallel: true, // 启用/禁用多进程并行压缩代码
      }),
    ],
  },
};