前端工程化与性能优化指南

前端工程化与性能优化指南

一、Webpack 核心配置与原理

1.1 Webpack 基础配置手写

1.1.1 最小化 Webpack 配置
javascript 复制代码
// webpack.config.js
// 1. 引入 Node.js 路径处理模块,用于处理文件路径
const path = require('path');

module.exports = {
  // 2. mode 指定构建模式
  //    - 'development':开发模式,代码不压缩,生成 sourcemap
  //    - 'production':生产模式,代码压缩,启用 tree-shaking
  mode: 'development',

  // 3. entry 指定入口文件
  //    Webpack 从这里开始递归解析所有模块,构建依赖图
  entry: './src/index.js',

  // 4. output 指定输出配置
  output: {
    // 5. filename 输出文件名
    //    [name] 是入口名称,[hash] 是本次构建的 hash 值
    //    hash 用于解决缓存问题:当文件内容变化时,hash 变化,浏览器重新请求
    filename: '[name].[hash].js',

    // 6. path 输出目录(必须是绝对路径)
    //    __dirname 是 Node.js 全局变量,表示当前文件所在目录的绝对路径
    path: path.resolve(__dirname, 'dist'),

    // 7. clean:构建前清理输出目录
    //    避免残留旧文件导致混乱
    clean: true
  }
};

1.1.2 完整开发环境配置
javascript 复制代码
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const webpack = require('webpack');

module.exports = {
  // 1. 入口配置
  entry: {
    // 入口名称:入口路径
    // 可以有多个入口,实现多页面应用
    main: './src/main.js',
    // 另一个入口,用于第三方库单独打包
    vendors: './src/vendors.js'
  },

  // 2. 输出配置
  output: {
    // [name] = 入口名称,保留多个入口时文件名不冲突
    // [hash:8] 取 hash 的前8位,平衡缓存和文件名长度
    filename: '[name].[hash:8].js',

    // 3. 输出目录(绝对路径)
    path: path.resolve(__dirname, 'dist'),

    // 4. 公开路径:生成的文件引用的基础路径
    //    适用于 CDN 部署或子目录部署
    publicPath: '/',

    // 5. 是否使用哈希命名(可用于缓存)
    //    实际项目中可用 contenthash 替代 hash
    hashDigestLength: 8,

    // 6. 资源模块的公开 URL 前缀
    //    用于 css、图片等资源在 HTML 中的路径
    assetModuleFilename: 'assets/[hash][ext][query]',

    // 7. 清理输出目录
    clean: true
  },

  // 8. 加载器配置(处理非 JS 文件)
  //    加载器从右到左执行(最后的先执行)
  module: {
    rules: [
      // 8.1 处理 JavaScript 文件
      {
        test: /\.js$/,
        // exclude 排除不需要处理的文件
        // 提升构建速度
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            // Babel 配置文件路径
            configFile: path.resolve(__dirname, '.babelrc'),
            // 开启缓存:编译过的文件缓存起来,加快下次构建
            cacheDirectory: true
          }
        }
      },

      // 8.2 处理 CSS 文件
      {
        test: /\.css$/,
        // use 数组从右到左执行:
        // 1. css-loader:将 CSS 转为 JS 模块(处理 @import、url() 等)
        // 2. style-loader:将 CSS 注入到 DOM 的 <style> 标签中
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              // 开启 CSS 模块化(针对 .module.css 文件)
              modules: {
                // 自定义生成的类名格式
                localIdentName: '[name]__[local]--[hash:base64:5]'
              }
            }
          }
        ]
      },

      // 8.3 处理 SCSS 文件
      {
        test: /\.scss$/,
        // 从右到左:sass-loader -> css-loader -> style-loader
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
          {
            loader: 'sass-loader',
            options: {
              // 指定 sass 实现(dart-sass/node-sass)
              // 推荐 dart-sass,性能更好
              implementation: require('sass')
            }
          }
        ]
      },

      // 8.4 处理图片文件
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/i,
        // asset/resource 将图片输出到输出目录
        // 适合大图,打包进 bundle 会导致体积过大
        type: 'asset/resource',
        // 自定义输出路径和文件名
        generator: {
          filename: 'images/[hash][ext][query]'
        }
      },

      // 8.5 处理字体文件
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[hash][ext][query]'
        }
      },

      // 8.6 处理 Vue 单文件组件
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },

  // 9. 插件配置
  plugins: [
    // 9.1 生成 HTML 文件
    //    自动注入 entry 生成的 JS 文件
    new HtmlWebpackPlugin({
      // 模板文件路径
      template: './public/index.html',
      // 输出文件名
      filename: 'index.html',
      // 是否注入 chunk(JS 文件)
      inject: true,
      // 是否压缩 HTML
      minify: {
        removeComments: true,    // 删除注释
        collapseWhitespace: true, // 折叠空白
        removeAttributeQuotes: true // 删除属性引号
      }
    }),

    // 9.2 Vue Loader 插件(必须)
    new VueLoaderPlugin(),

    // 9.3 DefinePlugin:定义全局常量
    //    替换源代码中的 process.env.NODE_ENV
    new webpack.DefinePlugin({
      // 注意:值必须是字符串,会被直接替换
      // 'development' 会替换为字符串 'development'
      // 而 NODE_ENV 会替换为实际的字符串
      'process.env.NODE_ENV': JSON.stringify('development'),
      'process.env.VUE_APP_BASE_API': JSON.stringify('/api')
    }),

    // 9.4 ProvidePlugin:自动加载模块
    //    当模块未找到时,自动注入指定模块
    //    不需要手动 import $ 或 import Vue
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery',
      Vue: ['vue/dist/vue.esm.js', 'default']
    })
  ],

  // 10. 解析配置
  resolve: {
    // 10.1 文件扩展名:自动补全后缀
    //    导入时不用写 .js / .vue 等后缀
    extensions: ['.js', '.vue', '.json', '.css'],

    // 10.2 路径别名:简化导入路径
    alias: {
      // '@' 通常指向 src 目录
      '@': path.resolve(__dirname, 'src'),
      // 统一第三方库的路径,方便替换
      'vue$': 'vue/dist/vue.esm.js',
      // 组件路径别名
      'components': path.resolve(__dirname, 'src/components'),
      'utils': path.resolve(__dirname, 'src/utils')
    },

    // 10.3 解析模块的目录
    //    减少解析路径,提升构建速度
    modules: [
      path.resolve(__dirname, 'src'),
      'node_modules'
    ],

    // 10.4 主文件字段
    //    用于 package.json 的 exports 字段
    mainFields: ['browser', 'module', 'main']
  },

  // 11. 开发服务器配置
  devServer: {
    // 11.1 静态文件目录
    static: {
      directory: path.join(__dirname, 'public')
    },

    // 11.2 端口号
    port: 8080,

    // 11.3 自动打开浏览器
    open: true,

    // 11.4 开启热更新(HMR)
    //    修改代码后浏览器自动刷新,只更新变化的部分
    hot: true,

    // 11.5 代理配置(解决跨域)
    proxy: {
      '/api': {
        // 代理目标服务器
        target: 'http://localhost:3000',
        // 是否改变 Origin(开发环境设为 true)
        changeOrigin: true,
        // 重写路径(去掉 /api 前缀)
        pathRewrite: { '^/api': '' }
      }
    },

    // 11.6 .historyApiFallback
    //    SPA 使用 HTML5 History API 时,404 请求回退到 index.html
    historyApiFallback: true,

    // 11.7 压缩
    compress: true
  },

  // 12. 优化配置
  optimization: {
    // 12.1 代码分割
    splitChunks: {
      chunks: 'all',
      // 分割的最小块大小(字节)
      minSize: 20000,
      // 最大请求数(限制并行下载数量)
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      cacheGroups: {
        // 第三方库单独打包
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: -10 // 优先级,数字越大越先处理
        },
        // 公共模块打包
        common: {
          minChunks: 2,
          name: 'common',
          chunks: 'all',
          priority: -20
        }
      }
    },

    // 12.2 runtime 代码单独打包
    //    使 chunk 缓存更有效
    runtimeChunk: 'single',

    // 12.3 模块标识符优化
    //    改变时只影响该模块,不影响其他模块的 hash
    moduleIds: 'deterministic',

    // 12.4 生产环境 tree-shaking
    usedExports: true,
    // 合并合并模块
    concatenateModules: true
  },

  // 13. 构建目标
  target: 'web',

  // 14. Source Map 配置
  //    开发环境用 cheap-module-source-map(快)
  //    生产环境用 hidden-source-map 或 false(安全)
  devtool: 'cheap-module-source-map',

  // 15. 统计信息输出
  stats: 'errors-warnings',

  // 16. 外部扩展
  //    运行时从 CDN 加载,不打包进 bundle
  externals: {
    vue: 'Vue',
    react: 'React'
  }
};

1.2 Webpack 高级配置

1.2.1 生产环境优化配置
javascript 复制代码
// webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const PrerenderSPAPlugin = require('prerender-webpack-plugin');

module.exports = (env, argv) => {
  // 1. 根据 mode 确定是否为生产环境
  const isProduction = argv.mode === 'production';

  return {
    // 2. 生产模式自动优化,但我们可以进一步控制
    mode: isProduction ? 'production' : 'development',

    // 3. 生产环境 source map 设为 false(安全)
    //    或者用 nosources-source-map(只有错误位置,没有源码)
    devtool: isProduction ? false : 'eval-source-map',

    // 4. 入口
    entry: {
      app: './src/main.js'
    },

    output: {
      // 5. 生产环境用 contenthash,实现长期缓存
      //    只有内容变化时 hash 才变化
      filename: isProduction
        ? 'js/[name].[contenthash:8].js'
        : 'js/[name].js',

      path: path.resolve(__dirname, 'dist'),

      // 6. 所有资源的基础路径(CDN 部署时需要)
      publicPath: 'https://cdn.example.com/',

      // 7. 不生成 Source Map 文件(安全)
      sourceMapFilename: '[file].map',

      clean: true
    },

    module: {
      rules: [
        // 8. CSS 处理:开发环境用 style-loader,生产环境提取为独立文件
        {
          test: /\.css$/,
          use: [
            // 开发环境:CSS 注入到 <style> 标签
            // 生产环境:MiniCssExtractPlugin 提取到 .css 文件
            isProduction
              ? MiniCssExtractPlugin.loader
              : { loader: 'style-loader' },
            { loader: 'css-loader' }
          ]
        },

        // 9. 图片资源优化
        {
          test: /\.(png|jpg|jpeg|gif|svg)$/i,
          type: 'asset',
          parser: {
            // 10. 数据URI策略:小图转 base64(减少请求)
            dataUrlCondition: {
              // 小于 8KB 的图片转为 base64,减少 HTTP 请求
              maxSize: 8 * 1024
            }
          },
          generator: {
            filename: 'images/[name].[hash:8][ext]'
          }
        }
      ]
    },

    plugins: [
      // 11. HTML 模板
      new HtmlWebpackPlugin({
        template: './public/index.html',
        // 12. 压缩 HTML
        minify: isProduction ? {
          removeComments: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true
        } : false
      }),

      // 13. 提取 CSS 为独立文件(生产环境)
      new MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash:8].css',
        chunkFilename: 'css/[name].[contenthash:8].chunk.css'
      }),

      // 14. 清理输出目录
      new CleanWebpackPlugin(),

      // 15. 预渲染 SPA(可选,优化首屏渲染)
      new PrerenderSPAPlugin({
        staticDir: path.join(__dirname, 'dist'),
        routes: ['/', '/about', '/contact'],
        minify: {
          collapseWhitespace: true,
          removeComments: true
        }
      })
    ],

    optimization: {
      // 16. 最小化器配置
      minimizer: [
        // 16.1 JS 压缩(Terser)
        new TerserPlugin({
          terserOptions: {
            parse: {
              // 开启 ESTree 解析(更精确)
              ecma: 2020
            },
            compress: {
              // 删除 console.log(上线后不需要)
              drop_console: true,
              // 删除 debugger
              drop_debugger: true,
              // 移除未使用的代码
              dead_code: true,
              // 优化布尔运算
              booleans: true,
              // 优化 if 语句
              conditionals: true,
              // 移除 unused 代码
              unused: true
            },
            mangle: {
              // 生产环境擦除变量名(压缩)
              safari10: true
            },
            output: {
              // 保持注释(许可证声明)
              comments: /@license/i
            }
          },
          extractComments: false, // 不生成 LICENSE 文件
          parallel: true           // 开启多进程压缩
        }),

        // 16.2 CSS 压缩(CssMinimizerPlugin)
        new CssMinimizerPlugin({
          minimizerOptions: {
            preset: [
              'default',
              {
                discardComments: { removeAll: true }
              }
            ]
          }
        })
      ],

      // 17. 代码分割
      splitChunks: {
        chunks: 'all',
        maxInitialRequests: 5,
        cacheGroups: {
          // 第三方库
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all',
            priority: 10
          },
          // 公共组件
          common: {
            minChunks: 2,
            name: 'common',
            chunks: 'all',
            priority: 5
          },
          // 高频库单独打包(可能长期缓存)
          vue: {
            test: /[\\/]node_modules[\\/](vue|vue-router|vuex)[\\/]/,
            name: 'vue-vendor',
            chunks: 'all',
            priority: 20
          }
        }
      },

      // 18. runtime 单独打包
      runtimeChunk: 'single',

      // 19. 生成模块映射(便于调试)
      moduleIds: 'deterministic',

      // 20. 启用 tree-shaking
      usedExports: true,
      // 21. 尽力压缩
      concatenateModules: true
    },

    // 22. 性能提示
    performance: {
      hints: isProduction ? 'warning' : false,
      // 超过 500KB 给出警告
      maxEntrypointSize: 512000,
      maxAssetSize: 512000
    }
  };
};

1.2.2 Webpack 插件手写
javascript 复制代码
// 1. 自定义 Webpack 插件基本结构
class CustomPlugin {
  // 1.1 插件构造函数
  //    compiler 是 webpack 实例,包含所有配置和状态
  constructor(options = {}) {
    // 保存插件配置
    this.options = options;
    // 插件名(用于日志和调试)
    this.pluginName = 'CustomPlugin';
  }

  // 1.2 apply 是插件的主入口
  //    Webpack 会在构建过程中调用此方法
  apply(compiler) {
    // 2. 监听 Webpack 生命周期钩子

    // 2.1 compilation:每次编译开始时调用
    //    compilation 是本次编译的实例,包含模块和 chunk 信息
    compiler.hooks.compilation.tap(this.pluginName, (compilation) => {
      // 3. 监听 compilation 的特定钩子
      //    这里监听优化资源的过程
      compilation.hooks.processAssets.tapAsync(
        {
          name: this.pluginName,
          // 资源处理阶段:在优化和生成资源之后
          stage: compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
        },
        (assets, callback) => {
          // assets 是本次编译生成的所有资源
          // 可以在这里修改、添加或删除资源

          for (const [filename, asset] of Object.entries(assets)) {
            console.log(`Generated asset: ${filename}`);
            console.log(`Size: ${asset.size()} bytes`);
          }

          // 处理完成后调用 callback
          callback();
        }
      );
    });

    // 2.2 emit:资源生成到输出目录之前调用
    //    可以在这里修改输出文件的内容
    compiler.hooks.emit.tapAsync(this.pluginName, (compilation, callback) => {
      // compilation.assets 包含所有要输出的资源
      const stats = compilation.getStats();
      const assets = Object.keys(compilation.assets);

      console.log(`Total assets: ${assets.length}`);

      // 2.3 添加新的资源文件
      //    可以生成额外的文件,如分析报告
      const manifest = {
        buildTime: new Date().toISOString(),
        assets: assets.map(name => ({
          name,
          size: compilation.assets[name].size()
        }))
      };

      // 添加一个 JSON 文件到输出
      compilation.assets['manifest.json'] = {
        source: () => JSON.stringify(manifest, null, 2),
        size: () => JSON.stringify(manifest).length
      };

      callback();
    });

    // 2.4 afterEmit:资源输出完成后调用
    //    适合做清理工作或发送通知
    compiler.hooks.afterEmit.tapAsync(this.pluginName, (compilation, callback) => {
      console.log('Build completed and files emitted');
      callback();
    });

    // 2.5 done:编译完成后调用
    //    不管成功还是失败都会调用
    compiler.hooks.done.tapAsync(this.pluginName, (stats, callback) => {
      const buildInfo = stats.toJson();
      console.log('Build done:', {
        time: buildInfo.time,
        assets: buildInfo.assets.length,
        warnings: buildInfo.warnings.length,
        errors: buildInfo.errors.length
      });
      callback();
    });
  }
}

// 使用自定义插件
module.exports = {
  plugins: [
    new CustomPlugin({
      // 插件配置
      verbose: true
    })
  ]
};

1.2.3 Webpack Loader 手写
javascript 复制代码
// 2. 自定义 Webpack Loader 基本结构

// 2.1 同步 Loader
//    简单场景,返回处理后的 source
function simpleLoader(source) {
  // source 是原始文件内容

  // 转换 source
  const transformed = source.replace(/旧文本/g, '新文本');

  // 返回处理后的内容
  // 可以返回 this.callback(err, result) 传递更多信息
  return transformed;
}

// 2.2 完整 Loader(带上下文信息)
function fullLoader(source, inputSourceMap) {
  // 1. 获取 loader 配置
  const options = this.getOptions();
  const callback = this.async();

  // 2. this.callback 用于异步处理
  //    第一个参数是错误,第二个是处理后的源码,第三个是 source map
  callback(null, transformedSource, sourceMap);

  // 或者报告错误
  // callback(new Error('处理失败'), null);
}

// 2.3 使用 loader-utils 解析选项
const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');

const schema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'number' }
  },
  additionalProperties: false
};

function loader(source) {
  const options = getOptions(this);

  // 验证选项
  validate(schema, options, {
    name: 'MyLoader',
    baseDataPath: 'options'
  });

  // 转换 source
  let transformed = source;

  // 根据配置处理
  if (options.name) {
    transformed = transformed.replace(/PROJECT_NAME/g, options.name);
  }

  // 返回(同步)
  return transformed;
}

// 2.4 完整 Loader 示例:markdown 转 HTML
const marked = require('marked');

function markdownLoader(source) {
  // 1. 获取配置
  const options = this.getOptions();

  // 2. 解析 markdown
  const html = marked(source, {
    // marked 配置
    gfm: true,
    breaks: options.breaks || false
  });

  // 3. 包装成 HTML 模块
  //    导出字符串,供其他模块引用
  const result = `
    const html = ${JSON.stringify(html)};
    export default html;
  `;

  // 4. 如果需要 source map
  //    返回数组 [result, sourceMap]
  return result;
}

// 2.5 Pitch Loader
//    在 Loader 主函数之前执行,可以决定是否跳过主函数
function pitchLoader(remainingRequest, precedingRequest, data) {
  // 1. remainingRequest:剩余的 loader 路径
  // 2. precedingRequest:已经执行过的 loader 路径
  // 3. data:可以在 loader 间传递数据

  // 如果返回非 undefined,会跳过所有后续 loader
  // 常用于条件加载
  if (someCondition) {
    return '';
  }
}

// 2.6 导出 Loader
module.exports = markdownLoader;
module.exports.pitch = pitchLoader;

1.3 Webpack 核心原理

1.3.1 Tapable 插件系统原理
javascript 复制代码
// Tapable 是 Webpack 的核心插件系统
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook } = require('tapable');
const { AsyncParallelHook, AsyncSeriesHook } = require('tapable');

// 1. SyncHook:同步钩子,回调依次执行,不关心返回值
class MyPlugin {
  constructor() {
    // 1.1 创建钩子,定义参数
    this.hooks = {
      // 1.2 定义一个同步钩子,接受 (name, age) 两个参数
      greet: new SyncHook(['name', 'age'])
    };
  }

  // 1.3 注册事件(tap)
  register() {
    this.hooks.greet.tap('Plugin1', (name, age) => {
      console.log('Plugin1:', name, age);
    });

    this.hooks.greet.tap('Plugin2', (name, age) => {
      console.log('Plugin2:', name, age);
    });
  }

  // 1.4 触发事件(call)
  call(name, age) {
    this.hooks.greet.call(name, age);
    // 输出:
    // Plugin1: Alice 25
    // Plugin2: Alice 25
  }
}

// 2. SyncBailHook:同步钩子,回调依次执行
//    如果某个回调返回非 undefined,后续回调不再执行
class MyBailPlugin {
  constructor() {
    this.hooks = {
      // 2.1 bail 钩子
      find: new SyncBailHook(['data'])
    };
  }

  register() {
    this.hooks.find.tap('CachePlugin', (data) => {
      if (data.cache) {
        return data.cache; // 返回值,后续不执行
      }
    });

    this.hooks.find.tap('FetchPlugin', (data) => {
      return fetchFromNetwork(data); // 一旦执行,后续不执行
    });
  }

  call(data) {
    const result = this.hooks.find.call(data);
    console.log('Result:', result);
  }
}

// 3. SyncWaterfallHook:同步钩子,依次传递结果
//    前一个回调的返回值传给下一个回调
class MyWaterfallPlugin {
  constructor() {
    this.hooks = {
      process: new SyncWaterfallHook(['initial'])
    };
  }

  register() {
    this.hooks.process.tap('Step1', (initial) => {
      return initial + ' -> Step1';
    });

    this.hooks.process.tap('Step2', (preResult) => {
      return preResult + ' -> Step2';
    });

    this.hooks.process.tap('Step3', (preResult) => {
      return preResult + ' -> Step3';
    });
  }

  call() {
    const result = this.hooks.process.call('Start');
    // 结果: 'Start -> Step1 -> Step2 -> Step3'
  }
}

// 4. AsyncSeriesHook:异步串行钩子
//    依次执行异步回调,每个完成后执行下一个
class MyAsyncPlugin {
  constructor() {
    this.hooks = {
      load: new AsyncSeriesHook(['resource'])
    };
  }

  register() {
    this.hooks.load.tapAsync('FileLoader', (resource, callback) => {
      fs.readFile(resource, (err, data) => {
        if (err) return callback(err);
        console.log('File loaded');
        callback(); // 必须调用 callback
      });
    });

    this.hooks.load.tapAsync('CacheLoader', (resource, callback) => {
      checkCache(resource, (err, cached) => {
        if (err) return callback(err);
        console.log('Cache checked');
        callback();
      });
    });
  }

  call(resource) {
    this.hooks.load.callAsync(resource, (err) => {
      if (err) console.error('Error:', err);
      else console.log('All done');
    });
  }
}

// 5. AsyncParallelHook:异步并行钩子
//    所有异步回调同时执行,全部完成后执行最终回调
class MyParallelPlugin {
  constructor() {
    this.hooks = {
      parallel: new AsyncParallelHook(['data'])
    };
  }

  register() {
    this.hooks.parallel.tapAsync('Task1', (data, callback) => {
      setTimeout(() => {
        console.log('Task1 done');
        callback(null, 'result1');
      }, 100);
    });

    this.hooks.parallel.tapAsync('Task2', (data, callback) => {
      setTimeout(() => {
        console.log('Task2 done');
        callback(null, 'result2');
      }, 50);
    });
  }

  call(data) {
    this.hooks.parallel.callAsync(data, (err, results) => {
      console.log('All tasks completed');
      // results 是所有回调结果的数组
    });
  }
}

1.3.2 Webpack 构建流程
javascript 复制代码
// Webpack 构建流程简化实现
class SimpleWebpack {
  constructor(options) {
    this.options = options; // webpack 配置
    this.modules = {};      // 模块缓存
    this.chunks = {};       // chunk 缓存
    this.assets = {};       // 资源缓存
  }

  // 1. run:启动构建
  run() {
    const { entry, output, module, plugins } = this.options;

    console.log('=== Webpack Build Start ===');

    // 2. 执行插件的 apply 方法(插件初始化)
    plugins.forEach(plugin => {
      if (typeof plugin.apply === 'function') {
        plugin.apply(this);
      }
    });

    // 3. 从入口文件开始编译
    const entryModule = this.buildModule(entry, {});

    // 4. 生成 chunk(代码块)
    const chunks = this.createChunks(entryModule);

    // 5. 输出资源
    this.emitAssets(chunks, output);

    console.log('=== Webpack Build Complete ===');
  }

  // 6. buildModule:编译单个模块
  buildModule(filename, parent) {
    // 6.1 读取文件内容
    const source = fs.readFileSync(filename, 'utf-8');
    console.log(`Building: ${filename}`);

    // 6.2 获取 loader 规则
    const rules = this.options.module?.rules || [];

    // 6.3 应用 loader 链
    let processedSource = source;
    for (const rule of rules) {
      if (rule.test.test(filename)) {
        // 执行 loader
        const loader = rule.use;
        if (typeof loader === 'string') {
          // 同步 loader
          const loaderFn = require(loader);
          processedSource = loaderFn(processedSource);
        }
      }
    }

    // 6.4 调用 Webpack 的 module API(处理依赖)
    const module = {
      id: filename,
      dependencies: [], // 存储依赖
      source: processedSource
    };

    // 6.5 解析依赖
    //    使用正则解析 import/require 语句
    const dependencyPattern = /require\(['"](.+?)['"]\)/g;
    let match;
    while ((match = dependencyPattern.exec(processedSource)) !== null) {
      const depPath = path.resolve(dirname(filename), match[1]);
      module.dependencies.push(depPath);
    }

    // 6.6 递归编译依赖模块
    module.dependencies.forEach(dep => {
      this.buildModule(dep, module);
    });

    return module;
  }

  // 7. createChunks:生成代码块
  createChunks(entryModule) {
    // 将模块组合成 chunk
    return [{
      id: 'main',
      modules: [entryModule],
      name: 'main'
    }];
  }

  // 8. emitAssets:输出资源
  emitAssets(chunks, output) {
    for (const chunk of chunks) {
      // 生成文件内容
      const filename = output.filename.replace('[name]', chunk.name);
      this.assets[filename] = this.generateCode(chunk);

      // 写入文件系统
      const outputPath = path.join(output.path, filename);
      fs.writeFileSync(outputPath, this.assets[filename]);
      console.log(`Emitted: ${outputPath}`);
    }
  }

  // 9. generateCode:生成最终代码
  generateCode(chunk) {
    // 拼接模块代码,生成 bundle
    return `
      (function(modules) {
        // 9.1 定义 require 函数
        function __webpack_require__(moduleId) {
          var module = {
            exports: {}
          };
          modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
          return module.exports;
        }

        // 9.2 启动模块加载
        return __webpack_require__("${chunk.modules[0].id}");
      })({
        "${chunk.modules[0].id}": function(module, exports, __webpack_require__) {
          ${chunk.modules[0].source}
        }
      })
    `;
  }
}

二、Vite 核心原理

2.1 Vite 核心配置

2.1.1 Vite 基础配置
javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import react from '@vitejs/plugin-react';
import cssPlugin from 'vite-plugin-css';
import { viteStaticCopy } from 'vite-plugin-static-copy';

// 1. defineConfig:类型安全的配置函数
//    提供 TypeScript 类型提示和自动补全
export default defineConfig({
  // 2. 项目根目录(默认为 process.cwd())
  root: process.cwd(),

  // 3. 公共基础路径
  //    - '/':绝对路径
  //    - './':相对路径(适合部署到任意路径)
  base: '/',

  // 4. 服务器配置(开发服务器)
  server: {
    // 4.1 端口
    port: 3000,

    // 4.2 是否自动打开浏览器
    open: true,

    // 4.3 代理配置
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    },

    // 4.4 CORS 配置
    cors: true,

    // 4.5 监听所有网卡(局域网可访问)
    host: true,

    // 4.6 热更新配置
    hmr: {
      // 热更新端口
      port: 3001,
      // 是否在同模块激活
      overlay: true
    }
  },

  // 5. 构建配置
  build: {
    // 5.1 目标浏览器
    target: 'es2015',

    // 5.2 输出目录
    outDir: 'dist',

    // 5.3 生成 sourcemap
    sourcemap: false,

    // 5.4 压缩配置
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },

    // 5.5 chunk 大小警告限制
    chunkSizeWarningLimit: 500,

    // 5.6 rollup 配置
    rollupOptions: {
      output: {
        // 手动分包
        manualChunks: {
          'vue-vendor': ['vue', 'vue-router', 'vuex'],
          'element-ui': ['element-plus']
        },
        // 文件名哈希
        entryFileNames: 'js/[name]-[hash].js',
        chunkFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash][extname]'
      }
    }
  },

  // 6. 插件配置
  plugins: [
    // 6.1 Vue 插件
    vue({
      // 是否使用 Vue 3 runtime
      include: [/\.vue$/],
      // 自定义块处理
      customBlocks: {
        'i18n': resolve(__dirname, 'src/i18n')
      }
    }),

    // 6.2 React 插件(Fast Refresh)
    react(),

    // 6.3 静态文件拷贝
    viteStaticCopy({
      targets: [
        { src: 'public/*', dest: 'public' }
      ]
    })
  ],

  // 7. 路径解析
  resolve: {
    // 7.1 文件扩展名
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],

    // 7.2 路径别名
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'components': path.resolve(__dirname, 'src/components'),
      'utils': path.resolve(__dirname, 'src/utils')
    }
  },

  // 8. 依赖优化(开发时)
  optimizeDeps: {
    // 8.1 预构建的依赖
    include: ['vue', 'vue-router', 'element-plus'],

    // 8.2 排除的依赖(不使用预构建)
    exclude: [],

    // 8.3 预构建入口
    entries: ['src/main.js']
  },

  // 9. CSS 配置
  css: {
    // 9.1 CSS 模块化配置
    modules: {
      // 类名生成格式
      localsConvention: 'camelCase',
      // 忽略的文件
      exclude: []
    },

    // 9.2 CSS 预处理器
    preprocessorOptions: {
      scss: {
        // 注入全局变量
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  },

  // 10. 日志配置
  logLevel: 'info',

  // 11. 环境变量
  envDir: path.resolve(__dirname, 'env'),
  envPrefix: 'VITE_'
});

2.2 Vite 核心原理详解

2.2.1 Vite 工作流程
javascript 复制代码
// Vite 工作流程简化实现

// 1. 开发环境:基于 ES Module 的开发服务器
//    不打包,按需编译,只编译浏览器请求的文件

class ViteDevServer {
  constructor() {
    // 1.1 已编译的模块缓存
    this.moduleCache = new Map();

    // 1.2 已转换的源码缓存(处理过 import/export)
    this.transformCache = new Map();

    // 1.3 依赖图谱
    this.depGraph = new Map();
  }

  // 2. 启动开发服务器
  async start() {
    const server = http.createServer(async (req, res) => {
      // 2.1 解析请求
      const url = req.url.split('?')[0];
      const filePath = path.join(process.cwd(), url);

      try {
        // 2.2 读取文件
        let source = fs.readFileSync(filePath, 'utf-8');

        // 2.3 如果是 JS/TS 文件,进行转换
        if (filePath.endsWith('.js') || filePath.endsWith('.ts')) {
          source = await this.transform(source, filePath);
        }

        // 2.4 发送响应
        res.setHeader('Content-Type', this.getContentType(filePath));
        res.end(source);
      } catch (err) {
        // 2.5 404 处理
        if (err.code === 'ENOENT') {
          res.statusCode = 404;
          res.end('Not Found');
        }
      }
    });

    server.listen(3000);
  }

  // 3. transform:转换源码
  //    核心:处理 import/export,改写为浏览器认识的格式
  async transform(source, filePath) {
    // 3.1 检查缓存
    if (this.transformCache.has(filePath)) {
      return this.transformCache.get(filePath);
    }

    // 3.2 使用 esbuild 转换代码
    //    esbuild 比 Babel 快 10-100 倍
    const result = await esbuild.transform(source, {
      // 目标环境
      target: 'esnext',
      // 启用 sourcemap
      sourcemap: true,
      // 是否支持 JSX
      jsx: 'automatic',
      // 源码格式
      format: 'esm'
    });

    // 3.3 处理依赖(import 语句)
    const deps = this.extractDeps(source);

    // 3.4 更新依赖图谱
    this.depGraph.set(filePath, {
      imports: deps,
      transformed: result.code
    });

    // 3.5 缓存转换结果
    this.transformCache.set(filePath, result.code);

    return result.code;
  }

  // 4. extractDeps:提取依赖
  //    正则匹配 import 语句
  extractDeps(source) {
    const deps = [];

    // 匹配: import xxx from 'yyy'
    // 匹配: import('xxx') 动态导入
    const importRegex = /import\s+(?:(?:[\w*{}\s]+ from )|(?:.* from )|(?:[\w*{}\s]+,\s*\{[\s\w*}]+ from ))?['"]([@\w/-]+)['"]/g;

    let match;
    while ((match = importRegex.exec(source)) !== null) {
      deps.push(match[1]);
    }

    return deps;
  }

  // 5. getContentType:获取 Content-Type
  getContentType(filePath) {
    const ext = path.extname(filePath);
    const types = {
      '.js': 'application/javascript',
      '.ts': 'application/typescript',
      '.css': 'text/css',
      '.html': 'text/html',
      '.json': 'application/json'
    };
    return types[ext] || 'text/plain';
  }
}

// Vite 的核心优势:
// - 开发环境:no-bundle,按需编译,速度快
// - 生产环境:Rollup 打包,代码分割,优化
// - HMR:模块热更新,毫秒级响应

2.2.2 Vite 热更新原理
javascript 复制代码
// Vite HMR(热模块替换)实现原理

// 1. HMR 架构
//    - WebSocket 实时推送更新
//    - 客户端根据更新类型决定如何处理

// 2. 更新类型
const HMR_UPDATE_TYPES = {
  // 2.1 js/vue 文件更新:重新执行模块
  'JS_UPDATE': 'js-update',

  // 2.2 CSS 更新:只需更新 <style> 标签内容
  'CSS_UPDATE': 'css-update',

  // 2.3 组件重新渲染
  'RELOAD': 'reload'
};

// 3. HMR 客户端实现
function setupHMRClient() {
  // 3.1 创建 WebSocket 连接
  const socket = new WebSocket('ws://localhost:3001');

  socket.addEventListener('message', async (event) => {
    const { type, path, timestamp } = JSON.parse(event.data);

    switch (type) {
      case 'js-update':
        // 4. JS 更新处理
        await handleJSUpdate(path);
        break;

      case 'css-update':
        // 5. CSS 更新处理
        await handleCSSUpdate(path);
        break;

      case 'reload':
        // 6. 完全重载(某些情况无法热更新)
        window.location.reload();
        break;
    }
  });

  // 4. handleJSUpdate:JS 更新处理
  async function handleJSUpdate(path) {
    // 4.1 从服务器获取更新后的模块
    const newModule = await fetchModule(path);

    // 4.2 更新模块缓存
    updateModuleCache(path, newModule);

    // 4.3 查找使用该模块的所有模块
    const affectedModules = findAffectedModules(path);

    // 4.4 依次执行受影响的模块
    for (const modulePath of affectedModules) {
      const module = getModule(modulePath);
      if (module && module.hot) {
        // 4.5 调用 Vue 的 update 钩子
        if (typeof module.hot.accept === 'function') {
          module.hot.accept();
        }
      }
    }

    // 4.6 打印更新信息
    console.log(`[HMR] Updated: ${path}`);
  }

  // 5. handleCSSUpdate:CSS 更新处理
  async function handleCSSUpdate(path) {
    // 5.1 获取新的 CSS 内容
    const newCSS = await fetchModule(path);

    // 5.2 找到对应的 <style> 标签
    const style = document.querySelector(`link[href*="${path}"]`);

    if (style) {
      // 5.3 直接替换 CSS 内容
      //    不需要刷新页面,用户体验好
      style.textContent = newCSS;
    }

    console.log(`[HMR] CSS Updated: ${path}`);
  }
}

// 6. Vite 插件的 HMR 集成点
//    插件通过 handleHotUpdate 实现 HMR
const vuePlugin = {
  handleHotUpdate({ server, file, modules }) {
    // 6.1 判断文件类型
    if (file.endsWith('.vue')) {
      // 6.2 解析 SFC(Vue 单文件组件)
      const descriptor = parse(file);

      // 6.3 如果是模板变化,只需更新组件实例
      if (descriptor.template) {
        // 发送 css-update 类型
        server.ws.send({
          type: 'css-update',
          path: file
        });

        // 6.4 更新组件的 template
        modules.forEach(m => {
          // 重新执行模块
          server.reload(m);
        });
      }

      // 6.5 如果是脚本变化,需要完整更新
      if (descriptor.script) {
        server.ws.send({
          type: 'reload',
          path: file
        });
      }
    }
  }
};

// Vite HMR 的优势:
// - 精确更新:只更新变化的模块,不重载整个页面
// - 速度快:基于 WebSocket,毫秒级响应
// - 用户体验好:CSS 更新无需刷新,状态保持

2.3 Vite 与 Webpack 对比

javascript 复制代码
// Vite vs Webpack 核心对比

// 1. 开发环境启动速度
//    Webpack:需要打包所有模块,构建时间长
//    Vite:基于 ESM,只编译请求的模块,秒级启动

// 2. 热更新速度
//    Webpack:重新构建依赖图,可能打包大量模块
//    Vite:精准更新,只更新变化的模块

// 3. 构建速度(生产环境)
//    Webpack:HMR 模式下可能较慢
//    Vite:使用 Rollup,.tree-shaking 更彻底

// 4. 配置对比
const compareConfig = {
  // Webpack 需要:
  webpack: {
    // 需要配置 mode
    mode: 'development',
    // 需要配置 entry
    entry: './src/main.js',
    // 需要配置 output
    output: { filename: '[name].js' },
    // 需要配置 module.rules
    module: {
      rules: [
        { test: /\.js$/, use: 'babel-loader' },
        { test: /\.vue$/, use: 'vue-loader' }
      ]
    },
    // 需要配置 plugins
    plugins: [
      new HtmlWebpackPlugin(),
      new VueLoaderPlugin()
    ],
    // 需要配置 resolve.alias
    resolve: {
      alias: { '@': path.resolve(__dirname, 'src') }
    }
  },

  // Vite 配置更简洁:
  vite: {
    // 不需要 mode(自动根据环境)
    // 不需要 entry(默认从 index.html 开始)
    // 不需要 output(自动处理)
    plugins: [vue()],
    // resolve 就是顶层配置
    resolve: {
      alias: { '@': path.resolve(__dirname, 'src') }
    }
    // server 和 build 是顶层配置
  }
};

// 5. 依赖预构建(预绑定)
//    Vite 使用 esbuild 预构建依赖,比 Webpack 快 10-100 倍
//    Webpack 使用 thread-loader 或 cache-loader 实现类似功能

// 6. 适用场景
const适用场景 = {
  vite: [
    '小型/中型项目(< 500 个模块)',
    '需要快速启动的开发环境',
    'Vue 3 / React 17+ 项目',
    'TypeScript 项目(esbuild 原生支持)'
  ],
  webpack: [
    '大型复杂项目(> 1000 个模块)',
    '需要高度自定义的构建流程',
    '旧项目迁移',
    '需要兼容性优先(IE11)'
  ]
};

三、前端性能优化

3.1 运行时性能优化

3.1.1 React 性能优化
javascript 复制代码
// 1. React.memo:包装组件,避免不必要的重渲染
//    只有 props 变化时才重渲染

import { memo } from 'react';

// 1.1 普通组件包装
const Button = memo(function Button({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
});

// 1.2 带比较函数的 memo(精细控制)
const ProductList = memo(
  function ProductList({ products, onSelect }) {
    return (
      <ul>
        {products.map(p => (
          <ProductItem
            key={p.id}
            product={p}
            onSelect={onSelect}
          />
        ))}
      </ul>
    );
  },
  // 1.3 自定义比较函数
  //    prevProps 和 nextProps 是前后两次的 props
  //    返回 true 表示相同时,不重渲染
  (prevProps, nextProps) => {
    // 只比较 products 数组内容(引用变化但内容相同时不重渲染)
    return prevProps.products === nextProps.products;
  }
);

// 2. useMemo:缓存计算结果
//    避免每次渲染都重新计算

import { useMemo } from 'react';

function ExpensiveList({ items, filter }) {
  // 2.1 依赖数组 [items, filter]
  //    只有 items 或 filter 变化时才重新计算
  //    items 和 filter 没变化时,直接返回缓存的结果
  const filteredItems = useMemo(() => {
    console.log('Filtering items...'); // 验证是否重新计算

    return items.filter(item => {
      // 过滤逻辑
      return item.name.includes(filter);
    });
  }, [items, filter]); // 依赖项

  return <List data={filteredItems} />;
}

// 2.2 useMemo 用于引用稳定
//    避免子组件因为引用变化而重渲染
const contextValue = useMemo(() => ({
  // 创建复杂的上下文值
  theme: 'dark',
  toggleTheme: () => {}
}), []); // 空依赖,引用永远不变

<ThemeContext.Provider value={contextValue}>
  {children}
</ThemeContext.Provider>;

// 3. useCallback:缓存回调函数
//    避免子组件因为回调函数引用变化而重渲染

import { useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);

  // 3.1 使用 useCallback 包装回调
  //    count 作为依赖时,count 变化才创建新函数
  //    空依赖 [] 时,函数引用永远不变
  const handleClick = useCallback((event) => {
    console.log('Clicked:', event.target);
    setCount(c => c + 1);
  }, []); // 空依赖,函数引用稳定

  return <MemoizedButton onClick={handleClick} />;
}

// 3.2 useCallback 的典型场景
//    传给使用 React.memo 包装的子组件的回调
const MemoizedButton = memo(function Button({ onClick, children }) {
  console.log('Button rendered');
  return <button onClick={onClick}>{children}</button>;
});

// 4. useTransition:标记非紧急更新
//    让紧急更新(如输入)优先渲染

import { useState, useTransition } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');
  // 4.1 isPending 表示过渡是否进行中
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // 紧急更新,立即响应

    // 4.2 startTransition 包裹的更新是不紧急的
    //    可以被输入等紧急更新打断
    startTransition(() => {
      // 这里是搜索相关的更新
      // 即使慢,也不会阻塞输入
      fetchResults(value);
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <LoadingSpinner />}
    </div>
  );
}

// 5. useDeferredValue:延迟更新
//    与 useTransition 类似,但用于值

import { useDeferredValue } from 'react';

function SearchResults({ query }) {
  // 5.1 deferredQuery 会比 query 延迟更新
  //    浏览器可以在延迟期间响应输入
  const deferredQuery = useDeferredValue(query);

  // 5.2 使用 isStale 判断是否在延迟中
  const isStale = query !== deferredQuery;

  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      {/* 根据 deferredQuery 渲染结果 */}
      <Results query={deferredQuery} />
    </div>
  );
}

// 6. useRef:保持引用稳定
//    用于存储不需要触发渲染的数据

function Timer() {
  // 6.1 ref 不会引起组件重渲染
  const intervalRef = useRef(null);
  const countRef = useRef(0);

  useEffect(() => {
    // 6.2 ref.current 可以直接修改
    intervalRef.current = setInterval(() => {
      countRef.current += 1;
      console.log('Count:', countRef.current);
    }, 1000);

    return () => clearInterval(intervalRef.current);
  }, []);

  return <div>{countRef.current}</div>;
}

// 7. 虚拟列表:大量数据渲染优化
import { useVirtualizer } from 'react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef(null);

  // 7.1 虚拟化配置
  const rowVirtualizer = useVirtualizer({
    count: items.length, // 总数量
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50, // 每行预估高度
    overscan: 5 // 视口外渲染数量
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: rowVirtualizer.getTotalSize() }}>
        {rowVirtualizer.getVirtualItems().map((virtualRow) => (
          <div
            key={virtualRow.key}
            style={{
              position: 'absolute',
              top: virtualRow.start,
              height: virtualRow.size
            }}
          >
            <ListItem item={items[virtualRow.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}

3.1.2 渲染性能优化
javascript 复制代码
// 1. 避免使用 index 作为 key
//    错误的做法:删除中间项会导致 index 变化,重新渲染
const BadList = ({ items }) => (
  <ul>
    {items.map((item, index) => (
      // index 作为 key,删除 item[1] 后,item[2] 的 key 从 2 变成 1
      // React 会认为这是同一个元素,只是位置变了
      <li key={index}>{item.name}</li>
    ))}
  </ul>
);

// 正确的做法:使用唯一 id
const GoodList = ({ items }) => (
  <ul>
    {items.map(item => (
      // 唯一 id 作为 key,删除 item[1] 时,item[2] 不受影响
      <li key={item.id}>{item.name}</li>
    ))}
  </ul>
);

// 2. 避免在渲染中创建新函数/对象
//    错误的做法:每次渲染都创建新回调
function BadComponent({ items }) {
  return (
    <ul>
      {items.map(item => (
        // onClick 每次都是新函数,导致子组件重渲染
        <Item key={item.id} onClick={() => console.log(item.id)} />
      ))}
    </ul>
  );
}

// 3. 列表项使用 React.memo + 稳定 key
const Item = memo(function Item({ id, onClick }) {
  return <li onClick={onClick}>{id}</li>;
});

// 4. 避免嵌套层级过深
//    深层嵌套的组件树会导致 diff 算法变慢
//    使用扁平化结构或 Portal

// 5. 使用 Fragment 减少节点数量
function TableRow() {
  return (
    // Fragment 不产生真实 DOM 节点,减少 DOM 节点数量
    <>
      <td>Name</td>
      <td>Age</td>
    </>
  );
}

// 6. 条件渲染优化
function ConditionalRender({ shouldShow, heavyComponent }) {
  // 6.1 早 return 避免不必要渲染
  if (!shouldShow) {
    return null; // 不渲染任何东西
  }

  // 6.2 懒加载非首屏组件
  const LazyHeavy = lazy(() => import('./HeavyComponent'));

  return (
    <>
      <Header />
      {/* 懒加载,只有在需要时才加载 */}
      {shouldShow && <LazyHeavy />}
    </>
  );
}

3.2 网络性能优化

3.2.1 资源加载优化
javascript 复制代码
// 1. 预加载关键资源
// <link rel="preload"> 提前加载即将使用的资源

// 预加载字体
<link rel="preload" href="/fonts/custom.woff2" as="font" crossorigin="anonymous">

// 预加载 JS
<link rel="preload" href="/main.js" as="script">

// 预加载 CSS
<link rel="preload" href="/styles.css" as="style">

// 2. 预连接
//    提前建立 TCP/TLS 连接
<link rel="preconnect" href="https://cdn.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">

// 3. 懒加载图片
// <img loading="lazy"> 原生懒加载
const LazyImage = ({ src, alt }) => (
  <img loading="lazy" src={src} alt={alt} />
);

// 4. 第三方资源优化
//    使用 Intersection Observer 实现懒加载

class LazyLoad {
  constructor(selector) {
    // 4.1 创建 IntersectionObserver
    //    当元素进入视口时触发回调
    this.observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            // 4.2 加载资源
            this.loadResource(entry.target);
            // 4.3 停止观察
            this.observer.unobserve(entry.target);
          }
        });
      },
      {
        rootMargin: '50px 0px', // 提前 50px 开始加载
        threshold: 0.01
      }
    );

    // 4.4 开始观察所有目标元素
    document.querySelectorAll(selector).forEach(el => {
      this.observer.observe(el);
    });
  }

  loadResource(element) {
    // 处理图片懒加载
    if (element.tagName === 'IMG') {
      element.src = element.dataset.src;
    }

    // 处理脚本懒加载
    if (element.tagName === 'SCRIPT') {
      const script = document.createElement('script');
      script.src = element.dataset.src;
      document.body.appendChild(script);
    }
  }
}

// 使用
new LazyLoad('img[data-src], script[data-src]');

// 5. 请求合并
//    将多个小请求合并为一个大请求

class RequestBatcher {
  constructor(batchSize = 10, delay = 100) {
    this.queue = [];        // 待处理请求队列
    this.batchSize = batchSize; // 每批数量
    this.delay = delay;      // 延迟时间(ms)

    // 定时器
    this.timer = null;
  }

  // 5.1 添加请求
  add(request) {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject });
      this.schedule();
    });
  }

  // 5.2 调度执行
  schedule() {
    if (this.timer) return;

    this.timer = setTimeout(() => {
      this.flush();
      this.timer = null;
    }, this.delay);
  }

  // 5.3 执行批量请求
  async flush() {
    // 取出最多 batchSize 个请求
    const batch = this.queue.splice(0, this.batchSize);

    // 批量执行
    try {
      const results = await batchRequest(batch.map(b => b.request));
      batch.forEach((item, index) => {
        item.resolve(results[index]);
      });
    } catch (err) {
      batch.forEach(item => item.reject(err));
    }
  }
}

// 6. DNS 预解析
//    提前解析域名,减少连接时间
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//api.example.com">
<link rel="dns-prefetch" href="//cdn.example.com">

3.2.2 缓存策略实现
javascript 复制代码
// 1. Service Worker 缓存策略

// 注册 Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => {
      console.log('SW registered:', registration.scope);
    });
}

// sw.js 内容
const CACHE_NAME = 'v1';
const STATIC_ASSETS = [
  '/',
  '/index.html',
  '/main.js',
  '/styles.css'
];

// 1.1 安装事件:缓存静态资源
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll(STATIC_ASSETS);
    })
  );
  // 跳过等待,立即激活
  self.skipWaiting();
});

// 1.2 激活事件:清理旧缓存
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames
          .filter(name => name !== CACHE_NAME)
          .map(name => caches.delete(name))
      );
    })
  );
  // 立即接管所有页面
  self.clients.claim();
});

// 1.3 请求拦截:实现缓存策略
self.addEventListener('fetch', (event) => {
  const { request } = event;
  const url = new URL(request.url);

  // 根据 URL 选择策略
  if (url.pathname.startsWith('/api/')) {
    // API 请求:网络优先,失败时用缓存
    event.respondWith(networkFirst(request));
  } else if (isStaticAsset(url.pathname)) {
    // 静态资源:缓存优先,失败时用网络
    event.respondWith(cacheFirst(request));
  }
});

// 1.4 缓存优先策略
async function cacheFirst(request) {
  // 先从缓存中查找
  const cached = await caches.match(request);
  if (cached) {
    return cached;
  }

  // 缓存中没有,发起网络请求
  const response = await fetch(request);

  // 将响应缓存
  const cache = await caches.open(CACHE_NAME);
  cache.put(request, response.clone());

  return response;
}

// 1.5 网络优先策略
async function networkFirst(request) {
  try {
    const response = await fetch(request);

    // 成功时更新缓存
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());

    return response;
  } catch (error) {
    // 失败时返回缓存
    const cached = await caches.match(request);
    if (cached) {
      return cached;
    }

    // 没有缓存,返回离线响应
    return new Response('Offline', { status: 503 });
  }
}

// 2. HTTP 缓存配置(服务器端)

// Node.js Express 示例
const express = require('express');
const app = express();

// 2.1 强缓存:Cache-Control
app.use('/static/', express.static('public', {
  // 资源可以被任何缓存存储
  maxAge: 31536000, // 1 年
  // 缓存时使用 Vary: Accept-Encoding
  etag: true
}));

// 2.2 协商缓存:Last-Modified / ETag
app.get('/api/data', (req, res) => {
  // 检查 If-Modified-Since
  const lastModified = req.headers['if-modified-since'];
  // 检查 If-None-Match
  const etag = req.headers['if-none-match'];

  const data = getData();

  // 如果没有变化,返回 304
  if (lastModified === data.lastModified) {
    return res.status(304).end();
  }

  if (etag === data.etag) {
    return res.status(304).end();
  }

  // 返回完整数据和缓存头
  res.set({
    'Last-Modified': data.lastModified,
    'ETag': data.etag,
    'Cache-Control': 'private, max-age=60'
  });
  res.json(data);
});

// 3. 浏览器内存缓存
// 使用 sessionStorage/localStorage 手动控制

class CacheManager {
  constructor(storage = localStorage) {
    this.storage = storage;
    this.prefix = 'cache:';
  }

  // 3.1 设置缓存(带过期时间)
  set(key, value, ttl = 3600000) {
    const data = {
      value,
      expiry: Date.now() + ttl
    };
    this.storage.setItem(this.prefix + key, JSON.stringify(data));
  }

  // 3.2 获取缓存
  get(key) {
    const raw = this.storage.getItem(this.prefix + key);
    if (!raw) return null;

    const data = JSON.parse(raw);

    // 检查是否过期
    if (Date.now() > data.expiry) {
      this.delete(key);
      return null;
    }

    return data.value;
  }

  // 3.3 删除缓存
  delete(key) {
    this.storage.removeItem(this.prefix + key);
  }

  // 3.4 清空所有缓存
  clear() {
    Object.keys(this.storage)
      .filter(key => key.startsWith(this.prefix))
      .forEach(key => this.storage.removeItem(key));
  }
}

3.3 构建性能优化

3.3.1 Webpack 构建优化
javascript 复制代码
// 1. 减少解析范围

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // 1.1 排除 node_modules,大幅提升速度
        exclude: /node_modules/,
        // 或者使用 include 精确指定
        include: path.resolve(__dirname, 'src'),
        use: 'babel-loader'
      }
    ]
  },

  // 2. 开启缓存
  cache: {
    // 2.1 使用文件系统缓存
    type: 'filesystem',
    // 2.2 缓存目录
    buildPath: '.cache',
    // 2.3 缓存配置
    cacheDirectory: path.resolve(__dirname, '.cache/babel'),
    // 2.4 压缩等级
    compression: 'gzip',
    // 2.5 缓存版本
    version: 'v1'
  },

  // 3. 并行构建
  optimization: {
    // 3.1 使用 thread-loader 启用多进程
    module: {
      rules: [
        {
          test: /\.js$/,
          use: [
            {
              loader: 'thread-loader',
              options: {
                workers: 4, // 启动 4 个 worker
                workerParallelJobs: 50
              }
            },
            'babel-loader'
          ]
        }
      ]
    },

    // 3.2 使用 TerserPlugin 多进程压缩
    minimizer: [
      new TerserPlugin({
        parallel: true, // 启用多进程
        terserOptions: {
          compress: {
            drop_console: true
          }
        }
      })
    ]
  },

  // 4. 减少查找路径
  resolve: {
    // 4.1 明确指定模块目录,减少搜索
    modules: [
      path.resolve(__dirname, 'src'),
      'node_modules'
    ],
    // 4.2 减少扩展名列表
    extensions: ['.js', '.vue', '.json'],
    // 4.3 使用 alias 快速定位
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'vue$': 'vue/dist/vue.esm.js'
    }
  },

  // 5. 分包策略
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 第三方库单独打包
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
          reuseExistingChunk: true // 复用已存在的 chunk
        },
        // 公共模块打包
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        }
      }
    },
    // 运行时单独打包
    runtimeChunk: 'single'
  },

  // 6. 生成环境优化
  mode: 'production',
  // 6.1 不生成 sourcemap
  devtool: false,
  // 6.2 启用 tree-shaking
  optimization: {
    usedExports: true,
    sideEffects: true
  },
  // 6.3 标记副作用
  module: {
    rules: [
      {
        test: /\.css$/,
        sideEffects: true
      }
    ]
  }
};

3.3.2 Vite 构建优化
javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  // 1. 依赖预构建
  optimizeDeps: {
    // 1.1 需要预构建的依赖
    include: [
      'vue',
      'vue-router',
      'vuex',
      'element-plus',
      'lodash-es'
    ],
    // 1.2 排除不需要预构建的
    exclude: [],
    // 1.3 构建算法
    buildId: 'vite',
    // 1.4 清理缓存
    clean: false
  },

  build: {
    // 2. 目标环境
    target: 'es2015',
    // 2.1 不生成 sourcemap
    sourcemap: false,
    // 2.2 压缩
    minify: 'terser',
    // 2.3 chunk 大小警告
    chunkSizeWarningLimit: 500,
    // 2.4 手动分包
    rollupOptions: {
      output: {
        manualChunks: {
          // Vue 相关
          'vue-vendor': ['vue', 'vue-router', 'vuex'],
          // UI 库
          'element-vendor': ['element-plus'],
          // 工具库
          'utils-vendor': ['lodash-es', 'axios']
        },
        // 分割 vendor
        vendorChunkId: 'vendors'
      }
    },

    // 3. CSS 代码分割
    cssCodeSplit: true,

    // 4. 资源内联阈值
    // 4.1 小于此大小的资源内联为 base64
    assetsInlineLimit: 4096 // 4KB

    // 5. 静态资源内联
    //    使用 ?inline 查询参数内联资源
    //    <script src="./utils.js?inline">
  },

  // 6. 压缩配置
  compress: {
    // 6.1 gzip 压缩
    gzip: true,
    // 6.2 gzip 压缩级别
    gzipSize: 0.5,
    // 6.3 brotli 压缩
    brotli: true
  },

  // 7. 构建分析
  // 使用 vite-plugin-visualizer
  plugins: [
    // 可视化构建分析
    // { stats: 'summary' }
  ],

  // 8. 环境变量注入
  define: {
    // 注入全局变量,避免运行时求值
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
  }
});

四、浏览器原理与前端架构

4.1 浏览器渲染原理

4.1.1 关键渲染路径
javascript 复制代码
// 1. 关键渲染路径(Critical Rendering Path)
//    从 HTML 到像素在屏幕上显示的过程

// 1.1 构建 DOM 树
//    HTML 解析器逐行解析 HTML,构建 DOM 树
//    遇到 <script> 会阻塞渲染(默认行为)

// 1.2 构建 CSSOM 树
//    CSS 解析器解析所有 CSS(内联、外链、inline)
//    CSS 是 "渲染阻塞":必须等到所有 CSS 下载解析完才能渲染

// 1.3 合成渲染树(Render Tree)
//    DOM + CSSOM = Render Tree
//    只包含可见节点(display: none 的不包含)

// 1.4 布局(Layout)
//    计算每个节点的位置和大小
//    首次布局后,后续更新叫 "重排"(reflow)

// 1.5 绘制(Paint)
//    将节点转换为像素
//    首次绘制后,后续更新叫 "重绘"(repaint)

// 1.6 合成(Composite)
//    将各层合成为最终图像
//    使用 GPU 加速

// 2. 优化关键渲染路径
// 2.1 减少关键资源数量
//    - 异步加载 JS:<script async> 或 <script defer>
//    - 合并 CSS
//    - 预加载关键资源

// 2.2 减少关键路径长度
//    - 精简 CSS(移除未使用的样式)
//    - 压缩资源

// 2.3 减少关键字节数
//    - gzip 压缩
//    - HTTP/2 多路复用

// 3. 示例:异步加载脚本
// 不阻塞渲染
<script defer src="/main.js"></script>
// defer 在 HTML 解析完成后执行,按顺序执行
// 阻塞渲染,但可以在下载期间解析 HTML

<script async src="/analytics.js"></script>
// async 下载完立即执行,不按顺序,可能阻塞渲染

// 4. CSS 加载优化
// <link rel="preload"> 提前加载关键 CSS
<link rel="preload" href="/critical.css" as="style" onload="this.rel='stylesheet'">

// 5. 内联关键 CSS
//    将首屏需要的 CSS 内联到 <head> 中
<style>
  /* 关键 CSS */
  body { margin: 0; padding: 0; }
  .header { background: #333; }
</style>

// 非关键 CSS 异步加载
<link rel="preload" href="/non-critical.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/non-critical.css"></noscript>

4.1.2 事件循环与渲染调度
javascript 复制代码
// 1. 事件循环(Event Loop)
//    JavaScript 单线程的执行模型

// 1.1 执行栈(Call Stack)
//    存放正在执行的函数
function foo() {
  bar(); // 调用 bar
}

function bar() {
  console.log('bar');
}

foo(); // foo 入栈 -> bar 入栈 -> bar 出栈 -> foo 出栈

// 1.2 任务队列(Task Queue)
//    宏任务:setTimeout, setInterval, I/O, UI rendering
//    微任务:Promise.then, MutationObserver, queueMicrotask

console.log('1'); // 同步,立即执行

setTimeout(() => {
  console.log('2'); // 宏任务,在下一个事件循环执行
}, 0);

Promise.resolve().then(() => {
  console.log('3'); // 微任务,在当前事件循环末尾执行
});

console.log('4'); // 同步

// 输出顺序:1, 4, 3, 2
// 微任务在下一个宏任务之前执行

// 2. requestAnimationFrame(RAF)
//    在下一次浏览器重绘之前调用
//    用于动画和视觉更新

let startTime;
function animate(timestamp) {
  if (!startTime) startTime = timestamp;
  const elapsed = timestamp - startTime;

  // 执行动画
  element.style.transform = `translateX(${elapsed}px)`;

  if (elapsed < 1000) {
    // 继续动画
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);
// 每秒 60 帧(大约 16.67ms)

// 3. requestIdleCallback
//    在浏览器空闲时执行低优先级任务
//    用于后台工作,不影响主线程

requestIdleCallback((deadline) => {
  // deadline.timeRemaining() 返回剩余时间
  // deadline.didTimeout 是否超时

  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    const task = tasks.shift();
    task();
  }

  // 如果还有任务,继续调度
  if (tasks.length > 0) {
    requestIdleCallback(arguments.callee);
  }
}, { timeout: 2000 }); // 2秒超时

// 4. 宏任务与微任务调度
console.log('script start'); // 1

setTimeout(() => console.log('setTimeout'), 0); // 宏任务

Promise.resolve()
  .then(() => console.log('Promise 1'))
  .then(() => console.log('Promise 2'));

queueMicrotask(() => console.log('queueMicrotask'));

console.log('script end'); // 2

// 输出:
// script start
// script end
// Promise 1
// queueMicrotask
// Promise 2
// setTimeout

// 5. 渲染时机
//    浏览器在每个宏任务之后检查是否需要渲染
//    同一事件循环内多次 DOM 修改,合并为一次渲染

// 错误的做法:多次修改触发多次重排
element.style.transform = 'translateX(100px)';
element.style.transform = 'translateX(200px)'; // 覆盖前一个

// 正确的做法:合并修改
element.style.transform = 'translateX(200px)'; // 只触发一次

// 6. 使用 requestAnimationFrame 批量更新
class BatchUpdater {
  constructor() {
    this.pendingUpdates = [];
    this.frameId = null;
  }

  scheduleUpdate(update) {
    this.pendingUpdates.push(update);

    if (!this.frameId) {
      this.frameId = requestAnimationFrame(() => {
        this.flush();
      });
    }
  }

  flush() {
    // 在下一帧批量执行所有更新
    this.pendingUpdates.forEach(fn => fn());
    this.pendingUpdates = [];
    this.frameId = null;
  }
}

4.2 前端架构设计

4.2.1 微前端架构
javascript 复制代码
// 微前端(Micro Frontends)架构实现

// 1. 微前端基座(主应用)
class MicroFrontend {
  constructor() {
    // 存储注册的子应用
    this.apps = {};
    // 当前激活的子应用
    this.currentApp = null;
  }

  // 1.1 注册子应用
  register(name, entry, props = {}) {
    this.apps[name] = {
      entry,     // 子应用入口(HTML/JS)
      props,     // 传递给子应用的 props
      instance: null, // 子应用实例
      activeRule: null // 激活规则
    };
  }

  // 1.2 设置激活规则
  setActiveRule(name, rule) {
    if (this.apps[name]) {
      this.apps[name].activeRule = rule;
    }
  }

  // 1.3 启动子应用
  async mount(name) {
    const app = this.apps[name];
    if (!app) throw new Error(`App ${name} not found`);

    // 获取子应用资源
    const { scripts, styles } = await this.loadAssets(app.entry);

    // 加载样式
    await this.loadStyles(styles);

    // 加载脚本并执行
    const mountFunction = await this.loadScripts(scripts);

    // 调用子应用的 mount 方法
    app.instance = mountFunction(app.props);

    this.currentApp = name;
  }

  // 1.4 卸载子应用
  async unmount(name) {
    const app = this.apps[name];
    if (app && app.instance) {
      // 调用子应用的 unmount 方法
      if (typeof app.instance.unmount === 'function') {
        await app.instance.unmount();
      }
      app.instance = null;
    }

    if (this.currentApp === name) {
      this.currentApp = null;
    }
  }

  // 1.5 加载资源
  async loadAssets(entry) {
    // 获取 HTML
    const html = await fetch(entry).then(r => r.text());

    // 解析 HTML 中的 script 和 link 标签
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');

    const scripts = Array.from(doc.querySelectorAll('script[src]'))
      .map(s => s.src);
    const styles = Array.from(doc.querySelectorAll('link[href]'))
      .filter(l => l.rel === 'stylesheet')
      .map(l => l.href);

    return { scripts, styles };
  }

  // 1.6 加载样式
  async loadStyles(urls) {
    await Promise.all(urls.map(url => {
      return new Promise((resolve, reject) => {
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = url;
        link.onload = resolve;
        link.onerror = reject;
        document.head.appendChild(link);
      });
    }));
  }

  // 1.7 加载脚本
  async loadScripts(urls) {
    // 依次加载脚本(保持顺序)
    for (const url of urls) {
      await this.loadScript(url);
    }
  }

  async loadScript(url) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = url;
      script.onload = resolve;
      script.onerror = reject;
      document.body.appendChild(script);
    });
  }
}

// 2. 子应用导出(子应用需导出以下生命周期)
// 子应用 main.js 示例:
export function mount(props) {
  // 创建应用实例
  const app = createApp(App);

  // 挂载到容器
  app.mount(props.container);

  // 返回实例
  return {
    unmount() {
      app.unmount();
    }
  };
}

// 3. 路由驱动
function setupRouter() {
  // 子应用路由
  const routes = [
    { path: '/app1/*', app: 'app1' },
    { path: '/app2/*', app: 'app2' }
  ];

  window.addEventListener('popstate', () => {
    renderRoute();
  });

  function renderRoute() {
    const path = window.location.pathname;

    for (const route of routes) {
      if (path.match(route.path)) {
        // 挂载对应的子应用
        microFrontend.mount(route.app);
        return;
      }
    }

    // 404
    microFrontend.unmount(currentApp);
  }

  // 拦截链接点击
  document.addEventListener('click', (e) => {
    const link = e.target.closest('a[href]');
    if (link && link.href.startsWith(window.location.origin)) {
      e.preventDefault();
      window.history.pushState(null, '', link.href);
      renderRoute();
    }
  });

  renderRoute();
}

// 4. 子应用通信(自定义事件)
// 4.1 主应用发送事件
function emit(event, data) {
  window.dispatchEvent(new CustomEvent(`mf:${event}`, { detail: data }));
}

// 4.2 子应用监听事件
window.addEventListener('mf:data', (e) => {
  console.log('Received:', e.detail);
});

// 4.3 使用 shared 模块共享状态
const sharedState = {
  user: null,
  setUser(user) {
    this.user = user;
    // 通知所有应用
    window.dispatchEvent(new CustomEvent('user:change', { detail: user }));
  }
};

// 4.4 子应用获取状态
function getSharedState() {
  return sharedState.user;
}

4.2.2 模块联邦(Module Federation)
javascript 复制代码
// Webpack 5 模块联邦(Module Federation)
// 允许独立构建的应用共享模块

// 1. 主机应用(Host)
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      // 1.1 应用名称
      name: 'host_app',

      // 1.2 暴露的模块(其他应用可引用)
      // 其他应用可以通过 import('host_app/navbar') 使用
      exposes: {
        './Navbar': './src/components/Navbar'
      },

      // 1.3 共享的依赖(多个应用共用同一个库)
      shared: {
        // 共享 vue
        vue: {
          singleton: true, // 单例模式,整个页面只有一个 vue 实例
          requiredVersion: '^3.0.0'
        },
        // 共享 vuex
        vuex: {
          singleton: true
        }
      }
    })
  ]
};

// 2. 远程应用(Remote)
// webpack.config.js
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      // 2.1 应用名称
      name: 'remote_app',

      // 2.2 暴露的模块
      exposes: {
        './Button': './src/components/Button',
        './Card': './src/components/Card'
      },

      // 2.3 远程地址(主机引用时从此地址加载)
      // 在开发环境可以是 localhost
      // 在生产环境需要指向实际部署地址
      filename: 'remoteEntry.js', // 生成的入口文件名

      shared: ['vue']
    })
  ]
};

// 3. 主机应用使用远程模块
// App.js
import React, { lazy, Suspense } from 'react';

// 3.1 方式一:直接 import(同步)
// import Button from 'remote_app/Button';

// 3.2 方式二:懒加载(异步)
const Button = lazy(() => import('remote_app/Button'));

function App() {
  return (
    <div>
      <h1>Host App</h1>
      <Suspense fallback={<div>Loading Button...</div>}>
        <Button />
      </Suspense>
    </div>
  );
}

// 4. 动态引用(运行时加载)
// 4.1 创建容器
const container = document.createElement('div');

// 4.2 动态加载远程模块
async function loadRemoteModule(remoteName, moduleName) {
  // 动态导入远程入口
  await __webpack_require__.e(`${remoteName}-${moduleName}`);

  // 获取模块
  return __webpack_require__(`.${remoteName}/${moduleName}`);
}

// 使用
async function loadButton() {
  const { default: Button } = await loadRemoteModule('remote_app', './Button');
  return Button;
}

// 5. 模块联邦类型共享
// 5.1 暴露类型
new ModuleFederationPlugin({
  name: 'shared_lib',
  // 暴露类型定义
  exposes: {
    './types': './src/types/index.ts'
  }
});

// 5.2 使用共享类型
// 在 tsconfig.json 中配置
{
  "compilerOptions": {
    "paths": {
      "shared_lib/*": ["./node_modules/shared_lib/*"]
    }
  }
}

// 6. 运行时共享
// 如果 remote_app 需要共享状态,可以通过 shared 模块

// remote_app 暴露一个 store
new ModuleFederationPlugin({
  name: 'remote_app',
  exposes: {
    './store': './src/store'
  },
  shared: ['vue']
});

// host_app 在使用时可以传递自己的 store
import("./remote_app/store").then(module => {
  // 使用 host 提供的 store
  module.initStore(hostStore);
});

4.3 前端监控与错误处理

4.3.1 错误监控实现
javascript 复制代码
// 1. 全局错误监控
class ErrorMonitor {
  constructor(options = {}) {
    this.options = options;
    this.errors = []; // 错误队列
    this.maxQueue = options.maxQueue || 100; // 最大队列长度
  }

  // 1.1 初始化监控
  init() {
    // 监控未捕获的异步错误
    window.addEventListener('error', (event) => {
      this.handleError({
        type: 'error',
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        stack: event.error?.stack
      });
    });

    // 监控未捕获的 Promise 错误
    window.addEventListener('unhandledrejection', (event) => {
      this.handleError({
        type: 'unhandledrejection',
        message: event.reason?.message || event.reason,
        stack: event.reason?.stack
      });
    });

    // 监控资源加载错误
    window.addEventListener('error', (event) => {
      if (event.target !== window) {
        this.handleError({
          type: 'resource',
          message: `Failed to load: ${event.target.src || event.target.href}`
        });
      }
    }, true);
  }

  // 1.2 处理错误
  handleError(error) {
    const errorInfo = {
      ...error,
      timestamp: Date.now(),
      url: window.location.href,
      userAgent: navigator.userAgent
    };

    // 添加到队列
    this.errors.push(errorInfo);

    // 超过最大长度,移除最早的
    if (this.errors.length > this.maxQueue) {
      this.errors.shift();
    }

    // 上报错误
    this.report(errorInfo);
  }

  // 1.3 上报错误
  report(errorInfo) {
    // 延迟发送,避免影响性能
    setTimeout(() => {
      this.sendToServer(errorInfo);
    }, 1000);
  }

  async sendToServer(errorInfo) {
    try {
      await fetch('/api/errors', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(errorInfo)
      });
    } catch (e) {
      console.error('Failed to report error:', e);
    }
  }

  // 1.4 获取错误列表
  getErrors() {
    return [...this.errors];
  }

  // 1.5 清空错误列表
  clear() {
    this.errors = [];
  }
}

// 使用
const monitor = new ErrorMonitor({ maxQueue: 50 });
monitor.init();

// 2. Vue 错误监控
Vue.config.errorHandler = (err, vm, info) => {
  console.error('Vue Error:', err);
  console.error('Component:', vm);
  console.error('Info:', info);
};

// 3. React 错误边界
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state,使下次渲染显示 fallback UI
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // 上报错误
    console.error('React Error:', error, errorInfo);

    // 可以在这里发送错误到服务器
    reportError(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <div>出错了</div>;
    }

    return this.props.children;
  }
}

// 使用
<ErrorBoundary fallback={<div>加载失败</div>}>
  <MyComponent />
</ErrorBoundary>;

// 4. 性能监控
class PerformanceMonitor {
  constructor() {
    this.metrics = {};
  }

  // 4.1 监控页面加载
  observeLoad() {
    // LCP (Largest Contentful Paint)
    new PerformanceObserver((entryList) => {
      const entries = entryList.getEntries();
      const lastEntry = entries[entries.length - 1];
      console.log('LCP:', lastEntry.startTime);
    }).observe({ entryTypes: ['largest-contentful-paint'] });

    // FID (First Input Delay)
    new PerformanceObserver((entryList) => {
      const entries = entryList.getEntries();
      entries.forEach(entry => {
        console.log('FID:', entry.processingStart - entry.startTime);
      });
    }).observe({ entryTypes: ['first-input'] });

    // CLS (Cumulative Layout Shift)
    new PerformanceObserver((entryList) => {
      let cls = 0;
      entries.forEach(entry => {
        if (!entry.hadRecentInput) {
          cls += entry.value;
        }
      });
      console.log('CLS:', cls);
    }).observe({ entryTypes: ['layout-shift'] });
  }

  // 4.2 监控资源加载
  observeResource() {
    new PerformanceObserver((entryList) => {
      entries.getEntries().forEach(entry => {
        console.log('Resource:', {
          name: entry.name,
          duration: entry.duration,
          size: entry.transferSize
        });
      });
    }).observe({ entryTypes: ['resource'] });
  }

  // 4.3 获取完整性能指标
  getMetrics() {
    const timing = performance.timing;

    return {
      // DNS 解析时间
      dns: timing.domainLookupEnd - timing.domainLookupStart,
      // TCP 连接时间
      tcp: timing.connectEnd - timing.connectStart,
      // TTFB (Time To First Byte)
      ttfb: timing.responseStart - timing.requestStart,
      // DOM 解析时间
      domParse: timing.domInteractive - timing.responseEnd,
      // DOM Content Loaded
      domReady: timing.domContentLoadedEventEnd - timing.navigationStart,
      // 首屏渲染时间
      fcp: performance.getEntriesByType('paint')[0]?.startTime,
      // 最大内容绘制
      lcp: this.getLCP()
    };
  }

  getLCP() {
    const entries = performance.getEntriesByType('largest-contentful-paint');
    return entries[entries.length - 1]?.startTime;
  }
}

五、综合架构设计

5.1 前端工程化体系

javascript 复制代码
// 完整前端工程化体系架构

// 1. 目录结构
const projectStructure = {
  'src': {
    // 页面/视图
    'pages': {
      'Home': {
        'index.js',        // 页面入口
        'index.scss',      // 页面样式
        'components': {}   // 页面级组件
      }
    },

    // 业务组件(可复用)
    'components': {
      'Button': {
        'index.js',
        'index.scss',
        'index.test.js'
      }
    },

    // 通用工具
    'utils': {
      'request.js',      // axios 封装
      'storage.js',      // 存储封装
      'format.js'        // 格式化工具
    },

    // 公共样式
    'styles': {
      'variables.scss',  // 变量
      'mixins.scss',     // mixin
      'reset.scss'       // 重置样式
    },

    // 服务层(API)
    'services': {
      'user.js',
      'product.js'
    },

    // 状态管理
    'store': {
      'index.js',
      'user.js',
      'cart.js'
    },

    // 路由配置
    'router': {
      'index.js',
      'routes.js'
    },

    // 全局配置
    'config': {
      'index.js',
      'dev.js',
      'prod.js'
    }
  },

  // 配置目录
  'config': {
    'jest.config.js',
    'eslintrc.js',
    'prettierrc.js',
    'commitlint.config.js'
  },

  // 脚本目录
  'scripts': {
    'build.js',    // 自定义构建脚本
    'analyze.js'   // 构建分析
  },

  // 文档
  'docs': {}
};

// 2. package.json scripts 配置
const packageScripts = {
  // 开发
  "dev": "vite --mode development",
  "dev:prod": "vite --mode production",

  // 构建
  "build": "vite build",
  "build:analyze": "vite build --mode analyze",

  // 测试
  "test": "jest",
  "test:watch": "jest --watch",
  "test:coverage": "jest --coverage",

  // 代码检查
  "lint": "eslint src --ext .js,.vue,.jsx,.tsx",
  "lint:fix": "eslint src --fix",
  "lint:style": "stylelint src/**/*.{css,scss,vue}",

  // 格式化
  "format": "prettier --write \"src/**/*.{js,vue,jsx,tsx,css,scss}\"",

  // 提交
  "commit": "cz",
  "push": "git push",

  // 完整检查
  "pre-commit": "npm run lint && npm run test",

  // CI/CD
  "ci": "npm install && npm run lint && npm run test && npm run build"
};

// 3. 环境变量配置
const viteEnvConfig = {
  // .env.development
  'VITE_APP_TITLE': 'My App (Dev)',
  'VITE_API_BASE_URL': 'http://localhost:3000',
  'VITE_ENABLE_MOCK': 'true',

  // .env.production
  'VITE_APP_TITLE': 'My App',
  'VITE_API_BASE_URL': 'https://api.example.com',
  'VITE_ENABLE_MOCK': 'false'
};

// 4. CI/CD 配置
// .github/workflows/main.yml
const ciConfig = `
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '16'
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '16'
      - run: npm ci
      - run: npm run test:coverage

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '16'
      - run: npm ci
      - run: npm run build
        env:
          VITE_API_BASE_URL: \${{ secrets.API_URL }}
`;

// 5. Monorepo 配置
// pnpm-workspace.yaml
const pnpmWorkspace = `
packages:
  - 'packages/*'
`;

// 6. 模块联邦在 Monorepo 中的应用
// packages/host/webpack.config.js
new ModuleFederationPlugin({
  name: 'host',
  remotes: {
    // 直接引用本地包
    remote_app: 'remote_app@http://localhost:3001/remoteEntry.js'
  },
  shared: ['vue']
});

六、学习清单

类别 题目 难度 重点
Webpack loader 和 plugin 区别 生命周期、执行顺序
Webpack Webpack 构建流程 ⭐⭐ Tapable、编译流程
Webpack splitChunks 原理 ⭐⭐ 分包策略、缓存
Webpack Tree-shaking 原理 ⭐⭐ sideEffects、usedExports
Webpack 手写 loader ⭐⭐ 同步/异步、pitch
Vite Vite 工作原理 ⭐⭐ ESM、按需编译
Vite Vite HMR 原理 ⭐⭐ WebSocket、模块更新
Vite Vite 与 Webpack 对比 启动速度、构建速度
React useMemo/useCallback 区别 依赖、缓存策略
React React.memo 原理 浅比较、自定义比较
React useTransition 作用 ⭐⭐ 并发模式、优先级
React 虚拟列表原理 ⭐⭐ 懒渲染、滚动计算
性能 关键渲染路径优化 ⭐⭐ CRP、阻塞分析
性能 防抖/节流原理 场景、区别
性能 浏览器缓存策略 ⭐⭐ 强缓存、协商缓存
架构 微前端实现 ⭐⭐⭐ qiankun、single-spa
架构 模块联邦原理 ⭐⭐ 共享依赖、远程加载
监控 错误边界原理 ⭐⭐ 生命周期、错误恢复
相关推荐
ZJY1321 小时前
1-1:搭建项目框架
架构
C137的本贾尼1 小时前
入主城堡:LangChain 核心架构与快速上手
架构·langchain
一切皆是因缘际会2 小时前
2026年AGI突围:自主智能体驱动,数字生命从架构落地到自我迭代全解析
人工智能·深度学习·机器学习·架构·系统架构·agi
2601_957780843 小时前
GPT-5.5时代:从“指令集“到“任务契约“的Prompt工程范式迁移
大数据·人工智能·gpt·架构·prompt
网络点点滴3 小时前
简述Node.js运行时核心架构
架构·node.js
Sylvia33.3 小时前
世界杯数据链路解析:从球场传感器到终端推送的毫秒级架构
java·前端·python·架构
布吉岛的石头3 小时前
Nacos服务发现与配置中心:微服务注册中心实战
微服务·架构·服务发现
喜欢流萤吖~4 小时前
微服务可观测性:让系统不再黑盒
微服务·架构
Kiyra4 小时前
限流不是加个计数器就行:用 Lua 脚本实现多维度原子限流
开发语言·人工智能·网络协议·职场和发展·架构·lua·ai-native