Webpack 实战从入门到大师

21 阅读18分钟

《Webpack 实战从入门到大师》 大纲一

第一章:Webpack 基础入门

1. Webpack 核心概念

  • 入口 (Entry)

    • 单入口配置:
      module.exports = {
        entry: './src/index.js'
      }
      
    • 多入口配置:
      module.exports = {
        entry: {
          main: './src/index.js',
          admin: './src/admin.js'
        }
      }
      
    • 实例应用:我们的项目使用多入口配置,分别为主页面和管理员页面创建独立的入口点,生成不同的打包文件。
  • 输出 (Output)

    • 基本配置:
      output: {
        filename: '[name].[contenthash:8].js',
        chunkFilename: '[name].[contenthash:8].chunk.js',
        path: path.resolve(__dirname, '../dist'),
        publicPath: '/',
        clean: true,
        assetModuleFilename: 'assets/[name].[hash:8][ext]',
      }
      
    • 实例应用:在我们的项目中,使用 contenthash 确保只有文件内容变化时才会生成新的文件名,有效利用浏览器缓存。当部署到 CDN 时,将 publicPath 设置为 CDN 地址。
  • 加载器 (Loaders)

    • JavaScript/JSX 处理:
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      }
      
    • CSS 处理:
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
      
    • SASS/SCSS 处理:
      {
        test: /\.(sass|scss)$/,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader'
        ]
      }
      
    • 图片处理:
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1021 // 8kb以下的图片转为内联
          }
        }
      }
      
    • 实例应用:在我们的项目中,使用 sass-loader 处理 SCSS 文件,使用资源模块处理图片,小于 8KB 的图片会被转换为 Data URL。
  • 插件 (Plugins)

    • HTML 生成:
      new HtmlWebpackPlugin({
        template: path.resolve(__dirname, '../public/index.html'),
        favicon: false,
        title: 'Webpack React 学习项目',
        filename: 'index.html',
        chunks: ['main'],
        templateParameters: {
          'process.env.NODE_ENV': process.env.NODE_ENV || 'development'
        }
      })
      
    • CSS 提取:
      new MiniCssExtractPlugin({
        filename: 'styles/[name].[contenthash].css',
        chunkFilename: 'styles/[id].[contenthash].css',
      })
      
    • Gzip 压缩:
      new CompressionPlugin({
        algorithm: 'gzip',
        test: /\.(js|css|html|svg)$/,
        threshold: 10210, // 只有大于10KB的资源才会被处理
        minRatio: 0.8, // 只有压缩率小于0.8才会被处理
      })
      
    • 打包分析:
      new BundleAnalyzerPlugin({
        analyzerMode: 'static',
        reportFilename: '../bundle-report.html',
        openAnalyzer: true,
      })
      
    • 实例应用:在我们的项目中,使用 HtmlWebpackPlugin 为每个入口点生成独立的 HTML 文件,在生产环境中使用 MiniCssExtractPlugin 提取 CSS 到单独文件,使用 CompressionPlugin 生成 gzip 压缩文件减小传输体积。
  • 模式 (Mode)

    • 开发模式:
      module.exports = {
        mode: 'development',
        devtool: 'eval-source-map'
      }
      
    • 生产模式:
      module.exports = {
        mode: 'production',
        devtool: 'source-map'
      }
      
    • 实例应用:我们的项目使用 webpack-merge 合并配置,在开发环境使用 development 模式获得更好的调试体验,在生产环境使用 production 模式获得更小的打包体积。

2. 环境配置

  • 配置文件拆分

    • 公共配置 (webpack.common.js):
      const path = require('path');
      const HtmlWebpackPlugin = require('html-webpack-plugin');
      
      module.exports = {
        entry: {
          main: './src/index.js',
          admin: './src/admin.js'
        },
        module: {
          rules: [
            {
              test: /\.(js|jsx)$/,
              exclude: /node_modules/,
              use: ['babel-loader']
            }
          ]
        },
        plugins: [
          new HtmlWebpackPlugin({
            template: './public/index.html',
            chunks: ['main']
          })
        ]
      };
      
    • 开发环境配置 (webpack.dev.js):
      const { merge } = require('webpack-merge');
      const common = require('./webpack.common.js');
      
      module.exports = merge(common, {
        mode: 'development',
        devtool: 'eval-source-map',
        devServer: {
          port: 3000,
          hot: true
        }
      });
      
    • 生产环境配置 (webpack.prod.js):
      const { merge } = require('webpack-merge');
      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
      const common = require('./webpack.common.js');
      
      module.exports = merge(common, {
        mode: 'production',
        devtool: 'source-map',
        plugins: [
          new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css'
          })
        ]
      });
      
    • 实例应用:在我们的项目中,将配置拆分为公共配置、开发环境配置和生产环境配置,使用 webpack-merge 合并配置,简化了环境特定的配置管理。
  • 环境变量

    • 使用环境变量:
      // webpack.config.js
      module.exports = env => {
        return {
          mode: env.production ? 'production' : 'development',
          // 其他配置...
        }
      };
      
    • HTML 模板中使用:
      <% if(process.env.NODE_ENV === 'production') { %>
        <!-- 生产环境特定内容 -->
      <% } else { %>
        <!-- 开发环境特定内容 -->
      <% } %>
      
    • 实例应用:在我们的项目中,使用环境变量区分开发和生产环境,在 HTML 模板中根据环境加载不同版本的 React 库,在开发环境使用非压缩版便于调试,在生产环境使用压缩版提高性能。

3. Webpack 与 Babel 集成

  • 基本配置

    • Webpack 配置:
      module: {
        rules: [
          {
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                cacheDirectory: true, // 启用缓存
              }
            }
          }
        ]
      }
      
    • Babel 配置 (.babelrc):
      {
        "presets": [
          ["@babel/preset-env", {
            "targets": "> 0.25%, not dead",
            "useBuiltIns": "usage",
            "corejs": 3
          }],
          "@babel/preset-react"
        ],
        "plugins": [
          "@babel/plugin-transform-runtime"
        ]
      }
      
    • 实例应用:在我们的项目中,使用 babel-loader 处理 JavaScript 文件,配置 @babel/preset-env 和 @babel/preset-react 支持现代 JavaScript 特性和 JSX 语法。
  • 浏览器兼容性

    • 目标浏览器配置:
      // .browserslistrc
      > 0.25%
      not dead
      not ie <= 11
      
    • polyfill 策略:
      // 自动按需导入 polyfill
      ["@babel/preset-env", {
        "useBuiltIns": "usage",
        "corejs": 3
      }]
      
      // 或使用 transform-runtime (避免全局污染)
      plugins: [
        ["@babel/plugin-transform-runtime", {
          "corejs": 3
        }]
      ]
      
    • 实例应用:在我们的项目中,使用 browserslist 配置目标浏览器,结合 @babel/preset-env 的 useBuiltIns: "usage" 实现按需导入 polyfill,减小打包体积。
  • 优化技巧

    • 缓存配置:
      use: {
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,
          cacheCompression: false // 禁用缓存压缩提高性能
        }
      }
      
    • 包含/排除文件:
      {
        test: /\.js$/,
        include: path.resolve(__dirname, 'src'), // 比 exclude 更高效
        // 或者
        exclude: /node_modules/,
        use: 'babel-loader'
      }
      
    • 实例应用:在我们的项目中,启用了 babel-loader 缓存并精确指定了处理范围,显著提高了构建速度,特别是在开发模式下的热更新。
  • TypeScript 与 Babel

    • 使用 Babel 编译 TypeScript:
      module: {
        rules: [
          {
            test: /\.(ts|tsx)$/,
            exclude: /node_modules/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: [
                  '@babel/preset-env',
                  '@babel/preset-react',
                  '@babel/preset-typescript'
                ]
              }
            }
          }
        ]
      }
      
    • 类型检查:
      // 使用 fork-ts-checker-webpack-plugin 在单独进程中进行类型检查
      plugins: [
        new ForkTsCheckerWebpackPlugin({
          typescript: {
            configFile: path.resolve(__dirname, './tsconfig.json')
          }
        })
      ]
      
    • 实例应用:在我们的 TypeScript 项目中,使用 Babel 编译 TypeScript 代码以获得更快的编译速度,同时使用 ForkTsCheckerWebpackPlugin 在单独进程中进行类型检查,兼顾了开发效率和类型安全。

4. Webpack 与 ESLint 集成

  • 基本配置

    • Webpack 配置:
      module: {
        rules: [
          {
            test: /\.(js|jsx|ts|tsx)$/,
            exclude: /node_modules/,
            use: ['babel-loader', 'eslint-loader']
          }
        ]
      }
      
    • ESLint 配置 (.eslintrc):
      {
        "extends": [
          "eslint:recommended",
          "plugin:react/recommended"
        ],
        "parser": "@babel/eslint-parser",
        "parserOptions": {
          "ecmaVersion": 2020,
          "sourceType": "module",
          "ecmaFeatures": {
            "jsx": true
          }
        },
        "rules": {
          "semi": ["error", "always"],
          "quotes": ["warn", "single"]
        }
      }
      
    • 实例应用:在我们的项目中,集成了 ESLint 进行代码质量检查,配置了适合团队的规则集,确保代码风格一致性和质量。
  • 优化配置

    • 使用 ESLint Plugin:
      // 替代 eslint-loader
      plugins: [
        new ESLintPlugin({
          extensions: ['js', 'jsx', 'ts', 'tsx'],
          exclude: 'node_modules',
          context: path.resolve(__dirname, 'src'),
          cache: true,
          cacheLocation: path.resolve(__dirname, '.eslintcache')
        })
      ]
      
    • 仅在开发环境启用:
      // webpack.dev.js
      plugins: [
        new ESLintPlugin({
          // 配置...
        })
      ]
      
    • 实例应用:在我们的项目中,使用 ESLintPlugin 替代 eslint-loader,并启用缓存,将 ESLint 检查时间减少了 70%,提高了开发效率。
  • 与编辑器集成

    • VS Code 设置:
      // .vscode/settings.json
      {
        "editor.codeActionsOnSave": {
          "source.fixAll.eslint": true
        },
        "eslint.validate": [
          "javascript",
          "javascriptreact",
          "typescript",
          "typescriptreact"
        ]
      }
      
    • Git Hooks:
      // package.json
      {
        "husky": {
          "hooks": {
            "pre-commit": "lint-staged"
          }
        },
        "lint-staged": {
          "*.{js,jsx,ts,tsx}": "eslint --fix"
        }
      }
      
    • 实例应用:在我们的项目中,配置了编辑器自动修复和 Git 提交钩子,确保代码在提交前符合 ESLint 规则,减少了代码审查中的风格问题。
  • 自定义规则

    • 项目特定规则:
      // .eslintrc.js
      module.exports = {
        // 基本配置...
        rules: {
          // 自定义规则
          'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
          'react/prop-types': 'off',
          'import/order': ['error', {
            'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
            'newlines-between': 'always'
          }]
        },
        // 覆盖特定文件的规则
        overrides: [
          {
            files: ['**/*.test.js'],
            env: {
              jest: true
            },
            rules: {
              'no-console': 'off'
            }
          }
        ]
      };
      
    • 实例应用:在我们的项目中,根据团队需求定制了 ESLint 规则,包括导入顺序、React 组件规则和环境特定规则,提高了代码质量和可维护性。

5. 高级配置

  • 解析 (Resolve)

    • 配置示例:
      resolve: {
        extensions: ['.js', '.jsx', '.json', '.scss', '.sass'],
        alias: {
          '@': path.resolve(__dirname, '../src'),
          'components': path.resolve(__dirname, '../src/components'),
          'styles': path.resolve(__dirname, '../src/styles'),
        }
      }
      
    • 实际应用:
      // 使用前
      import Button from '../../components/Button';
      import { colors } from '../../styles/variables';
      
      // 使用后
      import Button from 'components/Button';
      import { colors } from 'styles/variables';
      
    • 实例应用:在我们的项目中,使用别名简化了导入路径,避免了复杂的相对路径,提高了代码可读性和可维护性。
  • 优化 (Optimization)

    • 压缩配置:
      optimization: {
        minimize: true,
        minimizer: [
          new TerserPlugin({
            terserOptions: {
              compress: {
                ecma: 5,
                warnings: false,
                comparisons: false,
              },
              output: {
                ecma: 5,
                comments: false,
                ascii_only: true,
              },
            },
            parallel: true,
          }),
          new CssMinimizerPlugin()
        ],
      }
      
    • 代码分割配置:
      optimization: {
        splitChunks: {
          chunks: 'all',
          maxInitialRequests: Infinity,
          minSize: 20000,
          maxSize: 250000,
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name(module) {
                const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
                return `npm.${packageName.replace('@', '')}`;
              },
            },
            commons: {
              name: 'commons',
              minChunks: 2,
              reuseExistingChunk: true,
            },
          },
        },
        runtimeChunk: 'single',
      }
      
    • 实例应用:在我们的项目中,使用 TerserPlugin 压缩 JavaScript,使用 CssMinimizerPlugin 压缩 CSS,配置 splitChunks 将第三方库和公共代码提取到单独的文件中,减小主包体积,提高加载速度。
  • 性能 (Performance)

    • 配置示例:
      performance: {
        hints: 'warning',
        maxEntrypointSize: 512000, // 512KB
        maxAssetSize: 512000, // 512KB
      }
      
    • 实例应用:在我们的项目中,设置了入口点和资源的大小限制,当超过限制时会显示警告,提醒开发者注意资源大小,但不会阻止构建过程。
  • 外部扩展 (Externals)

    • 配置示例:
      externals: {
        'react': 'React',
        'react-dom': 'ReactDOM',
      }
      
    • HTML 中引用 CDN:
      <!-- 生产环境使用 -->
      <% if(process.env.NODE_ENV==='production') { %>
        <script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
      <% } else { %>
        <!-- 开发环境使用 -->
        <script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
      <% } %>
      
    • 实例应用:在我们的项目中,将 React 和 ReactDOM 从打包文件中排除,通过 CDN 加载,减小了打包体积,利用了 CDN 缓存提高加载速度。
  • 开发服务器 (DevServer)

    • 配置示例:
      devServer: {
        static: {
          directory: path.join(__dirname, '../public'),
        },
        historyApiFallback: true,
        port: 3000,
        hot: true,
        open: true,
        compress: true,
        client: {
          overlay: true,
          progress: true,
        },
      }
      
    • 实例应用:在我们的项目中,配置了开发服务器支持热模块替换,自动打开浏览器,显示编译错误和进度,提高了开发效率。

第二章:资源优化与性能提升

6. 资源优化

  • 代码分割

    • 多入口分割:
      entry: {
        main: './src/index.js',
        admin: './src/admin.js'
      }
      
    • 动态导入:
      // 使用动态导入实现懒加载
      const LazyComponent = lazy(() => import('./LazyComponent'));
      
      function App() {
        const [showLazy, setShowLazy] = useState(false);
        
        return (
          <div>
            <button onClick={() => setShowLazy(!showLazy)}>
              {showLazy ? '隐藏' : '加载'} 组件
            </button>
            
            {showLazy && (
              <Suspense fallback={<div>加载中...</div>}>
                <LazyComponent />
              </Suspense>
            )}
          </div>
        );
      }
      
    • 实例应用:在我们的项目中,使用多入口配置为主页面和管理员页面创建独立的包,使用动态导入实现组件懒加载,减小初始加载体积,提高首屏加载速度。
  • 懒加载

    • React 组件懒加载:
      import React, { lazy, Suspense } from 'react';
      
      // 懒加载组件
      const LazyComponent = lazy(() => import('./LazyComponent'));
      
      function App() {
        return (
          <Suspense fallback={<div>加载中...</div>}>
            <LazyComponent />
          </Suspense>
        );
      }
      
    • 实例应用:在我们的项目中,使用 React.lazy 和 Suspense 实现组件懒加载,只有当用户点击按钮时才会加载相应的组件代码,减小了初始加载体积。
  • 缓存优化

    • 使用 contenthash:
      output: {
        filename: '[name].[contenthash:8].js',
        chunkFilename: '[name].[contenthash:8].chunk.js',
      }
      
    • 提取运行时代码:
      optimization: {
        runtimeChunk: 'single'
      }
      
    • 实例应用:在我们的项目中,使用 contenthash 生成文件名,确保只有文件内容变化时才会生成新的文件名,提取运行时代码到单独的文件,优化了浏览器缓存策略。
  • 体积优化

    • JavaScript 压缩:
      optimization: {
        minimizer: [
          new TerserPlugin({
            terserOptions: {
              compress: {
                ecma: 5,
                warnings: false,
                comparisons: false,
              },
              output: {
                ecma: 5,
                comments: false,
                ascii_only: true,
              },
            },
            parallel: true,
          })
        ]
      }
      
    • CSS 压缩:
      optimization: {
        minimizer: [
          new CssMinimizerPlugin({
            minimizerOptions: {
              preset: [
                'default',
                {
                  discardComments: { removeAll: true },
                },
              ],
            },
          })
        ]
      }
      
    • 实例应用:在我们的项目中,使用 TerserPlugin 压缩 JavaScript,使用 CssMinimizerPlugin 压缩 CSS,启用 Tree Shaking 移除未使用的代码,显著减小了打包体积。

7. Webpack 性能优化

  • 构建性能优化

    • 缩小文件搜索范围:
      module: {
        rules: [
          {
            test: /\.js$/,
            include: path.resolve(__dirname, 'src'),
            use: 'babel-loader'
          }
        ]
      },
      resolve: {
        extensions: ['.js', '.json'], // 减少扩展名尝试
        mainFields: ['browser', 'module', 'main'], // 减少 package.json 字段查找
        modules: [path.resolve(__dirname, 'src'), 'node_modules'] // 指定模块查找目录
      }
      
    • 使用缓存:
      // babel-loader 缓存
      use: {
        loader: 'babel-loader',
        options: {
          cacheDirectory: true
        }
      }
      
      // 持久化缓存
      cache: {
        type: 'filesystem',
        buildDependencies: {
          config: [__filename] // 当配置文件变化时使缓存失效
        }
      }
      
    • 多进程构建:
      use: [
        {
          loader: 'thread-loader',
          options: {
            workers: 4, // 指定工作线程数
          }
        },
        'babel-loader'
      ]
      
    • 实例应用:在我们的大型项目中,通过缩小文件搜索范围、启用缓存和多进程构建,将构建时间从 2 分钟减少到 30 秒,极大提高了开发效率。
  • 运行时性能优化

    • 代码分割策略:
      optimization: {
        splitChunks: {
          chunks: 'all',
          cacheGroups: {
            vendors: {
              test: /[\\/]node_modules[\\/]/,
              priority: -10
            },
            common: {
              minChunks: 2,
              priority: -20,
              reuseExistingChunk: true
            }
          }
        }
      }
      
    • 预加载和预获取:
      // 预获取 - 空闲时加载
      import(/* webpackPrefetch: true */ './path/to/module');
      
      // 预加载 - 与父块并行加载
      import(/* webpackPreload: true */ './path/to/module');
      
    • 懒加载路由:
      // React Router 懒加载
      const Home = React.lazy(() => import('./pages/Home'));
      const About = React.lazy(() => import('./pages/About'));
      
      function App() {
        return (
          <Suspense fallback={<div>Loading...</div>}>
            <Switch>
              <Route path="/about" component={About} />
              <Route path="/" component={Home} />
            </Switch>
          </Suspense>
        );
      }
      
    • 实例应用:在我们的项目中,通过合理的代码分割策略、路由懒加载和预加载技术,将首屏加载时间减少了 40%,显著提升了用户体验。
  • 体积优化

    • Tree Shaking 增强:
      // package.json
      {
        "sideEffects": [
          "*.css",
          "*.scss"
        ]
      }
      
      // webpack.config.js
      optimization: {
        usedExports: true,
        sideEffects: true
      }
      
    • 动态 polyfill:
      <!-- 替代全量 core-js 引入 -->
      <script src="https://polyfill.io/v3/polyfill.min.js?features=default,Array.prototype.includes"></script>
      
    • 依赖优化:
      // 使用 webpack-bundle-analyzer 分析依赖
      plugins: [
        new BundleAnalyzerPlugin()
      ]
      
      // 替换大型依赖
      resolve: {
        alias: {
          'moment': 'dayjs', // 用更小的替代品替换大型库
        }
      }
      
    • 实例应用:在我们的项目中,通过标记 sideEffects、使用动态 polyfill 服务和优化依赖,将打包体积减少了 30%,显著提升了加载性能。
  • 监控与分析

    • 性能预算:
      performance: {
        hints: 'warning', // 'error' 会导致构建失败
        maxAssetSize: 250000, // 单个资源大小限制(字节)
        maxEntrypointSize: 400000, // 入口点大小限制(字节)
      }
      
    • 构建分析:
      // 生成 stats.json
      webpack --profile --json > stats.json
      
      // 使用 webpack-bundle-analyzer
      plugins: [
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          reportFilename: 'report.html',
          openAnalyzer: false
        })
      ]
      
    • 实例应用:在我们的项目中,设置了性能预算并定期运行构建分析,建立了性能监控流程,确保代码变更不会导致性能退化。

8. CDN 集成

  • 配置 CDN 路径

    • 设置 publicPath:
      // CDN 配置
      const CDN_URL = 'https://your-cdn-domain.com/assets/';
      
      output: {
        publicPath: CDN_URL,
      }
      
    • 实例应用:在我们的项目中,将 publicPath 设置为 CDN 地址,使所有资源引用都指向 CDN,提高了资源加载速度。
  • 排除第三方库

    • Webpack 配置:
      externals: {
        'react': 'React',
        'react-dom': 'ReactDOM',
      }
      
    • HTML 模板:
      <!-- 生产环境使用 -->
      <% if(process.env.NODE_ENV==='production') { %>
        <script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
      <% } else { %>
        <!-- 开发环境使用 -->
        <script crossorigin src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
      <% } %>
      
    • 实例应用:在我们的项目中,将 React 和 ReactDOM 从打包文件中排除,通过 CDN 加载,减小了打包体积,利用了 CDN 缓存提高加载速度。
  • 上传文件到 CDN

    • 方法一:手动上传脚本
      // scripts/upload-to-cdn.js
      const fs = require('fs');
      const path = require('path');
      const OSS = require('ali-oss');
      
      async function uploadToAliyun() {
        const client = new OSS({
          region: 'oss-cn-beijing',
          accessKeyId: 'YOUR_ACCESS_KEY_ID',
          accessKeySecret: 'YOUR_ACCESS_KEY_SECRET',
          bucket: 'your-bucket-name',
        });
        
        const files = fs.readdirSync('./dist');
        
        for (const file of files) {
          await client.put(file, path.join('./dist', file));
          console.log(`上传成功: ${file}`);
        }
      }
      
      uploadToAliyun().catch(console.error);
      
    • 方法二:Webpack 插件自动上传
      // webpack/plugins/WebpackCdnUploadPlugin.js
      class WebpackCdnUploadPlugin {
        constructor(options) {
          this.options = options;
        }
        
        apply(compiler) {
          compiler.hooks.afterEmit.tapPromise('WebpackCdnUploadPlugin', async (compilation) => {
            const outputPath = compilation.outputOptions.path;
            const assets = compilation.assets;
            
            // 上传文件到 CDN 的逻辑
            for (const [filename, asset] of Object.entries(assets)) {
              await this.uploadFile(path.join(outputPath, filename));
            }
          });
        }
        
        async uploadFile(filePath) {
          // 上传文件的具体实现
        }
      }
      
    • 实例应用:在我们的项目中,创建了上传脚本和 Webpack 插件,支持将构建文件上传到多种 CDN 服务商,包括阿里云 OSS、腾讯云 COS、AWS S3 和七牛云,实现了自动化部署流程。
  • CDN 配置文件

    • 配置示例:
      // cdn.config.js
      module.exports = {
        aliyun: {
          region: 'oss-cn-beijing',
          accessKeyId: 'YOUR_ACCESS_KEY_ID',
          accessKeySecret: 'YOUR_ACCESS_KEY_SECRET',
          bucket: 'your-bucket-name',
        },
        // 其他 CDN 服务商配置...
      };
      
    • 实例应用:在我们的项目中,使用配置文件管理 CDN 凭证,将其添加到 .gitignore 确保敏感信息不会被提交到版本控制系统。

9. 构建分析与优化

  • 构建分析

    • 使用 BundleAnalyzerPlugin:
      // webpack.prod.js
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
      
      if (env && env.analyze) {
        plugins.push(new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          reportFilename: '../bundle-report.html',
          openAnalyzer: true,
        }));
      }
      
    • 生成统计信息:
      # package.json
      "scripts": {
        "analyze:stats": "webpack --config webpack/webpack.prod.js --json > stats.json",
        "analyze:size": "webpack-bundle-size-analyzer stats.json"
      }
      
    • 实例应用:在我们的项目中,配置了 BundleAnalyzerPlugin 生成可视化的包分析报告,添加了脚本命令生成统计信息,帮助识别和优化大型依赖。
  • 持续集成

    • 构建脚本:
      # package.json
      "scripts": {
        "build": "webpack --config webpack/webpack.prod.js",
        "upload:cdn": "node scripts/upload-to-cdn.js",
        "deploy": "npm run build && npm run upload:cdn"
      }
      
    • 实例应用:在我们的项目中,配置了构建和部署脚本,实现了一键构建和部署到 CDN,简化了发布流程。

10. Webpack 调试与故障排除

18.1 常见错误类型与解决方案

  • 模块解析错误

    • 错误表现:
      Module not found: Error: Can't resolve 'module-name' in '/path/to/your/project'
      
    • 解决方案:
      // 检查模块是否已安装
      npm install module-name --save
      
      // 或检查路径是否正确
      import module from './correct-path/module';
      
      // 配置 resolve.alias 简化导入
      resolve: {
        alias: {
          '@': path.resolve(__dirname, 'src')
        }
      }
      
    • 实例应用:在我们的项目中,通过配置 resolve.aliasresolve.extensions,解决了大量模块解析错误,特别是在重构目录结构后。
  • Loader 配置错误

    • 错误表现:
      Module parse failed: Unexpected token
      You may need an appropriate loader to handle this file type
      
    • 解决方案:
      // 检查 loader 配置
      module: {
        rules: [
          {
            test: /\.jsx?$/,
            exclude: /node_modules/,
            use: 'babel-loader'
          }
        ]
      }
      
      // 确保 loader 已安装
      npm install babel-loader @babel/core --save-dev
      
    • 实例应用:在我们的项目中,通过系统检查每种文件类型的 loader 配置,解决了 CSS 模块、SVG 文件和 TypeScript 文件的解析错误。
  • 插件错误

    • 错误表现:
      Error: [plugin-name] is not a constructor
      
    • 解决方案:
      // 正确引入插件
      const HtmlWebpackPlugin = require('html-webpack-plugin');
      
      // 正确实例化插件
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html'
        })
      ]
      
    • 实例应用:在升级 Webpack 5 后,我们发现一些插件需要更新到兼容的版本,通过更新插件并调整配置,解决了构建过程中的插件错误。
  • 内存溢出错误

    • 错误表现:
      FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
      
    • 解决方案:
      # 增加 Node.js 内存限制
      # package.json
      "scripts": {
        "build": "node --max-old-space-size=4096 node_modules/.bin/webpack --config webpack.prod.js"
      }
      
    • 实例应用:在处理大型项目时,我们通过增加 Node.js 内存限制并优化 Webpack 配置,成功解决了构建过程中的内存溢出问题。

18.2 调试技巧与工具

  • 启用详细日志

    • 命令行参数:
      # 显示详细构建信息
      webpack --progress --verbose
      
      # 显示构建统计信息
      webpack --json > stats.json
      
    • 配置文件:
      // webpack.config.js
      module.exports = {
        // ...其他配置
        stats: {
          colors: true,
          reasons: true,
          chunks: true,
          modules: false,
          children: false,
          entrypoints: false,
          excludeAssets: [/\.map$/]
        }
      };
      
    • 实例应用:在我们的项目中,通过配置详细的 stats 输出,快速定位了构建过程中的问题,特别是在集成新功能时。
  • 使用 Source Maps

    • 开发环境配置:
      // webpack.dev.js
      module.exports = {
        // ...其他配置
        devtool: 'eval-source-map', // 快速重建,良好的调试体验
        // 或
        devtool: 'cheap-module-source-map' // 更快的重建速度
      };
      
    • 生产环境配置:
      // webpack.prod.js
      module.exports = {
        // ...其他配置
        devtool: 'source-map', // 完整 source map,但构建较慢
        // 或
        devtool: 'hidden-source-map' // 仅用于错误报告工具
      };
      
    • 实例应用:在我们的项目中,开发环境使用 eval-source-map 提供良好的调试体验,生产环境使用 hidden-source-map 配合错误监控工具,平衡了构建性能和调试需求。
  • 使用 webpack-dev-server 调试

    • 配置热更新:
      // webpack.dev.js
      module.exports = {
        // ...其他配置
        devServer: {
          hot: true,
          open: true,
          port: 3000,
          client: {
            overlay: true, // 在浏览器中显示错误
            progress: true // 显示编译进度
          }
        }
      };
      
    • 实例应用:在我们的项目中,配置了 webpack-dev-server 的错误覆盖层和编译进度显示,大大提高了开发过程中的问题定位效率。
  • 使用 webpack-bundle-analyzer 分析包大小

    • 安装与配置:
      npm install webpack-bundle-analyzer --save-dev
      
      // webpack.config.js
      const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
      
      module.exports = {
        // ...其他配置
        plugins: [
          new BundleAnalyzerPlugin({
            analyzerMode: 'static',
            reportFilename: 'bundle-report.html',
            openAnalyzer: false
          })
        ]
      };
      
    • 实例应用:在我们的项目中,定期使用 webpack-bundle-analyzer 分析构建产物,发现并优化了多个重复依赖和过大的模块,减小了最终包体积。

18.3 性能分析与优化

  • 使用 speed-measure-webpack-plugin 分析构建性能

    • 安装与配置:
      npm install speed-measure-webpack-plugin --save-dev
      
      // webpack.config.js
      const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
      const smp = new SpeedMeasurePlugin();
      
      module.exports = smp.wrap({
        // 你的 webpack 配置
      });
      
    • 实例应用:在我们的项目中,使用 speed-measure-webpack-plugin 分析构建过程,发现 babel-loader 和 sass-loader 是主要的性能瓶颈,通过启用缓存和多线程处理,将构建时间减少了 40%。
  • 构建缓存优化

    • Webpack 5 持久化缓存:
      // webpack.config.js
      module.exports = {
        // ...其他配置
        cache: {
          type: 'filesystem',
          buildDependencies: {
            config: [__filename]
          }
        }
      };
      
    • Loader 缓存:
      // webpack.config.js
      module.exports = {
        // ...其他配置
        module: {
          rules: [
            {
              test: /\.js$/,
              use: [
                {
                  loader: 'babel-loader',
                  options: {
                    cacheDirectory: true,
                    cacheCompression: false
                  }
                }
              ]
            }
          ]
        }
      };
      
    • 实例应用:在我们的项目中,启用 Webpack 5 的文件系统缓存和 babel-loader 缓存后,二次构建时间从 30 秒减少到 5 秒,极大提高了开发效率。
  • 多进程/多实例构建

    • 使用 thread-loader:
      // webpack.config.js
      module.exports = {
        // ...其他配置
        module: {
          rules: [
            {
              test: /\.js$/,
              use: [
                {
                  loader: 'thread-loader',
                  options: {
                    workers: 4
                  }
                },
                'babel-loader'
              ]
            }
          ]
        }
      };
      
    • 实例应用:在我们的大型项目中,对 babel-loader 和 ts-loader 使用 thread-loader 进行多线程处理,将构建时间减少了 30%,特别是在多核心 CPU 的环境中效果显著。

18.4 常见场景故障排除

  • 处理第三方库兼容性问题

    • 问题:某些第三方库可能不兼容 Webpack 5 或 ES 模块
    • 解决方案:
      // webpack.config.js
      module.exports = {
        // ...其他配置
      resolve: {
        alias: {
            'problematic-module': 'problematic-module/dist/compatible-version'
          }
        }
      };
      
    • 实例应用:在我们的项目中,通过别名配置和 noParse 选项,解决了多个不兼容 ES 模块的旧版库的问题,避免了不必要的转译和解析。
  • 解决 Tree Shaking 失效问题

    • 问题:预期的 Tree Shaking 没有生效,包体积过大
    • 解决方案:
      // package.json
      {
        "sideEffects": [
          "*.css",
          "*.scss"
        ]
      }
      
      // webpack.config.js
      module.exports = {
        // ...其他配置
        optimization: {
          usedExports: true,
          sideEffects: true
        }
      };
      
    • 实例应用:在我们的项目中,通过正确配置 sideEffects 和分析模块导出方式,解决了多个库的 Tree Shaking 失效问题,特别是对于 lodash 和 material-ui 等大型库。
  • 解决 CSS 提取和压缩问题

    • 问题:CSS 提取后样式丢失或压缩失败
    • 解决方案:
      // webpack.config.js
      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
      const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
      
      module.exports = {
        // ...其他配置
      module: {
        rules: [
          {
              test: /\.css$/,
              use: [
                MiniCssExtractPlugin.loader,
                {
                  loader: 'css-loader',
                  options: {
                    importLoaders: 1,
                    modules: {
                      localIdentName: '[name]__[local]--[hash:base64:5]'
                    }
                  }
                },
                'postcss-loader'
              ]
            }
          ]
        },
        plugins: [
          new MiniCssExtractPlugin({
            filename: 'styles/[name].[contenthash:8].css'
          })
        ],
        optimization: {
          minimizer: [
            '...',
            new CssMinimizerPlugin()
          ]
        }
      };
      
    • 实例应用:在我们的项目中,通过调整 CSS 模块配置和确保提取插件与 loader 版本兼容,解决了 CSS 模块化后样式丢失的问题。
  • 解决代码分割和异步加载问题

    • 问题:动态导入的模块加载失败或分割策略不合理
    • 解决方案:
      // webpack.config.js
      module.exports = {
        // ...其他配置
        output: {
          filename: '[name].[contenthash:8].js',
          chunkFilename: '[name].[contenthash:8].chunk.js',
          publicPath: '/'
        },
      optimization: {
          splitChunks: {
            chunks: 'all',
            maxInitialRequests: 25,
            minSize: 20000,
            cacheGroups: {
              vendors: {
                test: /[\\/]node_modules[\\/]/,
                priority: -10,
                reuseExistingChunk: true
              }
            }
          }
        }
      };
      
    • 实例应用:在我们的项目中,通过调整 splitChunks 配置和确保正确的 publicPath,解决了动态导入模块在生产环境中加载失败的问题。

18.5 构建监控与自动化测试

  • 集成持续集成 (CI) 构建检查

    • 配置 CI 脚本:
      # .github/workflows/webpack.yml
      name: Webpack Build
      
      on:
        push:
          branches: [ main ]
        pull_request:
          branches: [ main ]
      
      jobs:
        build:
          runs-on: ubuntu-latest
          
          steps:
          - uses: actions/checkout@v2
          
          - name: Use Node.js
            uses: actions/setup-node@v2
            with:
              node-version: '14'
              
          - name: Install dependencies
            run: npm ci
              
          - name: Build
            run: npm run build
              
          - name: Check bundle size
            run: npx bundlesize
      
    • 实例应用:在我们的项目中,配置了 GitHub Actions 工作流,在每次提交和 PR 时自动运行构建检查,确保构建不会失败,并监控包体积变化。
  • 使用 bundlesize 监控包体积

    • 配置:
      // package.json
      {
        "bundlesize": [
          {
            "path": "./dist/js/main.*.js",
            "maxSize": "100 kB"
          },
          {
            "path": "./dist/js/vendors.*.js",
            "maxSize": "250 kB"
          },
          {
            "path": "./dist/css/*.css",
            "maxSize": "50 kB"
          }
        ]
      }
      
    • 实例应用:在我们的项目中,使用 bundlesize 设置了各个资源的体积上限,当超过限制时会在 CI 中失败,有效防止了包体积的无意增长。
  • 自动化测试与构建验证

    • 配置测试脚本:
      // package.json
      {
        "scripts": {
          "test": "jest",
          "build": "webpack --config webpack.prod.js",
          "validate": "npm run test && npm run build && npm run bundlesize"
        }
      }
      
    • 实例应用:在我们的项目中,配置了完整的验证流程,包括单元测试、构建检查和包体积监控,确保每次发布的代码质量和性能。

18.6 线上问题调试

  • 使用 Source Map 进行线上调试

    • 生成 Source Map 但不公开:
      // webpack.prod.js
      module.exports = {
        // ...其他配置
        devtool: 'hidden-source-map'
      };
      
    • 配合错误监控工具:
      // 初始化错误监控
      Sentry.init({
        dsn: 'https://your-sentry-dsn.ingest.sentry.io/project',
        integrations: [new Integrations.BrowserTracing()],
        tracesSampleRate: 1.0,
      });
      
      // 上传 Source Map 到 Sentry
      // sentry.webpack.config.js
      const SentryWebpackPlugin = require('@sentry/webpack-plugin');
      
      module.exports = {
        // ...其他配置
      plugins: [
          new SentryWebpackPlugin({
            include: './dist',
            ignoreFile: '.sentrycliignore',
            ignore: ['node_modules', 'webpack.config.js'],
            configFile: 'sentry.properties'
          })
        ]
      };
      
    • 实例应用:在我们的项目中,使用 Sentry 进行错误监控,配合 hidden-source-map 和 SentryWebpackPlugin,实现了线上错误的精确定位和快速修复。
  • 使用性能标记进行性能分析

    • 添加性能标记:
      // 在关键代码处添加性能标记
      performance.mark('moduleA-start');
      
      // 执行模块 A 的代码
      
      performance.mark('moduleA-end');
      performance.measure('moduleA', 'moduleA-start', 'moduleA-end');
      
      // 在应用初始化完成后收集性能数据
      window.addEventListener('load', () => {
        const measures = performance.getEntriesByType('measure');
        // 发送性能数据到分析服务
        navigator.sendBeacon('/analytics', JSON.stringify(measures));
      });
      
    • 实例应用:在我们的项目中,通过在关键模块和组件中添加性能标记,结合 Performance API 和自定义分析服务,实现了线上应用性能的实时监控和分析。
  • 使用特性标记处理兼容性问题

    • 配置特性标记:
      // webpack.config.js
      module.exports = {
        // ...其他配置
      plugins: [
          new webpack.DefinePlugin({
            'process.env.FEATURE_A': JSON.stringify(true),
            'process.env.FEATURE_B': JSON.stringify(false)
          })
      ]
      };
      
    • 在代码中使用:
      // 根据特性标记启用或禁用功能
      if (process.env.FEATURE_A) {
        // 实现功能 A
      } else {
        // 使用替代实现
      }
      
    • 实例应用:在我们的项目中,使用特性标记管理新功能的发布和回滚,当发现线上问题时,可以快速禁用有问题的功能,而不需要重新部署整个应用。

11. Webpack 打包产物文件拆分

19.1 Webpack 产物文件生成机制

  • 入口点与产物的基本映射关系

    // webpack.config.js 中的入口配置
    entry: {
    main: './src/index.js',
    admin: './src/admin.js'
    }
    
    • 默认情况下,每个入口点会生成一个对应的输出文件
    • 上述配置会生成 main.jsadmin.js 两个基础产物文件
    • 文件命名受 output.filename 配置影响,如 [name].[contenthash:8].js
  • 输出配置对产物文件的影响

    output: {
    filename: '[name].[contenthash:8].js',
    chunkFilename: '[name].[contenthash:8].chunk.js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/'
    }
    
    • filename: 控制入口点打包后的文件名
    • chunkFilename: 控制非入口点 chunk 文件的命名
    • path: 指定所有输出文件的目标路径
    • publicPath: 指定浏览器引用资源的公共 URL
  • 文件名中的占位符含义

    • [name]: 模块名称,对应 entry 中定义的键名
    • [id]: 模块标识符,由 Webpack 内部生成
    • [hash]: 整个构建过程的 hash
    • [chunkhash]: 基于 chunk 内容的 hash
    • [contenthash]: 基于文件内容的 hash,更精确
    • [fullhash]: webpack 5 中的完整构建 hash

19.2 代码分割与产物文件的关系

  • 自动代码分割

    • Webpack 会根据以下情况自动进行代码分割:
      1. 使用动态导入 (import()) 的模块会被分割成单独的 chunk
      2. 使用 SplitChunksPlugin 配置的共享模块会被提取
      3. 使用 optimization.runtimeChunk 时,运行时代码会被提取
  • 动态导入产生的文件

    // 在 App.js 中使用动态导入
    const LazyComponent = lazy(() => import('./LazyComponent'));
    
    • 上述代码会生成一个额外的 chunk 文件,如 347.26a45f8a.chunk.js
    • 文件名中的数字 (347) 是 Webpack 自动分配的 chunk ID
    • 可以通过魔法注释自定义 chunk 名称:
      import(/* webpackChunkName: "lazy-component" */ './LazyComponent')
      
  • SplitChunksPlugin 配置与产物的映射

    optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        reactVendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react-vendor',
          priority: 40,
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 30,
        }
      }
    }
    }
    
    • 上述配置会生成以下额外文件:
      1. react-vendor.[contenthash:8].js: 包含所有 React 相关库
      2. vendors.[contenthash:8].js: 包含其他第三方库
    • 优先级 (priority) 决定了模块归属,React 模块会进入 react-vendor 而非 vendors

19.3 实际项目中的产物文件分析

  • 我们项目中的配置与产物对应关系

    // webpack.prod.js 中的 splitChunks 配置
    splitChunks: {
    chunks: 'all',
    maxInitialRequests: Infinity,
    minSize: 20000,
    maxSize: 250000,
    cacheGroups: {
      reactVendor: {
        test: /[\\/]node_modules[\\/](react|react-dom|scheduler|@remix-run|history|@babel\/runtime|prop-types)[\\/]/,
        name: 'react-vendor',
        priority: 40,
        enforce: true,
      },
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        priority: 30,
        name(module) {
          const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
          return `npm.${packageName.replace('@', '')}`;
        },
      },
      styles: {
        test: /\.(css|scss|sass)$/,
        name: 'styles',
        priority: 30,
        enforce: true,
      },
      // 其他 cacheGroups...
    }
    }
    
  • 实际生成的文件及其内容

    • react-vendor.3ce937e5.js (138 KB): 包含所有 React 相关库
    • runtime.c28515c2.js (2.61 KB): Webpack 运行时代码
    • styles.05b5c105.jsstyles/styles.956fa038ebb609dbbb9e.css: 样式文件
    • main.25afb3d6.js (6.77 KB): 主入口文件代码
    • admin.365e7069.js (4.84 KB): 管理员入口文件代码
    • 347.26a45f8a.chunk.js (672 bytes): 懒加载组件代码
  • 文件生成的决定因素

    1. 入口配置: entry 中的每个键会生成一个基础 bundle
    2. 代码分割配置: splitChunks 中的 cacheGroups 决定了如何提取公共模块
    3. 动态导入: 使用 import() 的模块会生成单独的 chunk
    4. 运行时配置: runtimeChunk: 'single' 会提取 Webpack 运行时到单独文件
    5. MiniCssExtractPlugin: 会将 CSS 提取到单独的文件中

19.4 影响产物文件的关键配置参数

  • splitChunks 参数详解

    • chunks: 控制哪些 chunks 参与分割 ('async'|'initial'|'all')
    • minSize: 生成 chunk 的最小体积 (bytes)
    • maxSize: chunk 的最大体积,超过会尝试分割
    • minChunks: 模块被引用的最小次数
    • maxInitialRequests: 入口点的最大并行请求数
    • maxAsyncRequests: 按需加载时的最大并行请求数
    • automaticNameDelimiter: 自动命名的分隔符
    • name: 拆分的 chunk 名称,true 表示自动生成
    • cacheGroups: 缓存组,定义提取模块的规则
  • cacheGroups 配置对产物的精确控制

    • test: 控制哪些模块被选中 (正则表达式或函数)
    • priority: 优先级,当模块同时满足多个 cacheGroup 时使用
    • reuseExistingChunk: 是否复用已有的 chunk
    • enforce: 是否强制创建,忽略 minSize、minChunks 等限制
    • name: 指定 chunk 名称,可以是字符串或函数
  • 实际应用中的最佳实践

    1. 为框架库单独分组: 将 React、Vue 等框架库单独打包,提高缓存效率
    2. 按功能模块分组: 将相关功能的模块打包在一起,减少请求数
    3. 动态导入关键路径外的组件: 减小初始加载体积
    4. 合理设置 minSize 和 maxSize: 避免过多的小文件或过大的单个文件
    5. 使用 contenthash: 确保只有内容变化的文件才会更新哈希值

19.5 调试与分析产物文件

  • 使用 BundleAnalyzerPlugin 分析产物组成
    // webpack.config.js
    const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
    
    module.exports = {
    plugins: [
      new BundleAnalyzerPlugin({
        analyzerMode: 'static',
        reportFilename: 'bundle-report.html',
      })
    ]
    };
    

第三章:现代前端工程化实践

12. Webpack 与 TypeScript 集成

  • 基本配置

    • Webpack 配置:
      module: {
        rules: [
          {
            test: /\.(ts|tsx)$/,
            exclude: /node_modules/,
            use: 'ts-loader'
          }
        ]
      },
      resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx']
      }
      
    • TypeScript 配置 (tsconfig.json):
      {
        "compilerOptions": {
          "target": "es5",
          "module": "esnext",
          "moduleResolution": "node",
          "jsx": "react",
          "esModuleInterop": true,
          "sourceMap": true,
          "strict": true
        },
        "include": ["src"]
      }
      
    • 实例应用:在我们的 TypeScript 项目中,使用 ts-loader 处理 TypeScript 文件,配置了适当的 tsconfig.json 以支持 React 和现代 JavaScript 特性。
  • 加载器选项

    • ts-loader 配置:
      use: {
        loader: 'ts-loader',
        options: {
          transpileOnly: true, // 仅转译,不类型检查,提高构建速度
          happyPackMode: true, // 与 thread-loader 配合使用
          experimentalWatchApi: true, // 提高增量构建性能
        }
      }
      
    • 类型检查:
      plugins: [
        new ForkTsCheckerWebpackPlugin({
          typescript: {
            diagnosticOptions: {
              semantic: true,
              syntactic: true
            },
            mode: 'write-references' // 提高性能
          }
        })
      ]
      
    • 实例应用:在我们的项目中,配置 ts-loader 的 transpileOnly: true 选项提高构建速度,同时使用 ForkTsCheckerWebpackPlugin 在单独进程中进行类型检查,实现了开发效率和类型安全的平衡。
  • 路径别名

    • Webpack 配置:
      resolve: {
        alias: {
          '@': path.resolve(__dirname, 'src')
        }
      }
      
    • TypeScript 配置:
      {
        "compilerOptions": {
          "baseUrl": ".",
          "paths": {
            "@/*": ["src/*"]
          }
        }
      }
      
    • 实例应用:在我们的 TypeScript 项目中,同时在 Webpack 和 TypeScript 配置中设置了路径别名,确保编译和类型检查都能正确解析导入路径。
  • 声明文件处理

    • 导入声明文件:
      // 为没有类型定义的模块创建声明文件
      // src/types/untyped-module.d.ts
      declare module 'untyped-module' {
        const content: any;
        export default content;
      }
      
    • 图片和样式模块:
      // src/types/assets.d.ts
      declare module '*.png' {
        const src: string;
        export default src;
      }
      
      declare module '*.css' {
        const classes: { [key: string]: string };
        export default classes;
      }
      
    • 实例应用:在我们的项目中,创建了自定义声明文件处理图片、样式和第三方库,确保 TypeScript 能够正确理解这些导入。

13. Webpack 与 WebAssembly

  • 基本配置

    • Webpack 5 原生支持 WebAssembly:
      // webpack.config.js
      module: {
        rules: [
          {
            test: /\.wasm$/,
            type: 'webassembly/async', // 或 'webassembly/sync'
          }
        ]
      },
      experiments: {
        asyncWebAssembly: true, // 启用异步 WebAssembly
        // 或
        syncWebAssembly: true, // 启用同步 WebAssembly (不推荐)
      }
      
    • 实例应用:在我们的项目中,配置 Webpack 支持异步加载 WebAssembly 模块,提高了复杂计算的性能,同时保持了良好的初始加载速度。
  • 导入 WebAssembly 模块

    • 异步导入:
      // 异步导入 WebAssembly 模块
      async function loadWasmModule() {
        try {
          const wasmModule = await import('./fibonacci.wasm');
          const result = wasmModule.fibonacci(10);
          console.log('Fibonacci(10) =', result);
        } catch (err) {
          console.error('WebAssembly 模块加载失败:', err);
        }
      }
      
      loadWasmModule();
      
    • 同步导入 (不推荐):
      // 同步导入 WebAssembly 模块 (需启用 syncWebAssembly)
      import * as wasmModule from './fibonacci.wasm';
      
      const result = wasmModule.fibonacci(10);
      console.log('Fibonacci(10) =', result);
      
    • 实例应用:在我们的项目中,使用异步导入方式加载图像处理 WebAssembly 模块,实现了高性能的图像滤镜功能,同时不阻塞主线程。
  • 与 Emscripten 集成

    • 编译 C/C++ 代码:
      # 使用 Emscripten 编译 C/C++ 代码为 WebAssembly
      emcc src/math.c -o dist/math.js \
        -s WASM=1 \
        -s EXPORTED_FUNCTIONS='["_add", "_subtract", "_multiply"]' \
        -s EXPORTED_RUNTIME_METHODS='["cwrap", "ccall"]' \
        -s ALLOW_MEMORY_GROWTH=1
      
    • 在 JavaScript 中使用:
      import { cwrap } from './math.js';
      
      // 初始化 WebAssembly 模块
      Module.onRuntimeInitialized = () => {
        // 包装 C 函数
        const add = cwrap('add', 'number', ['number', 'number']);
        const subtract = cwrap('subtract', 'number', ['number', 'number']);
        const multiply = cwrap('multiply', 'number', ['number', 'number']);
        
        // 调用函数
        console.log('10 + 20 =', add(10, 20));
        console.log('30 - 15 =', subtract(30, 15));
        console.log('7 * 8 =', multiply(7, 8));
      };
      
    • 实例应用:在我们的项目中,将现有的 C++ 图像处理库编译为 WebAssembly,实现了浏览器中的高性能图像处理功能,处理速度比纯 JavaScript 实现快 5-10 倍。
  • 性能优化

    • 代码分割:
      // 使用动态导入实现懒加载 WebAssembly 模块
      const WasmButton = () => {
        const [result, setResult] = useState(null);
        
        const handleClick = async () => {
          // 仅在需要时加载 WebAssembly 模块
          const { fibonacci } = await import(/* webpackChunkName: "wasm-fibonacci" */ './fibonacci.wasm');
          setResult(fibonacci(42));
        };
        
        return (
          <div>
            <button onClick={handleClick}>计算 Fibonacci(42)</button>
            {result !== null && <p>结果: {result}</p>}
          </div>
        );
      };
      
    • 内存管理:
      // 手动管理 WebAssembly 内存
      import { memory } from './image-processor.wasm';
      
      function processImage(imageData) {
        // 获取 WebAssembly 内存缓冲区
        const wasmMemory = new Uint8Array(memory.buffer);
        
        // 复制图像数据到 WebAssembly 内存
        wasmMemory.set(imageData.data, 0);
        
        // 调用 WebAssembly 函数处理图像
        const resultPtr = Module._processImage(0, imageData.width, imageData.height);
        
        // 从 WebAssembly 内存中读取结果
        const result = new Uint8Array(memory.buffer, resultPtr, imageData.data.length);
        
        // 创建新的 ImageData
        return new ImageData(
          new Uint8ClampedArray(result), 
          imageData.width, 
          imageData.height
        );
      }
      
    • 实例应用:在我们的项目中,通过懒加载 WebAssembly 模块和高效的内存管理,实现了复杂的实时视频滤镜功能,同时保持了良好的用户体验和内存占用。
  • 实际应用场景

    • 图像处理:
      // 使用 WebAssembly 实现图像模糊效果
      import { applyGaussianBlur } from './image-effects.wasm';
      
      function blurImage(imageElement, radius) {
        // 创建 Canvas 获取图像数据
        const canvas = document.createElement('canvas');
        canvas.width = imageElement.width;
        canvas.height = imageElement.height;
        
        const ctx = canvas.getContext('2d');
        ctx.drawImage(imageElement, 0, 0);
        
        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        
        // 使用 WebAssembly 函数处理图像
        const blurredData = applyGaussianBlur(imageData.data, canvas.width, canvas.height, radius);
        
        // 更新图像数据
        const blurredImageData = new ImageData(
          new Uint8ClampedArray(blurredData), 
          canvas.width, 
          canvas.height
        );
        ctx.putImageData(blurredImageData, 0, 0);
        
        return canvas.toDataURL();
      }
      
    • 数学计算:
      // 使用 WebAssembly 进行矩阵乘法
      import { multiplyMatrices } from './matrix-ops.wasm';
      
      function performMatrixCalculation(matrixA, matrixB) {
        const startTime = performance.now();
        
        // 使用 WebAssembly 进行矩阵乘法
        const result = multiplyMatrices(matrixA, matrixB);
        
        const endTime = performance.now();
        console.log(`矩阵计算耗时: ${endTime - startTime}ms`);
        
        return result;
      }
      
    • 实例应用:在我们的数据可视化项目中,使用 WebAssembly 实现了大规模数据集的实时处理和渲染,使得在浏览器中可以流畅地操作包含数百万数据点的三维图表。

14. 实际应用

  • 多页面应用

    • 配置示例:
      entry: {
        main: './src/index.js',
        admin: './src/admin.js'
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: './public/index.html',
          filename: 'index.html',
          chunks: ['main']
        }),
        new HtmlWebpackPlugin({
          template: './public/admin.html',
          filename: 'admin.html',
          chunks: ['admin']
        })
      ]
      
    • 实例应用:在我们的项目中,配置了多入口和多 HTML 模板,为主页面和管理员页面创建独立的入口点和 HTML 文件,实现了多页面应用。
  • React 集成

    • Babel 配置:
      // .babelrc
      {
        "presets": [
          "@babel/preset-env",
          "@babel/preset-react"
        ]
      }
      
    • 热模块替换:
      // webpack.dev.js
      devServer: {
        hot: true
      }
      
    • 实例应用:在我们的项目中,配置了 Babel 支持 JSX 语法,配置了热模块替换提高开发效率,使用 React.lazy 和 Suspense 实现代码分割和懒加载。
  • 样式处理

    • 开发环境配置:
      {
        test: /\.(sass|scss)$/,
      use: [
          'style-loader',
          'css-loader',
          'sass-loader'
        ]
      }
      
    • 生产环境配置:
      {
        test: /\.(sass|scss)$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader'
        ]
      }
      
    • 实例应用:在我们的项目中,在开发环境使用 style-loader 将样式注入到 DOM,在生产环境使用 MiniCssExtractPlugin 提取 CSS 到单独文件,配置了 SASS 支持,实现了模块化的样式管理。
  • 资源管理

    • 图片处理:
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1021 // 8kb以下的图片转为内联
          }
        },
        generator: {
          filename: 'images/[name].[hash][ext]'
        }
      }
      
    • 字体处理:
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name].[hash][ext]'
        }
      }
      
    • 实例应用:在我们的项目中,使用资源模块处理图片和字体文件,小图片转为内联 Data URL,大图片和字体文件输出到指定目录,优化了资源加载。

15. Webpack 与现代前端框架的最佳实践

  • React 项目优化

    • 生产环境配置:
      // webpack.prod.js
      const TerserPlugin = require('terser-webpack-plugin');
      const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
      
      module.exports = {
        mode: 'production',
        optimization: {
          minimizer: [
            new TerserPlugin({
              terserOptions: {
                parse: {
                  ecma: 8,
                },
                compress: {
                  ecma: 5,
                  warnings: false,
                  comparisons: false,
                  inline: 2,
                  drop_console: true,
                },
                output: {
                  ecma: 5,
                  comments: false,
                },
              },
              extractComments: false,
            }),
            new CssMinimizerPlugin(),
          ],
          splitChunks: {
            chunks: 'all',
            cacheGroups: {
              reactVendor: {
                test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
                name: 'vendor-react',
                priority: 20,
              },
              utilityVendor: {
                test: /[\\/]node_modules[\\/](lodash|moment|axios)[\\/]/,
                name: 'vendor-utility',
                priority: 10,
              },
              vendor: {
                test: /[\\/]node_modules[\\/]/,
                name: 'vendor',
                priority: 5,
              },
            },
          },
          runtimeChunk: 'single',
        },
      };
      
    • React 组件懒加载:
      // App.js
      import React, { Suspense, lazy } from 'react';
      import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
      import Loading from './components/Loading';
      
      // 使用 React.lazy 进行组件懒加载
      const Home = lazy(() => import(/* webpackChunkName: "home" */ './pages/Home'));
      const Dashboard = lazy(() => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard'));
      const Settings = lazy(() => import(/* webpackChunkName: "settings" */ './pages/Settings'));
      
      function App() {
        return (
          <Router>
            <Suspense fallback={<Loading />}>
            <Switch>
                <Route exact path="/" component={Home} />
                <Route path="/dashboard" component={Dashboard} />
                <Route path="/settings" component={Settings} />
            </Switch>
          </Suspense>
          </Router>
        );
      }
      
    • 实例应用:在我们的 React 项目中,通过合理的代码分割策略和组件懒加载,将初始加载体积减少了 45%,首屏加载时间从 2.5 秒降低到 1.3 秒,显著提升了用户体验。
  • Vue 项目优化

    • Vue CLI 配置:
      // vue.config.js
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
      
      module.exports = {
        chainWebpack: config => {
          // 修改文件引入自定义路径
          config.resolve.alias
            .set('@components', '@/components')
            .set('@assets', '@/assets')
            .set('@views', '@/views');
            
          // 生产环境优化
          if (process.env.NODE_ENV === 'production') {
            // 移除 prefetch 插件,改为手动控制
            config.plugins.delete('prefetch');
            
            // 压缩图片
            config.module
              .rule('images')
              .use('image-webpack-loader')
              .loader('image-webpack-loader')
              .options({
                mozjpeg: { progressive: true, quality: 65 },
                optipng: { enabled: false },
                pngquant: { quality: [0.65, 0.90], speed: 4 },
                gifsicle: { interlaced: false },
                webp: { quality: 75 }
              });
          }
        },
        configureWebpack: config => {
          if (process.env.NODE_ENV === 'production') {
            // 添加 bundle 分析
            if (process.env.ANALYZE) {
              config.plugins.push(
                new BundleAnalyzerPlugin({
                  analyzerMode: 'static',
                  reportFilename: 'bundle-report.html',
                  openAnalyzer: false
                })
              );
            }
            
            // 优化分包策略
            config.optimization = {
              ...config.optimization,
              splitChunks: {
                chunks: 'all',
                maxInitialRequests: Infinity,
                minSize: 20000,
                cacheGroups: {
                  vueVendor: {
                    test: /[\\/]node_modules[\\/](vue|vue-router|vuex)[\\/]/,
                    name: 'vendor-vue'
                  },
                  elementUI: {
                    test: /[\\/]node_modules[\\/]element-ui[\\/]/,
                    name: 'vendor-element'
                  },
                  commons: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor-commons',
                    priority: -10
                  }
                }
              }
            };
          }
        }
      };
      
    • Vue 路由懒加载:
      // router.js
      import Vue from 'vue';
      import Router from 'vue-router';
      
      Vue.use(Router);
      
      export default new Router({
        mode: 'history',
        routes: [
          {
            path: '/',
            name: 'home',
            component: () => import(/* webpackChunkName: "home" */ './views/Home.vue')
          },
          {
            path: '/about',
            name: 'about',
            component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
          },
          {
            path: '/dashboard',
            name: 'dashboard',
            component: () => import(/* webpackChunkName: "dashboard" */ './views/Dashboard.vue'),
            // 预加载子路由
            children: [
              {
                path: 'profile',
                name: 'profile',
                component: () => import(/* webpackChunkName: "dashboard-profile" */ './views/dashboard/Profile.vue')
              },
              {
                path: 'settings',
                name: 'settings',
                component: () => import(/* webpackChunkName: "dashboard-settings" */ './views/dashboard/Settings.vue')
              }
            ]
          }
        ]
      });
      
    • 实例应用:在我们的 Vue 项目中,通过优化 Webpack 配置和路由懒加载,将首屏加载时间减少了 40%,同时通过合理的预加载策略,提高了用户在应用内导航的流畅度。
  • Angular 项目优化

    • Angular CLI 自定义 Webpack 配置:
      // extra-webpack.config.js
      const CompressionPlugin = require('compression-webpack-plugin');
      const BrotliPlugin = require('brotli-webpack-plugin');
      
      module.exports = {
        optimization: {
          runtimeChunk: 'single',
          splitChunks: {
            cacheGroups: {
              vendor: {
                test: /[\\/]node_modules[\\/]/,
                name(module) {
                  // 获取第三方库名称
                  const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
                  return `vendor.${packageName.replace('@', '')}`;
                },
                chunks: 'all',
              },
            },
          },
        },
      plugins: [
          new CompressionPlugin({
            filename: '[path][base].gz',
            algorithm: 'gzip',
            test: /\.(js|css|html|svg|txt|eot|otf|ttf|gif)$/,
            threshold: 10210,
            minRatio: 0.8,
          }),
          new BrotliPlugin({
            filename: '[path][base].br',
            test: /\.(js|css|html|svg|txt|eot|otf|ttf|gif)$/,
            threshold: 10210,
            minRatio: 0.8,
          }),
        ],
      };
      
    • 配置 Angular 项目:
      // angular.json
      {
        "projects": {
          "my-app": {
            "architect": {
              "build": {
                "options": {
                  "aot": true,
                  "optimization": true,
                  "outputHashing": "all",
                  "sourceMap": false,
                  "namedChunks": true,
                  "extractLicenses": true,
                  "vendorChunk": true,
                  "buildOptimizer": true,
                  "budgets": [
                    {
                      "type": "initial",
                      "maximumWarning": "2mb",
                      "maximumError": "5mb"
                    },
                    {
                      "type": "anyComponentStyle",
                      "maximumWarning": "6kb",
                      "maximumError": "10kb"
                    }
                  ],
                  "extraWebpackConfig": "extra-webpack.config.js"
                }
              }
            }
          }
        }
      }
      
    • 懒加载 Angular 模块:
      // app-routing.module.ts
      import { NgModule } from '@angular/core';
      import { Routes, RouterModule } from '@angular/router';
      
      const routes: Routes = [
        {
          path: '',
          loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
        },
        {
          path: 'dashboard',
          loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
        },
        {
          path: 'admin',
          loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
        }
      ];
      
      @NgModule({
        imports: [RouterModule.forRoot(routes, {
          initialNavigation: 'enabled',
          scrollPositionRestoration: 'enabled',
          preloadingStrategy: PreloadAllModules
        })],
        exports: [RouterModule]
      })
      export class AppRoutingModule { }
      
    • 实例应用:在我们的 Angular 项目中,通过自定义 Webpack 配置和模块懒加载,将应用初始加载体积减少了 50%,同时通过预加载策略提高了后续导航的响应速度,大幅提升了用户体验。
  • 跨框架共享组件

    • 使用 Module Federation:
      // webpack.config.js (React 应用)
      const { ModuleFederationPlugin } = require('webpack').container;
      
      module.exports = {
        // ...其他配置
      plugins: [
          new ModuleFederationPlugin({
            name: 'reactApp',
            filename: 'remoteEntry.js',
            exposes: {
              './Button': './src/components/Button',
              './Card': './src/components/Card'
            },
            shared: {
              react: { singleton: true, eager: true },
              'react-dom': { singleton: true, eager: true }
            }
          })
        ]
      };
      
      // webpack.config.js (Vue 应用)
      const { ModuleFederationPlugin } = require('webpack').container;
      
      module.exports = {
        // ...其他配置
      plugins: [
          new ModuleFederationPlugin({
            name: 'vueApp',
            filename: 'remoteEntry.js',
            remotes: {
              reactApp: 'reactApp@http://localhost:3001/remoteEntry.js'
            },
            shared: {
              vue: { singleton: true, eager: true }
            }
          })
        ]
      };
      
    • 在 Vue 中使用 React 组件:
      // Vue 组件中
      <template>
        <div class="container">
          <h1>Vue 应用</h1>
          <div ref="reactButton"></div>
        </div>
      </template>
      
      <script>
      import React from 'react';
      import ReactDOM from 'react-dom';
      import { defineComponent, onMounted, ref } from 'vue';
      
      // 动态导入 React 组件
      const ReactButton = React.lazy(() => import('reactApp/Button'));
      
      export default defineComponent({
        setup() {
          const reactButton = ref(null);
          
          onMounted(() => {
            // 在 Vue 组件中渲染 React 组件
            ReactDOM.render(
              React.createElement(
                React.Suspense,
                { fallback: React.createElement('div', null, 'Loading...') },
                React.createElement(ReactButton, { label: '点击我' })
              ),
              reactButton.value
            );
          });
          
          return { reactButton };
        }
      });
      </script>
      
    • 实例应用:在我们的微前端项目中,使用 Module Federation 实现了 React、Vue 和 Angular 组件的无缝共享,使团队能够使用各自熟悉的框架开发,同时保持统一的用户体验和组件复用。
  • 性能优化最佳实践

    • 代码分割策略:
      // webpack.config.js
      module.exports = {
        // ...其他配置
      optimization: {
        splitChunks: {
          chunks: 'all',
            maxInitialRequests: 25,
            minSize: 20000,
          cacheGroups: {
              // 框架核心库
              framework: {
                test: /[\\/]node_modules[\\/](react|react-dom|vue|@angular\/core)[\\/]/,
                name: 'framework',
                priority: 40,
              },
              // UI 组件库
              ui: {
                test: /[\\/]node_modules[\\/](antd|@material-ui|element-ui)[\\/]/,
                name: 'ui',
                priority: 30,
              },
              // 工具库
              utils: {
                test: /[\\/]node_modules[\\/](lodash|moment|axios)[\\/]/,
                name: 'utils',
                priority: 20,
              },
              // 其他第三方库
            vendors: {
              test: /[\\/]node_modules[\\/]/,
                name: 'vendors',
                priority: 10,
            },
              // 公共业务代码
            common: {
              minChunks: 2,
                priority: 5,
                reuseExistingChunk: true,
              },
            },
          },
        },
      };
      
    • 预加载关键资源:
      <!-- index.html -->
      <head>
        <!-- 预加载关键 CSS -->
        <link rel="preload" href="/styles/critical.css" as="style">
        
        <!-- 预加载字体 -->
        <link rel="preload" href="/fonts/roboto.woff2" as="font" type="font/woff2" crossorigin>
        
        <!-- 预取可能需要的资源 -->
        <link rel="prefetch" href="/js/dashboard.chunk.js">
      </head>
      
    • 实例应用:在我们的大型 SPA 项目中,通过精细的代码分割策略和资源预加载技术,将首屏加载时间减少了 60%,同时通过合理的缓存策略,使重复访问的用户加载时间降低到 300ms 以内,大幅提升了用户体验和留存率。
  • SSR 与 SSG 集成

    • Next.js 配置:
      // next.config.js
      const withBundleAnalyzer = require('@next/bundle-analyzer')({
        enabled: process.env.ANALYZE === 'true',
      });
      
      module.exports = withBundleAnalyzer({
        webpack: (config, { isServer }) => {
          // 仅在客户端打包时应用
          if (!isServer) {
            // 优化图片
            config.module.rules.push({
              test: /\.(png|jpe?g|gif|webp)$/i,
              use: [
                {
                  loader: 'image-webpack-loader',
                  options: {
                    mozjpeg: { progressive: true, quality: 65 },
                    optipng: { enabled: false },
                    pngquant: { quality: [0.65, 0.90], speed: 4 },
                    gifsicle: { interlaced: false },
                    webp: { quality: 75 }
                  }
                }
              ]
            });
          }
          
          return config;
        },
        images: {
          domains: ['example.com', 'cdn.example.com'],
          deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
          imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
        },
        experimental: {
          optimizeCss: true,
          scrollRestoration: true,
        },
      });
      
    • Nuxt.js 配置:
      // nuxt.config.js
      export default {
        // 全局页面设置
        head: {
          htmlAttrs: {
            lang: 'zh-CN',
          },
          meta: [
            { charset: 'utf-8' },
            { name: 'viewport', content: 'width=device-width, initial-scale=1' },
            { hid: 'description', name: 'description', content: '网站描述' }
          ],
          link: [
            { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
            { rel: 'preconnect', href: 'https://cdn.example.com' }
          ]
        },
        
        // 构建优化
        build: {
          // 提取 CSS
          extractCSS: process.env.NODE_ENV === 'production',
          // 优化 CSS
          optimizeCSS: process.env.NODE_ENV === 'production',
          // 压缩 HTML
          html: {
            minify: {
              collapseBooleanAttributes: true,
              decodeEntities: true,
              minifyCSS: true,
              minifyJS: true,
              processConditionalComments: true,
              removeEmptyAttributes: true,
              removeRedundantAttributes: true,
              trimCustomFragments: true,
              useShortDoctype: true
            }
          },
          // 优化 JS
          terser: {
            terserOptions: {
              compress: {
                drop_console: process.env.NODE_ENV === 'production'
              }
            }
          },
          // 分析打包结果
          analyze: process.env.ANALYZE === 'true',
          // 优化导入
          transpile: ['lodash-es'],
          // 优化 Webpack
          optimization: {
            splitChunks: {
              cacheGroups: {
                styles: {
                  name: 'styles',
                  test: /\.(css|vue)$/,
                  chunks: 'all',
                  enforce: true
                }
              }
            }
          }
        },
        
        // 性能提示
        render: {
          bundleRenderer: {
            shouldPreload: (file, type) => {
              return ['script', 'style', 'font'].includes(type);
            }
          }
        }
      };
      
    • 实例应用:在我们的企业网站项目中,使用 Next.js 实现了服务端渲染和静态页面生成的混合策略,将首屏内容加载时间减少到 1 秒以内,同时通过增量静态再生成 (ISR) 技术,保持了内容的实时性和服务器负载的平衡,大幅提升了 SEO 效果和用户体验。

16. Webpack 与 DevOps/CI/CD

第四章:Webpack 高级特性与生态

17. Webpack 5 新特性

12.1 持久化缓存

  • 改进的缓存机制

    • Webpack 5 引入了持久化缓存,大幅提升了重复构建的性能

    // webpack.config.js module.exports = { // ...其他配置 cache: { type: 'filesystem', // 使用文件系统缓存 buildDependencies: { config: [__filename] // 当配置文件变化时,缓存失效 }, name: 'my-build-cache', // 缓存名称 version: '1.0' // 缓存版本 } };

  • 实例应用:在大型项目中启用持久化缓存

    • 在我们的项目中,启用持久化缓存后,二次构建时间从 30 秒减少到 5 秒
    • 缓存文件存储在 node_modules/.cache/webpack 目录下
  • 缓存失效策略

    • 通过 version 和 name 控制缓存版本
    • 通过 buildDependencies 指定构建依赖,当依赖变化时缓存失效
    • 可以通过环境变量 WEBPACK_CACHE_DISABLE=true 禁用缓存

12.2 资源模块类型 (Asset Modules)

  • 统一的资源处理
    • Webpack 5 引入了资源模块类型,取代了 file-loader、url-loader 和 raw-loader
    // webpack.config.js module.exports = { // ...其他配置 module: { rules: [ { test: /.png/, type: 'asset/resource' // 相当于 file-loader }, { test: /\.svg/, type: 'asset/inline' // 相当于 url-loader }, { test: /.txt/, type: 'asset/source' // 相当于 raw-loader }, { test: /\.jpg/, type: 'asset', // 自动选择 resource 或 inline parser: { dataUrlCondition: { maxSize: 4 * 1021 // 4kb } } } ] }, output: { // ...其他配置 assetModuleFilename: 'assets/[hash][ext][query]' } };
  • 实例应用:优化图片资源处理
    // 在组件中使用
    import React from 'react';
    import logoUrl from './logo.png'; // 大图片,作为文件处理
    import iconUrl from './icon.svg'; // 小图标,内联为 Data URL
    
    const Header = () => (
    <header>
      <img src={logoUrl} alt="Logo" className="logo" />
      <img src={iconUrl} alt="Icon" className="icon" />
    </header>
    );
    
    export default Header;
    

12.3 Tree Shaking 增强

  • 内部模块 Tree Shaking

    • Webpack 5 改进了对未使用模块的检测和删除
    • 支持对 CommonJS 模块进行 Tree Shaking
    • 新增了对嵌套模块的 Tree Shaking 支持
  • 副作用分析

    • 更精确的副作用分析,减少不必要的代码保留
      // package.json
      {
      "name": "my-package",
        "sideEffects": [
          "*.css",
        "*.scss",
        "./src/polyfills.js"
      ]
      

    }

  • 实例应用:优化 lodash 引入

    // 优化前
    import _ from 'lodash';
    const result = _.cloneDeep(obj);
    
    // 优化后 (使用具名导入)
    import { cloneDeep } from 'lodash-es';
    const result = cloneDeep(obj);
    
    // 或使用 babel-plugin-lodash
    // npm install --save-dev babel-plugin-lodash
    // .babelrc
    {
    "plugins": ["lodash"]
    }
    

12.4 模块联邦 (Module Federation)

  • 跨应用共享模块

    • 模块联邦允许多个独立的构建共享模块
    • 实现微前端架构的技术基础

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

    module.exports = { // ...其他配置 plugins: [ new ModuleFederationPlugin({ name: 'host', filename: 'remoteEntry.js', remotes: { app1: 'app1@http://localhost:3001/remoteEntry.js', app2: 'app2@http://localhost:3002/remoteEntry.js' }, shared: { react: { singleton: true }, 'react-dom': { singleton: true } } }) ] };

  • 子应用配置

    // 子应用 webpack.config.js
    const { ModuleFederationPlugin } = require('webpack').container;
    
    module.exports = {
    // ...其他配置
    plugins: [
      new ModuleFederationPlugin({
        name: 'app1',
        filename: 'remoteEntry.js',
        exposes: {
          './Button': './src/components/Button',
          './Header': './src/components/Header'
        },
        shared: {
          react: { singleton: true },
          'react-dom': { singleton: true }
        }
      })
    ]
    };
    
  • 实例应用:在主应用中使用远程组件

    // 在主应用中动态导入远程组件
    import React, { Suspense } from 'react';
    
    // 使用动态导入加载远程组件
    const RemoteButton = React.lazy(() => import('app1/Button'));
    const RemoteHeader = React.lazy(() => import('app1/Header'));
    
    const App = () => (
    <div>
      <h1>主应用</h1>
      <Suspense fallback={<div>加载中...</div>}>
        <RemoteHeader />
        <RemoteButton />
      </Suspense>
    </div>
    );
    
    export default App;
    

18. Webpack 迁移与升级指南