vite学习笔记

vite

1.vite是什么?

基于==ES-Module==的前端构建工具

2.为什么选择vite?

在浏览器支持 ES 模块之前,JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。

缓慢的服务器启动

当冷启动开发服务器时,基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。

缓慢的更新

在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。也就意味着,vite在启动的时候并不需要打包,也就意味着不用分析模块的依赖,也就是不用编译,因此,启动速度大大加快。按需编译。可以这样理解,vite就是对esbuild的进一步封装。

3.vite为什么比webpack要快?

这样要从两者的机制来看。

webpack

js应用程序静态模块==打包器==。它会递归地构建一个模块关系图,然后将这些模块打包成一个包或者多个包。

可以看到,它的运行过程是一个==串行==过程(由于js是单线程的),要读取项目中的所有模块,然后打包,最后运行。
vite学习笔记_第1张图片

基于 ESModule的打包程序

通过HTTP请求头来请求入口文件,模块中的import都是==动态引入==,即需要哪个,我最后就会引入哪个。

vite学习笔记_第2张图片

ESM称为实时绑定的规则,引入和导出的对象都指向同一个内存地址,也就是当值发生变化的时候,也会实时地发生变化。

但打包的过程,vite就没有webpack做的好。

  • vite侧重的是开发过程中的体验。
  • webpack解决的还是打包的问题。

所以vite也称为前端==构建==工具

4.什么是构建工具?

前面一直所说构建工具,这里就来详细谈一下。

【构建工具,主要做以下的工作】

代码转换:将TypeScript编译成JavaScript、将SCSS编译成CSS等。

文件优化:==压缩==JavaScript、CSS、HTML代码,压缩合并图片等。

代码分割:提取多个页面的公共代码,提取首屏**不需要执行部分的代码让其异步加载**。

模块合并:在采用模块化的项目里会有很多个模块和文件,需要==通过构建功能将模块分类合并成一个文件==。

自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。

代码校验:在代码被提交到仓库前需要校验代码==是否符合规范,以及单元测试是否通过==。

自动发布:更新代码后,自动构建出线上发布代码并传输给发布系统

5.使用vite构建vue项目

pnpm create vite@latest

与webpack的差别

vite学习笔记_第3张图片

index.html放在根目录:官网的解释是vite是服务器,index.html是项目入口文件

vite.config.js替换vue.config.js,作为vite项目依赖文件

vite学习笔记_第4张图片

@vitejs/plugin-vue提供支持vue3单文件组件

6.执行打包会报错

错误信息

Refused to apply style from ‘http://127.0.0.1:5500/assets/index-351bd726.css’ because its MIME type (‘text/html’) is not a supported stylesheet MIME type, and strict MIME checking is enabled.

引起此类报错的原因是==因为js、css等文件过多,需手动添加(./)获取当前文件路径,但js等文件过多手动添加路径==会引起冲突,所以直接在打包的配置文件中加上base:'./'

7.ES Module解析模块

与common.js不同的是,es module模块是在编译阶段,效率比common.js高。

ESM 的==运行机制是,JS 引擎在对脚本静态分析的时候,遇到模块加载命令import, 会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值(也就是在编译阶段只是做一个标志)。 因此,ESM 是动态引用,并且不会缓存值==,模块里面的变量绑定其所在的模块。

8.vite的依赖预构建

首次执行 vite 时,服务启动后会对 node_modules 模块和配置 optimizeDeps 的目标进行预构建!

为什么要预构建?

目的是为了兼容 CommonJS 和 UMD,以及提升性能。

兼容CommonJs UMD:因为vite的开发服务器将所有模块都视为esm,如果有CommonJs,需要将它转换成ESM.

性能:vite将有许多内置模块的依赖关系转换成单个模块,以便后续页面使用。一些模块将它们自己的文件相互导入,这样,当我们执行import的时候,执行的HTTP请求也会非常多。尽管服务器在处理这些请求时没有问题,但**大量的请求会在浏览器端造成网络堵塞**。导致页面的加载速度相当慢。通过预构建成为一个模块,我们就只需要一个HTTP请求了。

vite学习笔记_第5张图片

.vite/deps可以看预构建!!!

裸模块

一个项目中,只有==裸模块==才会构建!

何为裸模块?

  • 用名称去访问的模块是裸模块
  • 用路径去访问的不是裸模块

依赖预构建大致的流程是:

1、先收集打包的依赖,比如我在代码中引入了vue 那么匹配到vue后到node_modules中找到对应vue的esm模块js 2、然后把js都存储到一个对象中 (收集依赖的过程也用到了esbuild build方法,收集的过程是在一个esbuild插件中完成的)

3、得到一个所有依赖的对象后

4、再次用esbuild对这些依赖进行编译,

5、编译后的文件存储到.vite中 下次再使用依赖的时候直接从.vite中获取,不需要再次编译 预构建之后的产物代码,在 node_module 目录下的 .vite 文件夹 依赖预构建的产物会放在 它的deps 目录下

9.vite跨域配置

vite.config.js的配置:
server: {
 open: false,
 host: '127.0.0.1',
 port: 3456,
 proxy: {
 '^/api/': {
 target: 'https://www.bilibili.com/', // 后台服务器地址
 changeOrigin: true, /* 允许跨域 */
 rewrite: (path) => path.replace(/^\/api/, '')
 	}
 		}
 			}

10.vite开发环境,生产环境配置

开发环境:运行在本地的环境 npm run dev

生产环境:放到服务器上面的环境 npm run build

在项目根目录下(与package.json同级)新建配置文件 ----------

【1】 .env: 全局的,没有设置其他环境变量时,会加载这个文件里的内容, 比如所有版本都使用的是同一个接口地址时,就可以写在这一个文件里面就行

# .env
NODE_ENV = env
VITE_NAME='全局环境'
VITE_BASE_URL=''

【2】 .env.development: 开发环境下的配置文件,执行npm run dev命令,会自动加载.env.development文件 会覆盖.env这个文件里定义的环境变量

# .env.development
NODE_ENV = development
VITE_NAME='开发环境'
VITE_BASE_URL='/api'

【3】 .env.production: 生产环境下的配置文件,执行npm run build命令,会自动加载.env.production文件 会覆盖 .env这个文件里定义的环境变量

# .env.production
NODE_ENV = production
VITE_NAME='生产环境'
VITE_BASE_URL = 'http://xxxxxx/api'

".env.[name]"是可以自定义的,在package.json里面做对应的名称修改

根据Vite的约定规则,只有以“VITE_”开头的变量才会在客户端被捕获 捕获方式为:import.meta.env.{参数名},然后重新启动服务 执行 npm run dev 时候,vite自动去读取.env.development文件里面的配置 执行 npm run build 进行打包,vite自动将.env.production 的内容打包进去 --------------------

"scripts": {
 "dev": "vite --mode development",
 "build": "vite build --mode production",
 "start": "vite --mode production",
 "build:env": "vite build --mode development"
 

关于package.json 文件中的配置 dev 默认在本地开启测试环境的服务(mode=‘development’)

start 在本地开启正式环境服务 (mode=‘production’)

build 默认打包到正式环境(基础配置取.env.production 文件中内容)

build:env 默认打包到测试环境(基础配置取.env.development 文件中内容)

11.defineConfig和loadEnv

获取环境变量

loadEnv 接收==三个参数==

mode:模式

envDir:环境变量配置文件所在目录

prefix:接受的环境变量前缀,默认为 VITE_ 在vite中默认是VITE_,为 ‘’,则加载所有环境变量

defineConfig 传入一个方法,方法可以接收一个对象,

对象中两个参数:

command, mode command :

server(run dev)

build(run build)

export default defineConfig(({ mode, command }) => {
    const config = loadEnv(mode, './')
    console.log(config);
    return {
        server: {
            open: true,
            host: '127.0.0.1',
            port: 3456,
            proxy: {
                '^/api/': {
                    target: 'http://localhost:8080/', // 后台服务器地址
                    // target: config.VITE_TARGET,
                    changeOrigin: true, /* 允许跨域 */
                    rewrite: (path) => path.replace(/^\/api/, '')
                }
            }
        },
        base: './'
    }
})

/*
*{
  VITE_NAME: '开发环境',
  VITE_BASE_URL: '/api',
  VITE_USER_NODE_ENV: 'development'
}
/

然后就可以从config里面读取到配置文件的信息了。

12.如果我们有axios请求,打包后为啥会报错以及解决办法?

打包后dist目录中index.html直接 live server,运行不ok

因为==不是一个服务器==,根本没有跨域

再package.json的scripts中,

添加"preview":"vite preview --mode development"

因为刚才执行打包命令为: build:env 默认打包到测试环境(基础配置取.env.development 文件中内容)

所以预览也添加 --mode development 然后,执行 npm run preview,打包文件执行跨域o

13.vite使用css css module的模块化

vite处理原生css

Vite 默认就支持对 css 代码的处理

​ 可以通过.js文件中import引入css文件

​ 也可以link标签导入css文件

css modules 模块

CSS Module并不是css官方的标准,也不是浏览器的特性。而是通过一些构建工具(vite webpack)对它进行构建。对 CSS 类名和选择器的作用域进行限定的一种方式(类似命名空间)

命名规范为:css模块文件以.module.[css|less|scss]结尾

运行时的方案最典型的就是 BEM

它是通过 .block__element–modifier 这种==命名规范来实现的样式隔离, 不同的组件有不同的 blockName,只要按照这个规范来写 CSS,是能保证样式不冲突的==。 但是这种方案毕竟不是强制的,还是有样式冲突的隐患。

css隔离方案有两种:

​ 一类是==运行时的通过命名区分, 一类是编译时==的自动转换 CSS,添加上模块唯一标识。

​ 编译时的方案有两种,一种是 scoped,一种是 css modules

scopedvue-loader 支持的方案, 它是通过编译的方式在元素上添加了 data-xxx 的属性, 然后给 css 选择器加上[data-xxx] 的属性选择器的方式实现 css 的样式隔离

css-modules 在 vue、react 中都可以用, 它是**通过编译的方式修改选择器名字为全局唯一的方式来实现 css 的样式隔离**. CSS-Module引入的时候要以模块引入!!!

14.import模块

import.meta返回当前模块的信息,具体信息看看运行环境!

1、import.meta.url 返回当前模块的 URL 路径

2、import.meta.scriptElement 返回加载模块的那个

15.vite使用css变量

var()函数用于读取变量 还可以使用第二个参数,表示变量的默认值


定义变量:–变量名


全局变量

:root{
 --m-bg:#f00;
}
div{
 background:var(--m-bg)
}

16.vite路径别名

resolve: {
        alias: {
            "@csssrc": 'a/b/c'
        }
    }

17.CSS预处理器配置:

Vite 同时提供了对 .scss, .sass, .less, .styl 和 .stylus 文件的内置支持。 没有必要为它们安装特定的 Vite 插件,但必须安装相应的预处理器依赖

preprocessorOptions: {
 scss: 	{
 		additionalData: `@import "./src/assets/inx.scss";`
 	}}

npm install -g sass
# .scss and .sass
npm add -D sass
# .less
npm add -D less
# .styl and .stylus
npm add -D stylus

18.vite如何批量导入 js模块、json、图片资源

vite导入有两种方法:

1、Vite 支持 import.meta.glob

函数从文件系统导入多个模块 匹配到的文件默认是懒加载的,为动态导入通过遍历加 then 方法可拿到对应的模块文件详情信息 构建时,会分离为独立的 chunk 【使用 .then ,变成异步的了,会导致一些问题,此处不展开】

2、import.meta.globEager 直接引入所有的模块, 即静态 import 导入

==它们是 Vite 独有的功能==而不是一个 Web 或 ES 标准

导入批量js文件

const jsObj = import.meta.globEager("./assets/js/*")
console.log(jsObj);

使用

jsObj['./assets/js/1.js'].default()
jsObj['./assets/js/2.js'].default()
jsObj['./assets/js/3.js'].default()

导入批量图片

const imgObj = import.meta.globEager('./assets/img/*')

console.log(imgObj);
const keys = Object.keys(imgObj)
for (let p of keys) {
  images.value.push(imgObj[p].default)
}

导入图片

// 如果我们只是想获取脚本的url,不想导入脚本,可以通过在导入路径后添加 后缀
 //只是想获取脚本的url,不想导入脚本,添加?url后缀。
import jsUrl from './assets/js/a.js?url'
//import导入的图片会转换为一个路径
import imgsrc from './assets/vue.svg' 
 //?raw,以二进制的方式读取
import iconImg from './assets/images/icon.png?raw

19.vite插件的形式

Vite`插件是一个==拥有名称创建钩子==(build hook)或 生成钩子(output generate hook)的对象

export default {
 	name: 'my-vite-plugin',
 	resolveId(id) {},
 	load(id) {},
 	transform(code) {}
 }

如果插件需要==配置功能==,则是一个接受插件选项,返回插件函数的函数

eg:options是配置选项

export default function(options){
	name:'my-vite-plugin',
   	resolveId(id){},
    load(id){},
    transform(code){}
	}

20.vite插件的钩子

开发时,vite dev server会创建一个钩子容器,按照Roll up调用==创建钩子函数的规则==来请求各个钩子函数。

下面的钩子会在服务启动时调用一次(文件更新也不会调用)

- options:替换或操纵`rollup`选项
 - buildStart:开始创建

vite特有的钩子

- config: 修改Vite配置
 - configResolved:Vite配置确认
 - configureServer:用于配置dev server,可以进行中间件操作
 - transformIndexHtml:用于转换宿主页
 - handleHotUpdate:自定义HMR更新时调用

下面钩子每次有模块请求时都会被调用:(核心hook)

- resolveId:创建自定义确认函数,常用语定位第三方依赖(找到对应的文件)
 - load:创建自定义加载函数,可用于返回自定义的内容(加载文件源码)
 - transform:可用于转换已加载的模块内容(转变源码为需要的代码)


其余

强制插件排序:enforce
 pre:在 Vite 核心插件之前调用该插件
 默认:在 Vite 核心插件之后调用该插件
 post:在 Vite 构建插件之后调用该插件
 plugins: [{enforce: 'pre'}
按需应用插件:apply
 默认情况下插件在开发 (serve) 和生产 (build) 模式中都会调用。
 apply 属性指明它们仅在 'build''serve' 模式时调用:
 plugins: [{apply: 'build'}

前置插件的标准模板

export default function viteTransformCSSModulesPlugin() {
 const name = 'vite-plugin-transform-css-modules';
 return {
 enforce: 'pre',
 name,
 // raw,文件里面的代码;id,文件的绝对路径
 async transform(raw, id) { }
 }
 }

插件命名前缀格式

如果你的插件只适用于特定的框架:
 vite-plugin-vue- 前缀作为 Vue 插件
 vite-plugin-react- 前缀作为 React 插件
 vite-plugin-svelte- 前缀作为 Svelte 插件
Vite 专属的插件:
 Vite 插件应该有一个带 vite-plugin- 前缀、语义清晰的名称。
 在 package.json 中包含 vite-plugin 关键字。
 在插件文档增加一部分关于为什么本插件是一个 Vite 专属插件的详细说明
 (如,本插件使用了 Vite 特有的插件钩子)。

vite学习笔记_第6张图片

21.transform钩子

处理==图片== CSS VUE组件

transform(code, id):在每个传入模块请求时被调用,主要是用来转换单个模块;

code 待转换的代码,就是导入的文件内容
id 当前正在解析文件的绝对路径 或者 由 load 返回的值
export default function () {
    return {
        name: 'vite-plugin',
        transform(code, ids) {
            // console.log(typeof ids);
            if (ids.indexOf("1.png") > 0) {
                console.log(code);
                return `export default "/src/assets/3.png"`
            }
            // if (ids.indexof("1.png") > 0) {
            //     console.log(ids);
            //     console.log(code);
            // }
        }
    }
}

22.load钩子

浏览器发起请求以后,dev server 端会通过 middlewares 对请求做拦截,
然后对源文件做 resolve、load、transform 等操作,
然后再将转换以后的内容发送给浏览器。

resolveId 就是去找到对应的文件,输出本地的实际的路径,

load 输出是文件模块的代码字符串
 加载本地文件到内存中
 当然了你也可以在load里修改一下源码,
 然后再传入到 transform
 
transform 就是将源码转变成目标代码
 在这里进一步对源码进行操作
 转换完成的内容直接返回给浏览器
 它处理的是模块
 
 
【load 与 transform】:
 共同点:
 是它们都能修改内容;
 
 区别是:
 load,它处理的是文件
 transform,它转换的是模块
 
所以在load中,可以直接写,<template>之类的代码
但在 transform 中,就要通过h渲染函数

load(id):

(ids.indexof(“1.png”) > 0) {
// console.log(ids);
// console.log(code);
// }
}
}
}




## 22.load钩子

```js
浏览器发起请求以后,dev server 端会通过 middlewares 对请求做拦截,
然后对源文件做 resolve、load、transform 等操作,
然后再将转换以后的内容发送给浏览器。

resolveId 就是去找到对应的文件,输出本地的实际的路径,

load 输出是文件模块的代码字符串
 加载本地文件到内存中
 当然了你也可以在load里修改一下源码,
 然后再传入到 transform
 
transform 就是将源码转变成目标代码
 在这里进一步对源码进行操作
 转换完成的内容直接返回给浏览器
 它处理的是模块
 
 
【load 与 transform】:
 共同点:
 是它们都能修改内容;
 
 区别是:
 load,它处理的是文件
 transform,它转换的是模块
 
所以在load中,可以直接写,