web自学5——构建工具 Vite
前文记录了 JavaScript 模块化和 npm。模块化让我们可以拆分自己的代码,npm 让我们可以管理第三方依赖。
但 npm 安装的 package 并不能直接交给浏览器使用,这就引出了构建工具的需求。本文以 Vite 为例,简单了解前端构建工具解决的问题。
1. 构建工具
构建工具主要解决的是:源码和浏览器最终运行的文件之间存在差距。
浏览器原生的模块导入要求路径是它能理解的 URL。例如相对路径可以:
1 | import { createTodoItem } from "./todo.js"; |
完整 URL 也可以:
1 | import confetti from "https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/+esm"; |
但是 npm 安装的 package 通常会放在 node_modules/ 中。如果直接在浏览器里写:
1 | import lodash from "lodash"; |
这里的 "lodash" 既不是相对路径,也不是完整 URL。浏览器默认并不知道应该去 node_modules/ 中寻找 lodash,也不知道这个 package 的入口文件在哪里,所以这种写法不能直接交给浏览器运行。
除了模块路径解析,开发源码和最终网页之间还可能有其它差异。浏览器最终需要的是 HTML + CSS + JavaScript + 静态资源,但是开发时写的可能是:
- 多个 JS/TS 模块;
- npm package;
- CSS 预处理或 CSS module;
- 图片、字体等资源引用;
- 开发服务器和热更新需求。
构建工具的任务,就是在开发阶段把这些东西组织起来,并在发布阶段生成适合部署的静态文件。更直观地说,它负责把“开发时方便写的源码”转换成“浏览器能加载的资源”。
页面越接近浏览器原生能力,就越不需要构建工具;项目越接近工程化应用,就越需要构建工具。
如果页面很简单,其实完全不需要构建工具。例如:
1 | index.html |
只使用浏览器原生支持的 HTML、CSS、JavaScript,直接在 HTML 中引用 CSS 和 JS:
1 | <link rel="stylesheet" href="./style.css" /> |
这种情况下浏览器可以直接加载这些文件,不需要 npm,也不需要 Vite。即使使用浏览器原生的 ES Module,只要模块路径都是明确的相对路径,例如 ./main.js,也不一定需要构建工具。
但是当项目开始使用 npm package,或者需要更完整的开发体验时,构建工具就变得很自然。它需要解决的问题包括:
import "lodash"怎样映射到实际文件?- 多个模块之间的依赖图怎样组织?
- TypeScript 等源码怎样转换?
- CSS 和图片等资源怎样被打包或复制?
- 开发时怎样启动本地服务器并快速刷新?
- 发布时怎样生成
dist/?
在下面这些情况下,通常会需要构建工具:
- 使用 npm package,并希望直接通过
import xxx from "xxx"引入; - 使用 TypeScript、Sass、PostCSS 等需要转换的源码;
- 项目文件较多,希望有模块依赖分析、代码分割、压缩等生产构建能力;
- 希望开发时有本地服务器、HMR、错误提示等工具支持;
- 使用 React、Vue 等框架,尤其是需要 JSX、单文件组件等语法。
最后一条会在下一篇 React 中继续展开。这里先只关注构建工具本身。
2. Vite
Vite 是一个前端构建工具。同类工具还有 Webpack、Parcel、Rollup、esbuild 等,但是现在从零开始写一个普通前端小项目时,Vite 是比较常见的选择。
Vite 的常用命令通常写在 package.json 中:
1 | { |
开发时执行:
1 | npm run dev |
它会启动 Vite 开发服务器。浏览器访问的是这个本地服务,Vite 负责按需处理源码、解析模块,并在修改代码后快速更新页面。
发布前执行:
1 | npm run build |
它会执行生产构建,通常生成:
1 | dist/ |
dist/ 才是最终用于部署的构建产物。它不是可执行程序,而是一组给浏览器加载的静态资源。
构建后还可以执行:
1 | npm run preview |
这个命令用于在本地预览刚刚生成的 dist/,检查生产构建结果是否正常。它不是正式的生产服务器。
3. 创建项目
如果只想创建原生 JavaScript 项目,可以选择 vanilla 模板:
1 | npm create vite@latest todo-vite |
项目创建完成后,vanilla 模板的目录结构大致如下:
1 | todo-vite/ |
其中:
package.json记录项目依赖和dev、build等命令;node_modules/保存 npm 安装的 package;public/保存不需要经过 Vite 处理、可以直接复制到构建结果中的静态资源;src/保存主要源码,默认模板中的图片和计数器代码只是演示内容;index.html是浏览器访问页面时的入口。
与普通静态网页不同,Vite 把 index.html 也看作项目源码的一部分。默认的入口 index.html 非常简单,通过模块脚本引入 src/main.js:
1 |
|
浏览器仍然先加载 HTML,再根据这个标签加载 main.js。main.js 又可以继续导入 CSS、其它 JS 模块或 npm package、静态资源:
1 | import './style.css' |
执行 npm run dev 时,Vite 根据这些 import 处理模块及资源;执行 npm run build 时,Vite 再沿着相同的依赖关系生成 dist/。
4. TodoList 示例
默认页面只是 Vite 提供的演示。现在把它改成 TodoList:删除无用资源,保留 index.html 中的 #app,重写 style.css,再把业务拆成 main.js 和 todo.js。
改造后的源码结构如下:
1 | src/ |
加入 Vite 以后,仍然可以写原生 DOM 操作。Vite 只负责开发服务器、模块处理和构建流程,不负责 UI 本身如何组织。
例如 src/todo.js:
1 | export function createTodoItem(text) { |
src/main.js 可以写成:
1 | import "./style.css"; |
最后把默认的 src/style.css 替换为 TodoList 的样式:
1 | * { |
这个例子里,Vite 解决的是:
- 启动本地开发服务器;
- 处理
import "./style.css"这类资源引用; - 处理 JS 模块之间的依赖关系;
- 构建时生成
dist/。
