Mimir Yokohamaで続く改修、力を注ぐ

2018年8月5日、Mimir Yokohamaのソースリポジトリにはじめてのタグ、5.0が打たれた。

なぜはじめてのタグなのに5.0だったのか。

これは、Aki SI&Eのプロトタイプを1.0, Aki SIEのウェブサイトを2.0, Mimri YokohamaのWordPressを3.0, 現在のウェブサイトになったときを4.0とカウントしたものだ。

つまり、はじまって以来の、同一システムのままのバージョンアップとなった。

しかもこのタイミングである。 先日、大幅な編成変更と、いいね機能、コメント機能の追加を行った。 これで新しい船出だというタグではない。かといって同バージョン最終仕様としてのタグでもない。

これから次々とアップデートが予定されている。 その中での一区切りだった。

13年積み上げてきたものとは

PureBuiler Simplyの原型となっているのは2005年のACCS1である。

もうブログが流行り始めていた頃だったが、既に「事前生成戦略」に関するイメージはあった。 ブログを避けた理由は、一連の流れを持つ記事群を拾いにくくなること、そして時間とともに消えてしまうことだった。

「普遍的な内容を、きちんと分類して読みやすいように提供したい」と考えたわけだ。

様々な機能、様々なアイディアがあった。 実装されたものもあるし、実装されなかったものもある。 言語もPerl, PHP, Zsh, Rubyと変わってきた。 このような変遷をたどっているのはEQAIとこれだけで、まさに私のライフワークであり、また成長の軌跡でもあった。

この中でこだわってきたものもある。

例えば、デザイン性を保ったまま軽量・高速なウェブサイトを構築すること、可能な限り高いアクセシビリティを提供すること(環境や回線の違い、身体的ハンディキャップなどで差別しないこと)などもそうだ。 意味あるコンテンツを、読みやすい形で提供する、というのもある。

ACCSが追い求めてきた機能は次のようなものだった。

  • カテゴリで分類されて探しやすいインデックス
  • 検索機能
  • 意思表示機能 (コメント、と考えていることが多かった)
  • 連続した記事のページめくり
  • 要約の先読み
  • インライン用語集
  • prev, next, glossary, index, description情報を持たせる

PureBuilder Simplyは当初、Mimir YokohamaのWordPressページで提供されている全機能を提供する、ということを目標としていた。 これについては既に達成されている。そのために、従来のPureBuilderにはなかったタグ機能なども追加された。

「いいね機能」「コメント機能」はWordPressに完全な意味で標準であるものではないのだが、事実上付属するようなもの(特にコメント機能は)なので、これを追加したことで、事実上「WordPressを置き換える」というミッションは完遂した。

だが、同時に既にWordPressにはない機能の搭載もはじまっていた。 用語集機能はWordPressではなく、PureBuilderの伝統に由来する。そう、ACCSが目指していたものを達成する、という次のミッションに向かいはじめたのだ。

そこでつけられたのが5.0タグだった。

用語集機能

用語集機能という発想のスタート地点は、私が使っていたWindows 98SEマシンに搭載されていたインライン翻訳ソフトだった。

カーソルを載せるだけでその単語を翻訳してくれる、というソフトウェアは今持ってそれ以上のユーザービリティを提供するものはない。

「カーソルを載せたらわからない言葉を教えてくれる」というのは最高に便利だと思ったのだ。 現在はニコニコやはてなが似たような機能を提供しているが、あれはページが変遷してしまうため私からすれば理想的ではない。 どらちかといえばWikiに搭載されているインライン展開のほうがずっと理想的だ。

この機能は

  • 表示後にJavaScriptでtreatする
  • 生成時にHTMLを置き換える形で組み込む
  • ソースドキュメントを改変してから生成する

という3つのパターンを行ったり来たりしている。

PureBuilder SimplyではPost Plugins/Pre Pluginsの構造からこの3つとも選択肢として取ることができる。 Mimir YokohamaではPost PluginによるHTML置換え方式を取っている。

このあたりは試行錯誤の成果といえるだろう。 用語集ページも含めてYAMLの辞書ファイルから生成しており、文書に対して特別な処理は必要ない。

次の記事、前の記事

ACCSで最も苦戦したのがこの機能だ。

この解決については何度か言及しているが、今は前後関係をメタ情報として書く、という仕様になっている。

解決方法としては後退しているように見えるが、前後関係の自動解決はファイル名なり、もしくはなんらかのヒントなりを厳密に管理する必要があり、結構バグりやすいということを経験したのだ。

複雑な置換えやユーザーの管理によってバグを発生しうるようなものであるならばメタ情報をユーザー自身が書くべきだ、という割り切りは、これまでの歴史の中で手にしたバランス感覚である。

実のところ今の構成では前後を自動化することは難しくない。 だが、ユーザーがそれを守ってくれることを期待すべきではないだろう。

Post pluginsはページ生成が終わってから実行されるが、これは「他の記事の情報を取得できるようにする必要がある」からであり、 環境変数$pbsimply_indexesという形でデータベースのパスが渡っていることから

のようにして取得できるし、 連番に限るのであればもっと簡単に

とできる。 ちなみに、ディレクトリを認識させる方法は最新のコミットで環境変数$pbsimply_subdirに含まれるようになった。

このように「できるけれど、あえて手書き」だ。 これは、問題を簡単にするためと、この処理をPandocテンプレートを通じて行いたいためだ。 Pre Pluginsを使えばできるが、かなり複雑なことをすることになるため避けている。

意思表示機能

過去には「コメントを直接にHTMLファイル化し、objectで読ませる」ということをしていたこともある。

表示するかどうかを別として、コメント機能はそのときとあまり変わっていない。 表示させるために必要な部分を削ってシンプルになったくらいだ。

このような機能は本質的な部分は極めて簡単に書けることはこれまでの経験によって証明されている。 どの程度正当性を検証する必要があるかという点がwebアプリケーションの分量になる。

要約の先読み

まずは要約を入れる

いよいよ今回のハイライトだ。

descriptionへの対応自体は最初のリリース時点で

という記述があり、対応はちゃんとしていた。 だが、「書くのが面倒」「書いてもあまり意味がない」ということで放置していた。

「descriptionの先読み」は今まで実装計画には入っていたが、実装されたことはなかった。 そもそもdescriptionってSEOのために入れられているくらいで、「descriptionを読ませる」という発想はあまりない。

Firefoxだとこんなふうにブックマークのプロパティを表示するか、ブラウジングライブラリー上で詳細表示にするとdescriptionが表示されたりするのだが、これを見たことがある人という人は地球上に5桁いないのではないだろうか。

Firefoxのブックマークの詳細

だがdescriptionは入れたいと思っていたし、それを活用したいと思っていた。

そもそもの発端はトップページのレイアウト更新で、最新の更新記事と要約を(ニュースとは別に)表示したい、ということだった。 「どうせ記事の要約書くんだったらdescriptionに入れようよ」ということだ。

実はAtomフィードもスタンバイしている。

要約を見える形に

だが、これだけではおもしろくない。どうせ要約を表示するのならばぜひともユーザーに見える形にしたい。

私の文章は基本的に長いので(体系的でない短い文章に価値を感じていない)、読むのがしんどい人もいるだろう。 読むかどうか決めるために要約は重要だ。

要約を見たいタイミングとはいつだろう? やはり記事を読み始める前だろう。ならば本文前に

とかやってやればいいし、そのほうが効果的なのかもしれないが、既に「文書情報」という項目があることを考えるとちょっといただけない。

そこで文書情報に追加した上で「記事タイトルをクリックすると文書情報にジャンプする」という仕様にした。

これはヘルプページにも書いてあるけれども、誰も気づかなそうだ…

もうひとつ、利用者は多くなさそうだが、カテゴリインデックスがある。 ACCSとしてはこれが中心であり、ぜひとも使って欲しい機能だ。 世の中、情報を整頓するということに怠けすぎて、検索が全てになってしまっているので、使われていないような気もするけれど…

しかし私の意図としてはこのようなインデックスを活用してほしいというのがあるし、やはりタイトルだけではわかりにくい。 かといって変遷するとだるいので、変遷せずにインデックス上で要約を確認できると便利だ。

これはタイトルで関心をそそられた後の二次的な情報であり、通常は一覧性が高いほうがいい。 というわけで、ツールチップにしてみた。

PureBuilder Simply ACCS上でDescriptionを扱う

単純には記事タイトルにtitleで入れてあるため、ロールオーバーツールチップとして表示される。 だが、スマホだとこれが効かないので、補助的に“📖?”と表示して、これをタップすればツールチップが表示されるようにしてみた。 全く標準的でないインターフェイスなので、あまり気づいてもらえないような気もするけれど…

PBS ACCS用ツールチップ実装

何度見てもJavaScriptの複数代入が慣れない。 慣れればみやすそうだけども。

世の中的には割と珍しいDOM操作をしているが、これはelementに対してイベントリスナを設定するためで、innerHTMLだと二度手間になる。 基本的にやっていることは「記事部分 > リスト全体 > リンク」と絞り込んでいって要素を作成して追加する、という手順だ。 末っ子要素を追加するとき(今の要素の親要素の最後の子要素として追加する)はelement.parentNode.appendChildという手順は覚えておいてもいいかもしれない。

glosarryと共通のコードがライブラリとして読まれるようになっている。ライブラリはdeferだがasyncではない。

900pxを堺にしているのは、「サイドカラムがあるのであれば表示領域は少なくとも右側にサイドカラム幅はあるが、シングルカラムになるとそうではない」からだ。(このページのシングルカラム境界は800pxである)

機能チェックしているが、document.addEventListenerできないブラウザでJavaScriptに対応しているものはあまり残っていないだろうし、あっとしても単純にイベントリスナー設定時にエラーになるので放置してもいいかもしれない。 ただし、間違って複数回ライブラリが読まれたときのためにArt.tooltipはしておかないとイベントリスナが複数設定されてしまう。

PureBuilder Simplyはうまくいっている

PureBuilder Simplyがここまでうまくいっている理由としては、やはりPandocの強力さがなによりだろう。

PureBuilder SimplyはPandocが持っている機能をちょっと拡張する…という考え方をしている。 今までドキュメントジェネレーター自体を制作していた(PureDoc)ことと比べると問題はかなり簡単になっている。

Pandocの動作は必ずしも簡潔ではなく、自分で実装するのであればドキュメントジェネレーターにここまでの機能をもたせることはないだろう。 だが、Pandocがある以上はPandocを使いたい。

もしPandocがなければdocutilsを拡張することを考えただろうが、その場合はPureBuilder Simplyは今のように良いツールにはなっていなかっただろう。

PureBuilder Simplyが今ほど素晴らしいツールになったのは、Pandocがあったからこそだ。

Mimir Yokohamaに対して行われている様々な拡張は今の所PureBuilder Simplyに対して適用されていない。 これは、PureBuilder Simplyが生成するものに対する機能ではなく、Mimir Yokohama固有の、そしてテンプレートとCSSによるものだからだ。

だが、いくらかでも一般化してPureBuilder Simplyに還元していければと思っている。 PureBuilder Simplyのエコシステムの充実は普及には不可欠だろうから。

Mimir Yokohamaに「いいね機能」「コメント機能」を追加

概要

Weekly 10000PVを達成して機能強化に力の入っているMimir Yokohamaのウェブサイト。

連続の機能強化でついに「いいね機能」と「コメント機能」が追加された。

実は先日の「お問い合わせフォームの実装」は単にその機能を実装する最小限ではなく、簡単なアプリケーションを実装できるプラットフォームになっており、 アプリケーションを追加する条件が整っていたのだ。 また、そのためのテストもしてあった。

そのため、実は今回コード追加はわずかで、両方合わせても23行ほどにとどまる。 ごく簡単だが、確証が持てないためにテストと本番環境のための修正を行ったりして結構な時間がかかった。

これに関してはみるべきところはあまりない。 受け取ったパラメータをファイルに書き込めば良いだけだからだ。

ちなみに、連打しやすいアプリケーションを入れるために連打の対策もサーバーにしてあった。 実は先のパフォーマンスチューニングはこの対策によってパフォーマンスが低下してしまったため、これをカバーするついでに行ったものだった。

いいね機能の設計

いいね機能はごくシンプルだ。

Pandocテンプレートを使ってページタイトルを埋め込むことができるので、これだけ使うのであれば単にテンプレートの中にフォームを書けばいいだけ、ということになる。

実際はリファラ(Rack::Request#referer)及びユーザーエージェント(Rack::Request#user_agent), IPアドレス(Rack::Request#ip)も特定に使用している。

ポイントは「一度送信したら表示を変更、送信を無効化する」ということだ。

inputタグのdisabled属性を使うことで送信ボタンを無効化している。

そう、この機能はHTML上でインラインで書かれているのだ。 このような書き方はW3C的には推奨されないのだが、Googleは推奨している。 実際、これだけのためにJavaScriptファイルをロードさせることをしたくなかったので、インラインにした。

ポイントは

  • Legacy DOMにおいてフォーム部品は連想配列のようにアクセスできるようになっている
  • submitボタンのラベルはvalueである
  • disabledによってフォーム部品を無効化できる

である。

これらの処理は送信の「前に」行われる。 これは正しいことではないが、問題はない。 なぜならば

  • 送信できなかったからといってユーザーが修正するなどの手を入れる余地はない
  • 特に返信を必要とするものでもないので、送信失敗はクリティカルな問題でもない
  • サーバーエラーなどは表示されるようになっている

からだ。

また、画面変遷せずに送るだけ…というと、Ajaxで非同期に送るしかないように考えるかもしれないが、 実際は現代のブラウザは基本的に2XXステータスで空コンテンツを返すとページ変遷しないようになっている。

このような用途のために204(No Content)が用意されているため、204を返す仕様だ。 できるだけブラウザの標準機能に頼るようにしている。

このボタン、めっちゃgooglebotが押してくる…

コメント機能の設計

公開されるものではなく、sanitizeしなくても問題が発生しない設計になっている(単に文字列として扱う以上の取り扱いがなされる条件で使用しない)ため、非常に楽だ。

難しく考えるよりも、シンプルで挙動をちゃんと把握できていて、余計なことをしない方法をとるのが最も楽に、確実に、バグなく設計できる。

ただ、この機能に関しては「コメントフォームの表示」などが必要になり、JavaScriptが必要になった。 また、コメントするというあまりとらない行為のための部品であり、常にロードすることは非常に好ましくない。

そこでとった方法は「JavaScriptの遅延ロード」であり、「クリック時にelementを追加してJavaScriptを読み込む」という方法だ。

これでscript要素を追加している。

では呼び出されたあとどうしているのか…というと、こんな感じだ。

そう、コメントフォームは標準でHTMLに含まれていない。

パフォーマンス的にみても親切機能のためにもう重くなりつつあるドキュメントをこれ以上重くしたくないので、 「フォーム部分はJavaScriptがロードされたときにinnerHTMLで書く、という方法をとっている。

「クリックされたときにはじめて必要とされるのでドキュメントノードを追加してロードする」という手法は稀に使われるが、さすがにそれでHTMLドキュメントそのものを生成するというのはまず見ない手法になっている。

連打されたときのことを考えて、clickイベントを発生する値に特殊なプロパティを埋め込み、何度も実行されないようにしている。 なお、これは本当に連打したときだけ機能するもので、JavaScriptシングルスレッドなので一回このスクリプトに入っていまえばこのスクリプト中に割り込まれることはない。 だが、連打されるとイベントがキューに入ってしまうので何度も実行されてしまうことから、そうしたことがないようにしている。 シングルスレッドなので、このスクリプトが実行されているのにプロパティが設定されないうちにまた実行されるということはない。

元になるフォーム自体は存在していて、submitボタンを配置すればフォームの送信は可能だ。 そのため、submitイベントに対するイベントリスナーを設定するものはHTML上に静的に存在している。

その上で「フォームはやっぱり閉じられたほうがいいな」ということでボタンクリックに対するイベントの変更、及びボタンラベルの変更を行っている。

送信を行った場合はもうコメントフォームは使わないので、コメントフォームは非表示にして(削除はしていない)ラベルを変更している。

ラベル変更だが、input部品とは違ってbutton要素は子要素テキストノードがラベルになっているので、dataプロパティの書き換えによって変更している。

もともとHTML上でLevel0 DOMイベントを使っているので、スクリプト上でもLevel0 DOMによってイベントを変更している。 いつもaddEventListenerを使っていたので、珍しい。 イベントの削除はイベントにnullを代入するだけだ。

なお、HTMLで全て含めてしまえばJavaScriptは排除できるのだが、これ以上余計な要素を組み込むことはできれば避けたいためこのような仕様になっている。既にDOMコンパイルは割と重い。

また、デザインポリシーからいって、多くの場合余計なコメントフォームを常に表示させておくというのは美しくないとも思っている。 JavaScriptを使わずCSSでオンオフするようにもできるが、そうすると今度はボタンのテキストを変更するのが難しいし、連打や再送信を防ぐのも難しい。

このことから、なにがなんでもJavaScriptを使わないのが正義、ということでなければ、 このような付加コンテンツはユーザービリティの点からも素直に使うべきだと思う。

こちらもいいね機能同様、送信する前に状態を変更してしまい、成功すれば204を返す仕様。

Legacy DOM と DOM Level0 Eventに関して

Legacy DOMやDOM Level0 Eventについて意見をもとめられることがたまにあるのだが、私としてはあまり勧められないと考えている。

非常に簡単なので学習にはいいが、Legacy DOMはHTMLの構造に依存する。例えば

として、

とかやってしまうと、ドキュメントの構造が変わるたびに修正だ。

だが、「フォームにのみnameで使い」「フォームはW3C DOMで特定する」のであれば悪くない。

DOM Level0 Eventはイベントリスナーが1つしか登録できないためモジュール設計になっている場合や、なんらかのライブラリを使っている場合は使ってはいけない。 イベントを「追加」する場合はLevel2で、イベントを「設定」する場合はLevel0という使い分けも考えられる。 HTMLに直接書く場合はLevel0しか書けないので、その部品の基本的な動作と定義されているならLevel0でもいい。

ただし、その場合でもできればLevel2 Eventを「後から追加する」のが適切なので、DOM Level0 Eventの使いどころは今回のようなものが唯一だと思う。

Chromiumのインスペクタにいつの間にかモバイルデバイスインスペクションが入っていた

web分野の人にとっては当たり前の可能性もある知識だけれども、Chromiumのインスペクタにモバイルデバイス向けの検証機能がついていた。

Chromiumのモバイルインスペクタ

これをオンにすると、モバイル的な画面サイズとなり、同時にタッチエミュレーションが行われるようになる。 レンダリングが調整されるということはなく、あくまでこの2点が変更される。

モバイルインスペクタのガイド画面

Responsiveモードではサイズを柔軟に変更することができる。 実際に小さな画面ではどのように表示されるのかということをエミュレートできる。

また、デバイスごとの画面設定も可能。 候補は少ないがiPhoneは入っている。

機種や回線をエミュレートできる

ここで言う解像度は論理ピクセル数である。 例えばiPhone8の画面の実ピクセル数は1334×750だが、668×375と認識されている。

これはスケーリングによって1pxを実際の1ピクセルと対応させない、というもので、先日書いたので参考にしてほしい

拡大率は画面表示のサイズを変更するもので、リアルサイズに近づけることで物理的な大きさもチェックできる。

上部にあるガイドは各種デバイスに合わせた表示状態をエミュレートできるものになっている。 クリックするとResponsiveの状態で各プリセットにあったものになる。 Laptopが1024px, Laptop Lが1440pxとなっており、この上に4k2560pxもテストできるようになっている。

また、ネットワーク状態もエミュレートできる。 低速な回線ではどれくらいロードにかかるのかということもテスト可能だ。 offlineはページをロードしたあとにofflineにし、Ajaxが使えない状態をテストすることができる。

Low-endでMimir Yokohamaが4秒程度なのに対し、Chienomiは15秒ほどかかったので、その高速さにだいぶ満足している。

デバイスの論理ピクセル数なんて調べるのはとても大変なのでプリセットが入っているのはとても嬉しい。 サイトの設計にもよるが、私の設計だとアップロード前に十分なテストが行える。

メッセージフォームのサポート (Nginx + FastCGI + spawn-fcgi + Rack + Ruby)

あらまし

Mimir Yokohamaでついにお問い合わせ方法として「メッセージフォーム」が追加された。

なにがついになのか、なにをドヤっているのかと思うかもしれない。 まぁ、ドヤってはいないのだが。

実は私はかなり長い間ウェブアプリケーションをほとんど作っていない。 そして、今まで私が作ったウェブアプリケーションは、専用サーバーを持つサーブレットタイプか、もしくはCGIだった。

馬鹿にされがちなCGIだが、利便性は高く、頻繁にアクセスする性質を持たないアプリケーションには適している。

そして、そもそもウェブアプリケーションを作っていなかったのは、私が「事前生成戦略」の研究と実験に注力していたからで、 どちらかといえばウェブアプリケーションからは離れる方向にあった。 そして、ウェブアプリケーションを必要とするとしても大部分は静的ページとして提供できる方式を目指していたため、CGIで十分事足りたのである。

ちなみに、これまでウェブサーバーは

  • Apache
  • lighttpd
  • delegate
  • Nginx

という経過をたどっている。 Apacheは言うに及ばずlighttpdとdelegateはApacheよりもCGIが簡単だったので、「ほぼCGI」だった。

だが、時代は変わった。NginxはCGIをそもそもサポートしない。 私も新しい時代に対応する必要がある。

ちなみに、この作業は次の仕事のための実戦テストという意味合いもあった。

方針を考える

最も話が速いのはFastCGI Wrapである。

NginxはFastCGIをサポートしている。 FastCGIはプログラムをデーモンのように起動しっぱなしにする。

だが、通しで実行するプログラムとデーモンではそもそもの前提が違う。 そのためCGIプログラムをFastCGIとして動かすのはそれなりにハードルが高い。

そこでFastCGI Wrapの登場である。 FastCGIとして利用されるプログラムをFastCGI Wrapにする方式だ。 このラッパープログラムは要求に合わせて都度CGIプログラムをCGIインターフェイス経由で起動する。 結果的にFastCGIの意図は無視して従来型CGIを動作させるようにするというものだ。

この方法は結構出てくるのだが、基本的には既存のCGIプログラムを動作させる話である。

個人的な感覚としては、無駄なプロキシを噛ませるような方法を使ってまでCGIに固執したくない…というか、実はfcgi-wrapってそれなりにめんどくさい。

だったらFastCGI直というのもありかなぁ、と考えるわけだ。

ところが、やっぱりFastCGIはデーモン状のプログラムを想定しているわけで、やはり前提が違う。 要求として割と複雑なのか、デーモン化に関してはspawn-fcgiに担ってもらって、さらにRackを使う、というのがどうやら主流らしい。

だいぶ話が複雑になってきた。

サーバーはNginxである。NginxはFastCGIインターフェイスを経由してFastCGIプログラムにパラメータを渡し、応答を受け取る。

FastCGIプログラムはデーモンである。 Rubyでは次のようにしてFastCGIプログラムを書くことができる。

あるいは、CGIライブラリ互換インターフェイスを使うことで、#each_cgiの中身はまるっきりCGIと同じにすることもできる。

spawn-fcgiはこのデーモン部分を担う。 つまりeachしてる部分を担ってくれるわけだ。

プロセスとしてCGIインターフェイスで起動するわけではないので、fcgiwrapほどの互換性はない。 感覚はCGIに近いが、インターフェイスは意識する必要がある。

Rackはミドルウェアと呼ばれている。これはまずFastCGI抜きで話そう。

Rackはインターフェイスを担っている。 今までプログラムはCGIなり、あるいはFCGIなり、さらには各種フレームワークやサーブレットの様式(例えばSinatraとか)で書いていた。

Rackはこれらの違いを吸収するモジュール設計のものだ。 Rackに準拠したプログラムを書いておけば、たとえ愛用のフレームワークがディスコンになっても、サーバーが変わっても安心、というわけだ。

だが、Rack自身はサーバーではないからサーバーがいるのだが、Rack組み込みのサーバーというのはもう完全にRuby世界の住人だ。 だってRackはRubyのWebアプリケーションインターフェイスだから。

Passengerというソフトウェアがあって、これはwebサーバーのモジュールとしてRackに対応する。 Apacheでは比較的簡単だけれど、Nginxだと結構きつい。

そこでRackに対応したサーバーを立ててサーバーとサーバーでやりとりさせる、という方式がすごく現代的。 直接にRack経由でプログラムとやりとりするのはRackに対応したサーバーだけれど、Rackに対応したサーバーにwebサーバーとしての機能を持たせると大変なので、「本物のwebサーバーに矢面に立ってもらって、RackサーバーはあくまでRack対応に特化」というわけである。

Rackに特化したサーバーとしては(別にRackだけではないんだけど)、Webrick, Mongrel, Puma, Thin, Unicornあたりがある。

しかしRackでやりとりする方法があればいいので、FastCGI + Rackという方法もある。 それはRack側でFastCGI経由で受け取って、応答するためのハンドラが用意されている。

つまり、Unicornのようなサーバーを立てる代わりの手段としてFastCGIが使える。 FastCGIもデーモンを必要とするので別にFastCGIにすることで間に挟まってるものを減らす効果はない。 ただ話が楽になるだけである。

Unicornはむちゃくちゃ速いので、UnicornでUnixドメインソケットを使えば形式とししてはspawn-fcgiでUnixドメインソケットを使っているのと一緒だし、やっていることははるかに高度になる。 これが超モダンなやり方である。

が、あえてのFastCGI。 理由は管理する要素数を減らすためである。必要がないのにいかついものを使うことはしない。 これはサーバー運用のコツでもある。

なお、Rackに関してはかなり情報が少ない。 なんらかのフレームワーク…というか、ほぼRailsのバックエンドとしてのRackの話だけで、Rack単独の話ってない。 そして、FastCGIを使う話もない。これもだいたいなんらかのアプリケーションが「使ってる」あるいは「使わせる」話になる。

なんというか、みんなそんなに自分でプログラム作るってことをしてないのか… 世の中エンジニアたくさんいるのに、WordPressとRailsだけで満足なのか…

そんなわけで情報が猛烈に足りていない中、FastCGIとRackについて勉強することになったわけだ。

なお、Nginxでアプリケーションとやりとりする方法に関してはDiscourceで散々やったので経験済みだ。

なぜRackなのか

もちろんこのことからもわかるようにRackはなくても構わない。 spawn-cgiも使用せず単独のFastCGIアプリケーションを開発するのは容易である。

私が気にしたのはRubyのfcgiライブラリは2013年から更新が止まっているとい点だ。 また、Arch LinuxではfcgiライブラリはAURにもなく

# gem install --no-user-install fcgi

とするよりない。

ベーシックな機構であるFastCGIそのものが廃止になるようなことは考えにくいが、NginxのCGIの扱いのように消極的なサポートへと変遷する可能性はある。 その場合にアプリケーションの書き直しが発生してしまう。

Rackは現在主流であり、新規採用例も多い。 Rackが廃止になると影響を受ける範囲も非常に広いので今後10年は安泰だと思われる。

そこでFastCGI+Rackという構成にしたわけだ。 この場合でもRackはFastCGIをネイティブサポートしているわけではく、fcgiライブラリを使ったハンドラを同梱しているだけなのでfcgiライブラリは必要となる。実はこれを回避したかったのだが、結局はできなかった形だ。

とはいえ、この状態であればFastCGIを捨ててUnicornに移行するのも難しくはない。

とりあえずやってみる

Nginx

location / {
    root /var/www/testapp;
    fastcgi_pass /var/run/fcgi-testapp.sock
    fastcgi_index testapp.rb;
    include fastcgi_params;
}

Rack Application

Requestのほうはインターフェイスに絡むけれど、 Responseは単純に#finishでRackに沿った配列を返すための便利クラス。なくてもいい。

spawn-fcgi

# spawn-fcgi -U http -s /var/run/fcgi-testapp.sock /var/www/testapp/testapp.rb -n

試してるうちは-nつきにしてフォアグラウンドで実行するのが楽

実用的にする

起動スクリプト

forkingなので停止・再起動の制御のためPIDファイルを作る。

Systemd Unit

[Unit]
Description = FastCGI Rack Test Application
After = nginx.service

[Service]
Type = forking
PIDFile = /var/run/fcgi-testapp.pid
ExecStart = /usr/local/sbin/fcgi-testapp.bash
ExecStop = kill $MAINPID

[Install]
WantedBy = multi-user.target

forkingなので$MAINPIDがそのままでは使えないため、PIDFileで指定しておく。 Nginxのあとに起動しておいたほうがいいような気がしたけど、なくても構わない。 アクセスが激しい場合は逆にNginxの前に起動したほうがいいだろう

spawn-fcgi自体にはアプリをリロード、再起動するような機能はない。

おまけ

S-NailがSubjectも本文も、UTF-8をちゃんとエンコードしてくれるのですごくびっくりした。

「mailxとは違うのだよ!!!」ってことか。 さすがSMTPやPOPやIMAPにも対応しているだけのことはある。

ここの部分(MIMEエンコーディング)も自分でやるつもりだったので、かなり省力化された形。

今回の構築は他にも色々やったのだけれど、共有して意味のある部分はこれくらいのものだろう。

Mimir Yokohamaに用語集機能

Mimir Yokohamaに用語集機能が追加された。

用語集機能はACCS(PureBuilderに取り込まれる以前の)やAki PHP Content Collection時代に実装されていた。

シンプルにACCSではJavaScript+Ruby/CGIで、本文エレメント(テキストノード)を探索し、文字列を送信してRubyで置き変えたものを返してもらい、innerHTMLを置き換える方式だった。

APCCでは単純に力技で置換えていた。

PureBuilderではこの機能を実現していなかった。 正確には一時期実装していたこともあるが、十分な質でなかったのだ。

そしてついに復活した用語集機能。

Pre Pluginsで処理することも考えたが、Post PluginsでRubyで処理している。

「辞書をワード長が長い順に並べ替えて|で結合し、正規表現としてコンパイルして置き換える」という手法はかつてのコードで考えたものである。

用語集のページも単純な手法でMarkdownを生成している。

で、今回もライブラリなしのPureJSで処理している。 今回は44行ほどだが、18行は共有化できるものなので、もう少し短くすることも可能だ。 DHTMLのお手本のような初歩的なものである。

Event.stopPropagation()Event.stopPropagationと書いて悩むはめになったけど。

PureBuilder Simply 1.4 リリース

PureBuilder Simplyの1.4をリリースした

特に大きな変更点は以下の通りだ。

  • HTML生成前に処理できるPre Pluginsに対応した
  • Pre Plugins/Post Pluginsで環境変数から文書メタデータにアクセスできるようになった

Pre PluginsはPandocにかける前のドキュメントを加工するものである。 Markdownと比べReSTは自由度が低いこと、それぞれのドキュメントフォーマットに基づいて処理しなければならないことから、新たに_docformatというメタ値が追加された。

今回のポイントはPre Pluginsであり、メタデータを渡す仕様は反映していなかっただけで、実は1.2時代からあった。

Pre Pluginsはおもしろいこと書いていないので、どちらかといえばメタデータ渡しの話をしよう。

これはPre Plugionsの一部である。 IO.popenはコマンド群の前に環境変数を置くことができる。 シェル的にいうと

みたいなことだ。

もちろん、同じような手法はRubyでも使えるけれど、ちょっとめんどくさい。IO.popenの利便性は簡便に損なわれてしまう。

かゆいところに手の続くRubyは、ちゃんとそのプロセス用に環境変数を渡す方法を用意してくれているわけだ。

予め環境変数にセットするのと何が違うのか。

まず、自身の環境変数としてセットすると、メモリーを2個分使う。

また、環境変数がそのプログラム自身の制御に影響するケースでは問題が生じる。

さらに、何度もプロセスを起動するたびにセットしてしまうと、ガベージコレクションの問題が出る可能性がある。

結局、子プロセスに対して伝播したいだけの環境変数はこのプロセスに対してのみセットするのが適切、ということになる。 シェルにおいても

ではなく、

あるいは

とすべきである。

静的ウェブページでタグ機能を提供する

Mimir Yokohamaのページでタグ機能がバージョンアップし、完全に動作するようになった。

もともとWordPressで提供していたMimir Yokohamaのウェブページだが、独自システムに移行する際には「WordPressで利用していた機能はすべて提供する」という方針のもと、新しいサイト構築システムPureBuilder Simplyを開発して構築した。

PureBuilder Simplyは静的ページを生成するプログラムであり、webサーバーには静的ファイルを配置する。 これはパフォーマンス、セキュリティ、管理、リソースいずれにおいてもメリットが大きい。

基本的にこの考え方は「異なる内容を生成するタイミングより、同一の内容を返すタイミングのほうがずっと多い」ということに基づいており、キャッシュよりも合理的である。 一方、どうしても難しい要素もある。ひとつはページ生成パターンが無限である検索機能、そしてもうひとつはヒントを日本語のみにした場合のタグ機能だ。

検索機能はGoogleに頼っているが、タグ機能は難関だった。

タグ機能を作る

方針

  • 要求タイミングでの動的生成は行わない
  • PureBuilder Simplyの枠内で解決する。 もし解決不能な場合はPureBuilder Simplyを拡張する
  • (PureBuilder Simplyでサポートされている) eRubyテンプレートは使わない。あくまでPandocテンプレートで生成する
  • ファイル名が日本語になることはやむを得ないものとする (Nginxは日本語ファイル名に対してURIエンコーディングされたパスでアクセスできる)
  • リンクを日本語で書く(エンコードをブラウザに委ねる)ことは許容しない
  • ユーザー (この場合自分だけど) にタグに関して文書にタグ付けする以上の手間をかけさせない

タグをつける

既にPureBuilder Simplyでは 「Frontmatterに文書に関する追加的情報を書く」 という仕様となっている。1

単純にこれを反映したタグを書けば良い。

ページにタグ情報をつける

Pandocテンプレートで簡単につけることができる。

英語なら割とこれで済む話なのだけど2、日本語だと当然

みたいなHTMLが生成されてしまう。

そこで、post generate機能を利用する。 post generate機能はページを生成したあと生成ページを加工できるものだ。 基本的に第一引数として生成されたファイルパスが渡される。 このほか環境変数を通じて他の情報にもアクセスできたりするのだが、これはあまり利用を想定していない。

post generateは.post_generateディレクトリ内のファイルを順次Perlに渡す形で実行される。 Perlはshebang行を解釈するので、Perlで書かなければならないわけではない。 そして、スクリプトの出力にファイルは置き換えられる。

これは例えば

のような非常に簡単なフィルタが書けるということだ。

これを使って

のように変換してあげればタグのタイトルは日本語だがURIはエンコード済み、という形ができあがる。 もちろんスラッグへのマップを書いてもいいのだが、タグを管理するのは手間なので避けた。

タグページを作る

生成されたページの情報はindexes.rbmというファイルにRuby Mershal形式で保存される。 ここには本文は含まれないが、大概メタ情報にアクセスしたい場合と本文にアクセスしたい場合は別なので、分けている。

PureBuilder Simplyにおいて、「メタ情報を文書に書き、処理した情報はデータベースに書いておく」というのは設定上の核であると行って過言ではない。 これにより、文書のメタ情報を扱うことはPureBuilder Simplyの外で行うことができるのだ。

タグページは.tagcloud.rbというスクリプトによって生成しているが、 これはMarkdownページを生成する。つまり、「タグページ自体をPureBuilder Simplyによって生成すべきページとして生成する」のである。 タグを含むページを生成・更新した場合は再度タグページを生成し直すことになり、そのためにrefreshというスクリプトもある。この場合、文書ページではないタグのindexを処理されてしまうと困るので次のような処理になっている。

では.tagcloud.rbは、というとこちらも結構単純。

見ての通り著しい力技でデータベースを集計し、最終的にはMarkdownを出力している。

従来もほとんどこうだったのだが、ページ側でエスケープしていないという理由でエスケープしていなかったので追加した。

おわりに

もう少し難しいかと思っていたのだが、どうやらPureBuilder Simplyの設計が思っていた以上に優れていたようで、タグ機能もスムーズに実装することができた。

PureBuilder Simplyは傑作といって差し支えない出来になっている。 もともと思っていたよりもずっと優れたツールになっているのだ。

PureBuilder SimplyはPureBuilderとしては実に3作目である。 Zshの機能をフル活用したPureBuilder, Windowsでも動作可能なようRubyで書かれたPureBuilder2。ページの生成にはいずれも活用できたが、サイト構築労力が高く、安価な案件で利用するにはしんどいものがあった。 また、構築できる内容も割と画一的だったため、様々な要求に応えるのは難しかった。 Zshで書かれたPureBuilderは構築時に任意のZshスクリプトを実行できる方式だったため、なんでもできるといえばできるのだが、サイト構築がプログラミング色の強いものになっていた。これはちょっとユーザーフレンドリーではない。

PureBuilder Simplyは名前の通りずっとシンプルだが、いままでよりずっと強力になった。

小さなスクリプトを書くことは、多くのプログラマにとってはあまり馴染みのないことかもしれないが3、やろうと思えば発想さえ知れば決して難しいことではないはずだ。


  1. これはReSTでもdocutilが許容しないような規格化されていないメタ情報を書くということだ。

  2. ちなみに、Chienomiではタグはすべて英語になっている

  3. Unixに浸っている人はむしろ息をするようにのように行動するだろう。PureBuilder Simplyの考え方はこれに基づいている。

LuaTex-jaでNumber too big.エラーがようやく解消

LuaTexで日本語ドキュメントクラス(ltjsarticle, ltjarticleなど)を処理すると

! Number too big.
ltj@@jfont ->luafunction ltj@@jfont@inner 

l.53 \kanjiencoding{JY3}\selectfont

と言われてしまう、という問題が続いていた。 Manjaro Forumで質問したところ、再現するけどupstreamへ、ということだったのでLuaTeX-jaのほうにご報告させていただいたところ、チケットを切っていただいた

Manjaroではつい先日Texliveが201804にアップデートされたが、この問題はLuaTeX-jaが201806として解決してくださっている。

このため、

$ git clone 'https://scm.osdn.net/gitroot/luatex-ja/luatexja.git'
$ sudo rsync -r luatex-ja/src/ /usr/share/texmf-dist/tex/luatex/luatexja/

とすればとりあえずこの問題は解決する。

だが、今度は

! Undefined control sequence.
\lltjp_um_unmag_fsize: ...@preadjust@extract@font 
                                                  \cs_gset_eq:NN \lltjp_um_f...
l.106 \begin{document}

なんて言われてしまう。 これは、

ltjsarticle + unicode-math で をしていないときに起きるエラーのようです. 別チケットにします.(#38372)

とのことで、チケットを切っていただいた

現在のところkitagawa_testブランチになっているが、恐らく近日中に取り込まれるだろう。 なかなか長い戦いだったが、ようやく論文や教材の清書も捗るというものだ。

ちなみに、この影響でgendoc-pandoc.zshのほうはMathfontに対応した。

先のシェルスクリプトを形にしました

一時キーボード無効

Temporary Disable Keyboard @GitHub

Xinputを使用してデバイスを無効/有効にするためのもの。

主な変更点は次の通り

  • 無効化を「時間制」「ダイアログ」「無限」から選択できるようにした
  • Zenityダイアログを若干調整
  • ランチャー用の.desktopファイルを追加

Zshスクリプトだが、case内で非常に珍しい;&(フォールスルーする)を使用している。

ターミナルエミュレータ選択機能

Terminal Selector @GitHub

主な変更点は次の通り

  • ランチャー用の.desktopファイルを追加
  • KDE Service用の.desktopファイルを追加
  • Nemo用の.nemo_actionファイルを追加
  • 対応する端末を大幅に増加
  • 利用できない端末を選択肢から除外するように変更
  • Zenityダイアログを調整
  • 柔軟に端末を追加できるようにファイルマネージャでの起動用の引数対応が連想配列で使用できるように変更

連想配列を使用するための方式は次のようなものだ。

わざわざ連想配列をテンプレート文字列とし、そこから配列に変換している理由は

  • 連想配列に格納できるのは文字列のみ
  • 置き換えしてからではDIRがIFSを含む可能性がある

ハイアクセシブルウェブサイトとウェブデザイン (フォント関連)

font-familyの指定

font-familyにシステムデフォルトのフォントを並べているのを非常によく見る。 例えばYahoo! Japan

MSN Japan

Google (google.co.jp)

まず先に言うならば、Arialは欧文書体であり、フォント解釈を厳密に行う環境の場合、Arialが指定されることで文字化けが発生する。 ここではArialがないというケースを除外して次のように処理される。

  • Arialにグリフがないフォントを置き換える
  • Arialにグリフがないためフォント自体を置き換える
  • font-family自体を解釈せず既定のフォントで表示する
  • Arialのみで表示しようとする (グリフのない文字は文字化ける)

このうち最も困るのは最後のケースだが(Arialを指定したがために文字化ける)、実際は欧文をArialで表示し、和文を他のフォントで表示して「美しい」と感じるのは難しい1。それよりは全部和文書体の方がバランス的にもマシである可能性のほうが高いのだ。そのため、最初のケースも明らかに意図的でない限りはまずい動作をする。2

昔、ごく限られた条件下では、フォントが指定されないことに起因して文字化けるということがあった。 これは、ブラウザがデフォルトフォントを欧文フォントに決め打ちしており、フォントレンダラーによる文字選択は可能なので、指定しなければフォントレンダラーに選択を任せないために文字化けるという状況であった。

しかし、現状で考えると

  • 日本語版のWindows/Macであれば和文フォントがあり、メジャーなブラウザは和文フォントを選択する
  • 非日本語版でデフォルトの和文フォントがないのであれば、指定したところでそのフォントはないので使えない
  • 日本語環境を無視しているようなブラウザをわざわざ使用するユーザーは文字化けすれば設定すると思われる

ということから、システムデフォルトのフォントを指定することは 現実的に意味がない

強いて言えば、ヒラギノを優先して指定することで、Windowsユーザーでもモリサワ書体を購入しているユーザーにはヒラギノを優先して使わせる、ということも可能だが、それは「ヒラギノが他のどんな書体よりも美しい」というMaccerの思い込みに過ぎない。3

Mimir Yokohamaでは次のように指定している。4

通常フォントをサンセリフ体、本文フォントはセリフ体を指定している。 セリフ体のSource Han Serif JPを除くと全て商用フォントである。 これは、もしユーザーが商用フォントを持っているのであればそれを使ってもらおうということである。 本来のfont-familyの使い方であると言える。

適当に選択しているわけではなく、本文フォントとなるセリフ体には流れるようなグリフで、十分に収録グリフがあり、長文を読むのに疲れないフォントであることを選択基準にしている。 サンセリフ体についてはいわゆる「丸ゴシック」である。「丸ゴシック」という抽象フォントがないため、イメージとしてより好ましいフォントを並べる、という方式をとっている。もしあるのであればsans-serifではなくrounded-sanscjk-rounded-gothicのような指定がしたいところである。

font-family指定がシステムデフォルトフォントを指定するというのは、「システムデフォルトフォントが嫌いで、わざわざ商用フォントを入れて設定しているユーザーに対してシステムデフォルトフォントを強制する」ということでもある。 これはユーザービリティを高めているのではなく損ねている。 そんなことに貴重な数十バイトを使用するべきではないのではないか。

webfontについて

webfontはCSS 3.0で追加された、ウェブリソース上に配置されたフォントをダウンロードして使用するものである。 font-familyがシステムフォントしか指定できないために、結局システムデフォルトフォントばかり指定するという無意味に近い状況がこれによって改善された。

なんでこんなものが追加されたかというと、従来デザイン上特定のフォントを使用したい場合にどうしても「画像を使う」という状況であったが、W3Cとしては常に文字情報であればテキストとCSSを使うべしという思想である。 これを実現するためにCSSにテキストを重ねたり傾けたりして配置する機能と共に、特定のフォントを使わせる方法を提供したものである。

「デザイナーの意図するフォントを指定できるようにした」と解釈される場合も多いが、実のところそれは副次的なものであり、W3Cがこの仕様を制定した意図としては「テキスト画像を撲滅したい」ということであり、既にテキストとして表示されている情報がどのように表示されようが(W3Cにとっては)どうでも良いのだ。

だが、実際のところW3Cの意図に反して画像撲滅よりは従来からテキストで表示されていた部分のデザイナーの意図反映に使用されている5

このために「確実に好きなフォントが使える」程度の理由でwebfontが指定される傾向があるのだが、それはデザイナーの完全なエゴである。

欧文フォントであれば(W3Cが考えたように)ロゴ画像をダウンロードするのとさして差のないデータ量であり、それほど躊躇われるものではないのだが、和文フォントであると常用漢字のみ収録のものですら2MB程度ある。画像などよりもはるかに大きく、通信料金で考えると2円ほどかかる。 これを表示ごとに要求することになるのだ。6

しかも、そもそもフォントレンダラーは一種のバイトコードインタープリタであり、潜在的にはフォントファイルはウィルスとして機能しうる7

また、そうでなくてもテキストと異なるグリフを持つフォントをダウンロードさせることにより、コンピュータ(例えばGoogleやSymantecのチェック)を欺いてユーザーに嘘の情報を見せるサイトを作ることもできる。 これは詐欺などにおいて有効な手法である。

にも関わらず、現状主だったモバイルウェブブラウザにおいてはwebfontを無効化する機能というのはない。 私も仕事にしていたこともあるくらいだからフォントは大好きなのだが、それでも自分の望むデザインのためならユーザーに負担を強いたり、不快な思いをさせても構わないというデザイナーのエゴには賛同できない。

長文のための工夫

私が書くコンテンツは(この文章を含めて)往々にして長文である。

これは、意味ある情報を提供してこそ意味があるという思想に基づくもので、内容空疎なイメージ戦略や思考停止で選ばれるものを提供しようとは思っていないから…なのだが、長文を含めて読むのが得意な人は比較的少数だし、やはり長文を読むのはどうしても負担になりやすい。

そこで、可能な限り長文を読みやすいように心がけている。

本文に対する設定としては

  • フォントは細めで、流れるような(連続した文字を少ないコストで読める)明朝体
  • 自動カーニングで行間を詰める(長文の流し読みが楽になる)
  • これだけでは「文字量が多く黒い」と感じてしまうため、少しだけ文字間を空ける
  • 黒さを軽減するため、行間を少し空ける
  • 結果として段落間の行間が詰まってみえるため、段落間に空白をもたせる

このほか、リズム感のある句読点や、かぎかっこの使い方、言い回しの選択などの技法と組み合わせてできる限り「楽しく読める長文」を心がけている。

ウェブサイトは内容であって見てくれではない、ということを忘れてはいけない。


  1. この指定は「欧文のみの要素に対して欧文フォントを適用する」指定ではない。和文フォントが適用される条件でもArialのグリフがあるものはArialが使われるのだ。

  2. もちろん、欧文書体を和文書体に含まれていない特定のフォントを選択してほしいという理由で意図的にこのようなことをすることもできるが、その場合は和文書体よりも先に欧文書体を書くべきで、MS PGothicよりもArialが後、というのはArialの欧文よりMS PGothicの欧文のほうが好ましく、けれど和文書体にある欧文ではなくArialを使ってほしい、というよくわからない美的センスである。

  3. モリサワ書体を含め、商用フォントを選択肢とするのであれば、無条件に「ヒラギノが最高」ではなく、数多のフォントからそのサイト、その文章に適切なフォントを選択できるはずだ。

  4. Chienomiでは既存のWordPressテーマを使用しているため、私は関知していない。

  5. これは当然の帰結と言える。デザインされた画像を作成するのと比べて、CSSでそれを実現するのは難しいし、同じフォントであってもレンダリングで見た目に差が出ることから望むほどデザインを再現してもらえない。記述量も多く、しんどい。

  6. キャッシュがあるから、というのは傲慢である。特にモバイル環境ではキャッシュを取り直す状況は非常に多く、セッションごとに取得すると考えて間違いない。

  7. このために以前のWindowsではフォントインストールにセキュリティリスクがあるという警告が出されていた。