手把手教你手写 Vite Server(二)—— 插件架构设计(上)

简介: 手把手教你手写 Vite Server(二)—— 插件架构设计(上)

前言


上一篇文章,我们手写了一个 Vite Server,实现了一些基本的功能,例如:JS 编译、CSS 处理等,但是这些能力都是写死的,我们的 Vite 没有任何的可扩展性,如果需要新增功能,就必须得改 Vite 核心的代码。那么这次我们就来解决一下这个问题,将它改造成插件化架构,通过新增插件来新增能力,例如 Less 文件的编译。

本文的代码放在 GItHub 仓库,链接:github.com/candy-Tong/…,目录为 packages/2. my-vite-middleware-plugins


插件化架构


以下内容部分来自:《前端进阶:跟着开源项目学习插件化架构

插件化架构(Plug-in Architecture),有时候又被成为微内核架构(Microkernel Architecture),是一种面向功能进行拆分的可扩展性架构。微内核架构模式允许你将其他应用程序功能作为插件添加到核心应用程序,从而提供可扩展性以及功能分离和隔离

1686393933681.png


内核的功能相对稳定,不会因为功能的扩展而不断修改,功能的扩展通过插件来实现。


架构的设计关键


插件化架构,有三个设计的关键:

  • 插件管理
  • 插件连接
  • 插件通讯

我们用 Vue 插件作为例子,来解析这三个概念:


插件管理


核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件。常见的实现方法是插件注册表机制

对于 Vue 来说,通过调用 use() 方法使用插件。


import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createRouter } from 'vue-router'
const pinia = createPinia()
const router = createRouter()
const app = createApp({})
// vue-router 插件
app.use(router)
// pinia 插件
app.use(pinia)
app.mount('#app')
  • 使用 use 方法,注册需要使用的插件(告诉核心系统需要加载哪些插件)
  • use 方法实际上会立即加载插件(加载时机)
  • 加载插件实际上会调用插件的 install 函数(如何加载)

这个过程就是使用代码,提供了插件的注册表(注册了 vue-routerpinia)。

注册表的还可以是其他的形式,例如配置文件(Vite、Webpack),这种属于静态的注册表。而用代码形式的注册表,则是在运行时动态注册插件的。


插件连接


插件连接是指插件如何连接到核心系统。通常来说,核心系统必须指定插件和核心系统的连接规范,然后插件按照规范实现,核心系统按照规范加载即可。

下面是一个 Vue 的国际化插件


// plugins/i18n.js
export default {
  install: (app, options) => {
    // 支持 $translate('x,y,z'),视为读取 options.x.y.z
    app.config.globalProperties.$translate = key => {
      return key.split('.').reduce((o, i) => {
        if (o) return o[i]
      }, options)
    }
    app.provide('i18n', options)
  }
}

连接规范,这里就是指 install 函数,它提供了 app 参数,用于让插件能够获取到 Vue 实例,这就起到了连接的作用。该插件根据该连接规范,给 Vue 实例设置全局属性 $translate ,并且 provide 了名为 i18n 的内容。

Vue 的连接规范,比较简单,只有 install 一个函数,我们一般称这种函数为插件钩子(hook,你可以往钩子上挂任何东西,程序执行到 hook 的时候,你预先挂上/勾上 (hook) 的是什么,就执行什么。本质是一种回调函数)

实际上一个内核的连接规范,可能非常的复杂,就例如 Vite 的拥有非常多的插件钩子


插件通信


指插件之间,能够相互进行通信

在 Vue 插件中,其实并没有规定关于插件通信的内容,因为大多数插件,应该是互相独立的。

当然在特殊情况下,也有可能真的需要进行插件通信,这时候,可以通过 Vue 全局属性、Event Bus 等方式进行通信,但这些方式和通信规范,完全由开发者自行决定

同样的,Vite 也没有规定插件通信的内容。

最后,我们同一个图总结一下插件的三个关键设计:


1686394065963.png


Vite 的插件化改造


我们有了一些插件化架构的知识之后,要对我们手写的 Vite 进行插件化改造,主要要解决问题只有两个:

  • 实现插件管理:如何注册和加载插件(内部插件和外部插件)
  • 实现插件连接:如何设计插件的钩子,并实现插件

为了尽快的能看到效果,我会按照以下的顺序实现:

  1. 实现内部插件的注册和加载
  2. 设计插件的钩子,并实现几个内部插件
  3. 实现外部插件的注册
  4. 实现一个外部插件

当我们做完第二步的时候,其实就有一套较为完整的插件架构,可以看出插件化改造的效果了。


内部插件的注册和加载


首先我们定义一下 Plugin 的类型:


// src/node/server/plugin.ts
export interface Plugin {}

具体插件有什么,我们先不管,这个留到后面再进行设计。

内部插件的注册,我们只需要用代码实现一个注册表就行了


// src/node/plugins/index.ts
export function loadInternalPlugins(): Plugin[] {
  return [
    // 内部插件一
    // 内部插件二
    // 内部插件三 
    // ……    
  ];
}

插件加载的实现如下:


// src/node/server/index.ts
export async function createServer() {
  const plugins = loadInternalPlugins();
  const app = connect();
  // server 作为上下文对象,用于保存一些状态和对象,将会在 Server 的各个流程中被使用
  const server: ViteDevServer = {
    plugins,
    app,
  };
}


插件的加载非常简单,其实就是把插件保存起来

这里的 server 上下文对象用来保存 Dev Server 的实例和运行中会用到的一些对象内容,例如插件列表,该对象会贯穿整个 Vite 的运行周期,在各个流程中被使用。

那我们对应插件管理的概念来看:

  • 直接在 loadInternalPlugins注册内部的插件(告诉核心系统需要加载哪些插件)
  • loadInternalPlugins 会在 createServer立即执行(加载时机)
  • 插件被保存到 server 对象中(如何加载)

插件的加载,这里其实是做了简化的,实际上还会有插件过滤、插件排序等一系列操作,这里为了简单,直接返回插件列表的数组了。


设计插件钩子


我们先回顾一下我们在上一篇文章所实现的内容。

我们创建了一个 Dev Server,并利用中间件,实现了以下的功能

  1. 转换 TS、TSX 文件的请求
  2. 转换 CSS 文件的请求
  3. 处理其他静态资源的请求

示例项目中部分的时序图如下:

1686394178793.png


之前实现的 Dev Server 的核心代码如下:


export async function createServer() {
  const app = connect();
  app.use(transformMiddleware());
  app.use(cssMiddleware());
  app.use(staticMiddleware());
  http.createServer(app).listen(3000);
  console.log('open http://localhost:3000/');
}

Server 的架构如下:

1686394202337.png

可以看出,这三个中间件之间没有耦合,互不影响,它们只处理自己能处理的请求。

它们都共同依赖 app ,因为这是使用中间件的方式,

我们来分析一下,做成插件,需要什么:

  • 它们都依赖 在  app 对象,需要提供一个钩子,用于提供 app 对象(插件的连接规范)
  • 插件需要在 app 对象被创建后,加入中间件(钩子的执行时机)

1686394215736.png

目录
相关文章
|
6月前
|
设计模式 安全 Java
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
104 0
|
3月前
|
Kubernetes Serverless API
Kubernetes 的架构问题之利用不可变性来最小化对API Server的访问如何解决
Kubernetes 的架构问题之利用不可变性来最小化对API Server的访问如何解决
80 7
|
4月前
|
Web App开发 JavaScript 前端开发
Chrome插件实现问题之最新的 Chrome 浏览器架构有什么新的改变吗
Chrome插件实现问题之最新的 Chrome 浏览器架构有什么新的改变吗
|
4月前
|
JSON Go C++
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
开发与运维C++问题之在iLogtail新架构中在C++主程序中新增插件的概念如何解决
45 1
|
5月前
|
JavaScript 前端开发 Java
信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ&插件项目
信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ&插件项目
|
6月前
|
Web App开发 JavaScript 前端开发
分析网站架构:浏览器插件
分析网站架构:浏览器插件
|
6月前
|
存储 前端开发 JavaScript
Java电子病历编辑器项目源码 采用B/S(Browser/Server)架构
Java电子病历编辑器项目源码 采用B/S(Browser/Server)架构
103 0
|
6月前
|
运维 Oracle 关系型数据库
LIS实验室信息管理系统功能模块(Oracle数据库、Client/Server架构)
LIS实验室信息管理系统功能模块(Oracle数据库、Client/Server架构)
116 0
|
数据采集 数据库
医院LIS系统源码,SaaS架构的Client/Server应用
LIS系统集申请、采样、核收、计费、检验、审核、发布、质控、查询、耗材控制等检验科工作为一体的网络管理系统。它的开发和应用将加快检验科管理的统一化、网络化、标准化的进程。
|
Web App开发 Java 测试技术
2022 PlantUML 这款 IDEA 插件能搞,流程图、架构图,N种图... 简直神器!
2022 PlantUML 这款 IDEA 插件能搞,流程图、架构图,N种图... 简直神器!
613 0