diff --git a/docs/.vitepress/theme/components/SummaryCard.vue b/docs/.vitepress/theme/components/SummaryCard.vue index a19f5f3..f58adea 100644 --- a/docs/.vitepress/theme/components/SummaryCard.vue +++ b/docs/.vitepress/theme/components/SummaryCard.vue @@ -60,34 +60,46 @@ const props = defineProps({ \ No newline at end of file + diff --git a/docs/.vitepress/theme/components/appendix/frontend-engineering/AssetFingerprintDemo.vue b/docs/.vitepress/theme/components/appendix/frontend-engineering/AssetFingerprintDemo.vue index 0910bb3..1f261b2 100644 --- a/docs/.vitepress/theme/components/appendix/frontend-engineering/AssetFingerprintDemo.vue +++ b/docs/.vitepress/theme/components/appendix/frontend-engineering/AssetFingerprintDemo.vue @@ -221,7 +221,7 @@ diff --git a/docs/.vitepress/theme/components/appendix/frontend-engineering/DependencyGraphDemo.vue b/docs/.vitepress/theme/components/appendix/frontend-engineering/DependencyGraphDemo.vue index 41c591a..648fcd1 100644 --- a/docs/.vitepress/theme/components/appendix/frontend-engineering/DependencyGraphDemo.vue +++ b/docs/.vitepress/theme/components/appendix/frontend-engineering/DependencyGraphDemo.vue @@ -1,840 +1,130 @@ - diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js index c7fc3d4..02a2dd2 100644 --- a/docs/.vitepress/theme/index.js +++ b/docs/.vitepress/theme/index.js @@ -252,6 +252,16 @@ import MemoryPalaceActionDemo from './components/appendix/context-engineering/Me import KVCacheDemo from './components/appendix/context-engineering/KVCacheDemo.vue' import LostInMiddleDemo from './components/appendix/context-engineering/LostInMiddleDemo.vue' +// Frontend Engineering Components +import BuildPipelineDemo from './components/appendix/frontend-engineering/BuildPipelineDemo.vue' +import BundlerComparisonDemo from './components/appendix/frontend-engineering/BundlerComparisonDemo.vue' +import TreeShakingDemo from './components/appendix/frontend-engineering/TreeShakingDemo.vue' +import CodeSplittingDemo from './components/appendix/frontend-engineering/CodeSplittingDemo.vue' +import HotReloadDemo from './components/appendix/frontend-engineering/HotReloadDemo.vue' +import DependencyGraphDemo from './components/appendix/frontend-engineering/DependencyGraphDemo.vue' +import SourceMapDemo from './components/appendix/frontend-engineering/SourceMapDemo.vue' +import AssetFingerprintDemo from './components/appendix/frontend-engineering/AssetFingerprintDemo.vue' + // Agent Intro Components import AgentWorkflowDemo from './components/appendix/agent-intro/AgentWorkflowDemo.vue' import AgentLevelDemo from './components/appendix/agent-intro/AgentLevelDemo.vue' @@ -693,6 +703,16 @@ export default { app.component('KVCacheDemo', KVCacheDemo) app.component('LostInMiddleDemo', LostInMiddleDemo) + // Frontend Engineering Components Registration + app.component('BuildPipelineDemo', BuildPipelineDemo) + app.component('BundlerComparisonDemo', BundlerComparisonDemo) + app.component('TreeShakingDemo', TreeShakingDemo) + app.component('CodeSplittingDemo', CodeSplittingDemo) + app.component('HotReloadDemo', HotReloadDemo) + app.component('DependencyGraphDemo', DependencyGraphDemo) + app.component('SourceMapDemo', SourceMapDemo) + app.component('AssetFingerprintDemo', AssetFingerprintDemo) + // Agent Intro Components Registration app.component('AgentWorkflowDemo', AgentWorkflowDemo) app.component('AgentLevelDemo', AgentLevelDemo) diff --git a/docs/zh-cn/appendix/frontend-engineering.md b/docs/zh-cn/appendix/frontend-engineering.md index e4f7910..226f353 100644 --- a/docs/zh-cn/appendix/frontend-engineering.md +++ b/docs/zh-cn/appendix/frontend-engineering.md @@ -1,96 +1,98 @@ # 前端工程化与构建流水线 -> 💡 **学习指南**:本章围绕一个问题展开:**如何把你写的一堆代码,变成用户浏览器里能跑、跑得快的网站?** 这就像是问:如何把原材料变成成品,还要保证质量、控制成本? - -在开始之前,建议你先了解: - -- **什么是模块化**:如果你还不熟悉 ES6 的 `import`/`export`,可以先了解一下基础概念。 -- **命令行基础**:会用 `cd`、`npm` 等基础命令会帮助你更好地理解构建流程。 +::: tip 🎯 核心问题 +**如何把你写的代码,变成用户浏览器能跑的网站?** 这就像是问:如何把原材料变成成品,还要保证质量、控制成本?本章将带你深入理解前端工程化的核心概念和构建流程。 +::: --- -## 0. 引言:为什么前端越来越"重"了? +## 1. 为什么要"工程化"? + +### 1.1 从简单到复杂:前端开发的演变 + +回顾十年前的前端开发,那时候的我们工作方式非常简单:写几个 HTML 页面,内嵌一些 CSS 和 JavaScript,直接把文件拖到浏览器里就能看效果,部署的时候也只需要把文件夹上传到服务器,一个网站的总代码量可能也就几十 KB。那是一个"所见即所得"的时代,开发流程简单直接,几乎没有"工程化"这个概念。 + +但现代前端开发完全变了样。我们现在用 TypeScript 代替 JavaScript,这意味着需要编译;我们用 Vue 或 React 的组件化开发方式,需要额外的转换;我们用 Sass 或 Less 写 CSS,需要预处理;我们通过 npm 安装各种依赖包,最终需要打包。一个中大型项目的前端依赖可能上千个,总大小几百 MB,这与十年前的"简单直接"形成了鲜明对比。 + +
+
+ +**👴 十年前的开发方式** +- 写几个 HTML + CSS + JS 就是一个项目 +- 直接拖到浏览器就能看效果 +- 上传文件夹到服务器就完成部署 +- 整个项目代码量通常只有几十 KB + +
+
+ +**🚀 现代的开发方式** +- 使用 TypeScript,需要编译才能运行 +- 使用 Vue/React,需要转换成原生 JS +- 使用 npm 包管理,需要打包合并 +- 项目依赖动辄几百 MB + +
+
+ +**这就是"前端工程化"要解决的问题:如何管理复杂度,让开发效率更高、代码质量更好、用户体验更优。** -还记得十年前的前端开发吗?那时候的我们: +### 1.2 一个真实的踩坑故事:为什么你需要了解构建原理 -- 写几个 HTML 页面,内嵌一些 CSS 和 JavaScript -- 直接把文件拖到浏览器里就能看效果 -- 部署的时候,直接把文件夹上传到服务器 -- 一个网站的总代码量可能也就几十 KB +你可能会说:"我用 Vite 或者 Create React App,开箱即用,为什么还需要了解这些构建原理?" 让我讲一个真实的故事,你就会明白为什么这些知识如此重要。 -但现在的前端开发,完全变了样: +::: warning 小明的踩坑记 +小明是一个刚入职的前端新人,公司用的是 Vite 搭建的项目。有一天,产品经理跑过来说首页加载太慢了,用户都在抱怨,需要尽快优化。 -- 我们用 TypeScript 代替 JavaScript,需要编译 -- 我们用 Vue/React 的 JSX/SFC,需要转换 -- 我们用 Sass/Less 写 CSS,需要预处理 -- 我们用各种 npm 包,需要打包 -- 一个中大型项目的依赖可能上千个,总大小几百 MB +小明立刻行动起来:他压缩了图片、实现了路由懒加载、启用了 Gzip 压缩...一顿操作猛如虎,但首页加载速度依然很慢,问题根本没有解决。 -**这就是"前端工程化"要解决的问题。** +后来他请教师傅,师傅打开浏览器的开发者工具,看了一眼网络请求,立刻发现了问题所在:`vendor.js` 文件竟然有 2MB!原来小明为了使用某个日期格式化函数,直接引入了 `moment.js` 整个库,而 `moment.js` 包含了 100 多种语言的 locale 文件,大部分都是项目根本用不到的。 - +解决方案很简单:把 `moment.js` 换成 `dayjs`,或者按需引入 `date-fns`。这样改动之后,2MB 的体积瞬间变成了 2KB,首页加载速度提升了十几倍。 -### 1.1 前端工程的"三座大山" +小明从此明白了一个道理:**不了解构建和打包原理,你连问题出在哪都不知道,更别提解决问题了。** +::: -现代前端工程主要面临三大挑战: - -| 挑战 | 十年前 | 现在 | 解决方案 | -|------|--------|------|----------| -| **开发体验** | 刷新页面即可 | 热更新、类型检查、代码规范 | Vite、ESLint、TypeScript | -| **产物优化** | 无需优化 | Tree Shaking、代码分割、压缩 | Webpack、Rollup、Terser | -| **部署策略** | 直接上传 | CDN、缓存策略、版本控制 | CI/CD、Hash 文件名 | - -### 1.2 为什么你需要了解构建流程? - -你可能会说:"我用 Vite 或者 Create React App,开箱即用,为什么还需要了解这些?" - -让我讲一个真实的故事: - -> **小明的踩坑记** -> -> 小明是一个前端新人,公司用 Vite 搭建的项目。有一天,产品经理说首页加载太慢了,要优化。 -> -> 小明一顿操作:图片压缩、路由懒加载、启用 Gzip... 但首页依然慢。 -> -> 后来他请教师傅,师傅一看:`vendor.js` 有 2MB! -> -> 原来小明为了用某个日期格式化函数,引入了 `moment.js` 整个库,而 `moment.js` 包含了 100 多种语言的 locale 文件。 -> -> 解决方案:换成 `dayjs` 或者按需引入 `date-fns`。2MB 变成了 2KB。 -> -> 小明从此明白:**不了解构建和打包原理,你连问题出在哪都不知道。** +::: info 💡 核心启示 +构建工具不是黑魔法,理解它的工作原理能让你在遇到问题时快速定位、精准解决。更重要的是,它能在设计架构和选择依赖时帮你做出更明智的决策。 +::: --- -## 2. 核心概念:构建、打包、转译都是啥? +## 2. 核心概念:转译、打包、构建 -在深入工具之前,让我们先搞清楚几个经常被混淆的概念。 +在深入学习具体工具之前,我们需要先搞清楚几个经常被混淆的核心概念:转译、打包和构建。这三个词听起来很像,但实际上各有不同的含义和职责。为了帮助你更好地理解,我们用一个餐厅的比喻来类比它们之间的关系。 -### 2.1 转译(Transpile) +### 2.1 用餐厅比喻理解三个概念 -**是什么?** 把一种编程语言(或其新版本)转换成另一种(或其旧版本)的过程。 +想象你经营一家餐厅,每天要为顾客提供各种美食。这个过程中涉及到的环节,与前端工程化的三个核心概念惊人地相似: -**为什么需要?** +| 概念 | 🍽️ 餐厅比喻 | 实际作用 | 具体例子 | +|------|-------------|----------|----------| +| **转译** | 把中文菜谱翻译成英文,让外国厨师也能看懂 | 把新语法转换成浏览器能理解的旧语法 | ES2022 → ES5、TypeScript → JavaScript | +| **打包** | 把各桌点的菜装成一个个外卖盒,方便配送 | 把分散的模块文件合并成少数几个文件 | 100 个源文件 → 3 个 bundle 文件 | +| **构建** | 从接单、做菜、打包到配送的完整流程 | 从源代码到生产代码的完整转换过程 | 编译、检查、打包、优化、输出 | -- 浏览器不支持最新的 ES2022 语法 -- 要把 TypeScript 转成 JavaScript -- 要把 JSX/Vue SFC 转成纯 JS +### 2.2 转译(Transpile):代码的"翻译官" -**常见工具:** +转译,顾名思义就是"转换+编译",它的核心作用是把一种编程语言(或其新版本)转换成另一种(或其旧版本)。你可能会有疑问:为什么要这样做?直接写浏览器支持的代码不就行了吗? -- **Babel**:最老牌、生态最丰富的转译器 -- **SWC**:用 Rust 写的,速度极快(比 Babel 快 20 倍) -- **esbuild**:Go 写的,也很快,但功能相对简单 +答案在于浏览器兼容性问题。虽然 JavaScript 每年都会发布新版本,带来更强大的语法和 API,但浏览器的更新速度远远跟不上。如果你使用了最新的 ES2022 语法,在旧版浏览器上可能完全无法运行。转译工具的作用就是把你的"超前代码"转换成"保守代码",确保在所有浏览器上都能正常运行。 -**举个例子:** +::: details 🔧 转译示例:看看转译做了什么 +让我们看一个具体的例子。下面是你写的代码,使用了 ES2020 的可选链操作符和空值合并操作符: -```javascript -// 你写的 (ES2020+) +```js +// 你写的(ES2020+) const result = data?.items?.map(item => item.name) ?? [] +``` -// Babel 转译后 (ES5) +这段代码很简洁优雅,但在旧浏览器上会报语法错误。转译工具会把它转换成等价的、兼容性更好的代码: + +```js +// 转译后(ES5 兼容版本) var _data$items, _data$items$map var result = (_data$items$map = @@ -103,73 +105,121 @@ var result = : [] ``` -### 2.2 打包(Bundle) +可以看到,一行简洁的代码被转换成了多行"啰嗦"的代码,但后者可以在任何浏览器上正常运行。 +::: -**是什么?** 把多个分散的模块文件合并成一个(或几个)文件的过程。 +**常用的转译工具:** -**为什么需要?** +- **Babel** 是最老牌、生态最丰富的 JavaScript 转译器,几乎可以处理所有现代语法。它的插件系统非常强大,但也因为灵活性高导致配置相对复杂。 +- **SWC** 是用 Rust 语言重写的转译器,速度比 Babel 快 20 倍以上,正在被越来越多的项目采用,包括 Next.js 等知名框架。 +- **esbuild** 是用 Go 语言编写的,同样以速度著称,Vite 在开发模式下就使用它来进行快速转译。 -- 浏览器原生不支持 ES 模块(虽然有 `type="module"`,但生产环境还是需要考虑兼容性) -- 减少 HTTP 请求数(HTTP/1.1 时代很重要,HTTP/2 有所改善但仍有限度) -- 可以做更多的优化(Tree Shaking、代码分割等) +### 2.3 打包(Bundle):模块的"打包员" -**举个例子:** +打包是指把多个分散的模块文件合并成一个(或几个)文件的过程。在早期的前端开发中,我们习惯把所有代码写在一个 JS 文件里,但随着项目规模增大,这种方式变得难以维护。现代前端采用模块化开发,每个功能一个文件,但浏览器加载大量小文件会带来性能问题,这就需要打包工具来帮忙。 +::: tip 📦 什么是 ES 模块? +你可能听说过"ES 模块"这个词,它到底是什么? + +**先区分两个概念**: +- **ECMAScript(ES)**:是 JavaScript 的语言标准规范,定义了语法和 API +- **ES 模块**:是 ECMAScript 标准中定义的模块化方案,通过 `import` 和 `export` 语法导入导出代码 + +打个比方:ECMAScript 就像"普通话标准",而 ES 模块就像"普通话中的某种表达方式"。 + +```js +// utils.js - 导出模块 +export function add(a, b) { return a + b } +export function subtract(a, b) { return a - b } + +// main.js - 导入模块 +import { add, subtract } from './utils.js' +console.log(add(1, 2)) // 3 +``` + +**ES 版本小知识**:ECMAScript 每年都会发布新版本: +- **ES5(2009)**:经典版本,几乎所有浏览器都支持 +- **ES6/ES2015**:里程碑式大更新,引入了 `let/const`、箭头函数、**ES 模块**、`class` 等 +- **ES2016-ES2024**:每年持续添加新特性(如 `async/await`、可选链 `?.` 等) + +ES 模块正是在 ES6(2015年)引入的。在此之前,JavaScript 没有官方的模块系统,开发者只能用各种"民间方案"(如 CommonJS、AMD),这导致了模块规范不统一的问题。ES 模块统一了这些规范,成为现代前端开发的基石。 +::: + +**为什么需要打包?** 主要有三个原因:首先,虽然现代浏览器已经支持 ES 模块,但在生产环境中加载上百个小文件仍然会带来性能开销;其次,打包过程可以进行 Tree Shaking,自动删除未使用的代码,减小文件体积;最后,打包后可以做代码分割,实现按需加载,提升首屏速度。 + +::: details 📁 打包前后对比:看看打包做了什么 +**打包前的源码结构**(分散的多个文件): ``` -源代码结构: src/ -├── index.js (import a, b) +├── index.js (入口文件,导入其他模块) ├── utils/ -│ ├── a.js (import c) -│ ├── b.js -│ └── c.js +│ ├── a.js (工具函数 A) +│ ├── b.js (工具函数 B) +│ └── c.js (工具函数 C) └── components/ - └── Button.vue - -打包后: -dist/ -└── bundle.js (包含所有代码,按正确顺序组织) + └── Button.vue (按钮组件) ``` -### 2.3 构建(Build) +**打包后的产物**(合并后的少数文件): +``` +dist/ +├── index.[hash].js (主入口代码) +├── vendor.[hash].js (第三方库代码) +└── assets/ + └── logo.[hash].png (静态资源) +``` -**是什么?** 这是一个更广义的词,通常包含**转译 + 打包 + 各种优化**的完整流程。 +打包工具会分析文件之间的依赖关系,按照正确的顺序把它们合并到一起,同时进行各种优化。 +::: -**一个完整的构建流程通常包括:** +👇 **动手试试看**: +下面这个演示展示了代码分割如何实现按需加载。点击不同的路由,观察哪些代码被加载了: -1. **预编译**:TypeScript → JavaScript、Sass → CSS -2. **代码检查**:ESLint、类型检查 -3. **依赖解析**:分析模块依赖关系 -4. **转译**:Babel 转换语法 -5. **打包**:合并模块 -6. **优化**:压缩、Tree Shaking、代码分割 -7. **资源处理**:图片压缩、生成雪碧图 -8. **产物生成**:输出到 dist 目录 + -### 2.4 三者的关系 +### 2.4 构建(Build):完整的"生产线" -用一个餐厅比喻来理解: +构建是一个更广义的概念,它涵盖了从源代码到可部署产物的完整转换过程。一个完整的构建流程通常包括以下步骤: -| 概念 | 餐厅比喻 | 实际作用 | -|------|----------|----------| -| **转译** | 把中文菜谱翻译成英文给外国厨师看 | 把新语法转成浏览器能懂的旧语法 | -| **打包** | 把各桌点的菜装成一个个外卖盒 | 把分散的模块文件合并成 bundle | -| **构建** | 从接单、做菜、打包到上菜的完整流程 | 从源代码到生产代码的完整转换过程 | +1. **预编译阶段**:把 TypeScript 编译成 JavaScript,把 Sass 编译成 CSS +2. **代码检查阶段**:运行 ESLint 进行代码规范检查,运行 TypeScript 类型检查 +3. **依赖解析阶段**:分析模块之间的依赖关系,构建依赖图 + +👇 **动手看看**: +下面这个演示展示了项目中模块之间的依赖关系图谱。点击不同的节点,观察模块是如何相互引用的: + + + +4. **转译阶段**:使用 Babel 等工具转换语法,确保兼容性 +5. **打包阶段**:合并模块文件,应用 Tree Shaking 删除无用代码 +6. **优化阶段**:压缩代码、分割代码、提取公共模块 +7. **资源处理阶段**:压缩图片、生成雪碧图、处理字体文件 +8. **产物生成阶段**:输出最终文件到 dist 目录 + +理解这个完整流程非常重要,因为当构建出现问题时,你需要知道问题出在哪个环节,才能有针对性地解决。 --- -## 3. 实战案例:从0到1搭建工程化流程 +## 3. 实战:一个团队的工程化演进之路 -讲了这么多概念,让我们看一个真实的案例:某创业公司如何从"直接写 HTML"进化到"现代化工程化流程"。 +讲了这么多概念,让我们看一个真实的案例:某创业公司是如何从"直接写 HTML"一步步进化到"现代化工程化流程"的。这个案例中的很多问题和解决方案,可能正是你目前面临或将要面临的。 -### 3.1 第一阶段:原始时代(痛点初现) +### 3.1 演进的全景图 -**背景**:小团队,3个前端,做一个管理后台 +| 阶段 | 时代背景 | 主要特征 | 核心痛点 | +|------|----------|----------|----------| +| **阶段一:原始时代** | 早期小团队 | 直接写 HTML/JS/CSS | 全局变量污染、依赖混乱 | +| **阶段二:模块化** | 团队扩张期 | 引入 Webpack + Babel | 构建速度慢、配置复杂 | +| **阶段三:现代化** | 追求效率期 | 迁移到 Vite | 需要学习新工具链 | +| **阶段四:持续优化** | 成熟稳定期 | 不断改进和优化 | 避免常见的打包陷阱 | -**当时的工作方式**: +### 3.2 阶段一:原始时代——痛点初现 +在这个阶段,团队只有 3 个前端工程师,做一个管理后台项目。当时的项目结构非常简单:几个 HTML 页面,加上一些 CSS 和 JS 文件,以及一些图片资源。 + +::: details 查看当时的项目结构和代码方式 +**项目结构**: ``` -项目结构: project/ ├── index.html ├── login.html @@ -184,18 +234,16 @@ project/ ``` **遇到的问题**: +1. **全局变量污染**:所有变量都在全局命名空间,不同文件中的同名变量会互相覆盖 +2. **依赖管理混乱**:jQuery 插件必须先加载 jQuery,script 标签顺序错了就报错 +3. **代码难以复用**:想复用某个功能,只能复制粘贴代码 +4. **没有代码检查**:变量拼写错误等低级问题,只能运行后才发现 -1. **全局变量污染**:所有变量都在全局命名空间,经常冲突 -2. **依赖管理混乱**:jQuery 插件要先加载 jQuery,顺序错了就报错 -3. **代码难以复用**:想复用某个功能,只能复制粘贴 -4. **没有代码检查**:低级错误(如变量未定义)要运行后才发现 - -**当时的解决方案**(临时的、不优雅的): - -```javascript -// 用自执行函数模拟模块化 +**当时的临时解决方案**: +```js +// 用自执行函数模拟模块化(IIFE 模式) var ModuleA = (function () { - var privateVar = 'private' + var privateVar = 'private' // 私有变量,外部无法访问 function privateFn() { console.log(privateVar) @@ -203,34 +251,30 @@ var ModuleA = (function () { return { publicMethod: function () { - privateFn() + privateFn() // 暴露公共方法 } } })() -// 依赖管理靠注释说明 +// 依赖管理全靠注释说明 /** * @requires jquery.js (must load first) * @requires bootstrap.js */ ``` +::: -### 3.2 第二阶段:引入模块化(初见曙光) +这种开发方式在小项目中还能应付,但随着团队扩大到 8 人、项目变得越来越复杂,这些问题开始严重影响开发效率和代码质量,团队迫切需要一种更好的组织方式。 -**转折点**:团队扩充到 8 人,项目变复杂,原生模块化(ES6)开始普及 +### 3.3 阶段二:引入模块化——初见曙光 -**引入的工具**: - -1. **Webpack**:模块打包 -2. **Babel**:ES6+ 转译 -3. **ESLint**:代码检查 -4. **npm/yarn**:依赖管理 - -**新的项目结构**: +转折点出现在团队决定引入现代化工具链。他们选择了 Webpack 作为打包工具,Babel 作为转译器,ESLint 作为代码检查工具,并用 npm/yarn 管理依赖。这次升级带来了质的飞跃。 +::: details 查看引入 Webpack 后的变化 +**新的项目结构**(模块化组织): ``` src/ -├── components/ # Vue/React 组件 +├── components/ # 可复用的 UI 组件 │ ├── Button/ │ │ ├── index.js │ │ ├── Button.vue @@ -240,7 +284,7 @@ src/ │ ├── index.js │ ├── date.js │ └── http.js -├── services/ # API 服务 +├── services/ # API 服务层 │ ├── user.js │ └── order.js ├── assets/ # 静态资源 @@ -250,324 +294,229 @@ src/ └── main.js # 入口文件 ``` -**Webpack 配置(简化版)**: - -```javascript -// webpack.config.js -const path = require('path') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const { VueLoaderPlugin } = require('vue-loader') - -module.exports = { - entry: './src/main.js', - output: { - path: path.resolve(__dirname, 'dist'), - filename: '[name].[contenthash:8].js', - clean: true - }, - module: { - rules: [ - { - test: /\.vue$/, - loader: 'vue-loader' - }, - { - test: /\.js$/, - exclude: /node_modules/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'] - } - } - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(png|svg|jpg|gif)$/, - type: 'asset/resource' - } - ] - }, - plugins: [ - new VueLoaderPlugin(), - new HtmlWebpackPlugin({ - template: './public/index.html' - }) - ] -} -``` - -**带来的改善**: - -1. **模块化开发**:每个文件就是一个模块,通过 import/export 清晰管理依赖 -2. **代码复用**:组件和工具函数可以在不同项目中复用 -3. **代码质量**:ESLint 在保存时自动检查,类型检查(后来引入 TypeScript)在编译时发现问题 -4. **性能优化**:Webpack 的代码分割、懒加载让首屏加载变快 - -### 3.3 第三阶段:现代化工具链(当前实践) - -**新的痛点**(Webpack 时代): - -1. **构建速度慢**:项目大了以后,Webpack 冷启动要 30 秒以上 -2. **配置复杂**:Webpack 配置往往几百行,新人难以上手 -3. **HMR 慢**:修改代码后,热更新要等好几秒 - -**引入 Vite**:2021 年后,团队开始用 Vite 替代 Webpack - -**Vite 的优势**: - -| 对比项 | Webpack | Vite | -|--------|---------|------| -| 冷启动 | 30s+ | <1s | -| HMR 更新 | 3-5s | <100ms | -| 配置复杂度 | 高(需大量配置) | 低(约定优于配置) | -| 构建产物 | 成熟稳定 | 现代浏览器优化 | - -**现在的开发体验**: - -```bash -# 以前(Webpack) -npm run dev -# 等待 30 秒... -# [INFO] Compiled successfully in 30123ms -# 修改代码 -> 保存 -> 等待 5 秒看到效果 - -# 现在(Vite) -npm run dev -# 等待 300 毫秒... -# [INFO] ready in 312ms -# 修改代码 -> 保存 -> 瞬间看到效果 -``` - -### 3.4 团队踩过的坑(真实教训) - -**坑 1:依赖地狱** - -```javascript -// 不要这样做: -import moment from 'moment' // 2.5MB! - -// 推荐做法: -import dayjs from 'dayjs' // 2KB -// 或者按需导入 -import { format } from 'date-fns' // 按需打包 -``` - -**坑 2:Tree Shaking 失效** - -```javascript -// 问题:这会引入整个 lodash -import _ from 'lodash' -_.debounce(fn, 200) - -// 正确:只导入需要的函数 -import debounce from 'lodash/debounce' -// 或者使用 lodash-es +**现代化代码示例**: +```js +// ES6 模块化导入,依赖关系清晰 import { debounce } from 'lodash-es' -``` +import Button from '@/components/Button.vue' -**坑 3:缓存策略不当** - -```javascript -// 错误:没有 hash,更新后用户可能还在用旧代码 -import './utils.js' - -// 正确:使用 content hash -import './utils.a3f7b2c.js' - -// 现代工具会自动处理: -// Vite/Webpack 会自动添加 [contenthash] -``` - ---- - -## 4. 原理深入:构建工具的工作机制 - -了解了实际案例,让我们深入看看这些工具到底是怎么工作的。 - -### 4.1 Vite 为什么这么快? - -Vite 的核心理念是:**利用浏览器原生的 ES 模块支持,让开发时无需打包**。 - -**传统打包工具的工作方式(如 Webpack)**: - -``` -源代码 (100+ 文件) - ↓ -[构建时打包] - ↓ -Bundle (单个/几个大文件) - ↓ -浏览器 -``` - -问题:无论改多小的代码,都要重新打包整个项目。 - -**Vite 的工作方式**: - -``` -源代码 (100+ 文件) - ↓ -[不打包!直接按需编译] - ↓ -浏览器 ← 按需加载每个模块 -``` - -具体流程: - -1. **冷启动**:Vite 启动时只做一些轻量级的预处理,不需要打包,所以秒开 -2. **浏览器请求**:浏览器请求 `index.html` -3. **模块转换**:当浏览器通过 ` + +// ✅ 正确做法:使用 content hash +// Vite/Webpack 会自动处理: +// +// 内容变化时 hash 也会变化,浏览器会自动获取新版本 +``` +::: --- -## 5. 实战模板:vite.config.js 完整配置 +## 4. 原理深入:Vite 为什么这么快? -理论讲得差不多了,给你一个可直接使用的 Vite 配置模板: +了解了实际案例后,让我们深入看看 Vite 的工作原理,理解它为什么能比传统工具快这么多。 + + + +### 4.1 两种截然不同的工作方式 + +传统打包工具(如 Webpack)的工作方式是"先打包后服务":在启动开发服务器之前,它必须先把整个应用的所有模块打包成一个或几个 bundle 文件。这个过程中需要遍历所有源文件、解析依赖关系、转换代码、合并文件,项目越大,这个过程就越慢。 + +``` +传统打包工具的工作流程: + +源代码 (100+ 文件) + ↓ +[构建时全部打包] ← 这一步非常耗时! + ↓ +Bundle (单个/几个大文件) + ↓ +浏览器请求 → 返回打包后的文件 +``` + +Vite 的工作方式完全不同,它采用了"按需编译"的策略:启动时几乎不做任何打包工作,直接启动开发服务器。当浏览器请求某个模块时,Vite 才会实时编译这个模块并返回。 + +``` +Vite 的工作流程: + +源代码 (100+ 文件) + ↓ +[不打包!直接启动服务器] ← 几乎瞬间完成 + ↓ +浏览器请求 index.html + ↓ +浏览器发现