跳转内容

插件开发

WARNING

注意,开发插件我们假设您已经对TypeScript中级乃至更高层的知识有足够的了解,因为这属于涉及到ioc核心等更高级的内容。

unioc的核心由三部分组成:

  • 容器:内部有一个容器,存储着所有注册的类;
  • 启动器:一个抽象类,定义了一个App的启动流程;
  • 插件:自由修改启动器与容器,控制每一个类的初始化时、执行时、销毁时等各个阶段。

插件的权限是和启动器一样大的,极度自由,下面我们就来详细说明该如何撰写一个unioc插件。

unioc提供一个类似 Rollup插件 的插件系统。但相比于Rollup的钩子,unioc的钩子采用了一些简单有力单个英文单词对应着每个处理阶段。因为这是一个针对运行时的框架,而不是像rollup那样的编译时,所以也没有Rollup那么复杂的钩子执行逻辑。下面是一张简图,描述了插件各个钩子将会在什么时机执行。

插件上下文

每个插件钩子几乎都拥有一个上下文对象,他继承了基础的IContext上下文(IContext上下文为启动器和插件共有);此外,它还继承了IInternalLogger日志记录器,您可以使用warnerror等方法来记录插件运行日志。下面是插件上下文的类型定义:

ts
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-nameunioc:plugin-name:sub-plugin等。

enforcepriority

插件执行顺序,可选值为prepost,默认留空则会在prepost阶段之间执行。

WARNING

插件的执行顺序是先按照enforce的值来决定执行顺序,再按照priority的值来决定执行顺序。

install()

install钩子用于在插件执行器初始化前执行,您可以在这里执行一切初始化操作。它只有一个参数:将当前启动器实例传递给该方法。

ts
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钩子将会被调用。它的第一个参数是一个上下文对象,继承了一系列的类型。 但是宏观层次来看,这个钩子的职责是:在类实例化之前,修改类构造器参数。修改之后,该类构造器参数将不会被重新解析

ts
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钩子将会被调用。这个钩子的用途是:在类构造函数被解析完毕之后修改类构造函数

ts
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钩子将会被调用。这个钩子的用途是:在类实例化完毕之后修改类属性

ts
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钩子将会被调用。这个钩子的用途是:在类实例化完毕之后修改类属性

ts
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,那么它将会在这个钩子里执行NestJSonModuleInit方法。

ts
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等装饰器。

ts
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:执行结果的类型,resulterror
  • extraOptions:由ClassExecutor.execute传递的额外选项,这一般用来标识当前错误/结果是哪一个框架/插件执行的,以提供错误处理的操作空间,精准拦截错误。
ts
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>
}

贡献者

页面历史