Vite学习笔记

306 阅读10分钟

主要通过视频Vite世界指南(带你从0到1深入学习 vite)学习的vite,以下笔记大部分源于视频

vite和webpack比较

由于webpack支持多种模块化(commonjs 和 es6 module),所以一开始必须统一模块化代码,把所有依赖都读一遍再启动本地服务器 2023-08-11-12-00-45.png

但vite是先启动本地服务器,然后读到有使用依赖再去加载(可以理解为按需加载),从而加快了项目跑起来的速度 2023-08-11-12-01-22.png

vite的依赖预构建

依赖预构建的定义

依赖预构建是指vite找到对应依赖后调用esbuild将其他规范的代码转换成esmodule规范,然后放到当前目录下的node_modules/.vite/deps中

依赖预构建的好处

  1. 解决了第三方包会有不同导出模式的问题(CommonJS 或 AMD)
  2. 对路径上的处理直接使用.vite/deps,方便路径重写
  3. 解决网络多包传输性能问题(vite会把导入进来的东西统一集成在文件中,不用再次发送请求)

依赖预构建的设置

export default{
    optimizeDeps: {
        exclude: [] // 传入不想使用依赖预构建的包
    }
}

vite配置文件

语法提示

  1. 通过注释实现
/** @type {import('vite').UserConfig} */
export default {
  // ...
}
  1. 通过defineConfig实现
import { defineConfig } from 'vite'

export default defineConfig({
  // ...
})

根据不同环境实现配置

import { defineConfig } from 'vite'
import viteProdConfig from './vite.prod.config'
import viteBaseConfig from './vite.base.config'
import viteDevConfig from './vite.dev.config'

const envResolve = {
    "build": () => ({...viteBaseConfig, ...viteProdConfig}),
    "serve": () => ({...viteBaseConfig, ...viteDevConfig})
}

export default defineConfig(({command, mode, ssrBuild}) => {
  return envResolve[command]();
})

环境变量

vite中环境变量的原理: 利用dotenv插件,开启项目后读取.env文件,并解析文件中的环境变量注入到process对象上

在浏览器中获取环境变量

  1. 通过添加.env 或者.env.development或者.env.production文件设定环境变量
  2. 添加环境变量文件后,在对应的环境中通过import.meta.env即可获取到当前环境下的变量

注意,vite规定必须以VITE开头命名的才可以作为环境变量,但也可以通过以下方法修改

export default defineConfig({
  envPrefix: 'ENV'
})

在服务器中获取环境变量

export default defineConfig(({command, mode}) => {
    const env = loadEnv(mode, process.cwd(), '')
    console.log(env, 'process.env1') // 此时env就是添加了环境变量后的process.env
    return envResolve[command]()
})

注意,.env.[mode]文件中mode的命名应该与执行命令yarn dev --mode[mode]中的mode一致

vite简单工作原理

启动一个服务器,匹配请求地址找到对应的文件,通过fs模块读取文件内容然后把文件内容交给response.body

vite中css的处理

vite支持直接解析css, less, sass文件,不需要额外配置loader

  • vite处理css文件原理
  1. vite在读取到main.js中引用到了index.css
  2. 直接去使用fs模块去读取index.css中文件内容
  3. 直接创建一个style标签,将index.css中文件内容直接copy进style标签里
  4. 将style标签插入到index.html的head中
  5. 该css文件中的内容直接替为js脚本(方便热更新或者CSS机块化)同时设置Content-Type为js,从而让浏览器以js脚本的形式来执行该css后缀的文件

但是为了解决类名冲突问题,又引入了css模块化,只需要将css文件命名为componentA.module.css

  • vite处理module.css文件原理
  1. 识别到module.css ,表示需要开启css模块化
  2. 他会将你的所有类名进行一定规则的替换(例如将footer 换成 _footer_i22st_1)
  3. 同时创建一个映射对象{ footer:"footer_i22st_1" }
  4. 将替换过后的内容塞进style标签里然后放入到head标签中 (能够读到index.html的文件内容)
  5. 将componentA.module.css内容进行全部抹除,替换成JS脚本
  6. 将创建的映射对象在脚本中进行默认导出

对css modules进行配置

vite关于css modules的配置最终是交给postcss-modules处理,详情配置可以查阅:www.npmjs.com/package/pos…

export default defineConfig({
  css: {
    modules: {
      localsConvention: "camelCaseOnly", // 设置处理后的类名的展示形式(驼峰还是中划线)
      scopeBehaviour: "global", // 默认值是"local",表示开启css模块化,"global"表示关闭模块化
      generateScopedName: "[name]_[local]_[hash:5]", // 设置生成带有hash的类名的规范
      hashPrefix: "prefix", // 使得生成的hash更独特避免重复
      globalModulePaths: ["./index.module.css"] // 配置想要关闭css模块化的文件的路径
    }
  }
})

对css preprocessorOptions进行配置

在没有打包工具的时候less的实现原理是通过执行命令npx lessc [filename]来编译less文件,同时命令行可以带上具体的设置,参考lesscss.org/usage/#less… vite.config.js可以对预处理器进行配置,实现对预处理器的详细设置

export default defineConfig({
  css: {
    preprocessorOptions:{
      less:{
        math: "always", // 自动对表达式进行数学运算
        globalVars: { // 全局变量
          mainColor: "red"
        }
      }
    }
  }
})

对css postcss进行配置

postcss处理过程:

  1. 将代码进行编译生成原生css(less/ sass等预处理器也可以实现,目前已经停止对less/ sass等相关插件进行维护,建议直接使用less/ sass预处理器)
  2. 对高级css语法进行解析
  3. 前缀补全

在vite.config.js中对 postcss 进行配置,

export default defineConfig({
  css: {
    postcss:{
      plugins: [postcss-preset-env()]// postcss-preset-env可以实现很多功能,包括语法降级、自动补全等
    }
  }
})

postcss 插件列表详细查看:github.com/postcss/pos…

vite 对静态资源的处理

对png/jpg/mp4/svg文件的处理

Vite对js文件里引入的png文件都会转换成绝对路径,可以直接使用。

import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl

也可以配置引入的格式,如下

import imgUrl from './img.png?raw'

对json文件的处理

Vite已经内置了对json文件的解析,在js文件里导入json文件后vite会自动转化为对象,同时可以在导入的时候直接进行解构(有助于treeshaking)

// 使用vite打包工具支持以下方式导入
import {name} from './src/assets/json/index.json'

vite配置文件路径

对于频繁使用的文件路径可以在vite.config.js中配置

export default defineConfig({
  resolve: {
    alias:{
      '@': path.resolve(__dirname, './src'),
      '@assets': path.resolve(__dirname, './src/assets')
    }
  }
})

vite配置生产打包处理

打包后的文件通常会加入hash字符串,原因如下: 如果文件内容没有变化,那么打包后的文件名中的hash字符串不会改变,但凡文件内容发生变化,hash字符串就会发生变化。 文件名中使用hash字符串是为了避免前后打包生成的文件名重名导致浏览器使用缓存没有进行更新。 关于打包的配置如下

export default defineConfig({
  build: {
    rollupOptions: { // 配置rollup的构建策略,vite构建线上环境是交给rollup处理,配置参考https://cn.rollupjs.org/configuration-options/#output-assetfilenames
      output:{
        assetFileNames: "[hash:6].[name].[ext]" // 配置打包后静态资源文件名
      }
    },
    assetsInlineLimit: 4096, // 以base64格式打包阈值
    outDir: "buildDist", // 打包构建输出路径,默认为dist
    assetDir: "static" // 静态资源打包构建输出路径,默认为assets
  }
})

vite插件

vite社区插件列表:github.com/vitejs/awes… vite通过数组形式配置插件

export default defineConfig({
  plugins: []
})

vite-aliases

vite-aliases插件用于自动配置src路径为@,不再需要配置resolve.alias

export default defineConfig({
  plugins: [ViteAliases({
    prefix: '~' // 配置表示src目录的符号
  })]
})

vite-plugin-html

vite-plugin-html插件用于index.html的相关配置

export default defineConfig({
  plugins: [createHtmlPlugin ({
    inject: {
        data: {
          title: 'index', // 修改index.html的title,同时需要index.html文件中的<title>Document</title>改为<%= title %>
        }
  })]
})

vite-plugin-mock

配合mockjs使用,默认处理根目录下mock文件夹的内容

export default defineConfig({
  plugins: [viteMockServe ()]
})

vite-plugin-checker

Vite 仅执行 ts 文件的转译工作,并不执行任何类型检查,一般来说有 ts 类型报错不会在控制台显示,同时可以正常打包。使用 vite-plugin-checker 插件可以把 ts 错误显示在控制台上

export default defineConfig({
  plugins: [
    checker({
      typescript: true
    })
  ]
})

vite 独有的钩子

手写插件文件主要就是导出一个函数,函数返回一个config配置对象,可能会用到以下钩子

利用vite里的config钩子

如果插件的主要功能是对执行命令前config配置文件进行处理的话,使用config钩子。这个钩子接收原始用户配置和一个描述配置环境的变量,返回一个要修改的config对象,最后会被合并到vite.config.js的配置对象里

// myPlugins
module.exports = () => {
  return {
    config(config, env){
      // config: 目前配置对象
      // env: { mode: string, command: string } mode: production/ development  command: serve/ build
      return {
        // 这里返回想要修改的config对象的内容
      }
    }
  }
}

利用vite里的transformIndexHtml钩子

transformIndexHtml 是转换index.html的钩子,接收html, ctx 两个参数,返回内容作为新的html内容

module.export = () => {
  return{
    transformIndexHtml:{
      enforce: 'pre', // 该配置可以决定调用钩子的时刻,设置为pre可以在较早期执行当前钩子
      transform: (html, ctx) => {
      }
    }
  }
}

利用vite里的 configureServer 钩子

configureServer是用于配置开发服务器的钩子,接收一个server参数,可以通过下列方式自定义中间件

module.export = () => {
  return{
    configureServer(server){
      server.middlewares.use((req, res, next) => {
      // 自定义请求处理...
    })
    }
  }
}

通用钩子

上面的钩子是只有vite会触发,但在rollup里不会被触发。由于vite最后是借助rollup打包构建,所以以下钩子是rollup和vite通用钩子

  • options: 功能与config相同,只不过参数是rollup的配置config
  • buildStart: 功能与configResolved相同,在解析完vite配置之后调用

前端性能优化

  • 开发时构建速度优化:yarn dev/ yarn start 敲下后到呈现需要多久
    • webpack:cache-loader(如果两次构建源码没产生变化直接使用缓存)、thread-loader(开启多线程去构建)
    • vite:按需加载,不需要对这方面进行过多优化
  • 页面性能指标
    • 首屏渲染时间:fcp(first content paint 第一个元素渲染时长)
      • 懒加载
      • http优化:协商缓存 强制缓存
    • 页面中最大一个元素渲染时长:lcp(largest content paint)
  • js逻辑
    • 副作用的清除:如组件卸载时清除定时器
    • 防抖 节流
    • 卡浏览器帧率 浏览器渲染
  • css
    • 能继承就不要重复写
    • 避免过深的css层级嵌套
  • 构建优化
    • 优化体积:压缩,treeshaking,图片资源压缩,cdn加载,分包

使用vite进行分包

对于 node_modules 里在开发期间不会发生变化的资源分出去打包,利用浏览器文件名字不发生变化就不重新请求的缓存策略减少请求 node_modules 里的静态资源的次数

export default defineConfig({
  "build":{
    "rollupOptions": {
      "output":{
        "manualChunks": (id) =>{
          if(id.includes("node_modules")){
            return "vendor"
          }
        }
      }
    }
  }
})

使用vite进行gzip压缩

使用 vite-plugin-compression 插件对体积较大的文件进行gzip压缩减轻传输压力 压缩解压原理:服务端读取到gzip文件后会设置一个响应头,content-encoding: gzip,浏览器收到该结果会对文件进行解压(解压也需要时间,如果文件不是十分大不要进行压缩,适得其反)

export default defineConfig({
  plugins: [viteCompression()]
})

使用vite进行CDN分发

使用 vite-plugin-cdn-import 插件对体积较大的文件进行CDN分发加快传输速率。其原理是根据不同环境自动往head里注入script标签。

export default defineConfig({
  plugins: [viteCDNPlugin({
    modules:[
      {
        name: "lodash", 
        var: "_",
        path: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
      }
    ]
  })]
})

题外

使用path.resolve的原因

  1. node端在读文件时如果发现使用的是相对路径,会使用process.cwd()进行拼接。process.cwd获取当前node的执行目录,执行目录不一定是文件所在目录,所以不能直接使用相对路径
  2. 可以使用 __dirname + 绝对路径 生成一个相对路径,但是不同操作系统路径表示方法不一样,所以不能直接使用字符串拼接
  3. 使用path模块对__dirname和路径进行拼接,path本质上就是一个字符串处理模块,里面有很多路径字符串处理方法

node模块化简单原理

读取每个文件后把文件内容放在立即执行函数里达到隔离变量的目的,其中立即执行函数有五个参数,分别是exports, require, module, __filename 和 __dirname