Vite 零基础搭建前端项目

使用 pnpm

npm i -g pnpm
pnpm config set registry https://registry.npmmirror.com/

项目初始化

pnpm create vite

framework:react、variant:react-ts

// 进入项目目录 cd vite-project // 安装依赖 pnpm install // 启动项目 pnpm run dev

目录结构

项目目录结构如下:

. ├── index.html ├── package.json ├── pnpm-lock.yaml ├── src │ ├── App.css │ ├── App.tsx │ ├── favicon.svg │ ├── index.css │ ├── logo.svg │ ├── main.tsx │ └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts

在项目根目录中有一个index.html文件,这个文件十分关键,因为 Vite 默认会把项目根目录下的index.html作为入口文件。
也就是说,当你访问http://localhost:3000的时候,Vite 的 Dev Server 会自动返回这个 HTML 文件的内容。我们来看看这个 HTML 究竟写了什么:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>

这个 HTML 文件的内容非常简洁,在 body 标签中除了 id 为 root 的根节点之外,还包含了一个声明了type="module"script 标签:

<script type="module" src="/src/main.tsx"></script>

由于现代浏览器原生支持了 ES 模块规范,因此原生的 ES 语法也可以直接放到浏览器中执行,只需要在 script 标签中声明 type="module" 即可。比如上面的 script 标签就声明了 type=“module”,同时 src 指向了/src/main.tsx文件,此时相当于请求了http://localhost:3000/src/main.tsx这个资源,Vite 的 Dev Server 此时会接受到这个请求,然后读取对应的文件内容,进行一定的中间处理,最后将处理的结果返回给浏览器。

我们可以来看看 main.tsx 的内容:

import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import './index.css' ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode> )

你可能会诧异: 浏览器并不识别 tsx 语法,也无法直接 import css 文件,上面这段代码究竟是如何被浏览器正常执行的呢?

这就归功了 Vite Dev Server 所做的“中间处理”了,也就是说,在读取到 main.tsx文件的内容之后,Vite 会对文件的内容进行编译,大家可以从 Chrome 的网络调试面板看到编译后的结果:

var _jsxFileName = "D:\\workspace\\notes\\vite\\vite-project\\src\\main.tsx"; import __vite__cjsImport0_react from "/node_modules/.vite/deps/react.js?v=d559f595"; const React = __vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react.default : __vite__cjsImport0_react; import __vite__cjsImport1_reactDom_client from "/node_modules/.vite/deps/react-dom_client.js?v=d559f595"; const ReactDOM = __vite__cjsImport1_reactDom_client.__esModule ? __vite__cjsImport1_reactDom_client.default : __vite__cjsImport1_reactDom_client; import App from "/src/App.tsx"; import "/src/index.css"; import __vite__cjsImport4_react_jsxDevRuntime from "/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=d559f595"; const _jsxDEV = __vite__cjsImport4_react_jsxDevRuntime["jsxDEV"]; ReactDOM.createRoot(document.getElementById("root")).render(/* @__PURE__ */ _jsxDEV(React.StrictMode, { children: /* @__PURE__ */ _jsxDEV(App, {}, void 0, false, { fileName: _jsxFileName, lineNumber: 8, columnNumber: 5 }, this) }, void 0, false, { fileName: _jsxFileName, lineNumber: 7, columnNumber: 3 }, this)); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7QUFDQTtBQUNBOztBQUVBQSxTQUFTQyxXQUFXQyxTQUFTQyxlQUFlLE1BQXhCLENBQXBCLEVBQXNEQyxPQUNwRCx3QkFBQyxNQUFNLFlBQVA7QUFBQSxZQUNFLHdCQUFDLEtBQUQ7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQURGO0FBQUE7QUFBQTtBQUFBO0FBQUEsUUFERiIsIm5hbWVzIjpbIlJlYWN0RE9NIiwiY3JlYXRlUm9vdCIsImRvY3VtZW50IiwiZ2V0RWxlbWVudEJ5SWQiLCJyZW5kZXIiXSwic291cmNlcyI6WyJEOi93b3Jrc3BhY2Uvbm90ZXMvdml0ZS92aXRlLXByb2plY3Qvc3JjL21haW4udHN4Il0sImZpbGUiOiJEOi93b3Jrc3BhY2Uvbm90ZXMvdml0ZS92aXRlLXByb2plY3Qvc3JjL21haW4udHN4Iiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IFJlYWN0RE9NIGZyb20gJ3JlYWN0LWRvbS9jbGllbnQnXG5pbXBvcnQgQXBwIGZyb20gJy4vQXBwJ1xuaW1wb3J0ICcuL2luZGV4LmNzcydcblxuUmVhY3RET00uY3JlYXRlUm9vdChkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncm9vdCcpISkucmVuZGVyKFxuICA8UmVhY3QuU3RyaWN0TW9kZT5cbiAgICA8QXBwIC8+XG4gIDwvUmVhY3QuU3RyaWN0TW9kZT5cbilcbiJdfQ==

Vite 会将项目的源代码编译成浏览器可以识别的代码,与此同时,一个 import 语句即代表了一个 HTTP 请求,如下面两个 import 语句:

import "/src/index.css"; import App from "/src/App.tsx";

在 Vite 项目中,一个import 语句即代表一个 HTTP 请求。上述两个语句则分别代表了两个不同的请求,Vite Dev Server 会读取本地文件,返回浏览器可以解析的代码。当浏览器解析到新的 import 语句,又会发出新的请求,以此类推,直到所有的资源都加载完成。

现在,你应该知道了 Vite 所倡导的no-bundle理念的真正含义: 利用浏览器原生 ES 模块的支持,实现开发阶段的 Dev Server,进行模块的按需加载,而不是先整体打包再进行加载。相比 Webpack 这种必须打包再加载的传统构建模式,Vite 在开发阶段省略了繁琐且耗时的打包过程,这也是它为什么快的一个重要原因。

配置文件

在使用 Vite 的过程,我们需要对 Vite 做一些配置,以满足日常开发的需要。你可以通过两种方式来对 Vite 进行配置,一是通过命令行参数,如vite --port=8888,二是通过配置文件,一般情况下,大多数的配置都通过配置文件的方式来声明。

Vite 当中支持多种配置文件类型,包括.js.ts.mjs三种后缀的文件,实际项目中一般使用 vite.config.ts 作为配置文件,以脚手架项目中的配置为例,具体的配置代码如下:

import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()] })

可以看到配置文件中默认在 plugins 数组中配置了官方的 react 插件,来提供 React 项目编译和热更新的功能。

接下来,我们可以基于这个文件完成更加丰富的配置。例如我们可以修改页面入口文件 index.html 的位置。

页面的入口文件index.html并不在项目根目录下,而需要放到 src 目录下,如何在访问localhost:3000的时候让 Vite 自动返回 src 目录下的index.html呢?我们可以通过root参数配置项目根目录的位置:

// vite.config.ts import { defineConfig } from 'vite' // 引入 path 包注意两点: // 1. 为避免类型报错,你需要通过 `pnpm i @types/node -D` 安装类型 // 2. tsconfig.node.json 中设置 `allowSyntheticDefaultImports: true`,以允许下面的 default 导入方式 import path from 'path' import react from '@vitejs/plugin-react' export default defineConfig({ // 手动指定项目根目录位置 root: path.join(__dirname, 'src') plugins: [react()] })

当手动指定root参数之后,Vite 会自动从这个路径下寻找index.html文件,也就是说当我直接访问 localhost:3000的时候,Vite 从src目录下读取入口文件,这样就成功实现了刚才的需求。

生产环境构建

有人说Vite因为其不打包的特性而不能上生产环境,其实这种观点是相当有误的。

在开发阶段 Vite 通过 Dev Server 实现了不打包的特性,而在生产环境中,Vite 依然会基于 Rollup 进行打包,并采取一系列的打包优化手段。从脚手架项目的package.json中就可见一斑:

"scripts": { // 开发阶段启动 Vite Dev Server "dev": "vite", // 生产环境打包 "build": "tsc && vite build", // 生产环境打包完预览产物 "preview": "vite preview" }

相信你已经注意到其中的build命令了,没错,这个命令就是 Vite 专门用来进行生产环境打包的。但可能你会有点疑惑,为什么在vite build命令执行之前要先执行tsc呢?

tsc 作为 TypeScript 的官方编译命令,可以用来编译 TypeScript 代码并进行类型检查,而这里的作用主要是用来做类型检查,我们可以从项目的tsconfig.json中注意到这样一个配置:

{ "compilerOptions": { // 省略其他配置 // 1. noEmit 表示只做类型检查,而不会输出产物文件 // 2. 这行配置与 tsc --noEmit 命令等效 "noEmit": true, }, }

虽然 Vite 提供了开箱即用的 TypeScript 以及 JSX 的编译能力,但实际上底层并没有实现 TypeScript 的类型校验系统,因此需要借助 tsc 来完成类型校验(在 Vue 项目中使用 vue-tsc 这个工具来完成),在打包前提早暴露出类型相关的问题,保证代码的健壮性。

你可以试着执行一下这个打包命令:

PS D:\workspace\notes\vite\vite-project> pnpm run build > vite-project@0.0.0 build D:\workspace\notes\vite\vite-project > tsc && vite build vite v2.9.5 building for production... ✓ 34 modules transformed. dist/assets/logo.ecc203fb.svg 2.61 KiB dist/index.html 0.44 KiB dist/assets/index.62f502b0.css 0.75 KiB / gzip: 0.48 KiB

此时 Vite 已经生成了最终的打包产物,我们可以通过 pnpm run preview 命令预览一下打包产物的执行效果。

PS D:\workspace\notes\vite\vite-project> pnpm run preview > vite-project@0.0.0 preview D:\workspace\notes\vite\vite-project > vite preview > Local: http://localhost:4173/ > Network: use `--host` to expose

在浏览器中打开http://localhost:5000地址,你将看到和开发阶段一样的页面内容,证明我们成功完成第一个 Vite 项目的生产环境构建。

总结

本节中我们一起搭建了基本的前端开发环境,安装常用的编辑器、浏览器、Node.js 环境及包管理器 pnpm,接着我和你使用 Vite 的初始化命令创建一个 React 项目并成功启动,让你真切地体验到 Vite 的快速和轻量。

项目启动之后我也与你分析了项目背后的启动流程,强调了 一个 import 语句代表一个 HTTP 请求,而正是 Vite 的 Dev Server 来接收这些请求、进行文件转译以及返回浏览器可以运行的代码,从而让项目正常运行。

不仅如此,我还带你一起初步接触了 Vite 的配置文件,并尝试进行生产环境的打包,为下一节的学习作下了铺垫。