[JavaScript, Node.JS, TypeScript] Dynamic modules в NestJS
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
NestJS — фреймворк, вобравший в себя преимущества TypeScript, IoC/DI и структуру Angular, стремительно развивается, набирая популярность.Множество методик и практик описано в официальной документации. Рассмотрим написание собственного Dynamic module и его публикацию в npm. В качестве библиотеки используем Mailchimp Transaction API.Некоторые концепции опущены. Подробнее можно прочитать в публикации John Biundo — Build a NestJS Module for Knex.js.Dynamic moduleСоздание полноценного Dynamic module обрекает нас на выполнение некоторых рутинных приготовлений. Хорошо, что вышеупомянутый John Biundo подготовил для нас небольшую утилиту, которая позволяет автоматизировать рутину.Установим утилиту глобально dyn-schematics.
npm install @nestjsplus/dyn-schematics -g
Запустим следующую команду, заменив на имя модуля
nest g -c @nestjsplus/dyn-schematics dynpkg <NAME>
Для быстрого тестирования, предпочтительнее создать тестовый клиент
? Generate a testing client? Yes
Будет создана следующая структура проекта:
| .npmignore
| .prettierrc
| nest-cli.json
| package.json
| README.md
| tsconfig.build.json
| tsconfig.json
| tslint.json
|
\---src
| constants.ts
| index.ts
| mailchimp-habr.module.ts
| mailchimp-habr.providers.ts
| mailchimp-habr.service.ts
| main.ts
|
+---interfaces
| index.ts
| mailchimp-habr-module-async-options.interface.ts
| mailchimp-habr-options-factory.interface.ts
| mailchimp-habr-options.interface.ts
|
\---mailchimp-habr-client
mailchimp-habr-client.controller.ts
mailchimp-habr-client.module.ts
Обновите зависимости в package.json. Отлично подойдет npm-check-updates. При возникновении конфликтов версий, например, RxJS, установите совместимые версии, основываясь на зависимостях NestJS. Установим библиотеку Mailchimp Transaction API.
npm i --save @mailchimp/mailchimp_transactional
Библиотека mailchimp предлагает передать ей в качестве аргумента строковый литерал. Чтобы сильно не отходить от шаблона, созданного dyn-schematics, изменим тип основных параметров
//mailchimp-habr-options.interface.ts
export type MailchimpHabrOptions = string;
//mailchimp-habr-options-factory.interface.ts
import { MailchimpHabrOptions } from './mailchimp-habr-options.interface';
export interface MailchimpHabrOptionsFactory {
createMailchimpHabrOptions(): | Promise<MailchimpHabrOptions> | MailchimpHabrOptions;
}
//mailchimp-habr-module-async-options.interface.ts
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
import { MailchimpHabrOptionsFactory } from './mailchimp-habr-options-factory.interface';
import { MailchimpHabrOptions } from './mailchimp-habr-options.interface';
export interface MailchimpHabrAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
inject?: any[];
useExisting?: Type<MailchimpHabrOptionsFactory>;
useClass?: Type<MailchimpHabrOptionsFactory>;
useFactory?: (...args: any[]) => Promise<MailchimpHabrOptions> | MailchimpHabrOptions;
}
Изменим строковые константы на Symbol и добавим MAILCHIMP_HABR_TOKEN
// constants.ts
export const MAILCHIMP_HABR_OPTIONS = Symbol('MAILCHIMP_HABR_OPTIONS');
export const MAILCHIMP_HABR_TOKEN = Symbol('MAILCHIMP_HABR_TOKEN');
Внесем некоторые изменения в mailchimp-habr.service.ts
//mailchimp-habr.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { MAILCHIMP_HABR_OPTIONS} from './constants';
import { MailchimpHabrOptions } from './interfaces';
import * as Mailchimp from '@mailchimp/mailchimp_transactional/src';
interface IMailchimpHabrService {
getInstance(): Mailchimp;
}
@Injectable()
export class MailchimpHabrService implements IMailchimpHabrService {
private serviceInstance: Mailchimp;
constructor(
@Inject(MAILCHIMP_HABR_OPTIONS) private mailchimpHabrOptions: MailchimpHabrOptions,
) {}
async getInstance(): Mailchimp {
return this.serviceInstance ? this.serviceInstance : Mailchimp(this.mailchimpHabrOptions);
}
}
Изменения совсем небольшие, но они являются ключевыми для этого сервиса.Во-первых, сервис имеет декоратор @Injectable(), который говорит NestJS, что этот сервис может быть использован для DI. В свою очередь constructor сервиса, сам принимает параметры для инъекции, используя декоратор @Inject(MAILCHIMP_HABR_OPTIONS) с аргументом типа Symbol, который был создан автоматически, а потом нами изменён.Единственный метод нашего сервиса, отвечает за создание и возврат экземпляра. Его работу мы увидим при реализации самого Dynamic Module.Команда dyn-schematics заботливо создала для нас вспомогательную функцию для создания провайдера
//mailchimp-habr.providers.ts
import { MAILCHIMP_HABR_OPTIONS } from './constants';
import { MailchimpHabrOptions } from './interfaces';
export function createMailchimpHabrProviders(options: MailchimpHabrOptions) {
return [
{
provide: MAILCHIMP_HABR_OPTIONS,
useValue: options,
},
];
}
Она максимально тривиальна, поэтому, думаю, что не нуждается в пояснении. Подробнее вы можете узнать в документации NestJS.В NestJS Dynamic Modules должны реализовывать интерфейс статических модулей. За одним исключением, возвращаемый объект должен иметь дополнительно название модуля module: MailchimpHabrModule.Открыв файл mailchimp-habr.module.ts, рассмотрим статическую регистрацию
export class MailchimpHabrModule {
public static register(options: MailchimpHabrOptions): DynamicModule {
return {
module: MailchimpHabrModule,
providers: createMailchimpHabrProviders(options),
};
}
}
Имя метода можно изменить на forRoot, аналогично другим модулям NestJS.Метод достаточно прост, за исключением того, что в нем не хватает двух вещей, а именно нескольких провайдеров и массива exports. Без этих моментов, к сожалению, ничего работать не будет!Добавить недостающие элементы можно прямо в возвращаемый объект, или, если посмотреть на декоратор @Module класса MailchimpHabrModule , то можно заметить, что в нём указаны провайдеры и экспорты, хотя в них только один элемент! Элементы, указанные в декораторе @Module будут добавлены к возвращаемым из статических методов регистрации (forRoot/register и forRootAsync/registerAsync). Поэтому остаётся лишь добавить немного “магии”, а именно custom provider.Добавим следующие строки в файл mailchimp-habr.providers.ts
// mailchimp-habr.providers.ts
export const MailchimpProvider: Provider = {
provide: MAILCHIMP_HABR_TOKEN,
useFactory: async (mailchimpService) => mailchimpService.getInstance(),
inject: [MailchimpHabrService],
};
Добавим этот провайдер в декоратор @Module в файле mailchimp-habr.module.ts
@Global()
@Module({
providers: [MailchimpProvider, MailchimpHabrService],
exports: [MailchimpProvider, MailchimpHabrService],
})
export class MailchimpHabrModule {
/* */
}
Почти готово! Добавим немного “сахарку”, точнее декоратор для инъекций. Создайте файл (или прямо в модуле) с именем mailchimp-habr.decorator.ts
//mailchimp-habr.decorator.ts
import { Inject } from '@nestjs/common';
import { MAILCHIMP_HABR_TOKEN } from './constants';
export const InjectMailchimp = () => Inject(MAILCHIMP_HABR_TOKEN);
Теперь точно готово! Тестируем! Как? Для этого у нас есть минифицированный клиент, созданный при создании проекта.
import { Module } from '@nestjs/common';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller';
import { MailchimpHabrModule } from '../mailchimp-habr.module';
@Module({
controllers: [MailchimpHabrClientController],
imports: [MailchimpHabrModule.forRoot('YOUR_API_KEY')],
})
export class MailchimpHabrClientModule {}
import { Controller, Get } from '@nestjs/common';
import { InjectMailchimp } from '../mailchimp-habr.decorator';
@Controller()
export class MailchimpHabrClientController {
constructor(@InjectMailchimp() private readonly mailchimpHabrService) {}
@Get()
index() {
return this.mailchimpHabrService.users.ping();
}
}
Можно создать отдельный сервис и инъекцию ожидать в нём.
npm run start:dev
Откройте страницу в браузере http://localhost:3000/ (или воспользуйтесь curl, postman, insomnia...).Вы должны увидеть слово «PONG!», но это только при наличии действующего api key. Если у вас его нет, то достаточно проверить, что наш сервис имеет метод this.mailchimpHabrService.users.ping();А как насчет асинхронной регистрации? Да всё с ней отлично! Она уже будет работать! Попробуйте зарегистрировать модуль не через forRoot/register а через forRootAsync/registerAsync.
import { Module } from '@nestjs/common';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller';
import { MailchimpHabrModule } from '../mailchimp-habr.module';
@Module({
controllers: [MailchimpHabrClientController],
imports: [MailchimpHabrModule.forRootAsync({ useFactory: () => 'API_KEY' })],
})
export class MailchimpHabrClientModule {}
Проверяем... Работает!Но что, если мы не хотим указывать ключ на прямую в useFactory? Стоит попробовать воспользоваться другим модулей от NestJS — ConfigModule.Для этого нам нужно добавить его в imports и в inject при регистрации нашего модуля
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MailchimpHabrModule } from '../mailchimp-habr.module';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller';
@Module({
controllers: [MailchimpHabrClientController],
imports: [
ConfigModule.forRoot(),
MailchimpHabrModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (config: ConfigService) => {
return config.get('API_KEY');
},
inject: [ConfigService],
})],
})
export class MailchimpHabrClientModule {}
Получилось? Думаю, что нет. Скорее всего вы получили ошибку:
Что-то здесь не так. Следуя документации ищем ошибку... Внимательно посмотрев на forRootAsync, обнаружим, что мы никак не обрабатываем imports в options. Добавим.
export class MailchimpHabrModule {
public static forRootAsync(options: MailchimpHabrAsyncOptions,): DynamicModule {
return {
module: MailchimpHabrModule,
imports: options.imports ?? [], // Добавим imports
providers: [
...this.createProviders(options),
],
};
}
}
Пробуем ещё раз. Всё работает!Unit тестированиеNestJS поддерживает Jest «из коробки». Воспользуемся такой возможностью. Создадим файл mailchimp-habr.spec.ts
import * as Mailchimp from '@mailchimp/mailchimp_transactional/src';
import { Test } from '@nestjs/testing';
import { MAILCHIMP_HABR_TOKEN, MailchimpHabrModule } from './';
describe('Mailchimp forRoot', () => {
let mailchimpService: Mailchimp;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [MailchimpHabrModule.forRoot('MAILCHIPM_API_KEY')],
}).compile();
mailchimpService = moduleRef.get<Mailchimp>(MAILCHIMP_HABR_TOKEN);
});
describe('mailchimpHabrService', () => {
it('method ping exists', async () => {
expect(mailchimpService.users.ping).toBeDefined();
});
it('method is a function', async () => {
expect(mailchimpService.users.ping).toBeInstanceOf(Function);
});
it('users.ping() should return "PONG"', async () => {
const result = 'PONG';
jest
.spyOn(mailchimpService.users, 'ping')
.mockImplementation(() => result);
expect(await mailchimpService.users.ping()).toBe(result);
});
});
});
Все тесты проходят. Аналогично тестируем регистрацию через forRootAsync().ИтогDynamic modules NestJS позволяют «оборачивать» множество библиотек! Написание собственных модулей позволяет сократить код в проектах. А публикация их в npm, облегчает жизнь не только вам.Модуль доступен в npmи GitHub.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, CSS, HTML] Дизайнерский Multiselect на протеинах
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] React испортил веб-разработку (перевод)
- [Разработка веб-сайтов, Работа с видео, Программирование, Видеоконференцсвязь] How to make a multipoint WebRTC conference (MCU) with recording and screen-sharing in the browser
- [Разработка веб-сайтов, Работа с видео, Программирование, Видеоконференцсвязь] Как сделать многоточечную WebRTC-конференцию (MCU) с записью и демонстрацией экрана в браузере
- [Веб-дизайн, Разработка веб-сайтов, JavaScript, Программирование] Лёгкая, гибкая, производительная обёртка над Web Animations API — @okikio/animate (перевод)
- [JavaScript, Node.JS, DevOps] Непрерывная интеграция и развертывание (Deployment) с помощью Jenkins (перевод)
- [JavaScript, Программирование, VueJS] Как работают функции provide и inject во Vue 3?
- [Разработка веб-сайтов, HTML, Angular, TypeScript] Bindon: малоизвестные фишки шаблонов Angular
- [JavaScript, Разработка игр, Логические игры] Путеводитель разработчика по Garbo-боту
- [JavaScript, Программирование] Взлом JavaScript с помощью JavaScript (перевод)
Теги для поиска: #_javascript, #_node.js, #_typescript, #_typescript, #_nestjs, #_module, #_backend, #_javascript, #_node.js, #_typescript
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 03:22
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
NestJS — фреймворк, вобравший в себя преимущества TypeScript, IoC/DI и структуру Angular, стремительно развивается, набирая популярность.Множество методик и практик описано в официальной документации. Рассмотрим написание собственного Dynamic module и его публикацию в npm. В качестве библиотеки используем Mailchimp Transaction API.Некоторые концепции опущены. Подробнее можно прочитать в публикации John Biundo — Build a NestJS Module for Knex.js.Dynamic moduleСоздание полноценного Dynamic module обрекает нас на выполнение некоторых рутинных приготовлений. Хорошо, что вышеупомянутый John Biundo подготовил для нас небольшую утилиту, которая позволяет автоматизировать рутину.Установим утилиту глобально dyn-schematics. npm install @nestjsplus/dyn-schematics -g
nest g -c @nestjsplus/dyn-schematics dynpkg <NAME>
? Generate a testing client? Yes
| .npmignore
| .prettierrc | nest-cli.json | package.json | README.md | tsconfig.build.json | tsconfig.json | tslint.json | \---src | constants.ts | index.ts | mailchimp-habr.module.ts | mailchimp-habr.providers.ts | mailchimp-habr.service.ts | main.ts | +---interfaces | index.ts | mailchimp-habr-module-async-options.interface.ts | mailchimp-habr-options-factory.interface.ts | mailchimp-habr-options.interface.ts | \---mailchimp-habr-client mailchimp-habr-client.controller.ts mailchimp-habr-client.module.ts npm i --save @mailchimp/mailchimp_transactional
//mailchimp-habr-options.interface.ts
export type MailchimpHabrOptions = string; //mailchimp-habr-options-factory.interface.ts
import { MailchimpHabrOptions } from './mailchimp-habr-options.interface'; export interface MailchimpHabrOptionsFactory { createMailchimpHabrOptions(): | Promise<MailchimpHabrOptions> | MailchimpHabrOptions; } //mailchimp-habr-module-async-options.interface.ts
import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; import { MailchimpHabrOptionsFactory } from './mailchimp-habr-options-factory.interface'; import { MailchimpHabrOptions } from './mailchimp-habr-options.interface'; export interface MailchimpHabrAsyncOptions extends Pick<ModuleMetadata, 'imports'> { inject?: any[]; useExisting?: Type<MailchimpHabrOptionsFactory>; useClass?: Type<MailchimpHabrOptionsFactory>; useFactory?: (...args: any[]) => Promise<MailchimpHabrOptions> | MailchimpHabrOptions; } // constants.ts
export const MAILCHIMP_HABR_OPTIONS = Symbol('MAILCHIMP_HABR_OPTIONS'); export const MAILCHIMP_HABR_TOKEN = Symbol('MAILCHIMP_HABR_TOKEN'); //mailchimp-habr.service.ts
import { Injectable, Inject } from '@nestjs/common'; import { MAILCHIMP_HABR_OPTIONS} from './constants'; import { MailchimpHabrOptions } from './interfaces'; import * as Mailchimp from '@mailchimp/mailchimp_transactional/src'; interface IMailchimpHabrService { getInstance(): Mailchimp; } @Injectable() export class MailchimpHabrService implements IMailchimpHabrService { private serviceInstance: Mailchimp; constructor( @Inject(MAILCHIMP_HABR_OPTIONS) private mailchimpHabrOptions: MailchimpHabrOptions, ) {} async getInstance(): Mailchimp { return this.serviceInstance ? this.serviceInstance : Mailchimp(this.mailchimpHabrOptions); } } //mailchimp-habr.providers.ts
import { MAILCHIMP_HABR_OPTIONS } from './constants'; import { MailchimpHabrOptions } from './interfaces'; export function createMailchimpHabrProviders(options: MailchimpHabrOptions) { return [ { provide: MAILCHIMP_HABR_OPTIONS, useValue: options, }, ]; } export class MailchimpHabrModule {
public static register(options: MailchimpHabrOptions): DynamicModule { return { module: MailchimpHabrModule, providers: createMailchimpHabrProviders(options), }; } } // mailchimp-habr.providers.ts
export const MailchimpProvider: Provider = { provide: MAILCHIMP_HABR_TOKEN, useFactory: async (mailchimpService) => mailchimpService.getInstance(), inject: [MailchimpHabrService], }; @Global()
@Module({ providers: [MailchimpProvider, MailchimpHabrService], exports: [MailchimpProvider, MailchimpHabrService], }) export class MailchimpHabrModule { /* */ } //mailchimp-habr.decorator.ts
import { Inject } from '@nestjs/common'; import { MAILCHIMP_HABR_TOKEN } from './constants'; export const InjectMailchimp = () => Inject(MAILCHIMP_HABR_TOKEN); import { Module } from '@nestjs/common';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller'; import { MailchimpHabrModule } from '../mailchimp-habr.module'; @Module({ controllers: [MailchimpHabrClientController], imports: [MailchimpHabrModule.forRoot('YOUR_API_KEY')], }) export class MailchimpHabrClientModule {} import { Controller, Get } from '@nestjs/common';
import { InjectMailchimp } from '../mailchimp-habr.decorator'; @Controller() export class MailchimpHabrClientController { constructor(@InjectMailchimp() private readonly mailchimpHabrService) {} @Get() index() { return this.mailchimpHabrService.users.ping(); } } npm run start:dev
import { Module } from '@nestjs/common';
import { MailchimpHabrClientController } from './mailchimp-habr-client.controller'; import { MailchimpHabrModule } from '../mailchimp-habr.module'; @Module({ controllers: [MailchimpHabrClientController], imports: [MailchimpHabrModule.forRootAsync({ useFactory: () => 'API_KEY' })], }) export class MailchimpHabrClientModule {} import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { MailchimpHabrModule } from '../mailchimp-habr.module'; import { MailchimpHabrClientController } from './mailchimp-habr-client.controller'; @Module({ controllers: [MailchimpHabrClientController], imports: [ ConfigModule.forRoot(), MailchimpHabrModule.forRootAsync({ imports: [ConfigModule], useFactory: async (config: ConfigService) => { return config.get('API_KEY'); }, inject: [ConfigService], })], }) export class MailchimpHabrClientModule {} Что-то здесь не так. Следуя документации ищем ошибку... Внимательно посмотрев на forRootAsync, обнаружим, что мы никак не обрабатываем imports в options. Добавим. export class MailchimpHabrModule {
public static forRootAsync(options: MailchimpHabrAsyncOptions,): DynamicModule { return { module: MailchimpHabrModule, imports: options.imports ?? [], // Добавим imports providers: [ ...this.createProviders(options), ], }; } } import * as Mailchimp from '@mailchimp/mailchimp_transactional/src';
import { Test } from '@nestjs/testing'; import { MAILCHIMP_HABR_TOKEN, MailchimpHabrModule } from './'; describe('Mailchimp forRoot', () => { let mailchimpService: Mailchimp; beforeEach(async () => { const moduleRef = await Test.createTestingModule({ imports: [MailchimpHabrModule.forRoot('MAILCHIPM_API_KEY')], }).compile(); mailchimpService = moduleRef.get<Mailchimp>(MAILCHIMP_HABR_TOKEN); }); describe('mailchimpHabrService', () => { it('method ping exists', async () => { expect(mailchimpService.users.ping).toBeDefined(); }); it('method is a function', async () => { expect(mailchimpService.users.ping).toBeInstanceOf(Function); }); it('users.ping() should return "PONG"', async () => { const result = 'PONG'; jest .spyOn(mailchimpService.users, 'ping') .mockImplementation(() => result); expect(await mailchimpService.users.ping()).toBe(result); }); }); }); =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 03:22
Часовой пояс: UTC + 5