Vendureプラグイン開発の基礎¶
このドキュメントでは、Vendureプラグインの実装手順とベストプラクティスを解説します。既存チームメンバーがプラグイン開発をスムーズに開始できるよう、Vendure公式ドキュメントをベースに、プロジェクト固有の情報を統合しています。
関連ドキュメント:
- プラグインの設計方針やアーキテクチャパターンは プラグイン概要と設計方針 を参照
- Vendureの深層理解は Vendure開発ハンドブック を参照
- Vendure公式ドキュメント: Plugins Guide
目次¶
プラグインとは¶
Vendureプラグインは、Vendureの機能を拡張するためのモジュールです。プラグインは以下のような機能を提供できます:
- GraphQL APIの拡張: カスタムクエリ・ミューテーションの追加
- データモデルの拡張: カスタムエンティティやカスタムフィールドの追加
- ビジネスロジックの実装: サービスクラスによるビジネスロジック
- イベントハンドリング: Vendureのイベントシステムとの統合
- Vendure標準機能の拡張: ShippingCalculator、PromotionAction等のカスタム実装
プラグインの特徴¶
- NestJSモジュールの拡張: Vendureプラグインは
@VendurePlugin()デコレーターで装飾されたNestJSモジュール - 独立性: 各プラグインは独立したコンポーネントとして動作
- 再利用性: パッケージとして公開・共有が可能
- 設定可能性: 初期化時のオプションでカスタマイズ可能
プラグイン開発の推奨フロー¶
方法1: CLIによる自動生成(推奨)¶
Vendure CLIを使用すると、プラグインの雛形を自動生成できます。
# プラグインの自動生成
npx vendure add
# 対話式で以下を選択:
# - Plugin name: my-plugin
# - Package manager: pnpm
# - Plugin location: packages/plugins/my-plugin
生成されるファイル構成:
packages/plugins/my-plugin/
├── src/
│ ├── index.ts # プラグイン定義
│ ├── api/
│ │ └── api-extensions.ts # GraphQL拡張
│ ├── entities/
│ │ └── my-entity.ts # カスタムエンティティ
│ └── services/
│ └── my-service.ts # ビジネスロジック
├── package.json
└── tsconfig.json
自動生成後のカスタマイズポイント:
index.tsのプラグイン設定を要件に合わせて調整- GraphQLスキーマの定義
- サービスクラスの実装
- エンティティの定義
vendure-config.tsへの登録
方法2: 手動作成¶
既存プラグインを参考に手動で作成することも可能です。
推奨参考例:
- シンプルな例:
/packages/plugins/src/inventory-management.ts - 完全な例:
/packages/plugins/consent-system/src/index.ts
プラグインの基本構造¶
@VendurePluginデコレーター¶
プラグインは @VendurePlugin() デコレーターで装飾されたクラスとして定義します。
📚 公式ドキュメント: VendurePlugin API Reference
import { PluginCommonModule, VendurePlugin } from '@vendure/core';
import { MyService } from './services/my.service';
import { MyResolver } from './api/my.resolver';
import { MyEntity } from './entities/my.entity';
import gql from 'graphql-tag';
const myGraphqlSchema = gql`
extend type Query {
myCustomQuery: String!
}
`;
@VendurePlugin({
// NestJS標準オプション
imports: [PluginCommonModule],
providers: [MyService],
exports: [MyService],
// Vendure固有オプション
entities: [MyEntity],
compatibility: '^3.5.0',
shopApiExtensions: {
schema: myGraphqlSchema,
resolvers: [MyResolver],
},
adminApiExtensions: {
schema: myGraphqlSchema,
resolvers: [MyResolver],
},
configuration: config => {
// カスタムフィールドの追加等
return config;
},
})
export class MyPlugin {
static init(options?: MyPluginOptions) {
// プラグイン初期化ロジック
return MyPlugin;
}
}
メタデータオプション¶
NestJS標準オプション¶
| オプション | 説明 |
|---|---|
imports |
インポートする他のモジュール(通常はPluginCommonModuleを含む) |
providers |
サービスやリゾルバーなどのプロバイダー |
exports |
他のモジュールで使用可能にするプロバイダー |
controllers |
RESTコントローラー(Vendureでは通常使用しない) |
Vendure固有オプション¶
| オプション | 説明 |
|---|---|
entities |
TypeORMエンティティの配列 |
compatibility |
Vendureバージョンの互換性(セマンティックバージョニング) |
shopApiExtensions |
Shop API用のGraphQL拡張 |
adminApiExtensions |
Admin API用のGraphQL拡張 |
configuration |
VendureConfigを変更する関数 |
8ステップ実装ガイド¶
Step 1: プラグインファイルの作成¶
プラグインのエントリーポイント index.ts を作成します。
import { PluginCommonModule, VendurePlugin } from '@vendure/core';
@VendurePlugin({
imports: [PluginCommonModule],
})
export class MyPlugin {}
Step 2: エンティティの定義¶
TypeORMエンティティを定義して、カスタムデータモデルを追加します。
📚 公式ドキュメント: Database Entities
import { Entity, Column, ManyToOne } from 'typeorm';
import { VendureEntity, DeepPartial, ID, Customer } from '@vendure/core';
@Entity()
export class MyCustomEntity extends VendureEntity {
constructor(input?: DeepPartial<MyCustomEntity>) {
super(input);
}
@ManyToOne(() => Customer)
customer: Customer;
@Column()
customerId: ID;
@Column()
name: string;
@Column('text')
description: string;
}
重要:
- カスタムエンティティは
VendureEntityを継承 @Entity()デコレーターを使用- TypeORMの標準的なデコレーター(
@Column,@ManyToOne等)を使用
プラグインにエンティティを登録:
@VendurePlugin({
entities: [MyCustomEntity],
})
export class MyPlugin {}
Step 3: カスタムフィールドの追加¶
既存のVendureエンティティを拡張するには、カスタムフィールドを使用します。
📚 公式ドキュメント: Customizing Models with Custom Fields
@VendurePlugin({
configuration: config => {
config.customFields.Product = config.customFields.Product ?? [];
config.customFields.Product.push({
name: 'customField',
type: 'string',
label: [{ languageCode: 'ja', value: 'カスタムフィールド' }],
description: [{ languageCode: 'ja', value: '説明' }],
public: true, // Shop APIで公開するか
nullable: true,
defaultValue: 'default',
});
return config;
},
})
export class MyPlugin {}
カスタムフィールドのタイプ:
string,localeString,int,float,boolean,datetimetext,localeText(長いテキスト)relation(リレーション)
重要: カスタムフィールドを追加した後は、マイグレーションを生成して実行する必要があります。
Step 4: サービスの実装¶
ビジネスロジックをサービスクラスに実装します。
📚 公式ドキュメント: Vendure Services と RequestContext の詳細は 公式ドキュメントを参照
import { Injectable } from '@nestjs/common';
import { TransactionalConnection, RequestContext } from '@vendure/core';
import { MyCustomEntity } from '../entities/my-custom.entity';
@Injectable()
export class MyService {
constructor(private connection: TransactionalConnection) {}
async findAll(ctx: RequestContext): Promise<MyCustomEntity[]> {
return this.connection.getRepository(ctx, MyCustomEntity).find();
}
async create(
ctx: RequestContext,
input: { name: string; description: string },
): Promise<MyCustomEntity> {
const entity = new MyCustomEntity(input);
return this.connection.getRepository(ctx, MyCustomEntity).save(entity);
}
}
重要:
@Injectable()デコレーターを使用TransactionalConnectionを使用してデータベースにアクセス- すべてのメソッドで
RequestContextを第一引数として受け取る
プラグインにサービスを登録:
@VendurePlugin({
providers: [MyService],
})
export class MyPlugin {}
Step 5: GraphQLスキーマの拡張¶
GraphQLスキーマを定義してAPIを拡張します。
📚 公式ドキュメント: Extending the GraphQL API
import gql from 'graphql-tag';
const myGraphqlSchema = gql`
type MyCustomEntity {
id: ID!
name: String!
description: String!
}
extend type Query {
myCustomEntities: [MyCustomEntity!]!
myCustomEntity(id: ID!): MyCustomEntity
}
extend type Mutation {
createMyCustomEntity(input: CreateMyCustomEntityInput!): MyCustomEntity!
}
input CreateMyCustomEntityInput {
name: String!
description: String!
}
`;
プラグインに登録:
@VendurePlugin({
shopApiExtensions: {
schema: myGraphqlSchema,
resolvers: [MyResolver],
},
})
export class MyPlugin {}
Step 6: リゾルバーの実装¶
GraphQLリゾルバーを実装してAPIを提供します。
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import {
Ctx,
RequestContext,
Allow,
Permission,
Transaction,
} from '@vendure/core';
import { MyService } from '../services/my.service';
@Resolver()
export class MyResolver {
constructor(private myService: MyService) {}
@Query()
@Allow(Permission.Public) // 権限制御
async myCustomEntities(@Ctx() ctx: RequestContext) {
return this.myService.findAll(ctx);
}
@Mutation()
@Transaction() // トランザクション管理
@Allow(Permission.UpdateCatalog)
async createMyCustomEntity(
@Ctx() ctx: RequestContext,
@Args('input') input: { name: string; description: string },
) {
return this.myService.create(ctx, input);
}
}
重要なデコレーター:
@Query(),@Mutation(): GraphQLのクエリ・ミューテーション@Ctx(): RequestContextの取得@Args(): GraphQLの引数取得@Allow(): 権限制御@Transaction(): トランザクション管理
Step 7: バージョン互換性の指定¶
プラグインがサポートするVendureのバージョンを指定します。
@VendurePlugin({
compatibility: '^3.5.0', // Vendure 3.5.x と互換性あり
})
export class MyPlugin {}
本プロジェクトの標準:
- Vendure:
^3.5.0 - Node.js:
>=22.11.0
Step 8: VendureConfigへの登録¶
vendure-config.ts でプラグインを登録します。
// apps/vendure-server/src/vendure-config.ts
import { MyPlugin } from '@ritsubi/plugins';
export const config: VendureConfig = {
// ...
plugins: [
// ...既存のプラグイン
MyPlugin.init({
// プラグインオプション
enableFeatureX: true,
}),
],
};
ディレクトリ構造のベストプラクティス¶
標準的なプラグイン構成¶
packages/plugins/my-plugin/
├── src/
│ ├── index.ts # プラグイン定義(エントリーポイント)
│ ├── entities/ # TypeORMエンティティ
│ │ ├── my-entity.entity.ts
│ │ └── related-entity.entity.ts
│ ├── services/ # ビジネスロジック
│ │ ├── my.service.ts
│ │ └── helper.service.ts
│ ├── api/ # GraphQL API
│ │ ├── my.resolver.ts
│ │ ├── schema.ts # GraphQLスキーマ定義
│ │ └── types.ts # TypeScript型定義
│ ├── strategies/ # Vendure Strategy実装(オプション)
│ │ └── my-strategy.ts
│ └── jobs/ # バックグラウンドジョブ(オプション)
│ └── my-job.ts
├── package.json
├── tsconfig.json
└── README.md
プロジェクト固有の構成¶
本プロジェクトでは、2つのプラグイン管理方法を併用しています:
パターン1: パッケージとして管理(推奨)¶
packages/plugins/my-plugin/
└── src/
└── index.ts
例: consent-system, wishlist
パターン2: 共有ソースとして管理¶
packages/plugins/src/
└── my-plugin.ts
例: customer-visibility, sb-payment-link
マイグレーション管理¶
マイグレーションの生成¶
エンティティやカスタムフィールドを追加・変更した後、マイグレーションを生成します。
# Vendure CLIでマイグレーション生成
cd apps/vendure-server
pnpm run migrate:generate --name=add-my-custom-entity
生成されるファイル:
apps/vendure-server/src/migrations/
└── 1234567890123-add-my-custom-entity.ts
マイグレーションの実行¶
# 開発環境
pnpm run db:migrate:local
# 本番環境
pnpm run db:migrate
マイグレーションの確認¶
// 生成されたマイグレーションファイルの例
export class AddMyCustomEntity1234567890123 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE "my_custom_entity" (
"id" SERIAL PRIMARY KEY,
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
"name" VARCHAR NOT NULL,
"description" TEXT NOT NULL
)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "my_custom_entity"`);
}
}
重要:
- マイグレーションは自動生成されるため、手動での編集は最小限に
- 本番環境では
synchronize: falseを設定し、必ずマイグレーションを使用 - マイグレーションはバージョン管理にコミット
詳細は Vendure Migrations ガイド を参照してください。
ライフサイクルフック¶
NestJSライフサイクルフックの活用¶
プラグインはNestJSのライフサイクルフックを実装できます。
📚 公式ドキュメント: Plugin Lifecycle Methods
import { OnModuleInit, OnApplicationBootstrap } from '@nestjs/common';
@VendurePlugin({
// ...
})
export class MyPlugin implements OnModuleInit, OnApplicationBootstrap {
async onModuleInit() {
// モジュール初期化時の処理
console.log('MyPlugin: Module initialized');
}
async onApplicationBootstrap() {
// アプリケーション起動時の処理
console.log('MyPlugin: Application bootstrapped');
}
}
利用可能なライフサイクルフック¶
| フック | 実行タイミング |
|---|---|
onModuleInit |
モジュール初期化時 |
onApplicationBootstrap |
アプリケーション起動完了時 |
onModuleDestroy |
モジュール破棄時 |
beforeApplicationShutdown |
アプリケーションシャットダウン前 |
onApplicationShutdown |
アプリケーションシャットダウン時 |
サーバー/ワーカーコンテキストの区別¶
Vendureはサーバープロセスとワーカープロセスの両方で動作します。特定のコンテキストでのみ処理を実行したい場合は、ProcessContext
を使用します。
import { ProcessContext, Inject } from '@vendure/core';
@VendurePlugin({
// ...
})
export class MyPlugin implements OnApplicationBootstrap {
constructor(@Inject(ProcessContext) private processContext: ProcessContext) {}
async onApplicationBootstrap() {
if (this.processContext === ProcessContext.Server) {
// サーバープロセスでのみ実行
console.log('Running in server process');
} else if (this.processContext === ProcessContext.Worker) {
// ワーカープロセスでのみ実行
console.log('Running in worker process');
}
}
}
テストとデバッグ¶
GraphiQLでのAPI確認¶
GraphiQLを使用してプラグインのAPIをテストできます。
アクセス方法:
- Shop API:
http://localhost:4000/shop-api - Admin API:
http://localhost:4000/admin-api
クエリ例:
query {
myCustomEntities {
id
name
description
}
}
mutation {
createMyCustomEntity(
input: { name: "Test Entity", description: "Test Description" }
) {
id
name
}
}
詳細は API ドキュメント・可視化ガイド を参照してください。
ユニットテスト¶
サービスとビジネスロジックのテスト例:
📚 公式ドキュメント: Testing
import { Test } from '@nestjs/testing';
import { MyService } from '../services/my.service';
import { TransactionalConnection } from '@vendure/core';
describe('MyService', () => {
let service: MyService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
MyService,
{
provide: TransactionalConnection,
useValue: mockConnection,
},
],
}).compile();
service = module.get<MyService>(MyService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should create entity', async () => {
const result = await service.create(mockCtx, {
name: 'Test',
description: 'Test Description',
});
expect(result.name).toBe('Test');
});
});
E2Eテスト¶
GraphQL APIのE2Eテスト例は 実装例 を参照してください。
プラグイン公開(参考)¶
npmパッケージとして公開¶
プラグインをnpmパッケージとして公開する場合の手順:
package.jsonの設定
{
"name": "@your-org/vendure-my-plugin",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"peerDependencies": {
"@vendure/core": "^3.5.0"
}
}
- ビルドと公開
pnpm build
pnpm publish
Vendure Hubへの登録¶
公開したプラグインは Vendure Hub に登録できます。
注意: 本プロジェクトのプラグインは社内利用のため、公開は不要です。
参考リンク¶
Vendure公式ドキュメント¶
プラグイン開発ガイド:
- Plugins Guide - プラグイン開発の総合ガイド
- VendurePlugin API Reference - デコレーターAPIリファレンス
データモデル:
- Database Entity - カスタムエンティティの作成
- Custom Fields - カスタムフィールドの追加
- Migrations - データベースマイグレーション
GraphQL API:
- Extending the GraphQL API - GraphQL拡張ガイド
サービスとヘルパー:
- RequestContext - RequestContext APIリファレンス
テスト:
- Testing - プラグインテストガイド
その他:
- Vendure Hub - プラグインマーケットプレイス
プロジェクト内ドキュメント¶
- プラグイン概要と設計方針: アーキテクチャパターン、設計原則、データモデル設計
- Vendure開発ハンドブック: Vendureのアーキテクチャとコアコンセプト
- 個別プラグイン実装例: 顧客管理、価格システム、配送計算等の実装仕様
実装例¶
- シンプルな例:
/packages/plugins/src/inventory-management.ts - 完全な例:
/packages/plugins/consent-system/src/index.ts - 実装パターン集: implementation-examples/index.md
更新日: 2025-01-01 メンテナー: Ritsubi開発チーム