前段时间刷知乎,偶然间看到关注的尤大在 2021前端会有什么新的变化? 下写了一条回答:
会有很多人抛弃 webpack 开始用 vite
怀揣着好奇,面向 GitHub 编程的我非常熟练地打开了 vite 的 GitHub 主页。浏览完 readme 后,有几个地方给我留下了非常深刻的印象:
- Next Generation Frontend Tooling
- Instant Server Start
- astonishingly fast Hot Module Replacement
遂打算尝试在本地写个基于 vite + react 的 demo 验验货,好在 @vitejs/app 脚手架也提供了 react 和 react-ts 等项目的创建方式,最近 ts 用的火热,果断 react-ts 整起!
脚手架启动
第一步:@vitejs/app 脚手架创建项目,安装依赖,启动 dev
$ npm init @vitejs/app my-react-vite-app --template react-ts
$ npm install
$ npm run dev
启动后出现如下 Pre-bundling 说明、应用启动端口和启动时间信息
Optimizable dependencies detected:
react, react-dom
Pre-bundling them to speed up dev server page load...
(this will be run only when your dependencies or config have changed)
Vite dev server running at:
> Local: http://localhost:3000/
> Network: http://10.33.165.26:3000/
> Network: http://10.4.19.149:3000/
ready in 2535ms
dev 首次启动时间为 2535ms,比用 create-react-app 脚手架创建的项目快了很多,二次启动时间更是缩短到了首次启动时间的十分之一,amazing!所以 vite 到底采用了什么魔法?
vite 的魔法
vite 的做法如下:
-
首次启动,vite 分析项目 package.json 中的 dependencies 依赖,将其中使用 cjs 和 umd 模块的包转成 esm 模块
-
为了减少 http 请求数,提高页面加载性能,vite 将每个依赖单独打成一个 esm 模块(文件),例如项目的 package.json 的 dependencies 如下(有点 windows 和 webpack dll 的感觉)
{ "dependencies": { "react": "^17.0.0", "react-dom": "^17.0.0" } }打包后的文件为:
react-dom.js和react.js。假如一个包有很多内部模块,例如:lodash-es有超过 600 个内部模块,经过 vite 处理后也只会生成
lodash-es.js这一个文件,vite 会将 Pre-bundling 后的模块 cache 在当前项目node_modules/.vite文件夹下,Pre-bundling 花费的时间其实就是第一步和第二步花费的时间 -
客户端访问:通过实际的路由访问,浏览器加载 HTML script 中 esm 模块的入口文件
<script type="module" src="/src/main.tsx"></script>浏览器会解析入口文件,自动根据入口文件的 import 关系下载缓存在项目
node_modules/.vite文件夹下依赖的其他 esm 模块并使用。页面刷新或 HMR,对于请求的资源,dev server 会返回 304 Not Modified 和 200 OK (from disk cache) cache,充分利用浏览器特性来完成之前 webpack 寻找依赖和打包的处理过程 -
二次启动:只有以下四种情况任意一种发生,才会重新执行 Pre-bundling 过程(上述第一步和第二步)
- package.json 中的 dependencies 列表更新
- lockfiles 改变:package-lock.json、yarn.lock、pnpm-lock.yaml
- vite.config.js 配置文件改变
- node_modules/.vite 文件夹不存在
所以,为了体验飞一般的本地启动速度,建议使用提供 esm 模块的包作为 dependencies,可以省去步骤一花费的时间。另外,如果代码中没有引入 dependencies 中的某个包,建议把这个包放在 devDependencies 中,这样 vite 就不会对它进行 Pre-bundling 的处理
结合 antd
感受和体验过 vite 非一般的二次启动速度后,我想在 vite 中使用 antd,以期真正在项目中使用:
安装 antd 和 react-router-dom
$ npm install antd react-router-dom -S
在 src 目录下创建 layouts/index.tsx 布局文件,写下如下代码:
import React from "react";
import { Layout, Menu } from "antd";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Home from "@/pages/index";
import About from "@/pages/About";
import Users from "@/pages/Users";
import styles from "./index.module.less";
const { Header, Content } = Layout;
export default function App() {
return (
<Router>
<Layout className={styles.layout}>
<Header>
<div className={styles.logo} />
<Menu theme="dark" mode="horizontal" defaultSelectedKeys={["2"]}>
<Menu.Item key="/">
<Link to="/">Home</Link>
</Menu.Item>
<Menu.Item key="/about">
<Link to="/about">About</Link>
</Menu.Item>
<Menu.Item key="/users">
<Link to="/users">Users</Link>
</Menu.Item>
</Menu>
</Header>
<Content className={styles.content}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
<Route path="/users" exact component={Users} />
</Switch>
</Content>
</Layout>
</Router>
);
}
创建样式文件:index.module.less
.layout {
min-height: 100vh;
.logo {
float: left;
width: 120px;
height: 31px;
margin: 16px 24px 16px 0;
background: rgba(255, 255, 255, 0.3);
}
.content {
min-height: 280px;
margin: 24px;
background: #fff;
}
}
控制台报错,需要安装 less
21:50:31 [vite] Internal server error: Preprocessor dependency "less" not found. Did you install it?
安装 less(切记,因为 less 没有在代码中 import,所以将其放在 devDependencies 中)
$ npm install less -D
添加 Home、About 和 Users 三个页面,src/pages/index.tsx、src/pages/About/index.tsx、src/pages/Users/index.tsx:
import React from "react";
function Home() {
return <h1>Home</h1>;
}
export default Home;
// 以此类推....
控制台又报错,找不到 @/pages/index 模块:
21:57:52 [vite] Internal server error: Failed to resolve import "@/pages/index". Does the file exist?
由于我是 umi 党,所以沿用了 umi 引入依赖时,@ 定位到 src 目录下的习惯
修改 tsconfig.json,在 compilerOptions 中添加如下 baseUrl 和 path 的配置(这两个配置成对出现),让我们在代码中用 @ 导入文件时,能直接定位和提示 src 文件夹下的文件
{
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
tsconfig.json 中的配置只是起提示作用,当真正在查找文件时,没有配置 alias,还是会报错,所以除此之外,还需要配置 vite。打开 vite.config.ts,添加 alias 配置:
import reactRefresh from '@vitejs/plugin-react-refresh'
import { defineConfig } from 'vite'
import path from "path";
export default defineConfig({
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }],
plugins: [reactRefresh()],
});
刷新页面,发现 antd 样式丢失。
第一种方式,全量引入样式文件(这当然是我们不希望的)
import "antd/dist/antd.css"
第二种方式,如果用 webpack,我们可能会想到用 babel-plugin-import 来按需引入 antd 的样式文件,但用 vite,我们可以用 vite-plugin-imp 实现相同的效果。
安装 vite-plugin-imp
$ npm install vite-plugin-imp -D
配置 vite.config.ts 的 plugin
import reactRefresh from '@vitejs/plugin-react-refresh'
import { defineConfig } from 'vite'
import path from "path";
import vitePluginImp from "vite-plugin-imp";
export default defineConfig({
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }],
plugins: [
reactRefresh(),
vitePluginImp({
libList: [
{
libName: "antd",
style: (name) => `antd/lib/${name}/style/index.css`,
},
],
}),
],
});
再刷新页面就能看到效果,而且点击顶部菜单能够正常切换 content 区域的组件,但还是感觉样式有点奇怪
因为 antd 的 base 样式未引入,这里我选择在 src/main.tsx 中引入:
import "antd/lib/style/index.css";
引入后终于大功告成!
从图上我们能看到,dom 结构中按需引入了 basestyle、layout、menu 和我们自定义的样式
做个总结的话,vite 结合 antd 我们其实只做了下面几件事儿:
- 引入 less 依赖
- 配置
@/的 alias - 按需引入 antd 样式
下一步:
- CDN 访问图片
- 使用 hooks/redux/dva
参考链接
- vite文档目前貌似只有英文版(也可能是我没找到哈),可能因为目前代码处于 2.0 beta 阶段不稳定的缘故,英文文档更新的频率会比较高,所以暂时没有其他语言的翻译版本。
一更:最近发现按需加载还是有问题:如果只 import Table 这个组件,但是 Table 本身又依赖 baseStyle、button、empty、radio、checkbox、dropdown、spin、pagination 和tooltip 的样式,按照上面的代码,只会 import Table 本身的样式,其他样式就丢失了,虽然 baseStyle 手动 import 了,但是其他这么多样式都要 import 的话就很麻烦了,所以 baseStyle 手动 import 的方式还是不可取