自己整理的《Vite知识体系》笔记 | 青训营

329 阅读19分钟

Vite知识体系

一、浅谈架构工具

1. 为什么需要架构工具?

因为架构工具解决了前端开发具有以下的痛点:

  • 不同的模块化标准

    架构工具提供的解决方案:提供模块加载方案,兼容不同的模块规范。

  • 如何进行资源编译

    架构工具提供的解决方案:高级语法转换(less转CSS,ts转js)。

  • 产品质量(比如代码体积,代码性能,兼容性)

    架构工具提供的解决方案:产物压缩,无用代码删除,语法降级。

  • 开发效率

    架构工具提供的解决方案:热更新。

2. 什么是vite?为什么用vite?vite使用了什么进行搭建以及有什么特性?

  1. 定位:vite是新一代前端构建工具。

  2. 两大组成部分:No-bundle开发服务,源文件无需打包以及生产环境基于Rollup的Bundler

  3. 核心特征:高性能,dev服务器启动速度和热更新速度非常快!以及简单易用,开发者使用体验好。

当下架构工具普遍具有以下的问题:dev服务器启动速度慢,并且热更新速度慢。而vite解决了这个问题,下面是一个具体的例子展示了vite出色的打包速度:

QQ截图20230814174730.png

(1)vite与ESM

目前浏览器普遍对于原生ESM提供支持。Vite使用了ESM标准,最大限度地发挥了其优势。

ESM是ECMAScript Modules的缩写,是JavaScript的一种模块化标准,它允许我们在JavaScript文件中使用import和export语句来导入和导出变量、函数、类等。ESM可以让我们更好地组织和管理代码,避免命名冲突和全局污染,实现代码的复用和解耦。

要使用ESM,我们需要在script标签中添加type="module"属性,这样浏览器就会将该脚本视为一个ESM模块,并按照ESM的规则进行解析和执行。例如:

<script type="module" src="main.js"></script>

在ESM模块中,我们可以使用import语句来导入其他模块中导出的变量、函数、类等。例如:

// main.js
import { prop } from "./prop.js";
console.log(prop);

我们也可以使用export语句来导出当前模块中定义的变量、函数、类等。例如:

// prop.js
export const prop = "prop";

除了具名导入和导出外,我们还可以使用默认导入和导出,这样就不需要指定导入或导出的名称。例如:

// default.js
export default function() {
  console.log("default");
}
// main.js
import defaultFunc from "./default.js";
defaultFunc();

ESM还支持动态导入,即在运行时根据条件或逻辑来决定是否导入某个模块。这可以通过import()函数来实现,它返回一个Promise对象,表示模块的加载结果。例如:

// main.js
if (Math.random() > 0.5) {
  import("./moduleA.js").then(module => {
    module.funcA();
  });
} else {
  import("./moduleB.js").then(module => {
    module.funcB();
  });
}

基于ESM,我们就会有以下优势:

  1. 无需打包项目源代码。
  2. 天然的按需加载。
  3. 可以利用文件级的浏览器缓存。

(2)vite与Esbuild

vite深度利用了Esbuild,Esbuild 是一种新型的 JavaScript 打包工具,它的最大特色是速度快。它是由 Figma 前 CTO Evan Wallace 开发的,基于 Go 语言开发。它的核心目标是开创构建工具性能的新时代,同时创建一个易于使用的现代构建工具。主要特性包括极快的速度,无需缓存,支持 ES6 和 CommonJS 模块,支持对 ES6 模块进行 tree shaking,API 可同时用于 JavaScript 和 Go,兼容 TypeScript 和 JSX 语法,支持 Source maps 和 Minification,以及支持 plugins。

(3)vite地开箱即用能力

vite的开箱即用能力指的是vite提供了一些常见的前端开发功能或需求的内置支持或配置,使得开发者无需安装额外的插件或编写复杂的配置就可以直接使用。例如:

  • vite支持Vue、React、Svelte等主流框架的单文件组件(SFC)开发。
  • vite支持TypeScript、JSX、CSS预处理器(Less、Sass等)等高级语法,并自动转换为浏览器可执行的代码。
  • vite支持热模块替换(HMR),即在修改代码后无需刷新页面就可以看到更新后的效果。
  • vite支持静态资源处理,即可以将图片、字体等静态文件作为模块导入,并根据文件大小自动转换为Base64或文件引用。
  • vite支持环境变量和模式切换,即可以根据不同的开发环境或生产环境来设置不同的环境变量,并在代码中访问。
  • vite支持插件机制,即可以通过安装或编写插件来扩展vite的功能或定制化vite的行为。

二、如何使用vite?

要使用vite,我们首先需要安装Node.js和npm,这是前端开发的基本环境。然后,我们可以通过以下命令来全局安装vite:

npm install -g vite

安装完成后,我们可以使用vite命令来创建一个新的项目,这里我们以react为例:

vite create react-app

随后在其中选择为使用react进行开发。随后会在当前目录下创建一个名为react-app的文件夹,并在其中初始化一个基于react的项目。我们可以选择使用JavaScript或TypeScript作为开发语言,以及选择是否使用ESLint和Prettier等代码检查和格式化工具。

创建好项目后,我们可以进入项目目录,并安装依赖:

cd react-app
npm install

然后,我们就可以使用以下命令来启动开发服务器:

npm run dev

这样,我们就可以在浏览器中访问终端中给出的链接来查看我们的项目了。

vite创建的react项目的文件结构大体如下:

  • node_modules:存放项目依赖的模块。

  • public:存放静态资源,如图片、字体、图标等。

  • src:存放源代码,包括组件、样式、路由等。

    • App.jsx:根组件,定义了应用的主要结构和样式。
    • index.css:全局样式文件。
    • index.jsx:入口文件,渲染根组件到页面上。
    • logo.svg:react的logo图标。
  • .gitignore:定义了git忽略的文件或目录。

  • package.json:定义了项目的名称、版本、描述、依赖、脚本等信息。

  • vite.config.js:定义了vite的配置选项,如端口、代理、插件等。

在vite中进行多组件开发,我们可以遵循react的组件化思想,将应用拆分为多个可复用的组件,并在src目录下创建相应的components文件夹,在其中创建jsx或tsx文件。例如,我们可以创建一个Hello.jsx文件来定义一个简单的问候组件:

import React from "react";

export default function Hello(props) {
  return <h1>Hello, {props.name}!</h1>;
}

然后,在App.jsx中导入并使用该组件:

import React from "react";
import Hello from "./components/Hello";

export default function App() {
  return (
    <div className="App">
      <Hello name="vite" />
    </div>
  );
}

在vite中配置react的路由,我们可以使用react-router-dom这个库来实现路由管理。首先,我们需要安装该库:

npm install react-router-dom

然后,在src目录下创建一个routes.js文件来定义路由规则(这里以Home,About和Contact三个组件间的跳转为例):

import React from "react";
import { BrowserRouter, Switch, Route, Link } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Contact from "./Contact";

export default function Routes() {
  return (
    <BrowserRouter>
      <div className="nav">
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/contact">Contact</Link>
      </div>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/contact" component={Contact} />
      </Switch>
    </BrowserRouter>
  );
}

其中,Home、About和Contact是三个不同的组件,分别对应三个不同的页面。我们可以在src目录下创建相应的jsx或tsx文件来定义它们。例如,Home.jsx:

import React from "react";

export default function Home() {
  return <h2>Home</h2>;
}

最后,在App.jsx中导入并使用Routes组件:

import React from "react";
import Routes from "./routes";

export default function App() {
  return (
    <div className="App">
      <Routes />
    </div>
  );
}

这样,我们就可以在浏览器中切换不同的路由来查看不同的页面了。当然,你可以配置多个路由组件作为路由出口,实现更为复杂的功能。

在vite中配置axios,我们可以使用axios这个库来实现HTTP请求的发送和接收。首先,我们需要安装该库:

npm install axios

然后,在src目录下创建一个api.js文件来封装axios的实例和请求方法:

import axios from "axios";

// 创建axios实例,可以设置一些通用的配置,如基础URL,超时时间等
const instance = axios.create({
  baseURL: "https://jsonplaceholder.typicode.com",
  timeout: 5000,
});

// 设置请求拦截器,可以在发送请求前做一些处理,如添加请求头,添加token等
instance.interceptors.request.use(
  (config) => {
    // do something before request is sent
    return config;
  },
  (error) => {
    // do something with request error
    return Promise.reject(error);
  }
);

// 设置响应拦截器,可以在接收响应后做一些处理,如判断状态码,处理错误等
instance.interceptors.response.use(
  (response) => {
    // do something with response data
    return response;
  },
  (error) => {
    // do something with response error
    return Promise.reject(error);
  }
);

// 定义请求方法,可以根据不同的业务需求来封装不同的接口
export const getPosts = () => {
  return instance.get("/posts");
};

export const getPostById = (id) => {
  return instance.get(`/posts/${id}`);
};

export const createPost = (data) => {
  return instance.post("/posts", data);
};

export const updatePost = (id, data) => {
  return instance.put(`/posts/${id}`, data);
};

export const deletePost = (id) => {
  return instance.delete(`/posts/${id}`);
};

最后,在需要使用axios的组件中导入并调用相应的请求方法。例如,在Home.jsx中获取并展示所有的帖子:

import React, { useEffect, useState } from "react";
import { getPosts } from "./api";

export default function Home() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    getPosts()
      .then((res) => {
        setPosts(res.data);
      })
      .catch((err) => {
        console.error(err);
      });
  }, []);

  return (
    <div className="home">
      <h2>Posts</h2>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

三、vite整体架构

1. 依赖预打包

依赖预打包是vite的一个重要特性,它可以提高开发服务器的启动速度和热更新速度。依赖预打包的原理是,在第一次启动开发服务器时,vite会扫描项目中的所有依赖模块(即node_modules中的模块),并使用esbuild将它们转换为ESM格式,并缓存到一个临时目录中。这样,当开发服务器需要加载某个依赖模块时,就可以直接从缓存中读取,而无需再次转换。这样可以避免每次启动或更新时都要对所有的依赖模块进行转换,从而节省了时间和资源。

2. vite单文件编译(esbuild)

vite单文件编译是指vite在开发服务器中对项目源代码(即src目录下的文件)进行的即时编译。vite使用esbuild作为编译器,可以快速地将高级语法(如TypeScript、JSX、CSS预处理器等)转换为浏览器可执行的代码,并支持Source Map、HMR等特性。vite单文件编译的原理是,当浏览器请求某个源文件时,vite会拦截该请求,并根据文件类型和后缀名来调用相应的esbuild插件来进行编译,并将编译后的结果返回给浏览器。这样,浏览器就可以直接执行该文件,而无需等待整个项目打包完成。

3. 代码压缩

代码压缩是指在生产环境中对项目代码进行优化和精简的过程,目的是减少代码体积,提高代码性能,节省网络带宽。代码压缩通常包括以下几个方面:

  • 删除无用代码(Dead Code Elimination),即删除那些从未被使用或引用的变量、函数、模块等。
  • 删除注释和空白(Minification),即删除那些对代码逻辑无影响的注释、空格、换行等。
  • 重命名变量和函数(Mangling),即将那些长而有意义的变量和函数名替换为短而无意义的字符,以减少字符数。
  • 合并相似代码(Inlining),即将那些简单而频繁调用的函数或变量直接替换为它们的值或表达式,以减少函数调用开销。
  • 提取公共代码(Code Splitting),即将那些被多个页面或模块共享的代码提取出来,形成一个单独的文件,以避免重复加载。

4. 插件机制

插件机制是指vite允许开发者通过安装或编写插件来扩展vite的功能或定制化vite的行为。插件本质上是一个JavaScript对象,它可以定义一些钩子函数(Hook Functions),来在vite的不同阶段执行一些操作或逻辑。例如:

  • config:在vite加载配置文件之前或之后修改配置选项。
  • configureServer:在vite启动开发服务器之前添加自定义中间件或处理逻辑。
  • transform:在vite对源文件进行编译之前或之后修改文件内容或元数据。
  • buildStart:在vite开始打包项目之前执行一些操作。
  • renderChunk:在vite生成每个打包文件之前修改文件内容或元数据。
  • generateBundle:在vite生成所有打包文件之后执行一些操作。

四、vite进阶路线

1. 什么是esbuild,roolup,如何深入学习esbuild,rollup

esbuild和rollup都是JavaScript的打包工具,它们可以将多个JavaScript文件合并为一个或多个输出文件,并支持模块化、代码压缩、代码分割等特性。它们的主要区别在于:

  • esbuild是基于Go语言开发的,它使用Go语言自带的并发特性来实现极快的打包速度。esbuild适合用于开发环境中,因为它可以快速地对源代码进行编译和转换,并支持热更新等特性。
  • rollup是基于JavaScript开发的,它使用JavaScript的AST(抽象语法树)来进行代码分析和优化。rollup适合用于生产环境中,因为它可以更好地处理依赖模块和第三方库,并生成更小更高效的输出文件。

要深入学习esbuild和rollup,我们可以参考它们的官方文档和教程,以及一些相关的博客和视频。例如:

2. vite插件开发是什么,如何开发vite插件?

vite插件开发是指使用vite提供的插件API来编写自定义的插件,以扩展或定制vite的功能或行为。vite插件开发的步骤如下:

  • 创建一个JavaScript文件,导出一个函数或对象作为插件。
  • 在函数或对象中定义一些钩子函数(Hook Functions),来在vite的不同阶段执行一些操作或逻辑。
  • 在vite.config.js中导入并使用该插件。
  • 在package.json中添加该插件的相关信息,如名称、版本、描述等。
  • 发布该插件到npm或其他平台,或者在本地使用该插件。

一个简单的vite插件开发的例子:

// my-plugin.js
// 导出一个函数作为插件
module.exports = function myPlugin() {
  // 返回一个对象,定义一些钩子函数
  return {
    // 定义一个名为name的属性,表示插件的名称
    name: "my-plugin",
    // 定义一个config钩子函数,在vite加载配置文件之后执行
    config(config) {
      // 打印配置选项
      console.log(config);
      // 返回修改后的配置选项
      return {
        ...config,
        // 添加一个自定义选项
        myOption: "hello",
      };
    },
    // 定义一个transform钩子函数,在vite对源文件进行编译之前执行
    transform(code, id) {
      // 打印文件内容和路径
      console.log(code, id);
      // 返回修改后的文件内容和元数据
      return {
        code: code.replace("world", "vite"), // 将world替换为vite
        map: null, // 不生成Source Map
      };
    },
  };
};
// vite.config.js
// 导入自定义插件
const myPlugin = require("./my-plugin");

module.exports = {
  // 使用自定义插件
  plugins: [myPlugin()],
};
// package.json
{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "A simple vite plugin example",
  "main": "my-plugin.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": ["vite", "plugin"],
  "author": "Your Name",
  "license": "MIT"
}

可以在这个链接上获取关于vite插件开发的更为详细的信息:vueschool.io/lessons/cre…

3. 代码分割(拆包)

代码分割(Code Splitting)是指将一个大的代码文件拆分为多个小的代码文件的过程,目的是实现按需加载,提高应用的性能和用户体验。代码分割通常有以下几种方式:

  • 基于入口点(Entry Points)的代码分割,即根据不同的页面或功能来定义不同的入口文件,并将它们打包为不同的输出文件。这样,当用户访问某个页面或功能时,只需要加载对应的输出文件,而无需加载整个应用的代码。
  • 基于动态导入(Dynamic Imports)的代码分割,即在运行时根据条件或逻辑来决定是否导入某个模块,并将该模块打包为一个单独的输出文件。这样,当用户需要使用该模块时,才会去加载该输出文件,而无需一开始就加载所有的模块。
  • 基于公共依赖(Common Dependencies)的代码分割,即将那些被多个页面或模块共享的依赖模块(如第三方库等),提取出来,形成一个单独的输出文件。这样,可以避免重复打包和加载相同的依赖模块,并可以利用浏览器缓存来提高加载速度。

4. JS编译工具(babel)

JS编译工具(Babel)是一个JavaScript的转换器,它可以将高级语法(如ES6+、JSX等)转换为低级语法(如ES5等),以实现浏览器或其他环境的兼容性。Babel的工作原理是:

  • 解析:将源代码转换为AST(抽象语法树),并进行词法分析和语法分析。
  • 转换:根据配置或插件来对AST进行修改或替换,实现语法转换或优化。
  • 生成:将转换后的AST重新生成为目标代码,并添加一些辅助代码或注释。

5. 语法安全降级

语法安全降级是指在使用高级语法时,考虑到低版本浏览器或其他环境的兼容性问题,而采用一些低级语法或替代方案来实现相同或类似的功能或效果。例如:

  • 使用var代替let和const来声明变量。
  • 使用function代替箭头函数来定义函数。
  • 使用Promise或回调函数代替async和await来处理异步操作。
  • 使用Object.assign或展开运算符代替Object Rest/Spread来合并对象。
  • 使用数组方法或循环代替Array Destructuring来解构数组。

6. 服务器渲染SSR

服务器渲染SSR(Server-Side Rendering)是指在服务器端对页面进行渲染,并将渲染后的HTML字符串发送给浏览器的过程,目的是提高首屏渲染速度,提升SEO效果,改善用户体验。服务器渲染SSR通常有以下几种方式:

  • 基于模板引擎(Template Engine)的服务器渲染,即使用一些专门用于生成HTML字符串的库或框架,如EJS、Pug、Nunjucks等,在服务器端根据数据和逻辑来填充模板,并返回HTML字符串。

  • 基于同构应用(Isomorphic Application)的服务器渲染,即使用一些支持在服务器端和客户端运行的前端框架,如React、Vue、Svelte等,在服务器端使用框架提供的API来渲染组件,并返回HTML字符串,在客户端使用框架提供的API来绑定事件和数据,并实现交互和更新。这样,可以实现服务器端和客户端的代码复用,以及渲染结果的一致性。

  • 基于预渲染(Pre-rendering)的服务器渲染,即在构建阶段就对一些静态或不经常变化的页面进行渲染,并生成HTML文件,然后在部署阶段直接返回这些HTML文件。这样,可以减少服务器端的渲染开销,以及提高页面的加载速度。一些常用的预渲染工具或框架有Next.js、Nuxt.js、Sapper等。

7. 了解底层标准:CJS规范,ESM规范,HTTP2.0特性

CJS规范是CommonJS规范的缩写,它是一种JavaScript模块化标准,主要用于Node.js环境中。它使用require(‘module’)语句来导入其他模块,并使用module.exports或exports对象来导出当前模块中定义的变量、函数、类等。CJS规范的特点是:

  • 同步加载模块,即在执行require语句时就会阻塞代码的执行,直到模块加载完成。
  • 模块缓存机制,即在第一次加载某个模块时会将其缓存起来,以便后续重复加载时直接返回缓存结果。
  • 模块作用域机制,即每个模块都有自己独立的作用域,不会污染全局作用域。

ESM规范是ECMAScript Modules规范的缩写,它是一种JavaScript模块化标准,主要用于浏览器和现代Node.js环境中。它使用import和export语句来导入和导出模块中的变量、函数、类等。ESM规范的特点是:

  • 异步加载模块,即在执行import语句时不会阻塞代码的执行,而是返回一个Promise对象,表示模块的加载结果。
  • 静态解析模块,即在编译阶段就可以确定模块的依赖关系和导入导出内容,而不需要运行时动态分析。
  • 树摇优化机制,即在打包阶段可以删除那些从未被使用或引用的模块或模块成员,以减少代码体积。

HTTP2.0是HTTP协议的第二个主要版本,它是一种用于Web应用的网络传输协议。它基于SPDY协议,旨在提高Web性能和用户体验。HTTP2.0的特点是:

  • 二进制帧层,即将HTTP报文分割为多个二进制帧,并在客户端和服务器之间传输,以提高传输效率和安全性。
  • 多路复用机制,即在一个TCP连接上可以同时发送和接收多个请求和响应,并按照帧的标识符来重组报文,以解决HTTP1.x中的队头阻塞问题。
  • 头部压缩机制,即使用HPACK算法来压缩请求和响应的头部信息,并维护一个头部表来记录已经发送过的头部字段,以减少冗余数据的传输。
  • 服务器推送机制,即服务器可以主动向客户端推送一些资源,而无需客户端请求,以提前加载页面所需的资源,以减少延迟时间。
  • 流控制机制,即客户端和服务器可以根据自身的处理能力和网络状况来控制发送和接收数据的速率,以避免拥塞或浪费带宽。

8. vite社区生态:官方提供的部分插件

vite社区生态是指vite周围形成的一系列的资源、工具、插件、框架、教程、案例等,它们可以帮助开发者更好地使用和学习vite,也可以让开发者参与到vite的贡献和改进中。

vite官方提供了一些常用的插件,如vite-plugin-react、vite-plugin-vue、vite-plugin-svelte等,它们可以让vite支持不同的前端框架的开发和打包。还有一些其他的插件,如vite-plugin-legacy、vite-plugin-pwa、vite-plugin-md等,它们可以让vite实现一些特殊的功能或需求。具体还请参见这个链接:vitejs.dev/plugins/