插件开发
WARNING
注意,开发插件我们假设您已经对TypeScript中级乃至更高层的知识有足够的了解,因为这属于涉及到ioc核心等更高级的内容。
unioc的核心由三部分组成:
容器
:内部有一个容器,存储着所有注册的类;启动器
:一个抽象类,定义了一个App的启动流程;插件
:自由修改启动器与容器,控制每一个类的初始化时、执行时、销毁时等各个阶段。
插件的权限是和启动器一样大的,极度自由,下面我们就来详细说明该如何撰写一个unioc插件。
unioc提供一个类似 Rollup插件 的插件系统。但相比于Rollup的钩子,unioc的钩子采用了一些简单有力
的单个
英文单词对应着每个处理阶段。因为这是一个针对运行时的框架,而不是像rollup那样的编译时,所以也没有Rollup那么复杂的钩子执行逻辑。下面是一张简图,描述了插件各个钩子将会在什么时机执行。
插件上下文
每个插件钩子几乎都拥有一个上下文对象,他继承了基础的IContext
上下文(IContext
上下文为启动器和插件共有);此外,它还继承了IInternalLogger
日志记录器,您可以使用warn
、error
等方法来记录插件运行日志。下面是插件上下文的类型定义:
import type { IBootstrapDeriver, IClassWrapper, IContainer, IContext, IInternalLogger, IPlugin } from 'unioc'
export interface IHandleExecutorOptions {
/**
* The class wrapper.
*/
classWrapper: IClassWrapper
/**
* The `catch` error.
*/
error: unknown
/**
* The current method property key.
*/
propertyKey: PropertyKey
/**
* The current method arguments.
*/
methodArguments: unknown[]
/**
* The extra options.
*/
extraOptions?: Record<string, unknown>
}
export interface IPluginContext extends IContext, IInternalLogger, IBootstrapDeriver {
/**
* ### Get global container
*
* 🌏 Get the global container.
*/
getGlobalContainer(): IContainer
/**
* ### Handle
*
* ❌ A function that is used to handle when a class instance method throw an error.
* It can pass the error to enter the plugin pipe context.
*
* @param options - The position information of the `try/catch block`.
* @returns The plugin handle context.
*/
handle(options: IHandleExecutorOptions): Promise<IPlugin.Handle.Context>
}
其中,handle
方法用于创建一个错误处理管道;当在插件执行过程中遇到任何需要被catch
的错误,您可以调用handle
方法来创建一个错误处理管道,这样就能让其他插件有机会处理这个错误。
NOTE
比如,@unioc/adapter-nestjs
的错误拦截器@Catch
,就是通过handle
方法来创建的。
WARNING
此外,任何使用IClassExecutor.execute
执行的方法,都会被自动捕获,并传递给handle
方法。
name
插件名称,必须唯一,建议使用:
来分隔命名空间,比如unioc:plugin-name
、unioc:plugin-name:sub-plugin
等。
enforce
和 priority
插件执行顺序,可选值为pre
和post
,默认留空则会在pre
和post
阶段之间执行。
WARNING
插件的执行顺序是先按照enforce
的值来决定执行顺序,再按照priority
的值来决定执行顺序。
install()
install
钩子用于在插件执行器初始化前执行,您可以在这里执行一切初始化操作。它只有一个参数:将当前启动器实例传递给该方法。
import type { IBootstrap, IPluginContext } from 'unioc'
import type { Awaitable } from 'unioc/shared'
export interface IPlugin {
/**
* ### Install
*
* 🔧 When the plugin is installed, the hook will be call.
*/
install?(this: IPluginContext, bootstrap: IBootstrap): Awaitable<unknown>
}
resolve()
当一个类尝试进行实例化时,resolve
钩子将会被调用。它的第一个参数是一个上下文对象,继承了一系列的类型。 但是宏观层次来看,这个钩子的职责是:在类实例化之前,修改类构造器参数
。修改之后,该类构造器参数将不会被重新解析
。
import type { IAbstractClassWrapper, IConstructorArgumentModifier, IConstructorArgumentsGetter, IPluginContext } from 'unioc'
import type { Awaitable, IClass } from 'unioc/shared'
export interface IPlugin {
/**
* ### Resolve
*
* 🐶 When unioc try to create a class instance, the hook will be call at first.
* @returns You can provide constructor arguments value. The modified arguments will not be `re-resolved` again.
* @param ctx - The context of the plugin. You can get the current class wrapper from the context.
*/
resolve?(this: IPluginContext, ctx: IPlugin.Resolve.Context): Awaitable<unknown>
}
export namespace IPlugin {
export namespace Resolve {
export interface Context<TClass extends IClass = IClass> extends IAbstractClassWrapper<TClass>, IConstructorArgumentModifier, IConstructorArgumentsGetter {}
export type Handler = Exclude<IPlugin['resolve'], undefined>
}
}
construct()
当一个类实例化之前
,在类构造函数被解析完毕之后
,construct
钩子将会被调用。这个钩子的用途是:在类构造函数被解析完毕之后
再修改类构造函数
。
import type { IAbstractClassWrapper, IConstructorArgumentModifier, IConstructorArgumentsGetter, IPluginContext } from 'unioc'
import type { Awaitable, IClass } from 'unioc/shared'
export namespace IPlugin {
export namespace Construct {
export interface Context<TClass extends IClass = IClass> extends IAbstractClassWrapper<TClass>, IConstructorArgumentModifier, IConstructorArgumentsGetter {}
export type Handler = Exclude<IPlugin['construct'], undefined>
}
}
export interface IPlugin {
/**
* ### Construct
*
* 🐶 When after the constructor arguments resolved, the hook will be call.
* @returns You can modify the class constructor when the class constructor is resolved.
* @param ctx - The context of the plugin. You can get the current class wrapper from the context.
*/
construct?(this: IPluginContext, ctx: IPlugin.Construct.Context): Awaitable<unknown>
}
apply()
当一个类实例化完毕,在属性依赖
被解析之前,apply
钩子将会被调用。这个钩子的用途是:在类实例化完毕之后
再修改类属性
。
import type { IAbstractClassWrapper, IConstructorArgumentsGetter, IPluginContext, IPropertyModifier } from 'unioc'
import type { Awaitable, IClass } from 'unioc/shared'
export namespace IPlugin {
export namespace Apply {
export interface Context<TClass extends IClass = IClass> extends IAbstractClassWrapper<TClass>, IPropertyModifier, IConstructorArgumentsGetter {}
export type Handler = Exclude<IPlugin['apply'], undefined>
}
}
export interface IPlugin {
/**
* ### Apply
*
* 🍰 When the instance is created but not resolve property dependencies, the hook will be call.
* @returns You can modify the instance properties value.
* @param ctx - The context of the plugin. You can get or modify current class wrapper from the context.
*/
apply?(this: IPluginContext, ctx: IPlugin.Apply.Context): Awaitable<unknown>
}
transform()
当一个类实例化完毕而且在属性依赖
被解析之后,transform
钩子将会被调用。这个钩子的用途是:在类实例化完毕之后
再修改类属性
。
import type { IAbstractClassWrapper, IConstructorArgumentsGetter, IPluginContext, IPropertyModifier } from 'unioc'
import type { Awaitable, IClass } from 'unioc/shared'
export namespace IPlugin {
export namespace Transform {
export interface Context<TClass extends IClass = IClass> extends IAbstractClassWrapper<TClass>, IPropertyModifier, IConstructorArgumentsGetter {}
export type Handler = Exclude<IPlugin['transform'], undefined>
}
}
export interface IPlugin {
/**
* ### Transform
*
* 🧁 When the instance is created and property dependencies are resolved, the hook will be call.
*
* @returns You can modify the instance properties value.
* @param ctx - The context of the plugin. You can get or modify current class wrapper from the context.
*/
transform?(
this: IPluginContext,
ctx: IPlugin.Transform.Context,
): Awaitable<unknown>
}
ready()
当以上所有步骤都执行完毕,即预示着这个类可供外界使用;此时,ready
钩子将会被调用,一般可以在这里执行一些初始化操作,比如如果使用了@unioc/adapter-nestjs
,那么它将会在这个钩子里执行NestJS
的onModuleInit
方法。
import type { IClassWrapper, IPluginContext } from 'unioc'
import type { Awaitable, IClass } from 'unioc/shared'
export namespace IPlugin {
export namespace Ready {
export interface Context<TClass extends IClass = IClass> extends IClassWrapper<TClass> {
/**
* ### Get instance
*
* 🔍 Get current class instance.
* @note Instance is absolutely certain to exist in this hook. It is not `null` or `undefined`.
*/
getInstance(): InstanceType<TClass>
}
export type Handler = Exclude<IPlugin['ready'], undefined>
}
}
export interface IPlugin {
/**
* ### Ready
*
* 🧪 When the instance is created and property dependencies are resolved, the hook will be call.
* @param ctx - The wrapper context of the class.
*/
ready?<TClass extends IClass = IClass>(this: IPluginContext, ctx: IPlugin.Ready.Context<TClass>): Awaitable<unknown>
}
invoke()
当使用ClassExecutor.execute
执行一个类实例的方法时,invoke
钩子将会首先被调用。在这个钩子中,允许修改当前调用的方法的参数,这样的特性将会被用来实现类似@unioc/adapter-nestjs
中的@Body
、@Param
等装饰器。
import type { IClassWrapper, IPluginContext } from 'unioc'
import type { Awaitable, IClass } from 'unioc/shared'
export namespace IPlugin {
export namespace Invoke {
export interface Context<TClass extends IClass = IClass> extends IClassWrapper<TClass> {
/**
* ### Get method arguments
*
* 🔍 Get current method arguments.
*/
getMethodArguments(): readonly unknown[]
}
export type Handler = Exclude<IPlugin['invoke'], undefined>
}
}
export interface IPlugin {
/**
* ### Invoke
*
* 🔍 When the instance is created and property dependencies are resolved, the hook will be call.
* @param ctx - The wrapper context of the class.
*/
invoke?(
this: IPluginContext,
ctx: IPlugin.Invoke.Context,
): Awaitable<unknown>
}
handle()
当使用ClassExecutor.execute
执行一个类实例的方法时抛出的所有错误,以及被this.handle
捕获的错误,都会被传递给handle
钩子。该方法拥有三个参数:
ctx
:插件上下文对象;type
:执行结果的类型,result
或error
;extraOptions
:由ClassExecutor.execute
传递的额外选项,这一般用来标识当前错误/结果是哪一个框架/插件执行的,以提供错误处理的操作空间,精准拦截错误。
import type { IClassWrapper, IPluginContext } from 'unioc'
import type { Awaitable, IClass, IResult } from 'unioc/shared'
type IResultType = IResult['type']
export namespace IPlugin {
export namespace Handle {
export interface Context<TClass extends IClass = IClass> extends IClassWrapper<TClass> {
/**
* ### Get execute result
*
* 🥖 Get the execute result.
*/
getExecuteResult<T = unknown>(): T
/**
* ### Set execute result
*
* 🔧 Set the execute result.
*/
setExecuteResult<T = unknown>(result: T): this
/**
* ### Get property key
*
* 🔍 Get current invoke property key.
*/
getPropertyKey(): PropertyKey
/**
* ### Get method arguments
*
* 🔍 Get current method arguments.
*/
getMethodArguments(): readonly unknown[]
}
export type Handler = Exclude<IPlugin['handle'], undefined>
}
}
export interface IPlugin {
/**
* ### Handle
*
* 🌹 When the instance is created and property dependencies are resolved, the hook will be call.
* @param ctx - The wrapper context of the class.
* @param type - The type of the execution result.
*/
handle?(
this: IPluginContext,
ctx: IPlugin.Handle.Context,
type: IResultType,
extraOptions?: Record<string, unknown>,
): Awaitable<unknown>
}