Vite

118 阅读12分钟

为什么选择vite?

当我们开始构建越来越大型的应用的时候,需要处理的javascript代码量呈指数级别增长。我们开始遇到性能瓶颈,需要很长时间(甚至几分钟)才能启动开发服务器,即使使用HMR,文件修改后的效果也需要几分钟才能再浏览器中反映出来。

总结就是以下两个问题:

1,缓慢的服务器启动

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

2,缓慢的更新

在实践中在实践中我们发现,即使采用了HMR模式,其更新速度也会随着应用规模的增长而显著下降。

vite在启动的时候不需要打包,Vite是以原生ESM方式提供源码,这实际上让浏览器接管了打包程序的部分工作,这也意味着不需要分析模块的依赖,不需要编译,因此启动速度非常快,当浏览器请求某个模块的时候,再根据需要对模块内容进行编译,这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂,模块越多,vite的优势越明显,在Vite中,HMR是在原生ESM上执行的,因此HMR是非常快的。

Vite,Vite2.0宣布使用Esbuild预构建依赖,Esbuild则选择使用GO语言编写,在资源打包这种cpu密集场景下,Go更具性能优势。并且比以Javascript编写的打包预构建依赖快10-100倍。你可以理解为,Vite其实就是对esbuild的封装。

vite会不会直接把webpack干翻?

vite是基于es modules的, 侧重点不一样, webpack更多的关注兼容性, 而vite关注浏览器端的开发体验 vite的上手难度更低, webpack的配置是非常多的, loader, plugin

vite为什么比webpack快?

可以看一下webpack打包后的bundle.js文件: webpack打包文件解析

webpack打包后其实就是一个自执行文件,文件根据入口配置,构建完整的依赖树。

image.png

image.png

可以看出,webpack每次都是构建整个应用,而基于esm构建的方式,浏览器会根据请求,加载对应的es module,然后对对应模块进行构建显示。

ESM是javascript官方标准的标准化模块系统。 ESM标准规范了如何把文件解析为模块记录,如何实例化和如何运行模块。 ESM则使用称为实时绑定的方式,导出和导入的模块都指向相同的内存地址。 所以,当导出模块的值改变后,导入模块的值实时改变了。

Vite动态编译的方式,极大的缩减了编译时间,项目越复杂,vite的优势就越明显。 当需要打包到生产环境时,vite使用传统的rollup进行打包,因此,vite的主要优势还是在开发环境。

但是目前来看,webpack和vite并不是一个对立的关系。因为vite主要解决的还是开发体验的问题,就是速度快;而webpack它的主要工作还是打包,而且是各种类型文件的打包。这里还有一个兼容性的问题。

什么是构建工具,以及其他方案?

构建过程应该包括:预编译,语法检查,词法检查,依赖处理,文件合并,文件压缩,单元测试,版本管理等。

构建工具他让我们可以不用每次都关心我们的代码在浏览器如何运行, 我们只需要首次给构建工具提供一个配置文件(这个配置文件也不是必须的, 如果你不给他 他会有默认的帮你去处理), 有了这个集成的配置文件以后, 我们就可以在下次需要更新的时候调用一次对应的命令就好了, 如果我们再结合热更新, 我们就更加不需要管任何东西, 这就是构建工具去做的东西, 他让我们不用关心生产的代码也不用关心代码如何在浏览器运行, 只需要关心我们的开发怎么写的爽怎么写就好了

企业级项目里都可能会具备哪些功能

  1. typescript: 如果遇到ts文件我们需要使用tsc将typescript代码转换为js代码
  2. React/Vue: 安装react-compiler / vue-complier, 将我们写的jsx文件或者.vue文件转换为render函数
  3. less/sass/postcss/component-style: 我们又需要安装less-loader, sass-loader等一系列编译工具
  4. 语法降级: babel ---> 将es的新语法转换旧版浏览器可以接受的语法
  5. 体积优化: uglifyjs ---> 将我们的代码进行压缩变成体积更小性能更高的文件
  6. .....
之前我们写代码可能就是:
将App.tsx ---> tsc ---> App.jsx ---> React-complier ---> js文件,
现在有了构建工具之后:
我们写的代码一变化 ---> 有人帮我们自动去tsc, react-compiler, less, babel, 
uglifyjs全部挨个走一遍 ---> js

构建工具主要做以下工作:

  • 模块化支持:支持直接从node_modules里引入代码 + 多种模块化支持
  • 代码兼容: 比如babel语法降级,将Typescript编译成javascript,scss编译成css等。(不是构建工具做的, 构建工具将这些语法对应的处理工具集成进来自动化处理)
  • 文件优化,压缩javacript,css,html代码,压缩合并图片等。
  • 代码分割,提取多个页面的公共代码,提取首屏不需要执行部分的代码让其一步加载。
  • 模块合并,在采用模块化的项目里忽悠多个模块和文件,需要通过构建功能将模块分类合并成一个文件。
  • 优化开发体验:1,自动刷新,监听本地源代码的变化,自动重新构建,刷新浏览器,优化开发体验。(整个过程叫做热更新, hot replacement)2,开发服务器: 跨域的问题, 用react-cli create-react-element vue-cli 解决跨域的问题,
  • 代码校验,在代码被提交到仓库前需要校验是否符合规范,以及单元测试是否通过。
  • 自动发布,更新代码后,自动构建出线上发布代码并传输给发布系统。

前端常用的构建工具:

  • webpack,能够为前端构建提供较完整的解决方案,通过loader和plugin配置完成功能的补充。
  • Babel主要是将es6+的js语法转化为浏览器能够解析的es5语法,webpack中可以配置babel转译es6语法。
  • Rollup:是一个和webpack很类似但专注于es6的模块打包工具它的亮点在于,能针对es6源码进行tree shaking,在打包时静态分析,排除未使用的代码。
  • Parcel:极速0配置打包工具。
  • esbuild
  • grunt
  • gulp

ES module

Es6中引入了ESM,esm可以在编译时完成加载,可在编译期进行tree shaking.使用script标签引入esm文件,同时设置type=module,标识这个模块为顶级模块,浏览器将es,文件视为模块文件,识别模块的import语句并加载。。

Vite的依赖预构建

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

为什么要进行预构建 目的是为了兼容commjs和UMD,以提升性能,预构建,使用esbuild.

1,Commonjs和UMD兼容性:再开打阶段,Vite的开发服务器将所有代码视为原生ES模块。因为Vite必须先将CommonJs或UMD发布的依赖项转换为ESM.

当转换Commjs依赖时,Vite会执行智能导入分析,这样即使导出时动态分配的,,按名导入也会复合预期效果。

2,性能:Vite将会有许多内部模块的Esm依赖关系转化为单个模块,以提高后续页面加载性能。 一些包将他们的ES模块构建作为许多单独的文件相互导入。通过与构建lodash-es成为一个模块,我们只需要一个http请求。

并不是所有的模块都会被预构建,只有裸依赖才会执行预构建。

vite使用

1,配置不进行预构建的包

export default {
 optimizeDeps: {
   exclude: ["lodash-es"]
 }
}

当配置exclude的时候,不会对配置中的包进行与构建,此时,可以重新打开控制台进行查看请求,会发现load-es中依赖的其他包都会进行逐个请求。

2,vite配置文件的语法提示

  1. 如果你使用的是webstorm, 那你可以得到很好的语法补全
  2. 如果你使用是vscode或者其他的编辑器, 则需要做一些特殊处理
import { defineConfig } from "vite";
export default defineConfig({
 optimizeDeps: {
   exclude: 'lodash-es'
 }
})

3,不同的环境配置

过去我们使用webpack的时候, 我们要区分配置文件的一个环境

  • webpack.dev.config
  • webpack.prod.config
  • webpack.base.config
  • webpackmerge

现在我们依旧可以分开不同的配置文件,根据运行环境,加载不同的配置文件

import { defineConfig } from "vite";
import baseConfig from './vite.base.config';
import devConfig from './vite.dev.config';
import prodConfig from './vite.prod.config';

const config = {
   "build": () => {
     console.log("开发环境");
     return Object.assign({}, baseConfig, devConfig);
    },

    "serve": () => {
       console.log("生产环境");
      return Object.assign({}, baseConfig, prodConfig);
      }
}

export default defineConfig(({ command }) => {
   return config[command]();
})

command:build|serve,服务端运行配置文件的时候,可以根据command来选择加载不同的配置文件

4,环境变量的处理

vite内置了dot env来解析.env环境变量配置文件,会自动读取对应env中的变量注入到process变量下。

访问env变量分为服务端和客户端两种:

服务端

1,可以写多个.env配置文件,来配置不同环境的环境变量 .env下的环境变量是公共的。 
.env
.env.development
.env.production

2,配置加载:
export default defineConfig(({ command, mode }) => {
 const env = loadEnv(mode, process.cwd(), "");
 console.log(env)
 return config[command]();
})

配置之后:
当运行vite命令的时候,mode为development就会去加载.env.env.development下的环境变量
同理:
当运行vite build命令的时候,mode为production就会去加载.env.env.production下的环境变量

客户端

vite为了防止将隐私变量注入到import.meta.env中,我们需要在对应变量中加上vite_前缀,这个前缀是可以通过配置文件进行修改的

1,所有的环境变量加上VIT前缀,以使客户端能够访问到
VITE_APP_KEY = "UNIPUS";
VITE_APP_KEY_DEV = "UNIPUS";
VITE_APP_KEY_PROD = "UNIPUS";

2,在客户端,使用import.meta.env访问变量
console.log("import.mete.env:", import.meta.env)

5,vite加载css和less以及实现模块化

对于css文件,vite可以直接使用,less的处理也是只需要安装less即可,无需安装任何其他的loader就可以开箱即用。

indexA.module.css

.out {
  height: 200px;
  width: 200px;
  background - color: red;
}

js中使用

  import modulesA from './indexA.module.css'
  document.body.appendChild(div);
  div.className = modulesB.out;

less同理,运行之后查看html源文件,可以看到css代码已经被插入到对应的header中,并且原有的js文件已经被转化为对应的js模块,这主要是为了之后的热更新等。

处理过程如下:

  • vite在读取到main.js中引用到了Index.css

  • 直接去使用fs模块去读取index.css中文件内容

  • 直接创建一个style标签, 将index.css中文件内容直接copy进style标签里

  • 将style标签插入到index.html的head中

  • 将该css文件中的内容直接替换为js脚本(方便热更新或者css模块化), 同时设置Content-Type为js 从而让浏览器以JS脚本的形式来执行该css后缀的文件

    6,sourcemap的配置

    文件之间的索引:

假设我们的代码被压缩或者被编译过了, 这个时候假设程序出错, 他将不会产生正确的错误位置信息 如果设置了sourceMap, 他就会有一个索引文件map

sourceMap解决的问题极其的微小, 但是他的实现过程非常的复杂

7,vite静态资源的处理

vite天生支持静态资源,例如svg,img,json(无需转换)

import json from './index.json';
import url_raw from './file.png?raw'
import url_url from './file.png?url'
console.log(json);
console.log(url_raw);
console.log(url_url);
const img = document.createElement("img")
img.src = url_url;
document.body.appendChild(img)

对于svg可以通过传raw参数的方式,从而可以改变svg的样式。

import svg from './index.svg?raw';
document.body.innerHTML = svg;
const svgEle = document.getElementsByTagName("svg")[0];
svgEle.onmouseenter = function () {
    this.style.fill = red;
}

8,vite配置别名,以及使用配置别名插件

使用配置:

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

使用插件:

//安装vite - aliases
import { ViteAliases } from './node_modules/vite-aliases'
plugins: [
   ViteAliases()
]

9,vite mock数据

1,1,安装vite-plugin-mock mockjs插件,并配置vite-plugin-mock插件

import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig(({ command }) => {
  return {
    plugins: [
     viteMockServe({
        mockPath: 'mock',
        localEnabled: command === 'serve',
     }),
    ],
  }
})

2,配置mock接口,以及使用mockjs创建数据

import mockjs from 'mockjs'
const data = mockjs.mock({
'data|100': [
 {
   name: '@cname',
   ename: '@name',
   'id|+1': 1,
 },
],
})

module.exports = [
{
 method: 'post',
 url: '/api/users',
 response: ({ body }) => {
   return {
     msg: '成功',
     status: 200,
     data: data,
   }
 },
},
]

3,fetch发请求

fetch('/api/users', {
method: 'post',
})
.then((res) => {
 console.log('res.data-----' + res)
 console.log(res)
})
.catch((err) => {
 console.log('res.err-----' + err)
})

10,vite和ts的结合(vite-plugin-checker插件)

vite他天生就对ts支持非常良好, 因为vite在开发时态是基于esbuild, 而esbuild是天生支持对ts文件的转换的

vite只对ts文件进行转换, 并不会对ts文件进行类型检查

一般来说ts的错误并不会直接出现在控制台的,那么如何处理ts的错误,使之能够出现在控制台和界面上,阻止程序的进一步运行呢?这就需要vite-plugin-checker插件的处理

//安装插件 - yarn add vite - plugin - checker - D 也需要安装typescript
import checker from 'vite-plugin-checker'
export default defineConfig(({ command }) => {
return {
  plugins: [
    checker({
      typescript: true,
    }),
  ],
}
})

//还需要配置tsconfig文件:
{
"compilerOptions": {
  "skipLibCheck": true //是否跳过node_modules目录的检查
}
}

配置完成之后,如果ts文件有错误,就会在后台和前端页面输出错误,但是此时打包还是会成功,如果希望出现ts错误,就不再去打包,则需要以下的配置:

在package.json配置脚本如下:

"build": "tsc --noEmit && vite build"