18.1 基于 Webpack 构建

18.1 基于 Webpack 构建

现代单页应用(SPA)开发离不开强大的构建工具,Webpack 作为最主流的模块打包工具,提供了完整的解决方案。本节将深入讲解如何为SPA项目配置Webpack 5。

Webpack 5 核心概念

基础配置结构

// webpack.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  entry: './src/index.js',       // 应用入口文件
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/'             // 静态资源基础路径
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src') // 路径别名
    }
  },
  module: {
    rules: [ /* 加载器配置 */ ]
  },
  plugins: [ /* 插件配置 */ ]
};

关键配置详解

1. 处理JavaScript/JSX

module: {
  rules: [
    {
      test: /\.(js|jsx)$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [
            ['@babel/preset-env', { modules: false }],
            '@babel/preset-react'
          ],
          plugins: [
            '@babel/plugin-transform-runtime',
            'react-hot-loader/babel'
          ]
        }
      }
    }
  ]
}

2. 处理样式文件

{
  test: /\.(sa|sc|c)ss$/,
  use: [
    'style-loader',  // 开发环境使用
    {
      loader: 'css-loader',
      options: {
        modules: {
          auto: true,
          localIdentName: '[name]__[local]--[hash:base64:5]'
        }
      }
    },
    'postcss-loader', // 需要配置postcss.config.js
    'sass-loader'
  ]
}

3. 处理静态资源

{
  test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)$/,
  type: 'asset/resource',
  generator: {
    filename: 'assets/[hash][ext][query]'
  }
}

开发环境优化配置

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

module.exports = {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  devServer: {
    hot: true,
    open: true,
    port: 3000,
    historyApiFallback: true, // 支持HTML5 History API
    static: {
      directory: path.join(__dirname, 'public')
    },
    client: {
      overlay: {
        errors: true,
        warnings: false
      }
    }
  },
  plugins: [
    new ReactRefreshWebpackPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('development')
    })
  ]
};

生产环境优化配置

// webpack.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'production',
  devtool: 'source-map',
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          compress: {
            drop_console: true
          }
        }
      }),
      new CssMinimizerPlugin()
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css'
    })
  ]
};

高级特性配置

1. 模块联邦 (Micro Frontends)

// app1/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;

plugins: [
  new ModuleFederationPlugin({
    name: 'app1',
    filename: 'remoteEntry.js',
    exposes: {
      './Button': './src/components/Button'
    },
    shared: ['react', 'react-dom']
  })
]

// app2/webpack.config.js
plugins: [
  new ModuleFederationPlugin({
    name: 'app2',
    remotes: {
      app1: 'app1@http://localhost:3001/remoteEntry.js'
    },
    shared: ['react', 'react-dom']
  })
]

2. 性能分析

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

plugins: [
  new BundleAnalyzerPlugin({
    analyzerMode: 'static',
    reportFilename: 'bundle-report.html',
    openAnalyzer: false
  })
]

3. PWA支持

const WorkboxWebpackPlugin = require('workbox-webpack-plugin');

plugins: [
  new WorkboxWebpackPlugin.GenerateSW({
    clientsClaim: true,
    skipWaiting: true,
    maximumFileSizeToCacheInBytes: 5 * 1024 * 1024
  })
]

现代优化技巧

  1. 持久化缓存
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  }
};
  1. ESBuild 加速
const { ESBuildMinifyPlugin } = require('esbuild-loader');

module.exports = {
  optimization: {
    minimizer: [
      new ESBuildMinifyPlugin({
        target: 'es2015'
      })
    ]
  }
};
  1. 动态Polyfill
module.exports = {
  entry: ['core-js/stable', 'regenerator-runtime/runtime', './src/index.js']
};

完整配置示例

// webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './public/index.html',
      favicon: './public/favicon.ico'
    })
  ]
};

// webpack.config.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  
  return merge(common, isProduction 
    ? require('./webpack.prod.js')
    : require('./webpack.dev.js')
  );
};

最佳实践建议

  1. 配置分离

    • 基础配置 (webpack.common.js)
    • 开发配置 (webpack.dev.js)
    • 生产配置 (webpack.prod.js)
  2. 环境变量管理

    const dotenv = require('dotenv-webpack');
    
    plugins: [
      new dotenv({
        path: `./.env.${process.env.NODE_ENV}`
      })
    ]
    
  3. 构建速度优化

    • 使用 thread-loader 并行处理
    • 缩小 loader 作用范围 (include/exclude)
    • 启用持久化缓存
  4. 输出优化

    output: {
      filename: 'js/[name].[contenthash:8].js',
      chunkFilename: 'js/[name].[contenthash:8].chunk.js',
      assetModuleFilename: 'assets/[hash][ext][query]'
    }
    

通过以上配置,你已经可以构建一个现代化的SPA应用。接下来我们将学习如何实现客户端路由功能。

#前端开发 分享于 2025-03-25

【 内容由 AI 共享,不代表本站观点,请谨慎参考 】