webpack5配置实践篇

1,936 阅读4分钟

前言

webpack的配置真的是又多又杂,而且随着webpack从v4到v5之后又发生很多的变化,正好把一个react+ts项目中的webpack5的配置全部梳理了一遍,在此记录一下实践过程,以便以后查阅起来更方便。

配置文件目录结构

首先是配置文件的目录结构。

.
├── utils.js
├── webpack.common.js // 公有webpack配置文件
├── webpack.dev.js // development环境配置文件
└── webpack.prod.js // production环境配置文件

webpack依赖安装

npm i webpack webpack-cli -D

配置输入输出

  • 项目入口文件配置:
// webpack.common.js
module.exports = {
    entry: {
        index: './src/index.tsx'
    }
}
  • 项目输出文件配置: development模式的output如下:
output: {
    publicPath: '/',
    filename: 'js/[name].bundle.[fullhash].js',
    assetModuleFilename: 'img/[hash][ext][query]',
    path: resolveApp('dist'),
}

production模式的output如下:

output: {
    filename: 'js/[name].[contenthash:8].bundle.js',
    chunkFilename: 'js/[name].[contenthash:8].chunk.js',
    assetModuleFilename: 'img/[hash][ext][query]',
    publicPath: '',
    path: resolveApp('dist'),
}
  • 文件名中的hash的作用

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

  • 输出文件的fullhash, chunkhash, contenthash区别
  • fullhash: 若打包文件有任何一个文件发生了改动,hash值都会发生变化。
  • chunkhash: chunkhash和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值,这样可以避免所有打包的hash值都改变。
  • contenthash: contenthash主要根据提取出来的文件的内容进行hash。

我们的项目开发环境采用的js的输出文件名是fullhash(hash格式webpack5已被废弃)输出格式,生产环境采用的是contenthash的输出格式。

loader配置

ts,tsx相关文件支持

  • 依赖安装:
npm i esbuild-loader babel-loader ts-loader -D
  • 配置编写
// webpack.common.js
const devMode = process.env.NODE_ENV !== "production";
module: {
    rules: [{
        test: /\.(jsx|tsx)$/,
        include: resolveApp('src'),
        use: devMode ? [
          {
            loader: 'esbuild-loader',
            options: {
              loader: 'tsx',
              target: 'es2015',
              tsconfigRaw: require('../tsconfig.json')
            },
          }
        ] : ['babel-loader', {
            loader: 'ts-loader',
            options: {
              transpileOnly: devMode,
            }
          }
       ]
    }]
}

为了考虑编译速度,我们在本地开发环境采用编译速度更快esbuild-loader, 而开发环境我们需要更多的考虑兼容和稳定性,还是用babel-loader和ts-loader的组合。

css, scss文件支持

  • 依赖安装
npm i style-loader css-loader postcss-loader sass-loader mini-css-extract-plugin -D
// webpack.common.js
{
    test: /.s?css$/,
    use: [
      devMode ? "style-loader" : MiniCssExtractPlugin.loader,
      {
        loader: "css-loader",
        options: {
          importLoaders: 2,
        }
      },
      {
        loader: 'postcss-loader',
        options: {
          postcssOptions: {
            plugins: [
              [
                'postcss-preset-env',
              ],
            ],
          },
        },
      },
      "sass-loader",
    ]
},

图片资源支持

// webpack.common.js
{
    test: /\.(png|svg|jp[e]g|gif)$/i,
    include: [
      resolveApp('src')
    ],
    parser: {
      dataUrlCondition: {
        maxSize: 8 * 1024 // 8kb
      }
    },
    type: 'asset',
}

在webpack5之前可能需要各种loader处理静态资源,webpack5后资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

字体文件支持

{
    test: /.(woff|woff2|eot|ttf|otf)$/i,
    include:[
      resolveApp('src')
    ],
    type: 'asset/resource'
}

webpack-dev-server配置

由于webpack-dev-server是在development环境下的配置项,所以配置项写在webpack.dev.js文件之中,整个配置如下。

// webpack.dev.js
const { merge } = require('webpack-merge')
const { resolveApp } = require('./utils')
const common = require('./webpack.common')

module.exports = merge(common, {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  output: {
    publicPath: '/',
    filename: 'js/[name].bundle.[fullhash].js',
    assetModuleFilename: 'img/[hash][ext][query]',
    path: resolveApp('dist'),
  },
  devServer: {
    static: {
      directory: resolveApp('dist'),
    },
    compress: true,
    port: 3000,
    hot: true,
    host: 'local.m.jd.com'
  },
})

为了实现较快的启动速度,采取的devtool的模式是'eval-cheap-module-source-map'; 而插件@pmmmwh/react-refresh-webpack-plugin的作用是实现react代码可以热更新。以上就是一个简单的development环境的配置。

plugins配置

webpack.common.js的配置如下:

const HtmlWebpackPlugin = require('html-webpack-plugin')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = {
    ...
    plugins: [
        new HtmlWebpackPlugin({
          title: '测试页面',
          template: 'public/index.html'
        }),
        new ProgressBarPlugin({
          format: `:msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
        })
    ]
}

  • html-webpack-plugin:该插件将为你生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle。
  • progress-bar-webpack-plugin:该插件可以在命令行展示webpack编译进度。

webpack.dev.js的配置如下:

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
    ...
    plugins: [
      new ReactRefreshWebpackPlugin()
    ]
}
  • @pmmmwh/react-refresh-webpack-plugin:用于为 React 组件启用“快速刷新”。

webpack.prod.js的配置如下:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CmpWebpackPlugin = require("cmp-webpack-plugin")

module.exports = {
    ...
    plugins: [
        new BundleAnalyzerPlugin(),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
          filename: "css/index.[contenthash:8].css",
          chunkFilename: 'css/index.[contenthash:8].chunk.css'
        }),
        new CmpWebpackPlugin({
          format: "zip",
          output: resolveApp('./dist/webpack5'),
          src: resolveApp('./dist'),
          test: ["js/**", "*.html", "css/**", "img/**"],
          name: 'webpack5'
        })
    ]
}
  • webpack-bundle-analyzer:一个 plugin 和 CLI 工具,它将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。
  • clean-webpack-plugin:一个用于清理文件的插件,在production环境可以清除之前的打包文件。
  • mini-css-extract-plugin:本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
  • cmp-webpack-plugin:一个提供压缩功能的插件,这里是将dist目录下所有资源文件打成一个zip包。

优化配置

webpack.common.js配置如下:

module.exports = {
    ...
     resolve: {
       modules: ["src", "node_modules"],
       extensions: ['.js', '.jsx', '.ts', '.tsx'], // 因为我的项目只有这两种类型的文件,如果有其他类型,需要添加进去。
     },
     cache: {
       type: 'filesystem', // 使用文件缓存
     }
}

webpack.prod.js配置如下:

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
    ...
    optimization: {
        splitChunks: {
          chunks: 'all',
          maxSize: 20000,
          cacheGroups: {
            default: {
              idHint: "",
              reuseExistingChunk: true,
              minChunks: 2,
              priority: -20
            },
            defaultVendors: {
              test: /[\\/]node_modules[\\/]/,
              minChunks: 1,
              maxSize: 20000,
              priority: -10
            }
          },
        },
        minimize: true,
        minimizer: [
          new TerserPlugin({
            parallel: true,
            extractComments: true,
            terserOptions: {
              compress: {
                drop_console: false,
                drop_debugger: false
              }
            }
          }),
       new CssMinimizerPlugin()
    ]
  }
}

关于webpack的production环境的优化可以点此查看