第8章 8.5 Vite 构建工具入门

📖 这是「JavaScript 从入门到精通」第 40 章,上一章我们学了 npm 与 yarn 包管理,这一章我们要用它们解决一个更实在的问题——项目跑起来太慢了怎么办?

🎯 开场:为什么你的项目启动像老牛拉车?

你有没有遇到过这种情况:

  • 写了个小网页,点开 index.html,浏览器转圈转了 8 秒才显示内容
  • 改了一行 CSS,刷新了页面,内容没变——哦要清缓存
  • 项目大了之后,npm run dev 等了 半分钟才启动完成

我之前带过一个学生,他做一个 Vue 项目,每次改代码都要等 20 多秒才能看到效果。他都快疯了:「我就改了个按钮颜色,为什么要等这么久?」

这就是没有构建工具的痛。 传统方式是浏览器直接加载你的源文件,文件多了、依赖多了,网络请求就爆炸了。

这一章我们要学的 Vite,就是来解决这个问题的。学完你可以:

  • ✅ 把项目启动时间从 30 秒降到 1 秒
  • ✅ 改代码秒级看到效果(不用刷新)
  • ✅ 打包体积自\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\n\n动优化

🧱 基础:Vite 到底是什么?

1. 生活类比:快递站的故事

想象你要寄 100 个包裹:

  • 传统方式(浏览器直接加载):你把 100 个包裹一个个递给快递员,他跑 100 趟送到客户手里
  • Vite 方式:你把 100 个包裹打包成 5 个大箱子,快递员只跑 5 趟,而且箱子还会按需拆开

Vite 就是一个自动打包 + 智能分发的工具,让浏览器加载你代码的方式从「零售」变成「批发+零售」。

2. 核心概念先知道

概念一:ESM 原生支持

JavaScript 有一種新的加载方式叫 ES Module(ESM),你可以理解為「按需点菜」——需要什么再加载什么,不像以前一股脑全部加载。

// 传统方式:一次性导入所有
const utils = require('./utils.js')  // 全部加载

// ESM 方式:用到哪个再要哪个
import { foo } from './utils.js'  // 只加载 foo

Vite 充分利用 ESM,让浏览器按需请求,而不是一次请求所有文件。

概念二:HMR 热更新

HMR = Hot Module Replacement(热模块替换),大白话就是「改哪换哪,不用刷新」。

没有 HMR:你改 CSS → 手动刷新页面 → 整个页面重新加载 → 白屏 2 秒
有 HMR:你改 CSS → 浏览器直接更新那个样式 → 页面瞬间变化

概念三:按需编译

Vite 不会一启动就把所有文件都处理一遍。它是等浏览器请求哪个文件,才编译哪个文件

这就像点外卖:传统方式是「先做完整桌菜再上桌」,Vite 的方式是「做好了就立刻上,吃完再做下一道」。

3. 5 分钟搭一个 Vite 项目

废话少说,先跑起来:

# 创建项目(一路回车就行)
npm create vite@latest my-vite-app -- --template vanilla

# 进入目录
cd my-vite-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

你会看到类似输出:

VITE v5.x.x  ready in 320 ms

➜  Local:   http://localhost:5173/
➜  Network: use --host to expose
➝  press h + enter to show help

320 毫秒启动——这就是 Vite 的速度。传统 webpack 项目这个时间可能还在转圈。

4. 项目结构长什么样?

my-vite-app/
├── index.html        # 入口 HTML
├── package.json      # 项目配置
├── vite.config.js    # Vite 配置文件
├── main.js           # 你的主 JS 文件
└── style.css         # 你的样式文件

打开 index.html,你会看到:

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<!-- Vite 会自动注入 CSS 和 JS -->
</head>
<body>
<div id="app"></div>
<script type="module" src="/main.js"></script>
</body>
</html>

注意 <script type="module" src="/main.js"> —— 这就是 ESM 的标志,告诉浏览器「这是模块,用 ESM 方式加载」。


🔥 实战:3 个小项目

项目 1:改造你的第一个 Vite 页面(5 分钟)

目标:理解 Vite 如何处理 CSS 和 JS 交互

main.js 里写:

// main.js
import './style.css'

const app = document.getElementById('app')
const btn = document.createElement('button')
btn.textContent = '点我'
btn.className = 'magic-btn'

let count = 0
btn.addEventListener('click', () => {
count++
btn.textContent = `点了 ${count} 次`
// 改 CSS 类来触发 HMR
btn.className = count % 2 === 0 ? 'magic-btn' : 'magic-btn active'
})

app.appendChild(btn)

style.css 里写:

/* style.css */
body {
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}

.magic-btn {
padding: 12px 24px;
font-size: 18px;
background: #3498db;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}

.magic-btn:hover {
background: #2980b9;
transform: scale(1.05);
}

.magic-btn.active {
background: #e74c3c;
}

预期输出:点击按钮,文字变成「点了 X 次」,按钮颜色在蓝色和红色之间切换。

解释:Vite 自动帮我们处理了 ESM 导入的 CSS 和 JS,并且开启了 HMR——你改 CSS 保存,浏览器立刻更新,不用刷新页面。


项目 2:读取 JSON 数据做一个小天气卡片(15 分钟)

目标:学会用 Vite 处理本地数据 + 动态渲染 DOM

先创建 weather.json

[
{ "city": "北京", "temp": 28, "weather": "晴", "aqi": "良" },
{ "city": "上海", "temp": 31, "weather": "多云", "aqi": "优" },
{ "city": "广州", "temp": 33, "weather": "雷阵雨", "aqi": "轻度污染" }
]

修改 main.js

// main.js
import './style.css'
import weatherData from './weather.json'

function createWeatherCard(city, temp, weather, aqi) {
const card = document.createElement('div')
card.className = 'weather-card'

// 根据温度选择颜色
const tempColor = temp > 30 ? '#e74c3c' : temp > 25 ? '#f39c12' : '#3498db'

card.innerHTML = `
<h3>${city}</h3>
<div class="temp" style="color: ${tempColor}">${temp}°C</div>
<div class="weather">${weather}</div>
<div class="aqi">空气质量: ${aqi}</div>
`
return card
}

const app = document.getElementById('app')
app.innerHTML = '<h2>今日天气</h2>'

weatherData.forEach(city => {
const card = createWeatherCard(city.city, city.temp, city.weather, city.aqi)
app.appendChild(card)
})

添加 CSS:

/* style.css */
#app {
text-align: center;
padding: 20px;
}

.weather-card {
display: inline-block;
margin: 10px;
padding: 20px;
border-radius: 12px;
background: #f8f9fa;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
min-width: 120px;
}

.weather-card h3 {
margin: 0 0 10px;
}

.temp {
font-size: 32px;
font-weight: bold;
}

.weather {
margin: 8px 0;
color: #666;
}

.aqi {
font-size: 12px;
color: #999;
}

预期输出:三个天气卡片并排显示,温度数字根据温度高低显示不同颜色。

解释:Vite 的 import JSON 语法让我们可以直接导入 JSON 文件,Vite 会自动处理并转换为 JavaScript 对象。


项目 3:做一个待办事项小工具(15 分钟)

目标:组合项目 1 和 2 的能力,做一个有点真实用途的小工具

创建 todos.json

[
{ "id": 1, "text": "学会 Vite 基础", "done": true },
{ "id": 2, "text": "完成练习题", "done": false },
{ "id": 3, "text": "写一个自己的小项目", "done": false }
]

修改 main.js

// main.js
import './style.css'
import todosData from './todos.json'

const app = document.getElementById('app')

// 状态管理
let todos = [...todosData]

// 渲染函数
function render() {
app.innerHTML = `
<h2>我的待办清单</h2>
<div class="input-area">
  <input type="text" id="todo-input" placeholder="输入新待办..." />
  <button id="add-btn">添加</button>
</div>
<ul id="todo-list"></ul>
<div class="stats">共 ${todos.length} 项,已完成 ${todos.filter(t => t.done).length} 项</div>
`

const list = document.getElementById('todo-list')
const input = document.getElementById('todo-input')
const addBtn = document.getElementById('add-btn')

todos.forEach(todo => {
const li = document.createElement('li')
li.className = todo.done ? 'done' : ''
li.innerHTML = `
  <input type="checkbox" ${todo.done ? 'checked' : ''} data-id="${todo.id}" />
  <span>${todo.text}</span>
  <button class="delete-btn" data-id="${todo.id}">删除</button>
`
list.appendChild(li)
})

// 事件监听
addBtn.addEventListener('click', () => {
const text = input.value.trim()
if (text) {
  todos.push({
    id: Date.now(),
    text,
    done: false
  })
  render()
}
})

// 勾选事件(事件委托)
list.addEventListener('change', (e) => {
if (e.target.type === 'checkbox') {
  const id = parseInt(e.target.dataset.id)
  const todo = todos.find(t => t.id === id)
  if (todo) todo.done = e.target.checked
  render()
}
})

// 删除事件
list.addEventListener('click', (e) => {
if (e.target.className === 'delete-btn') {
  const id = parseInt(e.target.dataset.id)
  todos = todos.filter(t => t.id !== id)
  render()
}
})
}

render()

添加 CSS:

/* style.css */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}

.input-area {
display: flex;
gap: 10px;
margin-bottom: 20px;
}

#todo-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
}

#add-btn {
padding: 10px 20px;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}

#add-btn:hover {
background: #2980b9;
}

ul {
list-style: none;
padding: 0;
}

li {
display: flex;
align-items: center;
gap: 10px;
padding: 12px;
background: white;
border-radius: 6px;
margin-bottom: 8px;

box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

li.done span {
text-decoration: line-through;
color: #999;
}

.delete-btn {
margin-left: auto;
padding: 4px 10px;
background: #e74c3c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.stats {
margin-top: 15px;
text-align: center;
color: #666;
font-size: 14px;
}

预期输出:页面显示一个待办清单,可以勾选完成、点击删除、新增待办事项,底部显示统计数字。

解释:这个项目综合运用了 Vite 的 JSON 导入、ESM 模块化、HMR 热更新特性。代码虽然长,但结构清晰——数据管理、渲染逻辑、事件处理分开了。


💪 进阶:常见坑 + 调试技巧

坑 1:路径大小写

// ❌ 错误:Windows 下可能找不到文件
import MyModule from './MyModule.js'

// ✅ 正确:保持一致的大小写
import MyModule from './mymodule.js'

坑 2:忘记 type="module"

<!-- ❌ 错误:普通 script 标签不支持 import -->
<script src="main.js"></script>

<!-- ✅ 正确:加上 type="module" -->
<script type="module" src="/main.js"></script>

坑 3:Vite 找不到依赖

// ❌ 错误:裸导入(没有任何路径前缀),Vite 不知道从哪找
import { reactive } from 'vue'

// ✅ 正确:安装依赖后再导入
// npm install vue
import { reactive } from 'vue'

坑 4:相对路径习惯写成绝对路径

// ❌ 错误:以 / 开头是绝对路径,会从域名根目录找
import utils from '/utils.js'

// ✅ 正确:用相对路径或别名
import utils from './utils.js'

坑 5:修改 vite.config.js 后没重启

Vite 配置文件修改后,需要重新运行 npm run dev才能生效。

调试技巧:开启详细日志

# 开发时开启详细日志
npm run dev -- --debug

# 查看 Vite 处理的模块依赖
npm run dev -- --mode debug

另一个实用的调试方式是直接在代码里加 console.log——由于 HMR 的存在,保存文件后浏览器控制台会保留之前的日志,不会清空。


✏️ 练习题

练习 1(2 分钟):换个主题色
- 输入:把项目 1 的 .magic-btn 背景色从 #3498db 改成你喜欢的一个颜色
- 预期输出:页面刷新后按钮变成新颜色
- 提示:直接在 CSS 里改颜色值即可

练习 2(3 分钟):加一个条件判断
- 输入:在项目 1 的按钮点击事件里,加一个判断,当 count > 5 时弹窗提示「你点了太多次了!」
- 预期输出:点击超过 5 次后,浏览器弹出提示框
- 提示:使用 alert() 函数

练习 3(5 分钟):处理新的 JSON 数据
- 输入:创建一个 fruits.json,包含 3 种水果的名称和价格,用项目 2 的方式渲染成卡片
- 预期输出:3 个水果卡片并排显示
- 提示:参考 weather.json 的结构,换成水果数据即可

练习 4(8 分钟):把天气和待办组合
- 输入:把项目 2 的天气卡片和项目 3 的待办清单合并到一个页面显示
- 预期输出:页面上半部分是天气卡片,下半部分是待办清单
- 提示:修改 index.html 的布局,用 CSS flex 或 grid 分成两部分

练习 5(5 分钟):分析报错
- 输入:运行以下代码,说出报错原因并修复

// 假设这是 main.js 的内容
import data from './data.json'  // 假设 data.json 不存在

console.log(data)
  • 预期输出:浏览器控制台报错「404 Not Found」
  • 提示:检查文件路径是否正确,文件名是否拼写一致

作业:做一个「个人技能展示页」

  • 需求描述:做一个单页面,展示你学会的技能或想学的技能
  • 功能点:
    1. 从 JSON 文件读取技能列表(包含技能名、熟练度 1-100、分类)
    2. 根据熟练度自动显示进度条(>80 绿色,60-80 黄色,<60 红色)
    3. 点击技能可以标记为「已掌握」或「学习中」
  • 加分项:
    1. 添加分类筛选功能(比如只显示「前端」分类)
    2. 本地存储进度(刷新页面不丢失已标记的状态)
  • 验收标准:能跑起来 + 技能列表正确显示 + 进度条颜色正确 + 点击可切换状态
  • 提交方式:评论区贴代码或 GitHub 链接

📚 总结 + 资源

这一章我们学了 3 个核心点:

  1. Vite 利用 ESM 实现按需加载,浏览器需要什么才编译什么,所以启动飞快
  2. HMR 热更新让改代码秒级生效,不用手动刷新页面
  3. Vite 项目结构简单,一个入口 HTML + 几行配置就能跑起来

延伸资源

互动钩子:你用 Vite 跑过什么项目?有没有遇到过「npm run dev 等半天」的抓狂时刻?评论区聊聊,老粉优先回复!

👉 下一章我们要学习 Web Components,这是浏览器的原生组件化方案。学了 Vite 之后,你会发现「原来组件可以这么轻量」——下一章见!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。