オープンソースのWikiであるGROWIにはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。
今回は、インタラクティブな操作が可能になるGROWIプラグインを作ってみました。テンプレートも用意したので、手軽にGROWIプラグイン開発をはじめられます。
GROWIプラグインの難点
これまで、幾つかのGROWIプラグインを開発してきましたが、若干の不便さがありました。それは、Reactコンポーネントで表示処理を実装できるものの、useStateやasync/awaitがそのままでは実装できないことです。
ネットワークでデータを取得して表示に利用するというのは、 react-async
を使えば解決できますが、あらかじめデータを取得する必要があるなど若干の不便さは残ります。
特に、ボタンを押したタイミングで何かを実行したり、ユーザーの入力を受け付けるようなGROWIプラグインが実現できないのは不便さがありました。
Stencil.jsを使う!
GROWI自体はReactを作られており、全体がReactアプリとしてマウントされています。その中で別なReactコンポーネントを作り、インタラクティブな機能を実現できません。
そこで、解決策として考えたのがStencil.jsです。Stencil.jsは、Web Componentsを作るためのフレームワークで、できあがったWeb ComponentsはReactやVue.js、Angularなどでも利用できます。そして、Stencil.jsのWeb Componentsの中では、独自にインタラクティブなUIであったり、画面への表示反映などが実現できます。
ということで、Stencil.jsをGROWIプラグインの中で使ってみました。
できたもの
これはデモですが、GROWIでボタンを表示しています。そして、そのボタンを押すとカウントアップします。ネットワーク処理ももちろん使えるので、データを保存したり、逆に呼び出すことも可能です。
作り方
テンプレートはgoofmint/growi-plugin-script-template-stencil: GROWI script plugin template with stencilに作成しています。このプラグインでは <growi-component />
というタグを定義しています。
手順としては、まずStencil.jsでWeb Componentsを作成します。
Web Componentsの作成
growi-component/src
以下を編集します。 growi-component/src/components/my-component/growi-component.tsx
を見ると、だいたい内容が分かるかと思います。
render
メソッドでレンダリングを行い、変更される変数は @State
で指定します。イベントは onClick
などを使います。
import {
Component, Prop, State, h,
} from '@stencil/core';
// import { format } from '../../utils/utils';
@Component({
tag: 'growi-component',
styleUrl: 'growi-component.css',
shadow: true,
})
export class GrowiComponent {
/**
* The name
*/
@Prop() name: string;
/**
* The parametar1
*/
@Prop() params1: string;
/**
* The parametar2
*/
@Prop() params2: string;
/**
* The count
*/
@State() count = 0;
private getText = (): string => {
return `${this.name}. params1: ${this.params1} params2: ${this.params2}`;
};
onClick = (): void => {
this.count += 1;
};
render(): JSX.Element {
return (<div>Hello, World! I'm {this.getText()}! Count: {this.count}
<button onClick={this.onClick}>Click me</button>
</div>);
}
}
Web Componentsのビルド
ビルドは pnpm run build
でOKです。開発中は pnpm run build --watch
しておくと便利です。
Web Componentsの読み込み
GROWIでは、 src/Hello.tsx
にてWeb Componentsを読み込んでいます。
import '../growi-component/dist/components/growi-component';
読み込んでしまえば、後はレンダリングする際に <growi-component />
というタグを使うだけです。
export const helloGROWI = (Tag: React.FunctionComponent<any>): React.FunctionComponent<any> => {
return ({ children, ...props }) => {
try {
const { stencil, params1, params2 } = JSON.parse(props.title || '{}');
if (stencil) {
return (
<growi-component
name={children}
params-1={params1}
params-2={params2}>
</growi-component>
);
}
}
catch (err) {
// console.error(err);
}
// Return the original component if an error occurs
return (
<Tag {...props}>{children}</Tag>
);
};
};
GROWIページ内では、 :plugin[label]{params1=test2 params2=test2}
のように指定します。これをRemark Directiveとして処理する際に、 a
タグに置き換えています。一緒に stencil = true
も追加しておくことで、表示する際にプラグインなのかどうかを判別できます(上のコードへの処理分けに使っています)。
export const remarkPlugin: Plugin = () => {
return (tree: Node) => {
visit(tree, (node: Node) => {
const n = node as unknown as GrowiNode;
if (n.name !== 'plugin') return;
const data = n.data || (n.data = {});
// Render your component
const { value } = n.children[0];
data.hName = 'a'; // Tag name
data.hChildren = [{ type: 'text', value }]; // Children
// Set properties
data.hProperties = {
href: 'https://example.com/',
title: JSON.stringify({ ...n.attributes, ...{ stencil: true } }), // Pass to attributes to the component
};
});
};
};
Stencil.js + GROWIプラグインの可能性について
Stencil.jsを使うことで、以下のようなことが可能になると考えています。
- CSSにTailwindを使う
- RSSを取り込んで表示する際に、ついでにページを作る。これでRSSの内容をGROWIの検索対象にする
- モーダルを表示して、データをメンテナンスする
- 独自のBaaS(Supabaseなど)と組み合わせて、掲示板などを実装する
- Backlog APIなどからデータを取得するだけでなく、データメンテナンスも行う
実体はWeb Componentsなので、GROWIに限らず利用できますが、GROWIの中に組み込んで使えるのが利点ではないでしょうか。
GROWIコミュニティについて
プラグインの使い方や要望などがあれば、ぜひGROWIコミュニティにお寄せください。実現できそうなものがあれば、なるべく対応します。他にもヘルプチャンネルなどもありますので、ぜひ参加してください!
まとめ
GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。