0到1 搭建属于自己的Vite 项目

290 阅读7分钟

环境准备

准备好你的开发必备工具, 推荐安装VSCodeChrome浏览器。

Node

检查是否安装 Node.js,通过一下命令检查版本:

node -v

若没有安装,可以到 官网 去安装 Node.js

包管理器

都2022了,要跟上社区速度。 推荐用 pnpm, 需要 node 版本在 14.x 以上

npm i -g pnpm

它的优点,可以在掘金查看相关文章~,我想必是接下来的面试点之一了。

由于国内网络环境问题,pnpm 的源地址指向的是国外,下载速度慢,建议切到国内镜像,命令如下:

pnpm config set registry https://registry.npmmirror.com/

项目初始化

在终端命令行中输入如下的命令:

pnpm create vite

pnpm 首先会自动下载 create-vite 这个第三方包,然后执行这个包中的项目初始化逻辑。

后续的交互流程梳理如下:

  1. 输入项目名称;
  2. 选择前端框架;
  3. 选择开发语言。

搭建完成后,命令三件套,就可以启动你的项目了。

// 进入项目目录
cd vite-project // vite-project 是你前面输入的项目名称
// 安装依赖
pnpm install
// 启动项目
pnpm run dev

输完 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

src 里的内容,根据你选择的框架动态生成,因为我选择了 react,所以 src 里边的是 react 相关的代码文件。

看到 vite.config.ts 里面也会根据你选择得框架,动态引入对应的 vite plugin

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

更改项目根入口位置

初始化的项目,根入口(即index.html)指向的是 根目录,如果需要把入口放到 src目录下,我们可以通过配置vite.config.tsroot来更改入口

// 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()]
})

认识命令

package.json 中,你可以看到 scripts 默认的三条命令

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

有些人会疑问,vite build命令执行之前要先执行tsc呢?

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

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

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

通过 pnpm run build打包项目

通过 pnpm run preview 命令预览一下打包产物的执行效果。

css工程化方案

直接上手写原生 css 也不是不行,但原生 css 开发的各种问题,让人及其头疼,例如以下这些:

  1. 开发体验欠佳。比如原生 CSS 不支持选择器的嵌套:
// 选择器只能平铺,不能嵌套
.container .header .nav .text {
  color: blue;
}
​
.container .header .nav .box {
  color: blue;
  border: 1px solid grey;
}
  1. 样式污染问题。如果出现同样的类名,很容易造成不同的样式互相覆盖和污染。
// a.css
.container {
  color: red;
}
​
// b.css
// 很有可能覆盖 a.css 的样式!
.container {
  color: blue;
}
  1. 浏览器兼容问题。为了兼容不同的浏览器,我们需要对一些属性(如transition)加上不同的浏览器前缀,比如 -webkit--moz--ms--o-,意味着开发者要针对同一个样式属性写很多的冗余代码。
  2. 打包后的代码体积问题。如果不用任何的 CSS 工程化方案,所有的 CSS 代码都将打包到产物中,即使有部分样式并没有在代码中使用,导致产物体积过大。

针对如上原生 CSS 的痛点,社区中诞生了不少解决方案,常见的有 5 类。

  1. CSS 预处理器:主流的包括Sass/ScssLessStylus。这些方案各自定义了一套语法,让 CSS 也能使用嵌套规则,甚至能像编程语言一样定义变量、写条件判断和循环语句,大大增强了样式语言的灵活性,解决原生 CSS 的开发体验问题
  2. CSS Modules:能将 CSS 类名处理成哈希值,这样就可以避免同名的情况下样式污染的问题。
  3. CSS 后处理器PostCSS,用来解析和处理 CSS 代码,可以实现的功能非常丰富,比如将 px 转换为 rem、根据目标浏览器情况自动加上类似于--moz---o-的属性前缀等等。
  4. CSS in JS 方案,主流的包括emotionstyled-components等等,顾名思义,这类方案可以实现直接在 JS 中写样式代码,基本包含CSS 预处理器CSS Modules 的各项优点,非常灵活,解决了开发体验和全局样式污染的问题。
  5. CSS 原子化框架,如Tailwind CSSWindi CSS,通过类名来指定样式,大大简化了样式写法,提高了样式开发的效率,主要解决了原生 CSS 开发体验的问题。

CSS 预处理器

Vite 本身对 CSS 各种预处理器语言(Sass/ScssLessStylus)做了内置支持。也就是说,即使你不经过任何的配置也可以直接使用各种 CSS 预处理器。我们以 Sass/Scss 为例,来具体感受一下 Vite 的零配置给我们带来的便利。

由于 Vite 底层会调用 CSS 预处理器的官方库进行编译,而 Vite 为了实现按需加载,并没有内置这些工具库,而是让用户根据需要安装。例如我们选择了Sass,安装命令如下:

pnpm i sass -D

这样我们就能在项目中使用 Sass 了,写一个 react 小demo:

// index.tsx
import './index.scss';
export function Header() {
  return <p className="header">Header<span>This is Header</span></p>
};
​
// index.scss
.header {
  color: red;
  span {
    color: yellow;
  }
}

自动引入方案

例如我们定义了一个全局变量文件 variable.scss

// variable.scss
$theme-color: red;

然后我们使用它

@import "../../variable";

.header {
  color: $theme-color;
}

但是我们每次要使用 $theme-color属性的时候我们都需要手动引入variable.scss文件,那有没有自动引入的方案呢?答案是肯定的,需要我们额外在 Vite 中自定义配置,配置如下:

// vite.config.ts
import { normalizePath } from 'vite';
// 如果类型报错,需要安装 @types/node: pnpm i @types/node -D
import path from 'path';

// 全局 scss 文件的路径
// 用 normalizePath 解决 window 下的路径问题
const variablePath = normalizePath(path.resolve('./src/variable.scss'));


export default defineConfig({
  // css 相关的配置
  css: {
    preprocessorOptions: {
      scss: {
        // additionalData 的内容会在每个 scss 文件的开头自动注入
        additionalData: `@import "${variablePath}";`
      }
    }
  }
})

现在你可以直接在文件中使用全局文件的变量:

.header {
  color: $theme-color;
}

同样的,你可以对 lessstylus进行一些能力的配置,如果有需要你可以去下面的官方文档中查阅更多的配置项:

CSS Modules

CSS Modules 在 Vite 也是一个开箱即用的能力,Vite 会对后缀带有.module的样式文件自动应用 CSS Modules。接下来我们通过一个简单的例子来使用这个功能。

我们将 index.scss更名为index.module.scss,然后稍微改动一下index.tsx的内容,如下:

// index.tsx
import styles from './index.module.scss';
export function Header() {
  return <p className={styles.header}>This is Header</p>
};

f12 查看样式,我们会发现 p 标签的类名已经被处理成哈希值的形式:

css-module

说明现在 CSS Modules 已经正式生效了!同样的,你也可以在配置文件中的css.modules选项来配置 CSS Modules 的功能,比如下面这个例子:

// vite.config.ts
export default {
  css: {
    modules: {
      // 一般我们可以通过 generateScopedName 属性来对生成的类名进行自定义
      // 其中,name 表示当前文件名,local 表示类名
      generateScopedName: "[name]__[local]___[hash:base64:5]"
    },
    preprocessorOptions: {
      // 省略预处理器配置
    }

  }
}

css-module_2

这是一个 CSS Modules 中很常见的配置,对开发时的调试非常有用。其它的一些配置项不太常用,大家可以去这个地址进行查阅。

PostCSS

一般你可以通过 postcss.config.js 来配置 postcss ,不过在 Vite 配置文件中已经提供了 PostCSS 的配置入口,我们可以直接在 Vite 配置文件中进行操作。

首先,我们来安装一个常用的 PostCSS 插件——autoprefixer:

pnpm i autoprefixer -D

这个插件主要用来自动为不同的目标浏览器添加样式前缀,解决的是浏览器兼容性的问题。然后在 Vite 中接入这个插件:

// vite.config.ts 增加如下的配置
import autoprefixer from 'autoprefixer';

export default {
  css: {
    // 进行 PostCSS 配置
    postcss: {
      plugins: [
        autoprefixer({
          // 指定目标浏览器
          overrideBrowserslist: ['Chrome > 40', 'ff > 31', 'ie 11']
        })
      ]
    }
  }
}

配置完成后,我们回到 Header 组件的样式文件中添加一个新的 CSS 属性:

.header {
  <!-- 前面的样式省略 -->
  text-decoration: dashed;
}

执行pnpm run build命令进行打包,可以看到样式自动补上了浏览器前缀,如:

.index-module__header___IdNfn {
  <!-- 前面的样式省略 -->
  -webkit-text-decoration: dashed;
  -moz-text-decoration: dashed;
  text-decoration: dashed;
}

由于有 CSS 代码的 AST (抽象语法树)解析能力,PostCSS 可以做的事情非常多,甚至能实现 CSS 预处理器语法和 CSS Modules,社区当中也有不少的 PostCSS 插件,除了刚刚提到的autoprefixer插件,常见的插件还包括:

  • postcss-pxtorem: 用来将 px 转换为 rem 单位,在适配移动端的场景下很常用。
  • postcss-preset-env: 通过它,你可以编写最新的 CSS 语法,不用担心兼容性问题。
  • cssnano: 主要用来压缩 CSS 代码,跟常规的代码压缩工具不一样,它能做得更加智能,比如提取一些公共样式进行复用、缩短一些常见的属性值等等。

关于 PostCSS 插件,这里还给大家推荐一个站点:www.postcss.parts/ ,你可以去里面探索更多的内容。

CSS In JS

社区中有两款主流的CSS In JS 方案: styled-componentsemotion

对于 CSS In JS 方案,在构建侧我们需要考虑选择器命名问题DCE(Dead Code Elimination 即无用代码删除)、代码压缩生成 SourceMap服务端渲染(SSR)等问题,而styled-componentsemotion已经提供了对应的 babel 插件来解决这些问题,我们在 Vite 中要做的就是集成这些 babel 插件。

具体来说,上述的两种主流 CSS in JS 方案在 Vite 中集成方式如下:

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      babel: {
        // 加入 babel 插件
        // 以下插件包都需要提前安装
        // 当然,通过这个配置你也可以添加其它的 Babel 插件
        plugins: [
          // 适配 styled-component
          "babel-plugin-styled-components"
          // 适配 emotion
          "@emotion/babel-plugin"
        ]
      },
      // 注意: 对于 emotion,需要单独加上这个配置
      // 通过 `@emotion/react` 包编译 emotion 中的特殊 jsx 语法
      jsxImportSource: "@emotion/react"
    })
  ]
})

CSS 原子化框架

在目前的社区当中,CSS 原子化框架主要包括Tailwind CSSWindi CSS。Windi CSS 作为前者的替换方案,实现了按需生成 CSS 类名的功能,开发环境下的 CSS 产物体积大大减少,速度上比Tailwind CSS v2快 20~100 倍!当然,Tailwind CSS 在 v3 版本也引入 JIT(即时编译) 的功能,解决了开发环境下 CSS 产物体积庞大的问题。接下来我们将这两个方案分别接入到 Vite 中,在实际的项目中你只需要使用其中一种就可以了。我个人比较喜欢 Windi CSS 本身的attributifyshortcuts等独有的特性,因此首先从 windicss 开始说起。

Windi CSS

安装 windicss 及对应的 Vite 插件:

pnpm i windicss vite-plugin-windicss -D

配置使用它:

// vite.config.ts
import windi from "vite-plugin-windicss";

export default {
  plugins: [
    // 省略其它插件
    windi()
  ]
}

接着要注意在src/main.tsx中引入一个必需的 import 语句:

// main.tsx
// 用来注入 Windi CSS 所需的样式,一定要加上!
import "virtual:windi.css";

这样我们就完成了 Windi CSS 在 Vite 中的接入,接下来我们在 Header 组件中来测试,组件代码修改如下:

// src/components/Header/index.tsx
import { devDependencies } from "../../../package.json";

export function Header() {
  return (
    <div className="p-20px text-center">
      <h1 className="font-bold text-2xl mb-2">
        vite version: {devDependencies.vite}
      </h1>
    </div>
  );
}

除了本身的原子化 CSS 能力,Windi CSS 还有一些非常好用的高级功能,在此我给大家推荐自己常用的两个能力: attributifyshortcuts

要开启这两个功能,我们需要在项目根目录新建windi.config.ts,配置如下:

import { defineConfig } from "vite-plugin-windicss";

export default defineConfig({
  // 开启 attributify
  attributify: true,
});

首先我们来看看attributify,翻译过来就是属性化,也就是说我们可以用 props 的方式去定义样式属性,如下所示:

<button 
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  Button
</button>

这样的开发方式不仅省去了繁琐的 className 内容,还加强了语义化,让代码更易维护,大大提升了开发体验。

不过使用attributify的时候需要注意类型问题,你需要添加types/shim.d.ts来增加类型声明,以防类型报错:

import { AttributifyAttributes } from 'windicss/types/jsx';
​
declare module 'react' {
  type HTMLAttributes<T> = AttributifyAttributes;
}

shortcuts 用来封装一系列的原子化能力,尤其是一些常见的类名集合,我们在 windi.config.ts来配置它:

//windi.config.ts
import { defineConfig } from "vite-plugin-windicss";
​
export default defineConfig({
  attributify: true,
  shortcuts: {
    "flex-c": "flex justify-center items-center",
  }
});

比如这里封装了flex-c的类名,接下来我们可以在业务代码直接使用这个类名:

<div className="flex-c"></div>
<!-- 等同于下面这段 -->
<div className="flex justify-center items-center"></div>

插件推荐

@vitejs/plugin-vue

用于编辑vue文件

@vitejs/plugin-vuegithub.com/vitejs/vite/tree/main/packages/plugin-vue#readmeimg

ref.value转$ref

plugins: [
    vue({
      refTransform: true // 开启ref转换
    })
  ]

忽略.vue后缀

相信很多人在Vue2开发时,导入文件都是忽略.vue后缀的。但在Vite里,忽略.vue后缀会引起报错。

import { defineConfig } from 'vite'

export default defineConfig({
  resolve: {
    extensions: ['.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  }
})

这里要注意,手动配置extensions要记得把其他类型的文件后缀也加上,因为其他类型如js等文件默认是可以忽略后缀导入的,不写上的话其他类型文件的导入就变成需要加后缀了。

官方不建议忽略.vue后缀,所以建议写上.vue。

plugin-vue-jsx

添加vue3对tsx和jsx的支持

plugin-vue-jsxgithub.com/vitejs/vite…

vite-plugin-svg-icons

统一引入svg文件资源,作为图标使用

vite-plugin-svg-iconsgithub.com/anncwb/vite…

vite-plugin-eslint

给vite加入运行时的eslint支持

vite-plugin-eslintgithub.com/gxmari007/v…

vite-plugin-mock

用于模拟后端的服务

vite-plugin-mockgithub.com/vbenjs/vite…

vite-plugin-compression

提供gzip压缩

vite-plugin-compressiongithub.com/vbenjs/vite…

vite-plugin-html

提供index.html的压缩以及在index.html访问变量的功能

vite-plugin-htmlgithub.com/vbenjs/vite…

rollup-plugin-visualizer

打包分析

rollup-plugin-visualizergithub.com/btd/rollup-…

vite-plugin-vue-setup-extend

解决vue3下script setup语法糖下 ,手动设置组件name不方便的问题。可能会导致vue组件debuger时,断点位置不正确问题,使用前请慎重 (直至0.4.0版本依旧有该问题)

vite-plugin-vue-setup-extendgithub.com/vbenjs/vite…

unplugin-vue-components

实现vue组件库的自动按需导入

unplugin-vue-componentsgithub.com/antfu/unplu…

unplugin-auto-import

实现vue函数的自动导入

unplugin-auto-importgithub.com/antfu/unplu…

vite-plugin-vue-images

自动导入图片

vite-plugin-vuedoc

组件库文档站示例

vueuse

托尼团队主导的 vuehook库