Vite入门介绍

474 阅读16分钟

一个前端构建打包工具

Vite的更新比较频繁,要想能够使用的得心应手,首先要对Vite的版本迭代时间点,以及每一代相对于之前的变更,要有认知

Vite发布历史

下载.jpg

  1. 2020.4.21 vite 0.1随vue3.0一同发布,1.0并未落地, 作者意识到vite可以与框架无关,作为一种更通用的cli

  2. 2021.2.16,vite 2.0发布

    • vite和框架解耦
    • 选择rollup兼容插件api
    • 采用全新SSR运行时
    • 基于esbuild依赖预打包方案
  3. 2022.7.13 , vite 3.0发布

  4. 2022.12.9, vite4发布, 2023.3.16, vite 4.2发布

  5. 2023.11.16,vite 5.0发布

Vite特点

  • 配置使用比较简单,不用深入接触webpack那么多的配置
  • 默认集成了dev-server
  • 启动项目很快,编译很快
  • 便于扩展,兼容rollup的插件

类似产品:

  • Snowpack
  • WMR
  • @web/dev-server

区别: High Level API

  • webpack,rollup关注于如何构建细节功能实现,如文件加载如何编译加载
  • vite没有自己的编译能力
  • 源于esbuild, rollup, 只是集成了rollup功能, 启动了dev-server
  • 完全基于ESM加载方式的开发时
  • 减少了很多配置量,如核心dev server,各种loader,内置build

不同于webpack从入口分析模块打包生成chunk, vite是从浏览器向dev server发起请求,他不关心文件后缀名,如script type="module" src="/src/main.jsx"

优势

不像webpack配置繁杂, cra需要eject, vue/cli通过configureWebpack和chainWebpack,vite有自身的插件系统,而且继承了rollup

常用命令

# 支持多种方式创建vite+vue应用,用vite创建的vue应用没有直接创建vue应用丰富
# 初始化vite应用,会安装@vitejs/create-app
npm init @vitejs/app # 如果报错了可以换一种方式
npm create vue@latest # 能选择更多选项(我的h5商城就是用这个创建,已经废弃了@vue/lick, 是vite)
npm create vite@latest vue-app -- --tempalte vue

# 启动服务
vite
# 预览
vite preview
# 打包
vite build

需要注意,vite2版本使用npm i 会出现esbuild的报错问题, 用yarn

插件注意事项:通过安装vscode插件vue-official可以避免写shims-vue.d.ts,注意vue3项目要禁用掉vue2的插件vetur,volar插件已废弃

两个重要插件

@vitejs/plugin-vue:加载vue文件入口

@vitejs/plugin-vue-jsx:加载jsx文件入口

import { defineComponent } from "vue"

export default defineComponent({
  setup() {
    return () => {
      return <div>hello vue3 jsx</div>
    }
  }
}
// 然后mai.ts引入app.jsx  

Vite创建vue2

npm init @vitejs/app

此方法没有vue2的预设项,借助underfin/vite-plugin-vue2

Vite创建React项目 Vite对React的支持比Vue2还优先,借助插件@vitejs/plugin-react-refresh, 内部是FastRefresh,和react-hot-loader有关

FastRefresh的优势

  • 解决了很多rhl无法解决的问题
  • 速度很快
  • 支持局部更新
yarn create @vitejs/app
import { defineConifg } from "vite"
import reactRefresh from "@vitejs/plugin-react-refresh"

export default defineConifg({
  plugins: [reactRefresh()]
})

nodejs版本过高可能会运行失败

Vite+CSS

maxresdefault.jpg

  • Vite默认支持css,不需要做额外配置,如在jsx中直接引入css即可
  • 推荐使用css变量
  • 已经集成了postcss
import './styles/index.css

声明postcss.config.js文件

module.exports = {
  plugins: [require("@postcss-plugins/console")]
}

然后在样式表中就能够使用这个插件特性

:root {
  --main-bg-color: red;
}

:root {
  @console.error hello root
  color: var(--main-bg-color);
}

@import alias / resolve alias

export default defineConfig({
  resolve: {
    alias: {
      '@styles''/src/styles' // src前面的/不能省略,项目根目录
    }
  }
})

css中的@import url也可以使用该alias

@import url("@styles/other.css")

css modules

默认提供支持

CSS pre-processors

天然支持,无需配置,只需要下载对应的包就好

yarn add less

typescript继承

下载 (1).jpg

Vite天然支持typescript,无需额外配置,开发环境中Vite使用esbuild,只编译,不校验,esbuild本身也是支持ts语法,如果想要进行ts校验

只能手动tsc --noEmit

改动下build命令, 如何集成eslint+prettier参考后文

"scripts": {
  "build": "tsc --noEmit && vite build"
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    // 必配
    "module": "esnext",
    // 通过node方式解析模块
    "moduleResolution": "node",
    "strict": true,
    // ts解析jsx只会遵循react语法规范,不会使用插件
    // 对于vue来说,和react jsx规范不符,必须使用vue jsx插件
    "jsx": "preserve",
    // 可以直接调试ts代码
    "sourceMap": true,
    // 是否可以直接import json
    "resolveJsonModule": true,
    // 避免import * as react from "react"
    "esModuleInterop": true,
    // 包括哪些类型库
    "lib": ["esnext", "dom"]
  },
  // 必填,否则ts不知道校验哪里
  "include": ["src/**/*.ts","src/**/*.d.ts","src/**/*.tsx"]
}

vue-tsc for SFC

TS是不会解析vue文件中的ts代码,需要借助插件vue-tsx

vue-tsc --noEmit -p tsconfig.*.json

ts的isolatedModules

因为vite提供的ts编译只是编译单文件语法, 而ts可以关联不同模块之间的信息

设置了isolatedModules: true以后, 有以下代码情形会ts抛错

  • exports of non-value idenfifiers

当在一个ts文件中引入了另一个ts文件,如import {a}, export {a}, 使用起来会有问题

  • references to const enum members
import { A } from "./types"

// 会被直接干掉,对于vite的es buil提供的ts编译器不识别这个语法
decalre const enum Num {
  First = 0,
  Second = 1
}

export const a: A = {
  name"jason",
  ageNum.first
}
  • non-module files

模块中必须import或export {}

client types

vite提供了一些内置对象,如import.meta.url。此时没有对应的类型声明

设置tsconfig.compilerOptions.types = ["vite/client"]

设置了这个types,以下内容的代码会的得到vscode的智能提示

  • asset imports,没有client types无法引入png文件,会返回string类型
  • env
  • HMR API

import.meta.hot

其他特性

导入文件可以省略js等后缀名

静态文件处理

images.jpg

图片资源等静态资源

types:import x from '此处格式不同表现会不同'

  • url
import { a } from 'test'

此时a是一个变量,如果改成


import test from 'test?url'

test是路径/src/test.js

  • raw

返回文件内容

import test from 'test?raw'

返回文件内容字符串

  • worker/worker inline

针对web work,vite里使用web worker很简单

导入json:

跟webpack中使用方式一致

导入web assembly: 借助assemblyscript.org 提供的asc命令可以将代码转换成assembly代码,wasm二进制文件

在vite中导入wasm, 返回一个promise

// export function fib ...

import init from './fib.wasm'

init().then((m) => {
  m.fib(10)
})

Vite集成eslint和prettier

下载 (1).png

npm init @eslint/config

或者

yarn add eslint-config-stardard eslint-plugin-import eslint-plugin-promise eslint-plugin-node
  • eslint-config-stardard

规范代码风格,stardardjs的标准,主要是一些基础规则和避免常见错误

  • eslint-plugin-import

import导入的模块格式进行校验,如模块是否存在,路径大小写是否正确,是否使用正确别名等

  • eslint-plugin-promise

为promise的使用提供最佳实践,检查不必要的错误

  • eslint-plugin-node

在Node.js运行时中的规则校验,如模块循环引用,文件路径是否存在,错误捕获,全局对象是否安全等

module.exports = {
  extends: ["standard"],
  rules: {}
}

此外,还可以组合extends和plugins使用

  • react可以使用react-app的预设规则即presets,vue3可以使用plugin:vue/vue3-essential的预设
  • 对于typescript, 使用plugin:@typescript-eslint的预设
  • plugins当中可以使用vue, @typescript-esilnt, prettier等插件, 甚至你可以指定eslint 特定的parser,如@typescript-eslint/parser, vue-eslint-parser等包,可以提供更准确的ts语法解析,这样在代码中可以使用一些新特性,装饰器,类型注解等, 这些不在本文讨论范围内哈

勾选上vscoce的format on save, 设置default formatter为prettier 则保存自动格式化

"scripts": {
  "lint": "eslint --ext js src/"
}

针对eslint和prettier冲突问题, 如eslint报警告函数名后面要括号, 但是prettier不遵循这个规则,此时保存代码仍然eslint提示,可以通过修改eslint规则或者引入eslint-config-prettier

集成husky

yarn add husky -D
npx husky install
npx husky add .husky/pre-commit "npm run lint"

环境变量

images (1).jpg

内置环境变量import.meta.env挂载了如下属性

  • MODE
  • BASE_URL
  • PROD: 是否正式环境
  • DEV
  • SSR: 是否服务端渲染环境

production环境是没有import.meta.env这个对象

自定义env

项目根目录下新建.env, 需要使用VITE_开头,包括不限于:

.env.production

.env.development

.env.development.local

如果要设置其他自定义环境, 如.env.test,则启动脚本需改为

vite --mode test

在代码中引入自定义的环境变量, 如VITE_TITLE=XXX, 没有类型提示, 需要:

在src/vite-env.d.ts中

/// <reference types="vite/client" />

interface ImportMetaEnv {
  VITE_TITLE: string
}

以上是vite中一些开箱即用的功能,以下是一些更高级的功能

Vite中的热更新

vite cli生成的项目默认是开启热更新的,并且针对不同框架来实现的,如在vue3应用中是通过插件vueJsx实现

import { defineConfig } from "vite"
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'


export default defineConfig({
    plugins: [vue(), vueJsx()]
  })
})

而对于vite cli生成的普通项目来说, 要手动实现HMR

export function render() {
  document.querySelector("#app").innerHtml = `
    <h1>hello vite</h1>                       
  `
}
render()

if (import.meta.hot) {
  // 代表这个文件会接受自己的热更新
  import.meta.hot.accept((newModule) => {
    newModule.render() // 由于闭包,不能直接render()
  })
}

原理就是当修改main.js代码时,浏览器会重新请求main.js?import来替换老的js

glob import批量导入

来自于fast-glob库

const modules = import.meta.glob('./glob/*'// 会经过vite编译
// 或者globalEager

应用场景:多语言支持的json文件批量导入

Vite中的预编译优化

对于node_modules中的库会在首次启动之前,编译并缓存下来

CommonJs to ESM

如node_moduels/react/index.js中是采用commonjs管理,生成的.vite/react.js中,会做一些转化

import { defineConifg } from "vite"
import reactRefresh from "@vitejs/plugin-react-refresh"

export default defineConifg({
  plugins: [reactRefresh()],
  // 指定哪些项目需要经过/不经过预编译
  optimizeDeps: {
    // includes: [], vite会扫描,有些场景会扫描失败, 如插件产生的
    exclude: []
  }
})

零散文件打包到一起

比如引入了lodash-es, 如果配置忽略lodash-es,由于lodash模块中分成了很多包,导致浏览器在请求lodash时,会有很多的网络请求

观察网络请求会发现对于main.js其实设置了cache-control: no-cache。对于node_module中的包,时开启了强缓存

服务端集成/非nodejs服务

场景:有服务端模板引擎渲染的地方,html是服务返回的,如何把script插入html中,如有以下一段pug代码

html
  head
    title = title
  body
    div(id="app")
    script(src type="module" src="http://localhost:3000/@vite/client")
    script(src type="module" src="http://localhost:3000/src/main.js")

注意应用中图片路径会出问题,将前端应用放在后端目录下面即可

Node.js集成Vite开发时SSR

以react项目为例

项目根目录新建server.js

const exporess = require('express')
const app = express()

const { createServer: createViteServer } from 'vite'

createViteServer({
  // 非常重要,相当于启动vite的dev server
  server: {
    middlewareMode'html' // 另一个值是ssr
  }
}).then((vite) => {
  app.use(vite.middlewares)

  app.listen(4000)
})

于ssr mode, 读取html并返回到浏览器, 由于html不是vite server返回的,只有js,没有渲染过的内容,页面无法正常工作

const exporess = require('express')
const app = express()

const { createServer: createViteServer } from 'vite'

createViteServer({
  // 非常重要,相当于启动vite的dev server
  server: {
    middlewareMode'ssr'
  }
}).then((vite) => {
  app.use(vite.middlewares)
  // 注册路由
  app.get('*'async (req, res) => {
    // 1.fs读取index.html模板
    let temlate = ***
    const { render } = await vite.ssrLoadModule('src/server-entry.jsx')
    const html = await render(req.url)
    const res = template.replace('div id="root"中间的注释', html)

    res.send(res)
  })

  app.listen(4000)
})

新建server-entry.jsx

import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
import App from './App'

export function render(url, context) {
  return ReactDOMServer.renderToString(
    <StaticRouter location={url} context={context}>
      <App />
    </StaticRouter>
  )
}

经过了以上步骤还不够,HMR会失效并且报错,你需要

  // 注册路由
  app.get('*'async (req, res) => {
    // 1.fs读取html模板
    let temlate = ***
      // 这里
    template = await vite.transformIndexHtml(req.url, tempalte)
    const { render } = await vite.ssrLoadModule('src/server-entry.jsx')
    const html = await render(req.url)
    const res = template.replace('div id="root"中间的注释', html)

    res.send(res)
  })

HMR正常工作

Node.js集成Vite正式环境SSR

"scripts": {
  "build:client": "vite build --outDir dist/client",
  "build:server": "vite build --outeDir dist/server --ssr src/server-entry.jsx"
}

改动点就是读取的html是打包client目录中的html

const fs = require('fs')

const template = fs.readFileSync('dist/client/index.html''utf-8')

app.get('*', async (req, res) => {
  const render = require('./dist/server/server-entry.js').render
  const context = {}
  const html = await render(req.url, context)

  const res = template.replace('div id="root"中间的注释', html)

  res.send(res)
})

然而这会导致一个问题:请求/assets/vendors.js路径错误,没有做资源映射

app.use(express.static('dist/client'))

最后根据环境变量整合开发环境和生产环境逻辑

Vite其他配置项

以下配置只是简单列举了一下,具体的参数用法参照官网即可,可以跳到本文尾巴查看vite的变更历史细节: cn.vitejs.dev/config/

共享配置项

  • root 项目根目录位置,默认process.cwd()
  • base 类似webpack中的pubicPath
  • mode
  • define 类似于rollup的definePlugin,定义全局常量替换方式

export default defineConfig({ define: { APP_VERSION: JSON.stringify('v1.0.0'), API_URL: 'window.__backend_api_url', }, })

  • publicDir 静态资源存放目录,默认”public“
  • cacheDir 存储缓存文件的目录,默认node_modules/.vite
  • resolve.dedupe 程序中安装了两个不同版本同分依赖, 指定引用版本
  • resolve.conditions
  • resolve.mainFields package.json中的入口文件名,即requir引入的
  • resolve.extensions 要省略的扩展名
  • css.modules, css.postcss, css.preprocessorOptions
  • json.namedExports 关掉后就不能从json中直接导入某个变量
  • json.stringify 大json文件时开启这个性能会好
  • esbuild
  • assetsInclude 告诉是一个静态文件,后续放到assets中可以引入
  • logLevel 控制台日志级别,默认info
  • clearScreen 是否清屏
  • envDir 默认根目录

开发服务器选项

  • server 构建选项
  • build.target 构建的浏览器兼容目标,默认modules, 另一个选项esnext
  • build.polyfillDynamicImport
  • build.ourDir 输出路径
  • build.assetDir 静态资源路径
  • build.assetsInline_limit 内联引用资源大小
  • build.cssCodeSplit 启用、禁用css代码拆分,默认true
  • build.sourcemap
  • build.rollupOptions
  • build.commonjsOptions
  • build.lib 是否构建为库

lib.formats: 'es' | 'cjs' | 'umd' | 'iife', 默认es + umd

lib: {
  // Could also be a dictionary or array of multiple entry points
  entry: resolve(__dirname, 'lib/main.js'),
  name: 'MyLib',
  // the proper extensions will be added
  fileName: 'my-lib',
}
然后库下面的package.json中推荐配置

{
  "name""my-lib",
  "type""module",
  "files": ["dist"],
  "main""./dist/my-lib.umd.cjs",
  "module""./dist/my-lib.js",
  "exports": {
    ".": {
      "import""./dist/my-lib.js",
      "require""./dist/my-lib.umd.cjs"
    }
  }
}
  • build.manifest
  • build.minify boolean | 'terser' | 'esbuild'
  • build.terserOptions 传递给 Terser 的更多 minify 选项。
  • build.write 禁用磁盘写入
  • build.emptyOutDir 构建之前是否清空outDir
  • build.brotliSize 压缩大小报告
  • build.chunkSizeWarningLimit chunk大小警告限制
  • build.watch 开发插件使用

依赖优化选项

  • optimizeDeps.entries 默认情况,vite会抓取index.html检测需要预构建的依赖项,如果指定build.rollupOptions.input, vite将会转而去抓取这些入口点,如果两者都不合你意,则使用此选项指定自定义条目
  • optimizeDeps.include
  • optimizeDeps.exclude 预构建中强制排除的依赖项
  • optimizeDeps.keepNames 设置为true,可在函数和类上保留name,以防被重命名

SSR选项

  • rollup
  • rollup的一些简单入门可参考我的这篇文章:

qdovo.com/2022/05/09/…

  • 开源类库优先选择
  • 以ESM标准为目标的构建工具
  • 开创了tree shaking

常用命令

rollup -i index.js # 输出结果到命令行, -i/--input可以指定多个,搭配--dir使用
rollup -i index.js --file dist.js--format umd/cjs/es/iife 指定模块化

# --watch 监听
# --plugin json 指定插件

rollup.config.js

export default {
  // 入口文件路径
  input'./index.js', // 如果定义多入口:input: {entry1: 'resolve(__dirname, 'index.html'),', entry2: ''}
  // external: ['react'], 同webpack类似
  // 输出配置, 相应的如果是多入口,output: {dir: 'dist', chunkFileNames: 'chunk格式[name].js', entryFileNames: '入口文件名[name].js'}
  output:{
    // 输出文件路径
    file: 'dist/bundle.js'
    // 输出格式
    format: 'iife', // 默认是es + umd
    // 代码最上面的说明注释
    banner: '',
    globals: {
      // // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
      vue: 'Vue'
    }
  },
  plugins: []

}

可以导出对象, 数组 output也有plugin,是打包后的操作,如代码压缩

output也可以设置为一个数组

常用插件

  • @rollup/plugin-alias
  • @rollup/plugin-babel
  • @rollup/plugin-replace
  • @rollup/plugin-typescript 依赖typescript, tslib
  • @rollup/plugin-eslint
  • @rollup/plugin-image
  • @rollup/plugin-strip
  • @rollup/plugin-wasm

esbuild

esbuild index.js
# --outfile dist.js
# --bundle,打包操作,不仅仅是编译
# --target=esnext
# --platform=node
# --format=cjs
# --watch
# --define:TEST=12 # 类似于replacement
# --loader:.png=dataurl
  • esbuild对es的支持是不完整的,const编译为es5报错
  • 支持jsx,也有treeshaking功能,只在bundle时tree shaking

Vite 3相对Vite2的变更

不支持Nodejs12、13、15。14.18+ / 16+

支持的浏览器版本

Chrome >=87

Firefox >=78

Safari >=13

Edge >=88

配置项更改

  • alias => resolve.alias
  • dedupe => resolve.dedupe
  • build.base => base
  • build.brotliSize => build.reportCompressedSize
  • build.cleanCssOptions: vite现在使用esbuild压缩css

dev server更改

  • 默认端口变成了5173
  • 现在的默认主机名是localhost而不是127.0.0.1

SSR 更改

  • 使用ESM构建SSR,所有依赖都外部化

一般变更

  • SSR和lib模式下的JS文件扩展名现在根据其格式和包类型为输出JS entries 和chunks使用有效的扩展名(JS, mjs或cjs)。
  • Terser现在是一个可选的依赖项。如果您正在使用build。Minify: 'terser',你需要安装它。

import.meta.glob的一些使用变更

web assembly的用法

-import init from 'example.wasm'
+import init from 'example.wasm?init'

-init().then((exports) => {
+init().then(({ exports }) => {
  exports.test()
})

https证书问题

vite 2中自动创建证书, vite3可以手动创建证书或者仍然可以采用自动生成

Vite 4相对于Vite 3的变更

  • 使用Rollup 3
  • Rollup 3基本与2兼容

主要变更

导入css为字符串

在Vite 3中,导入. CSS文件的默认导出可能会导致CSS的双重加载。

import cssString from './global.css'

这种双重加载可能会发生,因为将会发出一个。CSS文件,并且很可能CSS字符串也将被应用程序代码使用,例如,由框架运行时注入。从Vite 4开始,.css默认导出已被弃用。在这种情况下需要使用内联查询后缀修饰符,因为它不会发出导入的.css样式。

import stuff from './global.css?inline'

生产构建变更

Vite构建现在将始终为生产环境构建,而不管传递了 --mode。以前,将模式更改为生产模式以外的模式将导致开发构建。如果您仍然希望为开发构建,您可以在. ENV文件中设置NODE ENV=development。在.env.{mode}文件中

在这个变化中,如果已经定义了ENV,那么vite dev和vite build将不再覆盖process.env.NODE。因此,如果您在构建之前设置了process.env.NODE ENV = 'development',那么它也将为开发而构建。这在并行运行多个构建或开发服务器时提供了更多的控制。

环境变量

Vite现在使用dotenv 16和dotenv-expand 9(以前使用dotenv 14和dotenv-expand 5)。如果您有一个包含#'的值,则需要用引号将它们括起来。

Vite 5相对于Vite 4的变更

Node.js 支持

不再支持Node.js 14/16/17/19, 需要Node.js 18/20+

Rollup4 支持

  • Vite 5使用Rolleup 4
  • 导入断言(assertions 属性)已被重命名为导入属性(attributes 属性)。
  • 不再支持 Acorn 插件。
  • 对于 Vite 插件,this.resolve 的 skipSelf 选项现在默认为 true。
  • 对于 Vite 插件,this.parse 现在只支持 allowReturnOutsideFunction 选项。

废弃CommonJS Node API

vite.config.js 内容使用ESM 语法。或者确保package.json 有type: module选项,或者使用.mjs/.mts,如vite.config.mjs

重新设计 define 和 import.meta.env.* 的替换策略

在 Vite 4 中,define 和 import.meta.env.* 特性在开发和构建中使用的是不同的替换策略:

在开发时,这两个特性分别作为全局变量注入到 globalThis 和 import.meta 中。

在构建时,这两个特性都使用正则表达式进行静态替换。

这导致在尝试访问这些变量时,开发和构建存在一致性问题,有时甚至导致构建失败。例如:

// vite.config.js
export default defineConfig({
  define: {
    __APP_VERSION__JSON.stringify('1.0.0'),
  },
})
const data = { __APP_VERSION__ }
// 开发:{ __APP_VERSION__: "1.0.0" } ✅
// 构建:{ "1.0.0" } ❌

const docs = 'I like import.meta.env.MODE'
// 开发:"I like import.meta.env.MODE" ✅
// 构建:"I like "production"" ❌

Vite 5 通过在构建中使用 esbuild 来处理替换,使其与开发行为保持一致。

这个改动不应该影响大部分设置,因为已经在文档中说明了 define 的值应该遵循 esbuild 的语法: