Webpack与Vite详解

文章目录

      • [1. Webpack](#1. Webpack)
        • [1.1. Webpack 的五个核心概念](#1.1. Webpack 的五个核心概念)
        • [1.2. webpack 编译的全流程](#1.2. webpack 编译的全流程)
          • [1.2.1. 初始化 (Initialization)](#1.2.1. 初始化 (Initialization))
          • [1.2.2. 第二阶段:编译构建 (Compilation) ------ 核心环节](#1.2.2. 第二阶段:编译构建 (Compilation) —— 核心环节)
          • [1.2.3. 输出生成 (Seal & Emit)](#1.2.3. 输出生成 (Seal & Emit))
        • [1.3. AST 是什么?](#1.3. AST 是什么?)
        • [1.4. Webpack 的核心优势](#1.4. Webpack 的核心优势)
        • [1.5. plugin 和 loader 的区别](#1.5. plugin 和 loader 的区别)
        • [1.6. Webpack 配置成使用 ES Module](#1.6. Webpack 配置成使用 ES Module)
      • [2. Vite](#2. Vite)
        • [2.1. Vite 的核心工作原理](#2.1. Vite 的核心工作原理)
        • [2.2. Vite常用配置](#2.2. Vite常用配置)
        • [2.3. Vite的代码分割](#2.3. Vite的代码分割)
      • [3. Vite vs Webpack](#3. Vite vs Webpack)
        • [3.1. Vite 热更新(HMR)核心原理](#3.1. Vite 热更新(HMR)核心原理)
        • [3.2. Vite 与 Webpack 的 HMR区别](#3.2. Vite 与 Webpack 的 HMR区别)
      • [4. ES6 向 ES5 的转换的方式](#4. ES6 向 ES5 的转换的方式)
      • [5. 配置优化打包速度或构建速度](#5. 配置优化打包速度或构建速度)
        • [5.1. webpack优化方案](#5.1. webpack优化方案)
        • [5.2. vite优化方案](#5.2. vite优化方案)

1. Webpack

1.1. Webpack 的五个核心概念

1.入口 (Entry)

指示 Webpack 应该从哪个文件开始打包。它是依赖图的起点

  • 单入口entry: './src/index.js'
  • 多入口:用于多页面应用(MPA)

2.输出 (Output)

告诉 Webpack 打包后的文件叫什么名字、存在哪个目录下

  • 通常配置为 dist 目录,文件名常带 Hash (如 bundle.[contenthash].js)以解决浏览器缓存问题

3.Loader(加载器)

Webpack 默认只认识 JavaScript 和 JSON。 Loader 让 Webpack 能够处理其他类型的文件(CSS, 图片, TypeScript, Vue, JSX 等),并将它们转换为有效的模块

  • css-loader :处理 CSS 中的 @importurl()
  • style-loader :将 CSS 注入到 DOM 的 <style> 标签中
  • babel-loader:将 ES6+ 代码转译为向后兼容的 JS

4.插件 (Plugin)

Loader 用于转换特定类型的模块 ,而 Plugin 则用于执行范围更广的任务。插件可以触及 Webpack 构建过程的每一个环节

  • HtmlWebpackPlugin:自动生成 HTML 文件并自动引入打包后的 JS
  • CleanWebpackPlugin :每次打包前自动清理 dist 目录
  • MiniCssExtractPlugin:将 CSS 提取为独立文件(而非打包在 JS 里)

5.模式 (Mode)

通过选择 development(开发)、production(生产)或 none,启用 Webpack 内置的优化

  • Production 模式:自动开启代码压缩(UglifyJS)、Tree Shaking(摇树优化)
1.2. webpack 编译的全流程

Webpack 的打包过程:

1.2.1. 初始化 (Initialization)
  1. 读取配置 :Webpack 会读取你的 webpack.config.js,并与默认配置合并,得到最终的参数
  2. 创建 Compiler 对象 :用上一步的参数初始化 Compiler 对象,它是 Webpack 的"总指挥部"
  3. 加载插件 (Plugins) :遍历配置中的 plugins,依次调用它们的 apply 方法。此时,插件开始监听 Webpack 生命周期中的各个事件节点(钩子)
1.2.2. 第二阶段:编译构建 (Compilation) ------ 核心环节

这是 Webpack 最忙碌的阶段:

  1. 确定入口 (Entry):根据配置找到所有的入口文件。

  2. 编译模块 (Make)

    • 从入口出发 ,调用匹配的 Loader 对文件进行转译(比如把 Sass 转成 CSS,把 TS 转成 JS)。

    • 解析依赖 :利用 AST(抽象语法树) 分析模块中的 importrequire 语句,找到它依赖的其他模块。

    • 递归扫描:对依赖的模块重复上述过程,直到所有文件都被处理成 JS 模块。

  3. 完成模块编译 :得到每个模块被转译后的最终内容以及它们之间的依赖关系图 (Dependency Graph**)**。

1.2.3. 输出生成 (Seal & Emit)
  1. 组装 Chunk :根据入口和模块之间的依赖关系,将多个模块组合成一个个 Chunk(代码块)
  2. 转换成 Assets:把每个 Chunk 转换成一个单独的文件(Asset),并加入到输出列表
  3. 写入文件系统 (Emit) :确定好输出路径和文件名(output.path & filename),将文件内容写入到硬盘的 dist 目录中
1.3. AST 是什么?

AST 是抽象语法树,将代码字符串解析成结构化的树形表示,每个节点代表代码中的一个语法结构

生成过程:

  1. 词法分析:将源代码拆分成 Token 流
  2. 语法分析:根据 Token 构建 AST

实际应用:

  • Babel:源码 → AST → 转换 → 新代码
  • ESLint:遍历 AST,检查违规模式
  • Prettier:解析成 AST,再按规则重新打印
  • Webpack:通过 AST 分析模块依赖
1.4. Webpack 的核心优势
  1. 模块化支持

Webpack 支持 ES ModulesCommonJSAMD 等所有主流模块化标准,让你可以在前端项目中像在后端一样使用 requireimport

  1. 强大的生态(Loader & Plugins)

几乎所有的前端需求(混淆、压缩、图片转 Base64、单元测试集成、代码校验)都有对应的插件或加载器

  1. 代码分割 (Code Splitting)

Webpack 可以将代码拆分成多个 Bundle

  • 按需加载:只有用户访问某个路由时,才下载对应的 JS(懒加载)
  • 公共代码提取 :将 React、Vue 等第三方库单独打成一个 vendor.js,充分利用浏览器缓存
  1. 热模块替换 (HMR)

在开发过程中,修改代码后,Webpack 只会替换发生变化的模块,而不需要刷新整个页面,极大地保留了应用当前的状态

1.5. plugin 和 loader 的区别
维度 Loader (转换) Plugin (插件)
功能定位 专注文件转换。将 A 转换为 B 专注流程控制。扩展 Webpack 功能
配置位置 module.rules 下,通常关联 test 正则 plugins 数组下,需要 new 实例
执行顺序 从右往左(或从下往上)链式调用 基于事件监听,由 Webpack 内部钩子触发
输出结果 返回转换后的字符串(JS 代码) 直接修改 Compilation 对象或输出 Assets

常用 Loaders:

  • babel-loader: ES6+ 转 ES5
  • ts-loader: TypeScript 转 JS
  • file-loader / url-loader: 处理图片和字体

常用 Plugins:

  • HtmlWebpackPlugin: 自动生成 HTML 并引入打包后的 JS
  • CleanWebpackPlugin: 每次构建前清理 dist 目录
  • DefinePlugin: 编译时注入全局常量(如 process.env.NODE_ENV
1.6. Webpack 配置成使用 ES Module
  1. 让 Webpack 配置文件支持 ESM

    • 修改后缀:将 webpack.config.js 重命名为 webpack.config.mjs 。Node.js 会自动将 .mjs 文件视为 ESM

    • 修改 package.json:在你的 package.json 中添加 "type": "module"。这样项目内所有的 .js 文件都会被默认视为 ESM

    • 使用转译工具:如果使用 TypeScript(webpack.config.ts),可以配合 ts-node 自动处理模块转换

  2. 让 Webpack 输出 ESM

webpack.config.js 中开启 experiments.outputModule

C++ 复制代码
export default {
  // 1. 开启实验性特性
  experiments: {
    outputModule: true,
  },
  output: {
    // 2. 指定库的导出格式为 'module'
    library: {
      type: 'module',
    },
    // 3. 必须指定环境,告诉 Webpack 支持 ES6 特性
    environment: {
      module: true,
    },
    filename: 'bundle.mjs',
  },
};
  1. Webpack 对 ESM 的底层处理机制

Webpack 对 ESM 的支持不仅仅是格式问题,还涉及性能优化:

  • Tree Shaking :ESM 是静态分析 的。Webpack 利用这一点,在构建阶段就能识别出哪些代码没被 import,从而在最终包中剔除它们
  • Scope Hoisting (作用域提升):Webpack 会尽可能将所有模块合并到一个函数作用域中,减少闭包开销,提升运行速度

2. Vite

2.1. Vite 的核心工作原理

Vite 的性能优势源于它对开发环境生产环境采用了不同的处理策略。

  1. 开发环境:基于原生 ESM (No-Bundle)

利用浏览器原生支持的 ES Modules (ESM)。服务器启动时不需要打包

  • 按需加载 :当浏览器解析到 import 语句时,会向服务器发起请求。Vite 拦截请求,只对该文件进行实时编译并返回
  1. 依赖预构建 (Dependency Pre-bundling)

    • esbuild :Vite 使用 Go 语言编写的 esbuild 预先将这些依赖打包成单个 ESM 模块
  2. 生产环境:基于 Rollup 打包

2.2. Vite常用配置

1.路径别名

JavaScript 复制代码
resolve: {
  alias: {
    '@': path.resolve(__dirname, './src'),
    '@components': path.resolve(__dirname, './src/components')
  }
}

2.代理配置

JavaScript 复制代码
server: {
  proxy: {
    '/api': {
      target: 'http://localhost:3000',
      changeOrigin: true,
      rewrite: (path) => path.replace(/^\/api/, '')
    }
  }
}

3.build.rollupOptions.manualChunks - 代码分割

作用:将第三方库拆分成独立 chunk,利用浏览器缓存

JavaScript 复制代码
build: {
  rollupOptions: {
    output: {
      manualChunks: {
        'react-vendor': ['react', 'react-dom'],
        'ui-vendor': ['antd', '@mui/material'],
        'utils': ['lodash', 'dayjs']
      }
    }
  }
}

4.css.preprocessorOptions - 预处理器全局变量

作用:每个 SCSS 文件自动导入全局变量,无需手动 @import

JavaScript 复制代码
css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@import "@/styles/variables.scss";`  // 自动注入
    }
  }
}

5.css.modules - CSS Modules 配置

JavaScript 复制代码
css: {
  modules: {
    localsConvention: 'camelCase',  // 类名转驼峰
    generateScopedName: '[name]__[local]__[hash:5]'  // 自定义生成规则
  }
}

6.optimizeDeps.include - 强制预构建

JavaScript 复制代码
optimizeDeps: {
  include: ['lodash-es', 'axios'],  // 预构建这些依赖
  exclude: ['your-local-lib']       // 排除某些依赖
}
2.3. Vite的代码分割

Vite 的代码分割基于 Rollup 的打包机制

核心原理:将原本打包成一个文件的代码,按照模块依赖关系、使用频率和更新频率,拆分成多个独立的 chunk 文件

底层机制:Rollup 的模块解析与分块算法
  1. 模块图构建(Module Graph)

Rollup 首先从入口文件开始,递归分析所有 import/export 语句,构建完整的模块依赖图

  1. 分块算法(Chunking Algorithm)

Rollup 根据以下规则决定如何分割:

  • 入口点分割:每个 entry 生成独立 chunk
  • 动态导入分割:import() 触发点自动生成新 chunk
  • 模块复用检测:被多个入口共享的模块提取为公共 chunk
  • 手动控制:通过 manualChunks 强制指定分组
代码分割是否越细越好,判断标准是什么
  1. 为什么"越细"反而可能"越慢"?

虽然细化分割可以减少单个包的体积,但会带来以下负面影响:

  • HTTP 连结开销:虽然 HTTP/2 支持多路复用,但每个请求仍有 header 头部开销和浏览器解析开销。几百个 1KB 的 JS 小文件,加载效率远低于一个 100KB 的文件。
  • 压缩率下降:Gzip 或 Brotli 压缩算法依赖于文本的重复性。文件分得太细,字典跨度变小,导致总体体积反而变大。
  • 依赖瀑布流 :如果 A 分包依赖 B,B 又依赖 C,浏览器必须串行下载,导致首屏 TTI(可交互时间)显著延迟
  • 维护成本上升:在几十个文件间跳转才能理解一个简单功能,违背了"高内聚"原则,增加了认知负担
  1. 判断标准:何时该分?

A. 变更频率(缓存命中率)

  • 基础库 (Vendor):如 React、ReactDOM、AntD。这些库几乎半年不动,应该拆成独立的长效缓存包
  • 业务逻辑:每天都在改,应该与基础库分离

B. 页面关联性(按需加载)

  • 路由维度 :这是最基础的。用户访问 /home 时,绝不应该加载 /admin 页面的 JS
  • 首屏无关性 :弹窗(Modal)、抽屉(Drawer)、复杂的图表库(如 Echarts)应该采用 dynamic import 异步加载,只有用户点击时才下载

C. 共享程度 (Common Chunks)

  • 多页复用:如果一个组件在 5 个页面中都被用到,且体积超过 30KB,就应该拆成独立的 Common Chunk
  1. 实战中的量化指标
  2. Bundle Analyzer :观察是否有巨大的"依赖孤岛"。如果某个包超过 200KB (Gzip),必须拆;如果小于 10K,建议合并
  3. Coverage 面板 :通过 Chrome DevTools 的 Coverage 查看 JS 冗余率。如果首屏加载的 JS 有 60% 以上 未被执行,说明分割不够细
  4. 用户网络环境:在 4G/3G 弱网环境下测试。如果由于请求数过多导致明显的阻塞,说明分得太碎了

3. Vite vs Webpack

Webpack:打包式 HMR

  • 所有模块被打包成 bundle:开发时也会打包,只是速度较慢
  • 模块被 ID 标识:打包后每个模块有唯一 ID
  • HMR runtime 注入 bundle:运行时替换模块代码

Vite:原生 ESM HMR

  • 不打包:直接输出原生的 ES Module
  • 模块即文件:每个文件就是一个独立的模块
  • 浏览器原生支持:通过 import 动态加载
维度 Webpack Vite
启动原理 全量构建:必须先抓取并编译所有模块,建立依赖图,最后才能启动服务器 按需构建:直接启动服务器,利用浏览器原生的 ES Modules (ESM) 能力,用到哪个文件才处理哪个文件
冷启动速度 较慢(数秒至数分钟) 极快(毫秒级)
HMR 速度 随项目增大而变慢 极快(恒定速度)
生产打包 自身 Bundle 打包 使用 Rollup 打包
依赖预编译 使用 JavaScript 编写的解析器处理所有依赖 使用 Go 语言 编写的 esbuild(快 10-100 倍)
浏览器要求 支持旧版浏览器(需 Polyfill) 开发环境要求支持原生 ESM 的现代浏览器
3.1. Vite 热更新(HMR)核心原理

Vite 的 HMR 基于浏览器原生 ES Module,核心链路是:文件监听 → 精确编译 → WebSocket 推送 → 动态导入 → 模块替换

整体架构
复制代码
┌─────────────┐     WebSocket      ┌─────────────┐
│   Vite Dev  │ ◄─────────────────► │   浏览器    │
│   Server    │     (双向通信)       │   (客户端)  │
└─────────────┘                     └─────────────┘
      │                                     │
      │ 1. 监听文件变化                       │ 4. 动态导入新模块
      ▼                                     ▼
   文件修改                            替换旧模块
3.2. Vite 与 Webpack 的 HMR区别
环节 Webpack HMR Vite HMR
触发更新 监听到文件变化,重新编译受影响的整个模块链路。 监听到文件变化,仅对该文件进行极速转译。
通信内容 发送包含 Hash 值和更新清单(manifest)的消息。 发送一个包含发生变动的文件路径的 JSON 指令。
浏览器响应 获取新的补丁(chunk.hot-update.js)并执行。 利用 import() 动态加载带时间戳的新模块:import('/src/App.tsx?t=xxx')。
性能损耗 由内而外:内部重新计算、打包、输出,压力在服务器。 由外而内:仅处理变动文件,剩下的交给浏览器按需加载。

4. ES6 向 ES5 的转换的方式

ES6 到 ES5 的转换主要通过 Babel 工具链完成。Babel 的核心工作流程分为三步:解析、转换、生成

Babel 是一个 JavaScript 编译器,它能将 ES6+ 代码转换为向后兼容的 ES5 版本,确保代码在旧版浏览器或环境中运行

  1. 解析 (Parse):使用 @babel/parser 将 ES6 代码字符串解析成抽象语法树 (AST)
  2. 转换 (Transform):使用 @babel/traverse 遍历 AST,并通过插件对节点进行修改(如将 let 转为 var、箭头函数转为普通函数),生成新的 ES5 结构的 AST
  3. 生成 (Generate):使用 @babel/generator 将转换后的 AST 重新生成为 ES5 代码字符串

现代化的替代方案:

  1. ESBuild (Go 编写):速度极快,常用于 Vite 的开发环境
  2. SWC (Rust 编写):Next.js 目前默认使用的编译器,性能远超 Babel

5. 配置优化打包速度或构建速度

5.1. webpack优化方案
  1. 缩小文件处理范围
  • resolve.extensions 只配置必要后缀
  • resolve.modules 固定到 node_modules
  • resolve.alias 减少递归查找
  • module.noParse 忽略已打包库(jQuery、lodash)
JavaScript 复制代码
// webpack.config.js
module.exports = {
  // 1. 精确控制 loader 处理范围
  module: {
    rules: [
      {
        test: /\.js$/,
        // ✅ 只处理源代码,跳过 node_modules
        include: path.resolve(__dirname, 'src'),
        // ❌ 排除不需要处理的目录
        exclude: /node_modules|dist|__tests__/,
        use: ['babel-loader']
      }
    ]
  },
  
  // 2. 减少 resolve 的搜索路径
  resolve: {
    // 只查找这些扩展名(越少越快)
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    // 固定查找路径,避免向上递归
    modules: [path.resolve(__dirname, 'node_modules')],
    // 设置别名,减少路径解析
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components')
    },
    // 避免解析这些库的依赖(它们已打包好)
    noParse: /jquery|lodash|moment/
  }
}
  1. 充分利用缓存机制
  • cache.type: 'filesystem'(Webpack5 内置缓存)
  • babel-loader 开启 cacheDirectory
  • eslint-loader 开启 cache
JavaScript 复制代码
// Webpack 5 内置缓存(最推荐)
module.exports = {
  cache: {
    type: 'filesystem',  // 持久化缓存到磁盘
    buildDependencies: {
      config: [__filename]  // 配置文件变化时失效缓存
    },
    cacheDirectory: path.resolve(__dirname, '.webpack_cache')
  }
}

// Babel loader 缓存
{
  test: /\.js$/,
  use: [{
    loader: 'babel-loader',
    options: {
      cacheDirectory: true,  // 开启缓存
      cacheCompression: false  // 不压缩缓存,读写更快
    }
  }]
}

// ESLint 缓存
{
  test: /\.js$/,
  use: [{
    loader: 'eslint-loader',
    options: {
      cache: true,
      cacheLocation: path.resolve(__dirname, '.eslint_cache')
    }
  }]
}
  1. 多进程/多线程并行处理
  • thread-loader 耗时的 loader(babel、ts)(用 thread-loader 处理 babel-loade)
  • terser-webpack-plugin 开启 parallel: true
JavaScript 复制代码
// thread-loader - 将耗时 loader 放到线程池
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'thread-loader',
            options: {
              workers: require('os').cpus().length - 1,  // CPU 核心数-1
              workerParallelJobs: 50,
              poolTimeout: 2000  // 闲置超时
            }
          },
          'babel-loader'
        ]
      }
    ]
  }
}

// Terser 压缩并行(Webpack 5)
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,  // 开启多进程压缩
        terserOptions: {
          compress: { drop_console: true }  // 生产环境删除 console
        }
      })
    ]
  }
}
5.2. vite优化方案
  1. 依赖预构建

    • 自动用 esbuild 预编译依赖

    • optimizeDeps.include 手动指定

  2. 浏览器缓存

    • 强缓存依赖,304 协商缓存源码
  3. 按需编译

    • 只编译当前访问页面,其他页面不处理
C++ 复制代码
// vite.config.js
export default {
  // 1. 优化依赖预构建
  optimizeDeps: {
    include: ['lodash-es', 'axios'],  // 强制预构建
    exclude: ['your-local-lib'],      // 排除不需要预构建的
    esbuildOptions: {
      target: 'es2020'  // 设置目标环境
    }
  },
  
  // 2. 按需编译(默认开启)
  server: {
    fs: {
      strict: false,  // 允许访问根目录外文件(谨慎使用)
    }
  },
  
  // 3. 构建优化
  build: {
    target: 'es2015',
    minify: 'esbuild',  // esbuild 比 terser 快 20 倍
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],  // 手动分包
          utils: ['lodash-es', 'dayjs']
        }
      }
    }
  }
}
相关推荐
冴羽yayujs3 小时前
GitHub 热门项目-日榜(2026-05-19)
前端·javascript·github
AIFQuant3 小时前
JavaScript 前端集成贵金属 K 线图:10 分钟快速实现
开发语言·前端·javascript·websocket·金融·期货api
不是山谷.:.3 小时前
websocket的封装
开发语言·前端·网络·笔记·websocket·网络协议
摇滚侠3 小时前
14 响应式网页 WEB 前端 WEB 开发 HTML5 + CSS3 + 移动 WEB
前端·css3·html5
vortex53 小时前
Shellinabox 使用指南:基于 Web 的终端模拟器
linux·前端·web ssh
小则又沐风a3 小时前
深入理解进程概念 第三章 进程调度切换
java·linux·服务器·前端
ZC跨境爬虫3 小时前
跟着 MDN 学 HTML day_63:(Web 中矢量图形的完整指南)
前端·javascript·数据库·ui·html
爱怪笑的小杰杰3 小时前
Leaflet 实现轨迹拐角自动圆弧化:基于球面几何的高精度平滑算法
前端·javascript·算法·无人机
恋猫de小郭3 小时前
2026 Google I/O ,意料之外的 Antigravity 2.0 和消失的 Gemini CLI
前端·人工智能·ai编程