Sätteriプラグイン開発 #3 外部リンクの新規タブ表示

はじめに
引き続きSätteri関連の記事になります。前回の記事では,features.mathでパースした数式ノードをKaTeXでレンダリングするmdastPluginを実装した話をしました。
やりたいこと
リンクの扱いは結構議論が分かれるところのようで,自分の中でも行ったり来たりを繰り返したんですが,最終的には「内部リンクは同じタブで開き,外部リンクは新規タブで開く」という仕様に決めました。自分の中で外部リンクは「補足ドキュメント」の意味合いが強く,分割タブで並べて表示したりして本文と照らし合わせながら読むことが多いので,左クリックの挙動として新規タブで開くのが自然かなと思いました。一方で内部リンクはどちらかというと並列処理よりは順次処理なイメージなので同タブかなーと。一応先生も
A common approach is to open external links in new tabs and internal links in the same tab.
と仰っているので,変な実装ではないはずだと思っています(汗)。
プラグインの必要性
フロントエンド素人なので,Claudeからの提案に対して正直「こんなことのためにわざわざプラグイン作らないといけないの?」とか思ってしまいました。
そもそもMarkdown(CommonMark)の[テキスト](URL)には属性を書く場所がないため,この記法から素直にHTMLを生成すると,hrefだけ持った素の<a>要素になります。その場合ブラウザのデフォルトの挙動として"ref=_self"(同タブで開く)になるわけで,よってMarkdownを解釈する過程のどこかにtargetを足す必要がありますよと。
じゃあそれをAstroの設定ファイルに1行追加して実現できないのかと思ったんですが,現状Astroにそういう機能は実装されていないようです。ほなしゃあない🥺
satteri-external-linksの導入
当初はClaudeが言うままに自前実装でやっていたんですが,「『Sätteri』とは」の記事を書く中でコミュニティプラグインの存在を知り,こっちも既存プラグインで実現できないか探させたところ,satteri-external-linksというhastプラグインが見つかりました。
やりたかったことが全部カバーされてるし,content(外部リンクの後ろにアイコンや「新しいウィンドウで開きます」といった注記を足せる)などの便利オプションも付いているので,流石に使わない理由がないかということで乗り換えた次第です。最初の設計の時点で提案してもらえなかったのは私の不徳の致すところですね。
設定は↓これだけです。
import satteriExternalLinks from "satteri-external-links";
export const externalLinkPlugin = satteriExternalLinks({
target: "_blank",
rel: [],
});
今回使ったのはtargetとrelだけですが,オプションは全部で7つあります(v0.1.1時点)。
| オプション | 既定値 | 内容 |
|---|---|---|
target |
なし | 対象リンクに付けるtargetの値。'_blank' | '_parent' | '_self' | '_top' |
rel |
['nofollow'] |
対象リンクに付けるrelの値。文字列または配列 |
properties |
なし | <a>要素に足す任意の属性。{ className: ['external-link'] }のように書く |
content |
なし | リンクの末尾に足すノード。アイコンや「opens in a new window」といった注記を差し込む用 |
contentProperties |
なし | 上のcontentを包む要素に付ける属性。{ className: ['sr-only'] }にすれば読み上げ専用にできる |
protocols |
['http', 'https'] |
「外部リンク」と見なすスキーム。'mailto'を足せばメールリンクも対象にできる |
test |
なし | 対象を更に絞り込むフック |
protocolsとtestを除く5つは,値そのものではなく(element) => 値というコールバックでも渡せます。「このドメインのときだけrelを変える」といった出し分けをしたい場合はそちらを使う形になります。
testに登録したコールバックはhrefを持つ<a>要素すべてに対して呼ばれ,trueを返すリンクにだけ処理を適用する形になります。当初は内部リンクも絶対URLで書いていたので,そのままだとtestにオリジンを見て自サイトかどうか判定する式を書く必要があったんですが,これを機に内部リンクは全て相対URLを使うという設計に変えたため,結果的に使わないことになりました。
このプラグインは既定でrel="nofollow"を付けるので,rel属性を付けたくない(ブラウザのデフォルト挙動に任せたい)場合はrel: []と明示する必要があります。relを設定しない理由については前回の記事をご覧ください。
まとめ
やっぱ知識って大事だなぁと思いました。それでは。
参考
- satteri (GitHub)
- Plugin API — Sätteri
- satteri-plugins (GitHub)
- @astrojs/markdown-satteri (GitHub)
- Configuration — Zola
- Markdown Extensions | VitePress
Mozillaが運営する,HTML・CSS・JavaScript・Web APIのリファレンスサイト。各機能の仕様・対応ブラウザ・使い方が網羅的にまとまっている。
ブラウザベンダーや標準化団体も編集に関わっており,Web開発における事実上の標準的な参照先になっている。
Rust製の静的サイトジェネレーター。単一のバイナリで動き,テンプレートエンジンもMarkdownパーサーも同梱しているため,Node.jsのような外部ランタイムやプラグインのインストールを必要としない。
Viteをベースにした,ドキュメントサイト向けの静的サイトジェネレーター。Vue製で,Markdownで書いた文書をそのままドキュメントサイトとして公開できる。
技術ドキュメントという用途に絞られている分,検索・サイドバー・外部リンクの扱いといった「よくある要求」が最初から組み込まれているのが特徴。

