vite 尝鲜

9,492 阅读3分钟

前段时间刷知乎,偶然间看到关注的尤大在 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 的做法如下:

  1. 首次启动,vite 分析项目 package.json 中的 dependencies 依赖,将其中使用 cjs 和 umd 模块的包转成 esm 模块

  2. 为了减少 http 请求数,提高页面加载性能,vite 将每个依赖单独打成一个 esm 模块(文件),例如项目的 package.json 的 dependencies 如下(有点 windows 和 webpack dll 的感觉)

    {
      "dependencies": {
        "react": "^17.0.0",
        "react-dom": "^17.0.0"
      }
    }
    

    打包后的文件为:react-dom.jsreact.js

    假如一个包有很多内部模块,例如:lodash-es有超过 600 个内部模块,经过 vite 处理后也只会生成 lodash-es.js 这一个文件,vite 会将 Pre-bundling 后的模块 cache 在当前项目 node_modules/.vite 文件夹下,Pre-bundling 花费的时间其实就是第一步和第二步花费的时间

  3. 客户端访问:通过实际的路由访问,浏览器加载 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 寻找依赖和打包的处理过程

  4. 二次启动:只有以下四种情况任意一种发生,才会重新执行 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.tsxsrc/pages/About/index.tsxsrc/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 的方式还是不可取