prettierで整形できて、minifyして書き出せる、MTML

  • 投稿日:
  • by
  • カテゴリ:

こんにちは、天野と申します。Movable Typeの開発に参加しています。

今日はMTML(Movable Type Markup Language)の話です。

MTMLを気持ちよく編集したい

公開される記事の典型的なMTMLは以下のようなものになります。

HTMLの属性の中にMTタグが入っていますが、「カスタム要素の入ったHTML」と思ってみれば特別見にくいものでもないと思います。VSCodeにMTMLの拡張を入れれば、属性の中にも色がついて見やすくなります。

ただMTMLはやはり独自のマークアップ言語で一般的なツールではパースできないので「自動整形ができない」「対応するタグで閉じられているか編集時に検証できない」という問題があります。上のGistの内容だと「</titl>」となっていてHTMLが壊れていますし、「encde_html」となっている箇所もあります。また「<mt:EntryTitle>」というのは「encode_html」を付け忘れていると思われます。

とういところで、「JSXで書ければ整形も検証も一般的なツールでできるのになぁ」というのを思ったのです。

MTMLとJSXの関係については以前もmt-data-api-react としてPoC的なことをやったことがあります。これは「既存のMTMLをそのままで、DataAPIから取得したデータでフロントエンドでレンダリングする」というテーマでした。今回はそれとは異なり「JSXを書いて、それをMTが処理できるMTMLに変換する」というものです。

JSXに馴染みのない方はここまで読んでnot for meだなと感じるかもしれませんが、この話はそうでもないのでもう少し読み進めてください。以下は前出のMTMLを書き換えたものですが、ほぼほぼXMLとして違和感はないだろうと思います。(ないということで話を進めます。)またこれはJSXとしてもValidであり、これを処理することができます。

Reactを書いている方は「JSXとしてもValid」というところで「Validかもしれないけど(TSXでは)型のエラーが出るよね」という話になってくると思いますが、JSXをReactでないJSXの処理系で型を付ければこれもいけます。

説明が長くなってきたので結果を見てみると、今回作ったmt-jsx-templateというコマンドで処理すると以下のような結果になります。

JSXで整形やバリデーションができる上に、つまるところJSXからMTMLへのトランスパイルになるので、その過程でMTMLをminifyして書き出すことができるのです。

実装部分は、honoというOSSのプロジェクトでJSXの処理系を実装したことがあったので、そこから一部をコピーしつつひとまず形にしました。

ちなみにTypeScriptで型も付けられるので、「encoee_html」もちゃんとエラーにできます。またトランスパイルのついでに「<$mt:EntryTitle$>」に自動で(デフォルトで)encode_htmlをつけることもできます。これは、結構いいんじゃないでしょうか。

JSXになればできること

JSXになると、自分でパースの努力をしなくてよくなるので「使われているCSSのクラス名を収集する」ということもやりやすくなります。(今までのテンプレートでも正規表現で収集すれば実用上十分な精度でできたと思いますが。)「JSXで書いてトランスパイルする」という処理が作業フローに組み込まれるのであれば、そのついでに「CSSのフレームワークから必要なCSSを抽出して最小限のCSSを作成する」というのもやろうかという話になってくると思います。

以下が、MasterCSSを使ったサイトのJSXテンプレートからクラス名を抽出して、使われているCSSのみを出力するイメージです。

このMTMLのclassを解析して、MasterCSSを使って以下のCSSを出力することができます。

フロントエンドに関しては、2023年においてはウェブサイトを運用する際には何らかのビルド作業が入っていることが多いと思いますが、MTMLや(MTMLの内容を踏まえた上での)CSSもビルドするようになってもいいのかもしれません。MTのプラグイン(主にPerl)でビルドできればよいようにも思いますが、プラグインだとサーバー上で動作させることになり制限があることも多いので、作業環境の方が自由に環境を作れるというのもあり、これはこれでよいアプローチだと思います。

「ここまでやるならJamstackで生成した方が素直じゃないか?」という指摘があっても否めない気もします。とはいえまあ、上と同じ理由で運用環境は自由にならないこともあるのでMTを使ううえではMTMLの開発効率が上がるのはよいことだと思います。

プレビューもしたい

「JSXでパースできる」ということは、mt:Entriesやmt:EntryTtileのモックを作ってあげれば、JSXを直接文字列化してテンプレートの出力結果も確認できそうです。これになってくると上で出たmt-data-api-reactとも近い話になってくるのですが。これをやるには時間が足りなかったのでやっていませんが、後はモックを頑張って作りさえすれば、プレビューできるというところまで来ていると思います。

今回作成したもの

mt-jsx-templateです。

$ npx mt-jsx-template ファイル名

だけですぐに動かせます。また標準入力からも読み込めますので、動くことだけ確認したい場合には以下のコマンドを実行すれば変換された結果を見ることができます。

$ curl https://gist.githubusercontent.com/usualoma/e3d68e800bd264e9cdb0f2b7b1b19a64/raw/7c0b05a92e95f01f4e317d8c7ab46cb118296b66/entry.mtml.tsx | npx mt-jsx-template

TSXを書いて実際に型の付くことを確認したい場合には以下のようにプロジェクトにインストールして、tsconfig.jsonを設定してもらえば好きなエディタで動作させることができます。

$ npm install --save-dev mt-jsx-template

属性値の型も定義されているので、補完が効いたり、マニュアルを参照できたりします。

これは、結構いいんじゃないでしょうか!

この記事について

この記事は Movable Type Advent Calendar 2023 の25日目の記事です。皆様お疲れさまでした。それでは良いお年を!

honoのJSXの特徴

  • 投稿日:
  • by
  • カテゴリ:

これはなんの記事か?

honoはJSXを処理する実装をもっています。JSXはJavaScriptの構文を拡張したもので...のような話は皆さんご存知でしょうしググってもらった方が正確な情報を得られると思うのでここでは書かずに、honoの実装での特徴的なところについて書いていこうと思います。

特徴は?

文字列の出力に特化していることです。Reactでいうところの`renderToString(<App />)` のみに対応しています。`<App />.toString()` が文字列になります。`document.body.innerHTML = <App />`が動くのはそのためです。

Reactとの互換性は?

一般的なHTMLの要素をはじめ、Function Component、Fragment、dangerouslySetInnerHTMLなど、JSXの基本的な構成要素は揃っているので、特に戸惑うこと無くJSXを書いてHTMLを出力することができると思います。

非互換の一番大きな点としては、今まで書いたことはなく質問されたこともなかったのですが、`Component`のクラスに対応していないことかもしれません。とはいえ、文字列の出力に特化していて状態を持ちたいことはないと思うので、クラスが必要になる場面は基本的にはないと思います。

地味な違いとしては"class"を"className"と書く必要がないので、HTMLをそのまま持ってきて必要なところだけコンポーネントに置き換えるときに楽です。そこはpreactと一緒ですね。(ただ逆にReactから持ってくるときにはclassNameをclassに置き換える必要がありますが。)

ベンチマークは?

honoといえば「Ultrafast & Lightweight」なのでその点にもこだわっています。

honojs/hono/tree/main/benchmarks/jsx にベンチマークがあり、React、Preact、Nano、という本家+メジャーなJSX処理系と比較しています。「honoに実装されている機能の範囲で比較した場合」というベンチマークなのでhonoに有利な条件ですが、この中で最速になっています。

またベンチマークの例でバンドルした場合のサイズ(honoの本体は含まずに、JSXの処理系だけをバンドルしたサイズ)は3.9kbと小さく、これも比較した中で最小です。

JSXの処理系の中で最速でしょうか?

ルーターのベンチマークに関してはRegExpRouterが「必要十分な機能を持ったルーターとしてはJSの中で最速」となっていますが、JSXに関しては少し状況が違います。

honoのJSXでは(他の多くのJSXの処理系と同じように)一度データ構造の木を作成して、そこから文字列化を行っています。一度データ構造を作ることで親コンポーネントが子を管理しやすくなり、細かい要望に応えやすくなります。(後述するErrorBoundaryで同期的エラーを補足する場合とか)

ただJSXの文字列化のアプローチとしてはこの「データ構造の木を作成」というところを省略する方法もあり、その場合には細かい管理はできないもののより高速に処理できます。このアプローチを採用している処理系には@kitajs/htmlがあり、これは速度だけをみるとhonoのJSXよりも高速です。

非同期なコンポーネント

honoの3.10からは非同期なコンポーネントを使うことができるようになりました。

`<App />.toString()` がPromiseを返すようになってしまう気持ち悪さはありますが、受け取る側でラップすれば通常の文字列と同じ感覚で使うことができます。

これは実はReactとも互換性があって、18.2まではエラーになるのですが、Reactの18.3(2023年12月現在は@nextのステータス)では`renderToReadableStream`を使うことで文字列にすることができます。Suspenseも不要です。

完全な互換性があるわけではないのであまり不用意なことは言えないのですが、honoのJSXは独自の実装ではあるものの意外と本家の挙動も意識しており、後述するSuspenseにおいても相互の乗り換えで混乱することがないようにしています。

もちろんですが、返されたPromiseは並列で処理されるので、同時にたくさんの非同期なコンポーネントを使っても大丈夫です。

Suspense

honoの3.10で非同期なコンポーネントのサポートすると同時に、Reactで使えるSuspenseも用意しました。

前項の話とも重なりますが、Reactの18.2まではSuspence[fallback]を使う場合にはPromiseをthrowするというのが作法だったと思いますが、18.3ではPromiseをreturnでもいけるようです。(Reactには詳しくないのですが。)honoでは、従来どおりのthrowする方法も、returnする方法も、どちらもサポートしています。(関連する機能としては`use`というhookもあり、honoでも一度実装したのですが、honoでは単に async/await で十分であり必要な場面がないだろうということで削除しました。)

以下のコードはhonoでもReact(18.3)でもどちらでも、最初に「Loading...」が表示され、fetchの完了後に内容が更新されるという動作になります。

ErrorBoundary

ErrorBoundaryはReactの本体では提供されていないのですが、bvaughn/react-error-boundaryという公式のドキュメントからもリンクされていて広く使われているライブラリがあり便利そうだったので、仕様を真似て実装しました。

以下のコードで、最初に「Loading...」が表示され、その後例外が投げられたタイミングで「Something went wrong」に更新されるという動作になります。

ErrorBoundary[onError]やErrorBoundary[fallbackRender]を使って、ログを記録したり、エラーの内容に応じて出力を切り替えることもできます。

以上、honoのJSXの実装の紹介でした。

これは Hono Advent Calendar 2023 の13日目の記事です。引き続きよろしくお願いします!

Passkey - Passkeyで認証できるようにするプラグイン

  • 投稿日:
  • by
  • カテゴリ:

これは何か

Movable Typeの管理画面へのサインインでPasskeyの認証を利用できるようにするプラグインです。

mt-plugin-Passkey

必要な環境

サーバー環境に以下のPerl moduleが必要なので、ちょっと環境は選ぶかもしれません。

  • Crypt::PK::ECC
  • Crypt::PK::RSA

感想

1Passwordなどのパスワードマネージャを利用している場合には、普段からサインインの際の手間は殆どないのであまり変わらないようにも思いますが、「システムとして、パスワードよりも安全で手間のかからないサインインの方法を提供できる」というのが、Passkeyでサインイン可能にする際のシステム側のモチベーションであるように思いました。

(Passkey対応なので当然ですが)iCloudにPasskeyを登録しておけば、「デスクトップの端末からMTにサインインする際にiPhoneを経由して顔認証でサインインする」というのも思いの他さくっと成功するのでちょっとした感動があります。

TODO

  • サインインをしたタイミングでPasskeyの登録を促す
  • セッションの有効期限切れからの再サインインでパラメータを引き継ぐ(今は簡易的に実装しているのでダッシュボードに戻ります)
  • HTMLを書くのが面倒でalert()やprompt()を多用しているのでちゃんとする(でも今のブラウザはalert()も野暮ったくないので、意外と気にならないような気がしますが)
  • MFAプラグインと連携して他要素認証としてちゃんと扱われるようにする

というのができるともう少し「Passkey対応サービス」の雰囲気が出せそうな気がしました。

この記事について

この記事はMovable Type Advent Calendar 2023の2日目の記事です!

これは何か

Honoのv3.8.0のリリースでは「Improve path matching in the router」が入り、内部で利用しているルーターの仕様が変更になったのですが、高速なルーターの実装であるRegExpRouterは変更に対応しつつも速度を維持することができました。これはその経緯を解説する記事です。

「Improve path matching in the router」とはどのような変更であったか?

RegExpRouterの話に入る前に「Improve path matching in the router」の説明をします。リリースノートにもありますが以下のような定義のとき、以前は1つのアプリケーション内で「異なる階層に、同じ名前のパラメータ」を指定すると例外が投げられてしまい実行できなかったのですが、これができるようになりました。

この例だけ見ると「なぜ今までこれができなかったのか?」というように見えますが、例えば以下のような定義のときに、今までは内部のインターフェイスが「`c.req.param()` の戻り値をすべてのハンドラで共有」となっていて、両方に一致した時に妥当な結果を返せなかったため、ルーティングの登録の時点で例外を投げる仕様にしていました。

この仕様を改善するために、「`c.req.param()` の戻り値をすべてのハンドラで共有」となっていた内部のインターフェイスを「`c.req.param()` の戻り値は対応するルーティングで指定されたもののみ」とするように変更して、パラメータ名がルーティング間で重複するような定義も受け入れるようにしたのが「Improve path matching in the router」の変更になります。

リクエストとレスポンスの対応で見ると、以下のように変わっています。

この変更のルーターへの影響

以前の仕様だとルーターが返すのは以下でしたが、

  • 一致したハンドラのリスト
  • 一致したパスのパラメータ

新しい仕様だと以下になります。

  • 一致したハンドラのリスト
  • ハンドラ毎のパラメータ

インターフェイスはだいたい以下のように変わっています。(最終的にはもう少し複雑になります。)

以前の仕様だと、一致したハンドラの数に関わらずリクエスト時に生成するオブジェクトの数は一定でしたが、このインターフェイスだとハンドラの数に比例して増えることになります。

オブジェクトの生成はどのランタイムでも特別遅いわけではないので、一般的なJSのアプリケーションの中ではそこまで避けるべきものでもないと思いますが、RegExpRouterは「JSのルーター界隈」という(ニッチな😀)世界で戦っているのでこの辺りが問題になりました。

RegExpRouterでの対応

RegExpRouterは(ご存知でない方は過去の資料を参照してください)正規表現の結果を可能な限りそのまま使うことで高速化しているので、それを活かせるようにルーター側のインターフェイスの方でも歩み寄ってもらって、以下のようにすることで対応しました。

急にまとまってしまって戸惑うかもしれませんが、minifyしたときのコードが小さくなるようにRecordからArrayにするというのも入れつつ最終的にこのようになりました。実際のコードではコメント付きでこんな感じになっています。詳細については実際のコードを見てもらうほうがいいかもしれません。

RegExpRouterに関してはこのインターフェイスにあわせて、上で出した例に対してはこのようなルーティングの結果を返すようになりました。

これは `[T, ParamIndexMap][]` と `ParamStash` のタプルですが、前者は `match()` の呼び出し前に準備しておくことができ、後者についてはコメントにある通り `String.prototype.match()` の戻り値そのものとなっています。`c.req.param(key)` の呼び出しではこのデータ構造から値を引いて返すので、その際には以前の仕様では発生しなかったオーバーヘッドが発生するのですが、実際の運用の環境でも多くは以下のような条件を満たすので、

  • 同じkeyで何度も `c.req.param(key)` が呼ばれるようなことはない
  • パスから取得するパラメータは多くても2〜3程度
  • middlewareは '*' や、静的なパス以下の '/js/*' のような使い方が多く、パラメータを取るケースは少ない

実運用の環境でも、そしてベンチマークでも、このオーバーヘッドが見える形で影響してくることはないものと思われます。

以上が、RegExpRouterが「Improve path matching in the router」の変更でも速度を維持できた(パラメータが少ない場合にはむしろ速くなるケースもある)理由です。

この記事について

この記事は Hono Advent Calendar 2023 の1日目の記事です。明日から毎日楽しみです。よろしくお願いします!

これはなにか?

先日公開した「MTML」に続いて「MT Custom Block Editor」という拡張機能を公開してみました。この拡張機能をインストールすると、MTのブロックエディタのスクリプト部分をVSCodeで編集したり、プレビューしたりできるようになります。

まだ細かいところは詰めていなくて検証の一環程度のものですが、CMSでのサイト構築段階におけるこういった形でのVSCodeとの連携について、可能性を感じさせるものにはなっているように思います。

今後のやることリスト

  • エディタ内のwebviewで表示するのは簡単で見た目も悪くないけれども、実際には任意のウェブブラウザで開けた方が便利であると思う
  • 実際の環境と同じように、画像ブロックも扱えるようにしたい
  • その他たくさん

この記事について

この記事はMovable Type Advent Calendar 2022の20日目の記事です!

これは何か

記事やウェブページ、またはコンテンツデータのデータとMTMLのテンプレートから画像を生成することができるようになるMovable Typeのプラグインです。主にog:imageやアイキャッチの画像を生成するユースケースを想定しています。

ImageTemplate

以下が簡単な出力例で、「記事内の画像を背景にして、ウェブサイトのテーマカラーで縁取りして、タイトルと執筆者情報を入れる」という画像です。

どのような問題を解決するか

og:imageやアイキャッチ画像は、記事ごとにデザイナーさんに作成してもらうことができるのであれば、それがベストだと思っています。そのようにして作られた画像は目を引くうえに、内容も上手に凝縮して詰め込まれていて素晴らしいです。

ただ現実問題としてそこまで整えることのできないことも多く、そういう環境で記事の作成者が画像を用意するとなると、統一感のある画像を用意するのはなかなか難しいのではないかと思っています。このプラグインではそいう環境で作業者に時間やスキルがない場合にも、「あらかじめデザイナーさんにテンプレートを用意してもらう」ことにより、一定の品質で統一感のある画像を用意できるようになるというところを目指しています。

デモ

使い方

インストール後に、SVGのデータを出力するテンプレートを作成すると使えるようになります。記事やウェブページとの関連付けはテンプレート名で行います。それぞれ以下の名前で作成してください。

記事

image_template_entry_並び順(数字)_表示名

ウェブページ

image_template_page_並び順(数字)_表示名

コンテンツデータ

image_template_content_data_コンテンツタイプのID_並び順(数字)_表示名

テンプレートの例

デモのために作成したテンプレートです。いろいろと決めうちで品質はよくないですが、雰囲気は伝わるかと思います。ルート要素に指定したwidthとheightが、出力される画像のサイズになります。

TODOリスト

  • SVGでなくて、HTMLで書くこともできるようにする
  • 現在は「埋め込める画像はjpegのみ」「出力できる画像はpngのみ」なので、ちゃんと他のフォーマットも扱えるようにする
  • JavaScriptで、SVGの要素を調整して、その結果を出力できるようにする
  • 管理画面で関連付けや並べ替えができるようにする
  • その他、たくさんあります。

この記事について

この記事は Movable Type Advent Calendar 2022 の25日目の記事です。皆様お疲れさまでした。それでは良いお年を!

「MTML」というVisual Studio Codeの拡張機能を公開しました

  • 投稿日:
  • by
  • カテゴリ:

これはなにか?

Visual Studio Codeの拡張機能に興味が出たので、MTML(Movable Type Markup Language)向けのものを作ってみました。以下のように動作します。(音声での説明も入っています)

既存の拡張機能との関係

MTML向けの拡張機能やスニペットのデータはすでに公開されたものがあって、以前のアドベントカレンダーで紹介されているものもあります。

これらは実運用の中での知見が反映されたものでそれぞれすばらしいものなので、今回の拡張機能についてはこれらの置き換えを目指すものではなく、「もう一つの選択肢」というものとして考えています。ただ今回の拡張機能は「オプションで特定の機能のみを有効にする」ということもできるので、「リファレンスの表示機能のみを有効にする」というような形で、既存の機能と併用するという使い方もできるものになっています。

機能の説明

シンタックスハイライト

ファイルの編集時に言語を「MTML」にすると、MTタグのシンタックスハイライトが有効になります。「MTML」は「HTML」で書いても概ねいい感じに表示されるものですが、「,」区切りの属性値であったり、属性値の中にMTタグを書く場合にうまくハイライトされないという問題があります。この拡張機能を使うとそのようなケースでもいい感じに表示されるようになります。
また「HTML」のシンタックスも理解するのでHTMLの部分もそのままきれいに表示されます。

ただ、言語として「MTML」を選択してしまうと「HTML」向けのVisual Studio Codeのシンタックスハイライト以外の機能が使えなくなってしまうので、そこは若干、このシンタックスハイライトがほんとに便利なのかどうか、微妙だと思うところです。

補完

MTタグや、タグ毎のモディファイア、そしてグローバルモディファイアを補完することができます。「<」の場合には全てのMTタグが対象になり「<$」まで打てばファンクションタグのみが有効になります。

初期値では「mt:Var」のフォーマットで補完されますが、設定により「MTVar」のフォーマットに変更することもできます。

この機能はデフォルトで「HTML」や「CSS」でも有効になります。設定により「HTML」や「CSS」では無効にすることもできます。

リファレンスマニュアルの表示

MTタグにマウスカーソルを合わせると、リファレンスマニュアルを表示することができます。

この機能はデフォルトで「HTML」や「CSS」でも有効になります。設定により「HTML」や「CSS」では無効にすることもできます。

ブロックタグの折りたたみ

ブロックタグを折りたたむことができます。

この機能はデフォルトで「CSS」でも有効になります。設定により無効にすることもできます。

今後のやることリスト

  • リファレンスの表示の整形や、保管される値の候補の改善
  • 同じフォルダ内の他のファイルも考慮した、変数名の補完
  • theme.yamlのデータを参照した、カスタムフィールドのMTタグの補完

この記事について

この記事はMovable Type Advent Calendar 2022の7日目の記事となっています。ここまでですでに、API、最近のパッケージリリース、Google Chromeの拡張機能、プラグイン、などいろいろな話題がでていて面白いです。まだまだ前半なので、今後の記事も楽しみです。引き続きよろしくお願いします。

v8のlastIndexOfが(相対的に)遅い

  • 投稿日:
  • by
  • カテゴリ:

最近honoにコントリビュートをしていて、正規表現の結果からの文字列を探索する際にindexOf()の代わりにlastIndexOf()を使ったら最適化できないだろうかと考えたりしていたのですが、だめでした。これはその際に調べたことのメモです。

まず最初に、1000個のcaptureを返す正規表現の結果から500番目を探すパターンでベンチマークをとってみます。

どのバージョンでもindexOf()が文字通り桁違いに速く、lastIndexOf()はforやwhileにすら負けて最下位になっています。(ただindexOf()を使った場合にも変数へのアサインを挟んだだけでここまでの差にはならなかったりするので、どれも基本的には速く、ちょっとした条件の変化で結果が変わったりする程度のものでもあるとは思います。)単純に1つの空文字列を探索する場合にはindexOf()を使うのがよさそうです。

v8のコードを見てみたところ、indexOfの方はc++のコードで、lastIndexOfはマクロのコードで書かれていたので、その辺りで違いが出ているのかもしれません。

次に、以下のように「最大で2つの連続した空文字列が返ってくる match() の結果から、一番最後の空文字列の位置を取得する(captureは2000個)」というケースでのベンチマークをとってみます。

これはバージョンごとの違いが大きく、v16だと「indexOf()とforループがほぼ同じ性能」で、v17だと「indexOf()が最速で2倍近い性能」、そしてv18(pre)だと「forループが最速」となりました。

結論のようなものはなかなか難しいですが。

  • 正規表現の結果から最初の1つの文字列を探す場合にはArray.prototype.indexOf()が桁違いに速い
  • いまのところArray.prototype.lastIndexOf()は遅い
  • 少し条件が込み入ってくるとforループが最速になることもある

というところでしょうか。v18でもネイティブで実装されたlastIndexOf()よりもforループのの方が早くなるケースがあるというのは若干複雑な気持ちにはなりました。

TypeScriptのConditional Typesでinferを使って足し算を定義する

  • 投稿日:
  • by
  • カテゴリ:

String Literal TypesTemplate Literal Typesを使うとGenericsや関数の引数の文字列から型を作れるという話を聞いたので、勉強も兼ねて足し算を定義してみました。

以下のような感じになります。 `Expr<"1 + 1">` が `"10"` というString Literal Typeになります。2進数の足し算のみに対応していて、未対応の式ではUnrecognizedExpression型になります。3項以上でも計算できます。

型が補完されるエディタを使っていると、答を自分で打たなくても補完されたりして楽しいです。

これをtscで変換すると以下のようになるのですが、これを見て、TypeScriptの型はほんとに実行時には影響のないものなのだなと実感することもできます。

足し算の定義は業務の役には立たないものの、このあたりを勉強しつつTypeScriptを使っているウェブのフレームワーク(🔥)にAdded type to c.req.param key.というPRを作ってみて、引数から型を生成するようなパターンには可能性があるなと感じたりもしています。

ちなみにTypeScriptではdocument.querySelector(`a`)と指定したときに引数からHTMLAnchorElementが推論されたりして面白いのですが、`div a`や`a[href^="#"]`だとHTMLElementになってしまうというのがあるのですが、以下のように指定すると要素名っぽいものから推論されるようにできます。(面白いけど、やっぱりこれも使う機会はないとは思いますが。)

MTBockEidtorのカスタムブロックでData APIを使う方法

  • 投稿日:
  • by
  • カテゴリ:

これはどんな記事か?

Movable Type 7でプラグインとして利用できるMTBlockEditorでは、管理画面で作成する「カスタムブロック」やプラグインから追加する独自のブロックでブロックの種類を追加することができますが、それらのブロックの中でData APIを使うことも可能です。

この記事ではいくつかのパターンを紹介していきます。

管理画面で作成するカスタムブロック

まず最初に「カスタムブロック」を使った例の紹介になりますが、最初に書いておくとこの方法は実際のところなかなか厳しいです。

  • srcがdata:であるiframeの内で実行されれるため、JavaScriptでData APIにアクセスする際のOriginヘッダーがnullになります。そのため mt-config.cgi で DataAPICORSAllowOrigin の環境変数に対して null を指定する必要があります(これは、外部のサイトからでも JavaScript でアクセス可能にするという設定になります)
  • カスタムスクリプトで実行するためプレビューが更新されるたびにスクリプトが実行される形になり、実行毎の状態を保存することができません。つまりプレビューが更新されるたびにData APIへのアクセスが発生します。CGIで実行している時にはレスポンスが悪くなったり、また認証を必要とする場合にはプレビューが更新されるたびに認証が発生する形になったりしてしまう問題があります。

というところが主な厳しいポイントです。

しかし、そういう部分もありつつではありますが、IPアドレス制限やBASIC認証を設定すれば利用できるケースもありますし、まずはPoCとして、実現が可能かどうかというところも大事だと思うので、以下で簡単な例を紹介します。

カスタムスクリプトの効率的な開発と運用の1つの方法

本題に入る前に、カスタムスクリプトの開発と運用の方法を1つ紹介します。

公式でもよく例として出されているカスタムスクリプトに直接style要素やscript要素を書くやり方は、一つの管理しやすい方法ではありますが、JavaScriptの処理が増えてくるとコーディングが大変であったり更新がしにくくなったりする面もあります。そういった時には、cssやjsのファイルを外部に置いて、linkやscript要素で読み込む方法を使うことができます。例えば以下のような感じです。

CSSはlink要素を使い、JavaScriptはscript要素のsrc属性を使って指定します。JavaScriptにパラメータを渡す場合にはdata属性を使います。script要素にdefer属性(またはtype=moduleにしてもよいかもしれません)を指定することでDOMContentLoadedで囲む必要もなくなりシンプルにすることができます。

webpackを使っていれば「webpack serve」でバンドルしたファイルをローカルの開発サーバーから参照したりすることもできるので、「ファイルの更新 -> ブロックエディタの編集画面のリロード」だけで(カスタムブロックの保存操作などをすることなく)開発をしていくことができるようになります。

今回の例でもこのやり方で開発を行っています。

公開されている記事のリンクを挿入する

ここからが本題です。Data APIを使って記事データを取得してリンクを挿入するブロックを作ります。

ブロックを追加するとキーワードの入力欄が表示されるので、そこにキーワードを入れて検索ボタンをクリックすると候補が出てきて、選択するとその記事のリンクが挿入されるというものです。

CSSとJavaScriptは以下のようにしました。

GitHubに保存しておくとjsdelivrなどを使ってCDN経由で参照できるので、カスタムブロックのカスタムスクリプト欄は以下のようになります。

以下のように動作します。

前述の通りカスタムブロックでは厳しいポイントも多いですが、状況によっては利用できそうな雰囲気も感じられるのではないかと思います。

プラグインから追加する独自のブロック

プラグインから独自のブロックを追加する場合にはカスタムブロックの時にあったような制限はなく、管理画面へJavaScriptを挿入するような自由度で作成することができます。(その反面、XSSなどのセキュリティ的な問題が発生しないように配慮する必要がありますが、雛形作成ツールから作成した場合にはセキュリティ的な問題が起きにくいようにもなっています。)

この記事ではData APIを使った独自のブロックの例として、DataAPIBlockExampleとを作成しました。基本的な動作としては前述のカスタムブロックでの例と同じですが、DataAPIProxy を使って現在のユーザーでサインインした状態で記事を取得するので、未公開の記事のリンクも挿入できるようになっています。その他にも以下のような違いがあります。

  • カスタムブロックのように何度も読み込まれたりしないので、よりリッチな表現を利用しやすい
    • また表現については、CMSのスタイルをそのまま利用できるので、統一感のあるUIで作りやすい
  • 「挿入時の検索語」のような(出力に含まない)メタ情報も、簡単に保存しておくことができる

以下のように動作します。

雛形作成ツールで作成されたものからはそれほど多くない差分でここまで動くようになるので、構築するサイトにあわせた独自のブロックをこういった形で作成をするというのも、現実的な選択肢であると思います。

この記事について

この記事は Movable Type Advent Calendar 2021 の25日目の記事です。皆様お疲れさまでした。それでは良いお年を!