プラグインと Facade 拡張

必要な機能を組み合わせ、公開 API でコンテンツと業務 UI を拡張します。

Univer はプラグインで製品と機能を構成し、Facade API でコンテンツ操作をアプリケーションに公開します。必要なエンジン、機能、UI を選ぶほか、業務ロジックをプラグインにまとめ、型付きのメソッドとしてアプリや Agent ツールから呼び出せます。

必要なコンテンツ機能を組み合わせる

Preset は導入を簡単にするプラグインの組み合わせです。Plugin モードでは製品、機能、設定をホストが明示的に選択します。どちらも同じプラグイン機構を使用します。

層役割主な構成要素
製品とコンテンツモデルUnit、コンテンツ構造、基本操作を管理する。Sheet、Doc、Slide、Relational Table、Canvases、PDF の製品プラグイン。
機能プラグイン特定分野の機能を追加する。数式、フィルター、チャート、コメントなど。
UI と描画プラグイン表示と操作の入口を提供する。Canvas 描画、メニュー、ツールバー、ショートカット、コンポーネント。
Facade 拡張導入済みの機能を公開 API として提供する。ワークブック、範囲、文書、オブジェクトの操作と意味のあるイベント。

製品ロジックと UI を分離することで、対応するコンテンツ機能を Node.js、Worker、ブラウザーで再利用できます。Headless ホストは必要なコンテンツプラグインを登録します。メニュー、DOM、対話操作の API には対応する UI 環境が必要です。

依存関係、ライフサイクル、リソースの所有者

プラグインは @DependentOn(...) で直接の依存関係を宣言し、ランタイムが依存関係と生成順序を調整します。未登録の依存プラグインは既定の設定で登録されます。独自の設定が必要な場合は、依存先を先に設定して登録してください。パッケージのインストールや、スタイル、ロケール、Facade エントリー、Worker の読み込みは別途必要です。

プラグイン内部では依存性注入(DI)により Service、Controller、モデルを公開サービス識別子で接続します。対応する設定や注入境界でプラットフォーム実装を置き換えられるため、業務ロジックがすべての依存先を生成・管理する必要はありません。

フック適した処理
onStarting()依存関係、コマンド、コントローラーを登録する。未作成の Unit や DOM を参照しない。
onReady()対応するコンテンツの準備後、モデルに依存する処理を初期化する。
onRendered()レンダラー、Canvas、DOM が必要な拡張を初期化する。
onSteady()初期表示に必須ではない処理を後回しにする。

コマンド登録、イベント購読、ショートカット、描画モジュールは所有者が管理します。disposable を返す登録には disposeWithMe() を使い、プラグインやコントローラーとともに解放します。mergeMenu() の戻り値は現在 void であり、disposable ではありません。

異なる入口でコンテンツ変更の仕組みを共有する

ツールバー、ショートカット、業務 API は同じ業務コマンドを呼び出せます。Agent ホストも必要な機能を導入したうえで同じ入口を再利用できます。コマンドシステムは業務上の意図、永続化する変更、一時的な操作を分離します。

種類役割例
COMMAND対象を検証し、業務手順、Mutation、Operation を組み立てる。売上レポートの書き込み、行の削除。
MUTATION明示的でシリアライズ可能な引数でモデルを変更し、再生や対応する共同編集処理に利用する。セル内容の更新、行の挿入。
OPERATION一時的な操作状態を変更する。選択範囲、スクロール位置、パネルの変更。
100%

たとえば FRange.setValues() は SetRangeValuesCommand を呼び出し、そのコマンドが SetRangeValuesMutation と取り消し用データを準備します。異なる入口でも同じ実行経路を通るため、既存の検証、インターセプター、変更処理を再利用できます。図の取り消し、共同編集、履歴にはそれぞれの仕組みが必要です。Mutation を登録するだけで自動的に利用できるわけではありません。

プラグインの機能を Facade API として公開する

Facade はコンテンツのオブジェクトを扱う公開インターフェースを提供し、内部サービスの検索やコマンド引数の組み立てを隠します。既存の Facade メソッドを組み合わせることも、独自の業務コマンドを呼び出すこともできます。

次の例では FUniver に generateSalesReport() を追加します。コマンドは次節で実装します。各部分が実行時メソッド、実行時登録、TypeScript の型を担当します。

sales-report.facade.ts

TypeScript
import { FUniver } from '@univerjs/core/facade';import type { ISalesReportParams } from './sales-report.plugin';import { GenerateSalesReportCommand } from './sales-report.plugin';export interface IFUniverSalesReport {  generateSalesReport(params: ISalesReportParams): Promise<boolean>;}export class FUniverSalesReport extends FUniver implements IFUniverSalesReport {  override generateSalesReport(params: ISalesReportParams): Promise<boolean> {    return this._commandService.executeCommand(GenerateSalesReportCommand.id, params);  }}FUniver.extend(FUniverSalesReport);declare module '@univerjs/core/facade' {  interface FUniver extends IFUniverSalesReport {}}

FUniver.extend() が実行時にメソッドを追加し、モジュール拡張は型だけを追加します。利用側は side-effect import で拡張ファイルを実行する必要があります。クラスの export やプラグイン登録だけでは、別の Facade エントリーは実行されません。パッケージでは専用の /facade サブパスを公開し、package.json#sideEffects で登録処理を保持できます。

この方式は extend() を明示的に備える Facade クラスに限定してください。公開されているクラスがすべて mixin に対応するわけではありません。メソッドには明確な業務名を使い、既存 API や別の拡張との重複を避けます。

例:アプリと Agent で同じ売上レポートを作る

この最小プラグインは、指定した Sheet の A1:D5 を英語の売上データ、集計式、ヘッダースタイルで上書きします。ワークブックとワークシートの ID を明示的に受け取り、選択範囲やフォーカスに依存しません。

対象とサイズを確認してから既存の SetRangeValuesCommand を呼び出し、コンテンツ変更と取り消し/やり直しの処理を再利用します。プラグインは onStarting() でコマンドを登録し、disposable を管理します。固定データはデモ用です。実際のアプリでは検証済みの業務入力に置き換えられます。

出力例。読みやすいように背景色、数値の表示形式、行の高さ、列幅を調整しています。

sales-report.plugin.ts

TypeScript
import type { ICellData, ICommand, Workbook } from '@univerjs/core';import {  CommandType, DependentOn, ICommandService, Inject, Injector,  IUniverInstanceService, Plugin, UniverInstanceType,} from '@univerjs/core';import { SetRangeValuesCommand, UniverSheetsPlugin } from '@univerjs/sheets';export interface ISalesReportParams {  unitId: string;  subUnitId: string;}export const GenerateSalesReportCommand: ICommand<ISalesReportParams> = {  id: 'sales-report.command.generate',  type: CommandType.COMMAND,  handler: (accessor, params) => {    if (!params) return false;    const sheet = accessor.get(IUniverInstanceService)      .getUnit<Workbook>(params.unitId, UniverInstanceType.UNIVER_SHEET)      ?.getSheetBySheetId(params.subUnitId);    if (!sheet || sheet.getRowCount() < 5 || sheet.getColumnCount() < 4) return false;    const value: ICellData[][] = [      ['Region', 'Q1', 'Q2', 'H1 Total'].map((v) => ({        v, t: 1, s: { bg: { rgb: '#17324D' }, cl: { rgb: '#FFFFFF' }, bl: 1 },      })),      [{ v: 'East', t: 1 }, { v: 128000, t: 2 }, { v: 142000, t: 2 }, { f: '=SUM(B2:C2)' }],      [{ v: 'West', t: 1 }, { v: 96000, t: 2 }, { v: 118000, t: 2 }, { f: '=SUM(B3:C3)' }],      [{ v: 'North', t: 1 }, { v: 110000, t: 2 }, { v: 125000, t: 2 }, { f: '=SUM(B4:C4)' }],      [{ v: 'Total', t: 1 }, { f: '=SUM(B2:B4)' }, { f: '=SUM(C2:C4)' }, { f: '=SUM(D2:D4)' }],    ];    return accessor.get(ICommandService).executeCommand(SetRangeValuesCommand.id, {      ...params,      range: { startRow: 0, endRow: 4, startColumn: 0, endColumn: 3 },      value,    });  },};@DependentOn(UniverSheetsPlugin)export class SalesReportPlugin extends Plugin {  static override pluginName = 'SALES_REPORT_PLUGIN';  static override type = UniverInstanceType.UNIVER_SHEET;  constructor(    _config: undefined,    @Inject(Injector) protected override readonly _injector: Injector,    @ICommandService private readonly _commands: ICommandService,  ) { super(); }  override onStarting(): void {    this.disposeWithMe(this._commands.registerCommand(GenerateSalesReportCommand));  }}

Sheet 導入ガイドに従って製品と数式機能を設定してから、業務プラグインと Facade 拡張を読み込みます。ここでの univer は設定済みのインスタンスです。Node.js の設定は Headless Web SDK を参照してください。

TypeScript
import { FUniver } from '@univerjs/core/facade';import '@univerjs/sheets/facade';import '@univerjs/engine-formula/facade';import { SalesReportPlugin } from './sales-report.plugin';import './sales-report.facade';univer.registerPlugin(SalesReportPlugin);const univerAPI = FUniver.newAPI(univer);const workbook = univerAPI.createWorkbook({ name: 'Quarterly Sales' });const worksheet = workbook.getActiveSheet();const target = { unitId: workbook.getId(), subUnitId: worksheet.getSheetId() };const generated = await univerAPI.generateSalesReport(target);if (!generated) throw new Error('Sales report could not be generated');await univerAPI.getFormula().onCalculationResultApplied();console.log(worksheet.getRange('D5').getValue()); // 719000

コマンドの成功は書き込み完了を意味します。数式の値を読む前に計算結果の適用を待ちます。この例の D5 は 719000 です。

アプリのボタンはクリックハンドラーから generateSalesReport(target) を呼べます。Agent のツールハンドラーも、Headless ホストに同じプラグインと Facade 拡張を導入すれば同じメソッドを使えます。業務動作と対象は共通で、UI は入口とフィードバックを担当します。独自プラグインの導入だけでは Agent ツールには公開されません。ツールの登録と Unit の認可はアプリが担当します。

実装上の境界と関連資料

  • 既存のコンテンツコマンドを再利用する:本例は SetRangeValuesCommand を使います。新しい Mutation には逆操作や対応する共同編集連携が必要です。新しい永続リソースを追加する場合はシリアライズも実装し、モデルを直接変更しないでください。
  • 実行・計算・送信を区別する:複数の Facade 呼び出しは自動的な原子トランザクションではありません。本例は一度の範囲書き込みです。共同編集ホストは Runtime の契約に従って変更を送信し、確認する必要があります。
  • 環境に合わせて構成する:コンテンツと UI のプラグインを分けて導入し、Univer の同一リリース系列のパッケージは同じバージョンに揃えます。UI 文言はロケールリソースで管理し、Agent の入口ではアプリ側で認可と引数検証を行います。

関連資料:Univer アーキテクチャ · Facade の導入 · Facade API リファレンス · UI カスタマイズ · Agent と自動化

機能一覧へ戻る

このガイドはいかがですか?

© 2026 DreamNum Co., Ltd.