Skip to content

插件

¥Plugin

插件是一种将功能分解为更小部分的模式。为我们的 Web 服务器创建可重用组件。

¥Plugin is a pattern that decouples functionality into smaller parts. Creating reusable components for our web server.

定义插件就是定义一个单独的实例。

¥Defining a plugin is to define a separate instance.

typescript
import { 
Elysia
} from 'elysia'
const
plugin
= new
Elysia
()
.
decorate
('plugin', 'hi')
.
get
('/plugin', ({
plugin
}) =>
plugin
)
const
app
= new
Elysia
()
.
use
(
plugin
)
.
get
('/', ({
plugin
}) =>
plugin
)
.
listen
(3000)

我们可以通过将实例传递给 Elysia.use 来使用该插件。

¥We can use the plugin by passing an instance to Elysia.use.

localhost

GET

该插件将继承插件实例的所有属性,包括状态、装饰、派生、路由、生命周期等。

¥The plugin will inherit all properties of the plugin instance, including state, decorate, derive, route, lifecycle, etc.

Elysia 还会自动处理类型推断,因此你可以想象在主实例上调用所有其他实例。

¥Elysia will also handle the type inference automatically as well, so you can imagine as if you call all of the other instances on the main one.

提示

请注意,插件不包含 .listen,因为 .listen 会为使用情况分配端口,而我们只希望主实例分配该端口。

¥Notice that the plugin doesn't contain .listen, because .listen will allocate a port for the usage, and we only want the main instance to allocate the port.

插件

¥Plugin

每个 Elysia 实例都可以是一个插件。

¥Every Elysia instance can be a plugin.

我们可以将逻辑解耦到一个新的单独的 Elysia 实例中,并将其用作插件。

¥We can decouple our logic into a new separate Elysia instance and use it as a plugin.

首先,我们在差异文件中定义一个实例:

¥First, we define an instance in a difference file:

typescript
// plugin.ts
import { 
Elysia
} from 'elysia'
export const
plugin
= new
Elysia
()
.
get
('/plugin', () => 'hi')

然后我们将实例导入主文件:

¥And then we import the instance into the main file:

typescript
import { Elysia } from 'elysia'
import { plugin } from './plugin'

const app = new Elysia()
    .use(plugin)
    .listen(3000)

配置

¥Config

为了使插件更有用,建议允许通过配置进行自定义。

¥To make the plugin more useful, allowing customization via config is recommended.

你可以创建一个函数,该函数接受可能改变插件行为的参数,以提高其可复用性。

¥You can create a function that accepts parameters that may change the behavior of the plugin to make it more reusable.

typescript
import { Elysia } from 'elysia'

const version = (version = 1) => new Elysia()
        .get('/version', version)

const app = new Elysia()
    .use(version(1))
    .listen(3000)

函数式回调

¥Functional callback

建议定义一个新的插件实例,而不是使用函数回调。

¥It's recommended to define a new plugin instance instead of using a function callback.

函数式回调允许我们访问主实例的现有属性。例如,检查特定路由或存储是否存在。

¥Functional callback allows us to access the existing property of the main instance. For example, checking if specific routes or stores existed.

要定义函数式回调,请创建一个接受 Elysia 作为参数的函数。

¥To define a functional callback, create a function that accepts Elysia as a parameter.

typescript
import { 
Elysia
} from 'elysia'
const
plugin
= (
app
:
Elysia
) =>
app
.
state
('counter', 0)
.
get
('/plugin', () => 'Hi')
const
app
= new
Elysia
()
.
use
(
plugin
)
.
get
('/counter', ({
store
: {
counter
} }) =>
counter
)
.
listen
(3000)
localhost

GET

传递给 Elysia.use 后,函数式回调的行为与普通插件相同,只是属性直接赋值给主实例。

¥Once passed to Elysia.use, functional callback behaves as a normal plugin except the property is assigned directly to the main instance.

提示

你不必担心函数式回调和创建实例之间的性能差异。

¥You shall not worry about the performance difference between a functional callback and creating an instance.

Elysia 可以在几毫秒内创建 10,000 个实例,新的 Elysia 实例的类型推断性能甚至比函数式回调更好。

¥Elysia can create 10k instances in a matter of milliseconds, the new Elysia instance has even better type inference performance than the functional callback.

插件数据去重

¥Plugin Deduplication

默认情况下,Elysia 将注册任何插件并处理类型定义。

¥By default, Elysia will register any plugin and handle type definitions.

有些插件可能会被多次使用以提供类型推断,从而导致重复设置初始值或路由。

¥Some plugins may be used multiple times to provide type inference, resulting in duplication of setting initial values or routes.

Elysia 通过使用名称和可选种子来区分实例,从而避免了重复实例:

¥Elysia avoids this by differentiating the instance by using name and optional seeds to help Elysia identify instance duplication:

typescript
import { Elysia } from 'elysia'

const plugin = <T extends string>(config: { prefix: T }) =>
    new Elysia({
        name: 'my-plugin', 
        seed: config, 
    })
    .get(`${config.prefix}/hi`, () => 'Hi')

const app = new Elysia()
    .use(
        plugin({
            prefix: '/v2'
        })
    )
    .listen(3000)
localhost

GET

Elysia 将使用名称和种子创建校验和,以识别实例是否已注册,如果已注册,Elysia 将跳过插件的注册。

¥Elysia will use name and seed to create a checksum to identify if the instance has been registered previously or not, if so, Elysia will skip the registration of the plugin.

如果未提供种子,Elysia 将仅使用名称来区分实例。这意味着即使你多次注册该插件,它也只会注册一次。

¥If seed is not provided, Elysia will only use name to differentiate the instance. This means that the plugin is only registered once even if you registered it multiple times.

typescript
import { Elysia } from 'elysia'

const plugin = new Elysia({ name: 'plugin' })

const app = new Elysia()
    .use(plugin)
    .use(plugin)
    .use(plugin)
    .use(plugin)
    .listen(3000)

这允许 Elysia 通过重用已注册的插件来提高性能,而无需一遍又一遍地处理插件。

¥This allows Elysia to improve performance by reusing the registered plugins instead of processing the plugin over and over again.

提示

种子可以是任何内容,从字符串到复杂对象或类。

¥Seed could be anything, varying from a string to a complex object or class.

如果提供的值是类,Elysia 将尝试使用 .toString 方法生成校验和。

¥If the provided value is class, Elysia will then try to use the .toString method to generate a checksum.

服务定位器

¥Service Locator

当你将带有状态/装饰器的插件应用于实例时,该实例将获得类型安全。

¥When you apply a plugin with state/decorators to an instance, the instance will gain type safety.

但如果你不将插件应用到另一个实例,它将无法推断类型。

¥But if you don't apply the plugin to another instance, it will not be able to infer the type.

typescript
import { 
Elysia
} from 'elysia'
const
child
= new
Elysia
()
// ❌ 'a' is missing .
get
('/', ({ a }) =>
a
)
Property 'a' does not exist on type '{ body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 59 more ... | 511 ? { ...; }[Code] : Code>(co...'.
const
main
= new
Elysia
()
.
decorate
('a', 'a')
.
use
(
child
)

Elysia 引入了服务定位器模式来解决这个问题。

¥Elysia introduces the Service Locator pattern to counteract this.

Elysia 将查找插件校验和并获取其值或注册一个新值。从插件推断类型。

¥Elysia will lookup the plugin checksum and get the value or register a new one. Infer the type from the plugin.

因此,我们必须提供插件参考,以便 Elysia 找到合适的服务来增加类型安全性。

¥So we have to provide the plugin reference for Elysia to find the service to add type safety.

typescript
import { 
Elysia
} from 'elysia'
const
setup
= new
Elysia
({
name
: 'setup' })
.
decorate
('a', 'a')
// Without 'setup', type will be missing const
error
= new
Elysia
()
.
get
('/', ({ a }) =>
a
)
Property 'a' does not exist on type '{ body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 59 more ... | 511 ? { ...; }[Code] : Code>(co...'.
// With `setup`, type will be inferred const
child
= new
Elysia
()
.
use
(
setup
)
.
get
('/', ({
a
}) =>
a
)
const
main
= new
Elysia
()
.
use
(
child
)
localhost

GET

守护

¥Guard

Guard 允许我们将钩子和方案一次性应用于多个路由。

¥Guard allows us to apply hook and schema into multiple routes all at once.

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
guard
(
{
body
:
t
.
Object
({
username
:
t
.
String
(),
password
:
t
.
String
()
}) }, (
app
) =>
app
.
post
('/sign-up', ({
body
}) =>
signUp
(
body
))
.
post
('/sign-in', ({
body
}) =>
signIn
(
body
), {
beforeHandle
:
isUserExists
}) ) .
get
('/', 'hi')
.
listen
(3000)

此代码将 body 的验证应用于 '/sign-in' 和 '/sign-up',而不是逐一内联模式,但不应用于 '/'。

¥This code applies validation for body to both '/sign-in' and '/sign-up' instead of inlining the schema one by one but applies not to '/'.

我们可以将路由验证总结如下:

¥We can summarize the route validation as the following:

路径已验证
/sign-up
/sign-in
/

Guard 接受与内联钩子相同的参数,唯一的区别在于你可以将钩子应用于作用域内的多个路由。

¥Guard accepts the same parameter as inline hook, the only difference is that you can apply hook to multiple routes in the scope.

这意味着上面的代码被翻译成:

¥This means that the code above is translated into:

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
post
('/sign-up', ({
body
}) =>
signUp
(
body
), {
body
:
t
.
Object
({
username
:
t
.
String
(),
password
:
t
.
String
()
}) }) .
post
('/sign-in', ({
body
}) =>
body
, {
beforeHandle
:
isUserExists
,
body
:
t
.
Object
({
username
:
t
.
String
(),
password
:
t
.
String
()
}) }) .
get
('/', () => 'hi')
.
listen
(3000)

分组守护

¥Grouped Guard

我们可以通过为组提供 3 个参数来使用带前缀的组。

¥We can use a group with prefixes by providing 3 parameters to the group.

  1. 前缀 - 路由前缀
  2. 守护 - Schema
  3. 范围 - Elysia 应用回调

使用与 guard 相同的 API,将第二个参数应用于该参数,而不是将 group 和 guard 嵌套在一起。

¥With the same API as guard apply to the 2nd parameter, instead of nesting group and guard together.

请考虑以下示例:

¥Consider the following example:

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
group
('/v1', (
app
) =>
app
.
guard
(
{
body
:
t
.
Literal
('Rikuhachima Aru')
}, (
app
) =>
app
.
post
('/student', ({
body
}) =>
body
)
) ) .
listen
(3000)

从嵌套的 groupped 守卫中,我们可以通过为组的第二个参数提供守卫作用域来将组和守卫合并在一起:

¥From nested groupped guard, we may merge group and guard together by providing guard scope to 2nd parameter of group:

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
group
(
'/v1', (
app
) =>
app
.
guard
(
{
body
:
t
.
Literal
('Rikuhachima Aru')
}, (
app
) =>
app
.
post
('/student', ({
body
}) =>
body
)
) ) .
listen
(3000)

其语法如下:

¥Which results in the follows syntax:

typescript
import { 
Elysia
,
t
} from 'elysia'
new
Elysia
()
.
group
(
'/v1', {
body
:
t
.
Literal
('Rikuhachima Aru')
}, (
app
) =>
app
.
post
('/student', ({
body
}) =>
body
)
) .
listen
(3000)
localhost

POST

范围

¥Scope

默认情况下,钩子和模式仅适用于当前实例。

¥By default, hook and schema will apply to current instance only.

Elysia 具有封装范围,以防止意外的副作用。

¥Elysia has an encapsulation scope for to prevent unintentional side effects.

作用域类型用于指定钩子的作用域,是封装的还是全局的。

¥Scope type is to specify the scope of hook whether is should be encapsulated or global.

typescript
import { 
Elysia
} from 'elysia'
const
plugin
= new
Elysia
()
.
derive
(() => {
return {
hi
: 'ok' }
}) .
get
('/child', ({
hi
}) =>
hi
)
const
main
= new
Elysia
()
.
use
(
plugin
)
// ⚠️ Hi is missing .
get
('/parent', ({ hi }) =>
hi
)
Property 'hi' does not exist on type '{ body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends number | keyof StatusMap, const T = Code extends 100 | ... 59 more ... | 511 ? { ...; }[Code] : Code>(co...'.

从上面的代码中,我们可以看到父实例中缺少 hi,因为如果未指定作用域,则默认为本地作用域,并且不会应用于父实例。

¥From the above code, we can see that hi is missing from the parent instance because the scope is local by default if not specified, and will not apply to parent.

要将钩子应用到父实例,我们可以使用 as 来指定钩子的作用域。

¥To apply the hook to the parent instance, we can use the as to specify scope of the hook.

typescript
import { 
Elysia
} from 'elysia'
const
plugin
= new
Elysia
()
.
derive
({
as
: 'scoped' }, () => {
return {
hi
: 'ok' }
}) .
get
('/child', ({
hi
}) =>
hi
)
const
main
= new
Elysia
()
.
use
(
plugin
)
// ✅ Hi is now available .
get
('/parent', ({
hi
}) =>
hi
)

范围级别

¥Scope level

Elysia 有以下 3 个级别的作用域:作用域类型如下:

¥Elysia has 3 levels of scope as the following: Scope type are as the following:

  • 本地(默认) - 仅应用于当前实例和后代

  • scoped - 应用于父级、当前实例和后代

  • global - 应用于所有应用该插件的实例(所有父级、当前实例和后代)

让我们通过以下示例回顾一下每种作用域类型的功能:

¥Let's review what each scope type does by using the following example:

typescript
import { Elysia } from 'elysia'

// ? Value base on table value provided below
const type = 'local'

const child = new Elysia()
    .get('/child', 'hi')

const current = new Elysia()
    .onBeforeHandle({ as: type }, () => { 
        console.log('hi')
    })
    .use(child)
    .get('/current', 'hi')

const parent = new Elysia()
    .use(current)
    .get('/parent', 'hi')

const main = new Elysia()
    .use(parent)
    .get('/main', 'hi')

通过更改 type 值,结果应如下所示:

¥By changing the type value, the result should be as follows:

typechildcurrentparentmain
'local'
'scoped'
'global'

范围转换

¥Scope cast

要将钩子应用到父级,可以使用以下方法之一:

¥To apply hook to parent may use one of the following:

  1. inline as 仅适用于单个钩子
  2. guard as 适用于某个守卫中的所有钩子
  3. instance as 适用于某个实例中的所有钩子

1. 内联为

¥ Inline as

每个事件监听器都会接受 as 参数来指定钩子的作用域。

¥Every event listener will accept as parameter to specify the scope of the hook.

typescript
import { 
Elysia
} from 'elysia'
const
plugin
= new
Elysia
()
.
derive
({
as
: 'scoped' }, () => {
return {
hi
: 'ok' }
}) .
get
('/child', ({
hi
}) =>
hi
)
const
main
= new
Elysia
()
.
use
(
plugin
)
// ✅ Hi is now available .
get
('/parent', ({
hi
}) =>
hi
)

然而,此方法仅适用于单个钩子,可能不适用于多个钩子。

¥However, this method is apply to only a single hook, and may not be suitable for multiple hooks.

2. 守护为

¥ Guard as

每个事件监听器都会接受 as 参数来指定钩子的作用域。

¥Every event listener will accept as parameter to specify the scope of the hook.

typescript
import { Elysia, t } from 'elysia'

const plugin = new Elysia()
	.guard({
		as: 'scoped', 
		response: t.String(),
		beforeHandle() {
			console.log('ok')
		}
	})
    .get('/child', 'ok')

const main = new Elysia()
    .use(plugin)
    .get('/parent', 'hello')

Guard 允许我们在指定作用域的情况下,一次性将 schemahook 应用于多个路由。

¥Guard alllowing us to apply schema and hook to multiple routes all at once while specifying the scope.

但是,它不支持 deriveresolve 方法。

¥However, it doesn't support derive and resolve method.

3. 实例为

¥ Instance as

as 将读取当前实例的所有钩子和架构范围,并进行修改。

¥as will read all hooks and schema scope of the current instance, modify.

typescript
import { 
Elysia
} from 'elysia'
const
plugin
= new
Elysia
()
.
derive
(() => {
return {
hi
: 'ok' }
}) .
get
('/child', ({
hi
}) =>
hi
)
.
as
('scoped')
const
main
= new
Elysia
()
.
use
(
plugin
)
// ✅ Hi is now available .
get
('/parent', ({
hi
}) =>
hi
)

有时我们也想将插件重新应用于父实例,但由于受 scoped 机制的限制,它只能应用于一个父实例。

¥Sometimes we want to reapply plugin to parent instance as well but as it's limited by scoped mechanism, it's limited to 1 parent only.

要应用于父实例,我们需要将作用域提升到父实例,而 as 是实现此目的的完美方法。

¥To apply to the parent instance, we need to lift the scope up to the parent instance, and as is the perfect method to do so.

这意味着如果你拥有 local 作用域,并希望将其应用于父实例,则可以使用 as('scoped') 来提升它。

¥Which means if you have local scope, and want to apply it to the parent instance, you can use as('scoped') to lift it up.

typescript
import { 
Elysia
,
t
} from 'elysia'
const
plugin
= new
Elysia
()
.
guard
({
response
:
t
.
String
()
}) .
onBeforeHandle
(() => {
console
.
log
('called') })
.
get
('/ok', () => 'ok')
.
get
('/not-ok', () => 1)
Argument of type '() => number' is not assignable to parameter of type 'InlineHandler<NoInfer<IntersectIfObjectSchema<{ body: unknown; headers: unknown; query: unknown; params: {}; cookie: unknown; response: { 200: string; }; }, {}>>, { decorator: {}; store: {}; derive: {}; resolve: {}; } & { ...; }, {}>'. Type '() => number' is not assignable to type '(context: { body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends 200 | "OK", T extends Code extends 200 ? { ...; }[Code] : Code extends "Continue" | ... 59 mor...'. Type 'number' is not assignable to type 'Response | MaybePromise<string | ElysiaCustomStatusResponse<200, string, 200>>'.
.
as
('scoped')
const
instance
= new
Elysia
()
.
use
(
plugin
)
.
get
('/no-ok-parent', () => 2)
Argument of type '() => number' is not assignable to parameter of type 'InlineHandler<NoInfer<IntersectIfObjectSchema<{ body: unknown; headers: unknown; query: unknown; params: {}; cookie: unknown; response: { 200: string; }; }, {} & {}>>, { decorator: {}; store: {}; derive: {}; resolve: {}; } & { ...; }, {}>'. Type '() => number' is not assignable to type '(context: { body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends 200 | "OK", T extends Code extends 200 ? { ...; }[Code] : Code extends "Continue" | ... 59 mor...'. Type 'number' is not assignable to type 'Response | MaybePromise<string | ElysiaCustomStatusResponse<200, string, 200>>'.
.
as
('scoped')
const
parent
= new
Elysia
()
.
use
(
instance
)
// This now error because `scoped` is lifted up to parent .
get
('/ok', () => 3)
Argument of type '() => number' is not assignable to parameter of type 'InlineHandler<NoInfer<IntersectIfObjectSchema<{ body: unknown; headers: unknown; query: unknown; params: {}; cookie: unknown; response: { 200: string; }; }, {} & {}>>, { decorator: {}; store: {}; derive: {}; resolve: {}; } & { ...; }, {}>'. Type '() => number' is not assignable to type '(context: { body: unknown; query: Record<string, string>; params: {}; headers: Record<string, string | undefined>; cookie: Record<string, Cookie<unknown>>; server: Server | null; ... 6 more ...; status: <const Code extends 200 | "OK", T extends Code extends 200 ? { ...; }[Code] : Code extends "Continue" | ... 59 mor...'. Type 'number' is not assignable to type 'Response | MaybePromise<string | ElysiaCustomStatusResponse<200, string, 200>>'.

后代

¥Descendant

默认情况下,插件仅将钩子应用于自身及其后代。

¥By default plugin will apply hook to itself and descendants only.

如果钩子在插件中注册,继承该插件的实例将不会继承钩子和模式。

¥If the hook is registered in a plugin, instances that inherit the plugin will NOT inherit hooks and schema.

typescript
import { Elysia } from 'elysia'

const plugin = new Elysia()
    .onBeforeHandle(() => {
        console.log('hi')
    })
    .get('/child', 'log hi')

const main = new Elysia()
    .use(plugin)
    .get('/parent', 'not log hi')

要将钩子应用于全局,我们需要将钩子指定为全局。

¥To apply hook to globally, we need to specify hook as global.

typescript
import { Elysia } from 'elysia'

const plugin = new Elysia()
    .onBeforeHandle(() => {
        return 'hi'
    })
    .get('/child', 'child')
    .as('scoped')

const main = new Elysia()
    .use(plugin)
    .get('/parent', 'parent')
localhost

GET

延迟加载

¥Lazy Load

默认情况下,模块会主动加载。

¥Modules are eagerly loaded by default.

Elysia 会加载所有模块,然后在启动服务器之前注册并索引所有模块。这强制要求所有模块在开始接受请求之前都已加载。

¥Elysia loads all modules then registers and indexes all of them before starting the server. This enforces that all the modules have loaded before it starts accepting requests.

虽然这对于大多数应用来说没有问题,但对于运行在无服务器环境或边缘函数中的服务器来说,这可能会成为瓶颈,因为在这些环境中启动时间非常重要。

¥While this is fine for most applications, it may become a bottleneck for a server running in a serverless environment or an edge function, in which the startup time is important.

延迟加载可以通过在服务器启动后逐步索引模块来减少启动时间。

¥Lazy-loading can help decrease startup time by deferring modules to be gradually indexed after the server start.

当某些模块很重且导入启动时间至关重要时,延迟加载模块是一个不错的选择。

¥Lazy-loading modules are a good option when some modules are heavy and importing startup time is crucial.

默认情况下,任何未使用 await 的异步插件都被视为延迟模块,导入语句被视为延迟加载模块。

¥By default, any async plugin without await is treated as a deferred module and the import statement as a lazy-loading module.

两者都将在服务器启动后注册。

¥Both will be registered after the server is started.

延迟加载模块

¥Deferred Module

deferred 模块是一个异步插件,可以在服务器启动后注册。

¥The deferred module is an async plugin that can be registered after the server is started.

typescript
// plugin.ts
import { Elysia, file } from 'elysia'
import { loadAllFiles } from './files'

export const loadStatic = async (app: Elysia) => {
    const files = await loadAllFiles()

    files.forEach((asset) => app
        .get(asset, file(file))
    )

    return app
}

在主文件中:

¥And in the main file:

typescript
import { Elysia } from 'elysia'
import { loadStatic } from './plugin'

const app = new Elysia()
    .use(loadStatic)

Elysia 静态插件也是一个延迟模块,因为它异步加载文件并注册文件路径。

¥Elysia static plugin is also a deferred module, as it loads files and registers files path asynchronously.

延迟加载模块

¥Lazy Load Module

与异步插件相同,延迟加载模块将在服务器启动后注册。

¥Same as the async plugin, the lazy-load module will be registered after the server is started.

延迟加载模块可以是同步函数或异步函数,只要该模块与 import 一起使用,就会被延迟加载。

¥A lazy-load module can be both sync or async function, as long as the module is used with import the module will be lazy-loaded.

typescript
import { Elysia } from 'elysia'

const app = new Elysia()
    .use(import('./plugin'))

当模块计算量大且/或阻塞时,建议使用模块延迟加载。

¥Using module lazy-loading is recommended when the module is computationally heavy and/or blocking.

为了确保模块在服务器启动前注册,我们可以在延迟模块上使用 await

¥To ensure module registration before the server starts, we can use await on the deferred module.

测试

¥Testing

在测试环境中,我们可以使用 await app.modules 等待延迟加载和懒加载模块。

¥In a test environment, we can use await app.modules to wait for deferred and lazy-loading modules.

typescript
import { describe, expect, it } from 'bun:test'
import { Elysia } from 'elysia'

describe('Modules', () => {
    it('inline async', async () => {
        const app = new Elysia()
              .use(async (app) =>
                  app.get('/async', () => 'async')
              )

        await app.modules

        const res = await app
            .handle(new Request('http://localhost/async'))
            .then((r) => r.text())

        expect(res).toBe('async')
    })
})