一种新的、更快的web开发工具 官方文档
vite 简介
- Vite 是一个面向现代浏览器的更轻, 更快的 web 应用开发工具
- 它基于 ECMAScript 标准原生模块系统 (ES Modules) 实现
- 它的出现是为了解决 webpack 在开发阶段使用 webpack-dev-server 冷启动时间过长, 另外, webpack-hmr 热更新反应速度慢的问题(webpack项目越大越慢)
为什么选择vite
- 解决 webpack 在开发阶段使用 webpack-dev-server 冷启动时间过长, 另外, webpack-hmr 热更新反应速度慢的问题
- 使用,上手简单
- 便于扩展(虽然是新的工具,但是可直接使用rollup的生态,rollup已经相对成熟,并且在某些方面打包比webpack有优势)
为什么vite会那么快
-
底层实现上,vite是基于esbuild 预构建依赖的。
esbuild 使用 go 编写,并且比以 js 编写的打包器预构建依赖, 快 10 - 100 倍。
因为 js 跟 go 相比实在是太慢了,js 的一般操作都是毫秒计,go 则是纳秒。
-
和webpack相比,启动方式也是有区别的
webpack 原理图:
vite原理图:
- webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果
- 由于vite在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite的优势越明显
- 由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。vite充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。
- vite是按需加载,webpack是全部加载:在HMR(热更新)方面,当改动了一个模块后,vite仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。
- vite的优势在开发环境:当需要打包到生产环境时,vite使用传统的rollup(也可以自己手动安装webpack来)进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ES Module,因此在代码中(除了vite.config.js里面,这里是node的执行环境)不可以使用CommonJS
vite原理
Vite利用了浏览器native ES module imports特性,使用ES方式组织代码,浏览器自动请求需要的文件,并在服务端按需编译返回,完全跳过了打包过程。关键变化是index.html中的入口文件导入方式
这样main.js中就可以使用ES6 Module方式组织代码:
vite需要根据请求资源类型做不同解析工作,比如App.vue,返回给用户的内容如下(返回的是js代码,这里是经过sfc解析返回的):
手写简易vite实现:
手撸Vite,揭开Vite神秘面纱基于这篇文章自己尝试写了下简易版vite
vite基础应用
-
创建vite项目
使用NPM:
npm init vite使用YARN:
yarn create vite -
选择生成的框架
- vanilla
- vue
- react
- preact
- lit
- svelte
注意,这里选择的vue是指vue3.0,暂不支持直接生成vue2项目,但是有现成的模板可以下载
-
index.html与项目根目录在一个 Vite 项目中,
index.html在项目最外层而不是在public文件夹内。这是有意而为之的:在开发期间 Vite 是一个服务器,而index.html是该 Vite 项目的入口文件。 -
配置路径别名
resolve: { alias: { "@": "/src", }, }, -
vite中使用css的各种功能
@import内联和变基#Vite 通过
postcss-import预配置支持了 CSS@import内联,Vite 的路径别名也遵从 CSS@import。换句话说,所有 CSSurl()引用,即使导入的文件在不同的目录中,也总是自动变基,以确保正确性。@import url('/src/styles/test.scss');关于在项目中配置全局scss
css:{ preprocessorOptions: { scss: { // additionalData: `./src/styles/var.scss";`, additionalData: [ '@import "@/styles/var.scss";', ] } } } -
vite 中使用typescript
Vite 天然支持引入
.ts文件Vite 仅执行
.ts文件的转译工作,并 不 执行任何类型检查。并假设类型检查已经被你的 IDE 或构建过程接管了(你可以在构建脚本中运行tsc --noEmit或者安装vue-tsc然后运行vue-tsc --noEmit来对你的*.vue文件做类型检查)。yarn add vue-tsc在package.json
"build": "vue-tsc --noEmit && vite build", -
项目中使用tsx,jsx
安装插件
yarn add @vitejs/plugin-vue-jsx -Dvite.config.ts中引入即可import vueJsx from "@vitejs/plugin-vue-jsx"; export default defineConfig({ plugins: [vueJsx()] })
vite高级应用
-
热更新功能(如何实现热更新)
Vite 提供了一套原生 ESM 的 HMR API。 具有 HMR 功能的框架可以利用该 API 提供即时、准确的更新,而无需重新加载页面或清除应用程序状态。
注意,你不需要手动设置这些 —— 当你通过
create-vite创建应用程序时,所选模板已经为你预先配置了这些。Vite 通过特殊的
import.meta.hot对象暴露手动 HMR API。来实现热更新首先,请确保用一个条件语句守护所有 HMR API 的使用,这样代码就可以在生产环境中被 tree-shaking 优化:
if (import.meta.hot) { // HMR 代码 }hot.accept(cb)
要接收模块自身,应使用
import.meta.hot.accept,参数为接收已更新模块的回调函数:export const count = 1 if (import.meta.hot) { import.meta.hot.accept((newModule) => { console.log('updated: count is now ', newModule.count) }) }Vite 的 HMR 实际上并不替换最初导入的模块:如果 HMR 边界模块从某个依赖重新导出其导入,则它应负责更新这些重新导出的模块(这些导出必须使用
let)。此外,从边界模块向上的导入者将不会收到更新。示例
先生成一个纯净的vite项目(纯净的vite项目并不含有热更新功能)
修改main.js文件如下
import './style.css' export function rander() { document.querySelector('#app').innerHTML = ` <h1>Hello Vite!</h1> <a href="https://vitejs.dev/guide/features.html" target="_blank">sssss</a> ` } rander() // Vite 通过特殊的 import.meta.hot 对象暴露手动 HMR API。 if(import.meta.hot){ import.meta.hot.accept((newModule) => { newModule.rander() }) }打开浏览器观察
通过上面的了解,大概知道了热更新的逻辑与原理。
Vite实现热更新,主要是通过创建WebSocket建立浏览器与服务器建立通信,通过监听文件的改变像客户端发出消息,客户端对应不同的文件进行不同的操作的更新 -
glob-import 批量导入功能
require context 是 webpack 提供的特有的模块方法,并不是语言标准,所以在 vite 中不再能使用 require context。但如果完全改为开发者手动 import 模块,一来是对已有代码改动容易产生模块导入的遗漏;二来是放弃了这种「灵活」的机制,对后续的开发模式也会有一定改变。但好在 vite2.0 提供了 glob 模式的模块导入。该功能可以实现上述目标。当然,会需要做一定的代码改动:
webpack中批量引入:
// 举例,批量导入全局组件 const globJs = require.context('./globle/', true, /.js$/) console.log(globJs); globJs.keys().forEach(key => { console.log(key) console.log(globJs(key).default) const component = globJs(key).default app.component(component.name, component) }) vite中批量
使用glob
const globModules = import.meta.glob('./glob/*') // 参数可为正则表达式 例如 './glob/*-[0-9].ts' console.log(globModules); Object.entries(globModules).forEach(([k,v])=>{ console.log(k+':', v); // 使用导入模块的方法 v().then(m=>{ console.log(k+':'+ m.default); m.test() }) })使用globEager
const globModules = import.meta.globEager('./glob/*') // 参数可为正则表达式 例如 './glob/*-[0-9].ts' console.log(globModules); Object.entries(globModules).forEach(([k,v])=>{ console.log(k+':', v); v.test() })
vite插件开发
-
vite插件是什么
使用Vite插件可以扩展Vite能力,比如解析用户自定义的文件输入,在打包代码前转译代码,或者查找第三方模块。
插件调用顺序
- 别名处理Alias
- 用户插件设置
enforce: 'pre' - Vite核心插件
- 用户插件未设置
enforce - Vite构建插件
- 用户插件设置
enforce: 'post' - Vite构建后置插件(minify, manifest, reporting)
示例:新建test-plugin.js,写入测试代码
export default (enforce) => {
return {
name: 'test',
enforce,
buildStart() {
console.log('buildStart', enforce);
},
resolveId() {
console.log('resolveId', enforce);
}
}
}
在vite.config.js引入插件
import testPlugin from "./src/plugins/test-plugin";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
testPlugin('post'),
testPlugin(),
testPlugin('pre'),
]
})
执行结果

2. ##### vite插件的形式
`Vite`插件是一个**拥有名称**、**创建钩子**(build hook)或**生成钩子**(output generate hook)**的对象**。
```
export default {
name: 'my-vite-plugin',
resolveId(id) {},
load(id) {},
transform(code) {}
}
```
如何插件含有配置功能,他的形式应该是一个接受插件选项,**返回插件对象的函数**
```
// options为配置选项
export default function (options) {
return {
name: 'my-vite-plugin',
resolveId(id){},
load(id){},
transform(code){},
}
}
```
0. ##### 插件钩子(类似于vue生命周期)
开发时,`Vite dev server`创建一个插件容器按照`Rollup`调用创建钩子的规则请求各个钩子函数。
下面的钩子会在服务启动时调用一次(文件更新也不会调用)
- options:替换或操纵`rollup`选项
- buildStart:开始创建
vite特有的钩子
- config: 修改Vite配置
- configResolved:Vite配置确认
- configureServer:用于配置dev server,可以进行中间件操作
- transformIndexHtml:用于转换宿主页
- handleHotUpdate:自定义HMR更新时调用
下面钩子每次有模块请求时都会被调用:(核心hook)
- resolveId:创建自定义确认函数,常用语定位第三方依赖(找到对应的文件)
- load:创建自定义加载函数,可用于返回自定义的内容(加载文件源码)
- transform:可用于转换已加载的模块内容(转变源码为需要的代码)
下面钩子会在服务器关闭时调用一次:
- buildEnd:
- closeBundle
##### 范例:钩子调用顺序测试
```
export default function myExample () {
// 返回的是插件对象
return {
name: 'hooks-order',
// 初始化hooks,只走一次
options(opts) {
console.log('options');
},
buildStart() {
console.log('buildStart');
},
// vite特有钩子
config(config) {
console.log('config');
return {}
},
configResolved(resolvedCofnig) {
console.log('configResolved');
},
configureServer(server) {
console.log('configureServer');
// server.app.use((req, res, next) => {
// // custom handle request...
// })
},
transformIndexHtml(html) {
console.log('transformIndexHtml');
return html
// return html.replace(
// /<title>(.*?)</title>/,
// `<title>Title replaced!</title>`
// )
},
// 通用钩子
resolveId(source) {
console.log(resolveId)
if (source === 'virtual-module') {
console.log('resolvedId');
return source;
}
return null;
},
load(id) {
console.log('load');
if (id === 'virtual-module') {
return 'export default "This is virtual!"';
}
return null;
},
transform(code, id) {
console.log('transform');
if (id === 'virtual-module') {
}
return code
},
};
}
```
执行结果
```
config
configResolved
options
configureServer
buildStart
transformIndexHtml
load
load
transform
transform
```
实战部分敬请期待