S.F. Page

Programming,Music,etc...

静的ブログジェネレータはなんとか進めてる - jekyllのような静的ブログジェネレータをnode.jsで作る(4)

静的ブログジェネレータの製作は何とかかんとか飽きずに進めている。よってブログ更新も滞りがちである。
今のところの成果は以下に格納。

かなりいろいろいじっているので、どこをどうしたかもはやわからなくなっているが、一応書いておこう。

デザイン

デザインはblog.jxck.ioを参考にしている。ブログの構成としては、年毎のアーカイブメニュー、カテゴリごとのアーカイブメニュー、それと記事で構成される。トップページは年毎のアーカイブで一番新しい年のものでいいかなと今のところ思っている。
今のところの見た目は以下。

アイコンはOcticonsで、SVGファイルをインライン展開して使用している。展開する際不要な属性はすべて削除している。

一応レスポンシブデザインになっている。

メニューは簡素で、ハンバーガーメニューを使わないことにした。ナビゲーション・メニューはヘッダー・フッターに固定位置に配して、記事間の移動(次の記事・前の記事)はフッター・ナビゲーションで行うことにした。デザインはcssnextを使っているが、結構適当にやっている。多分無駄な記述が多いと思う。いずれ整理しなければと思っている。cssで思うのはflexboxがすごい便利だということだ。これからのレイアウトはflexboxで行うのが普通になるだろう。

静的ブログジェネレータ

静的ブログジェネレータの進捗は以下の通りだ。

  • はてなブログ記事を吸出す機能 … ほぼ完成
  • はてな記事を静的ブログジェネレータのファイルフォーマット(json属性付き.mdファイル)に変換する機能 … ほぼ完成
  • .mdファイルをブログコンテンツ(HTML)に変換する機能 … 6割くらい完成
    • markdown部分をHTMLに変換する機能 … markedで実装済み
    • 数式(tex)をSVGに変換する機能 … MathJaxで実装ずみ
    • コードハイライティング機能 … highlight.jsで実装ずみ
    • はてな記法を変換する機能 … 未実装
  • 年毎アーカイブメニューを作成する機能 … 8割くらい
  • カテゴリごとアーカイブメニューを作成する機能 … 1割くらい
  • AMP対応HTMLファイル出力機能 … 未実装
  • RSS対応機能 … 未実装
  • サイトマップ … 未実装
  • Blog Ping対応 … 未実装(サーバ側のスクリプトで実装予定)
  • SNSサポート機能 … 未実装
  • githubのwebhookを受けてホストファイルを更新し、更新・追加ファイルをgzip圧縮する機能 … 実装の見直しを実施したが、チェックは未実施

今回のこのジェネレータは、JSで何かを作ったら、即座に公開できるような環境を目指したいと思ったりもしているので、そういうことをするのに便利な機能も実装したいと思っている。例えば、githubレポジトリのソースコードへのURLを指定したら、そのコードをシンタックスハイライティングしながら取り込む機能とかね。

あとは関連記事情報をAI使って表示してみたいなぁ。。

これが完成したら、bl.ocks.orgやgithubでホストしているWebコンテンツはすべて集約しようかなとも思っている。単にプロキシするだけに終わるかもしれないけどね。

OneDriveのimageのembedで得られるurlが404になっていた。ブログ用の画像保管場所としては使えんな。。

表題の件、悲しいなぁ。。

OneDriveには保存した画像をブログに貼り付けるためのURLを生成する機能がある。私はこれを使って画像を貼り付けている。

しかしこの機能を使って生成したURLがいつのまにか「404 Not Found」になっていることに気が付いた。例の静的ブログジェネレータではてなの過去記事を移行するコードをテストしているときに発見した。

こういうことになってしまったのは2度目だが、今回は状況が違う。

上の記事では回避策としてはtumblrに画像を格納することで回避したが、tumblrでも同じことが発生してえらい目にあってしまった。

上記のようなことがあって、その回避のために「embed」機能ができたはずなのだが、その「embed」で作ったURLが気が付くと変わってしまっている。コミュニティにも同じ件で書き込みがあった。

そういうわけで私は画像保管場所としてのOneDriveの使用はやめることにする。生成されるURLがPermanentでない以上私の用途では使えないからね。なんでOneDriveはこんな仕様になっているんだろうか。不思議でしょうがないな。。

厄介なのはOneDriveの生成するURLはハッシュっぽい文字列になるので、例えば画像ファイルを私がホストしているVPSに移したとしてもリンクは手動で張り替えなくてはならない。面倒なことこの上ない。

非同期プログラミングは難しいね。やっぱり。 - jekyllのような静的ブログジェネレータをnode.jsで作る(3)

静的ブログジェネレータの製作は進めている。 進捗は以下のとおりである。

  • はてなブログのデータをエクスポートするコード -> ほぼ完成
  • エクスポートしたデータを静的ブログジェネレータ用データに変換するコード -> ほぼ完成
  • 静的ブログジェネレータ本体 -> 5割程度作ったところで、設計変更。作り直し中。

といったところである。

非同期プログラミングというのは難しい。私はPromiseを使っているので、Callback Hellには陥っていないものの、Promise Hellに陥りつつあり、これを回避すべく最近nodeで使えるようになったasync/awaitを使うことにした。
しかしこのasync/awaitというのも結構易しく見えて難しいもののひとつである。同期的な処理のように書けて非常にコードがわかりやすい。 が、実のところわかりやすく見えるだけで、非同期処理には変わりない。結局リソースコントロールの難しさやメソッドの起動タイミングの不明瞭さに直面してにっちもさっちもいかなくなってしまうのである。async/awaitで書くと非同期処理で書いていることを忘れてしまい、そのことが混乱に拍車をかけてしまったりする。そのうちasync/await hellという言葉も出来たりして。。

書いてみて難しいと感じたのは、再帰的処理と非同期メソッドの組み合わせである。この処理は実際の挙動を事前に把握してコードを書くことは難しいと感じた。書いてみて動きを確かめ、そしてコードを修正するという進め方が一番良いようだ。非同期処理のイディオムがまだ身についていないのも多分にあるだろうけれども、同期処理に比べて同じことをするにも考えなければならないことが飛躍的に増えるのは間違いなく、そのような挙動は実際に確かめるのが一番良いと思う。

しかしなぜこのようなプログラミングが難しい非同期処理をnodeは取り入れたのだろうか。それはJSがシングルスレッドで動作するからである。同期的メソッドだけだと、リソースアクセスで待ちが発生して、ブロックされてしまい、それが終わるまで次の処理ができないからである。これは私のように一人で使用するようなアプリやツールを作るのであればあまり問題にはならないが、Webサービスとかだと複数の要求を遅滞なく裁かないといけないので、1つの要求上でブロックしてしまうと、それ以降の要求をずっと待たしてしまうことになってしまうのである。それを回避するためにはリソースアクセスでブロックが発生した場合、ブロックが解除されて続きの処理ができるようになるまではほかの要求の処理ができるようにする必要がある。これを可能にするのが非同期コールバックという考え方である。nodeでいうところの非同期コールバックは割り込みという考え方に近い。これを実装するためにnodeではlibuvというライブラリを使用している。シングルスレッドで非同期コールバックを実現するためにマルチスレッドを使用しているというのが興味ぶかいところでもある。

この非同期処理のプログラミングの難しさを考えると、マルチスレッドのほうが楽かというとそうではない。マルチスレッドであればリソースブロックの問題は比較的容易に解決できるが、その代わりリソース競合という問題が起きる。このリソース競合を調整し、スレッドセーフにするために同期オブジェクト(クリティカルセクション・ミューテックス・セマフォなど)を使ってリソースアクセスをコントロールするのだが、これはこれで難しい問題(デッドロックとかね)があるのである。

JSの世界はシングルスレッドなので、リソース競合は起きない(同時に同じリソースをアクセスすることはない)ため、その分考え方をシンプルにすることができるのが利点である。さらにはコンテキスト・スイッチが発生しないので、本来は同一時間中に裁ける処理数は、マルチスレッドに比べて勝るはず(その代わり、OSが行う部分をJS側で肩代わりする必要がある)である。が、今の時代はCPUもマルチコアでHWで複数スレッドが走る(CPUもマルチスレッド・マルチプロセスをサポートするように進化している)なので、一概にそうとも言えない面もある。

この話の流れだと静的ブログジェネレーターの設計変更はそのような非同期プログラミングを回避するために行う雰囲気である。が、それだけではない。nodeでプログラミングをする限り非同期処理は不可避である。
最初の考えでは処理が遅くてちょっと実用に耐えられそうにないので設計変更することにしたのである。

静的ブログジェネレータは、あるディレクトリに格納されている.mdファイルを読みだして、公開用のディレクトリに.htmlに変換して保存するものである。 当初はディレクトリの検索を非同期かつ再帰的に行いつつ、.htmlファイルの変換も行うようなコードを書いた。最初はpromiseベースであったが途中からasync/awaitで書き直し、見た目同期的なコードとなった。しかし実際の挙動が意図したもの(見た目通り)とならず、数日悩むことになった。その問題は解決したが、今後追加機能などでコードを修正・追加を加えるのがつらそうで結局、 ディレクトリとHTML変換を同時にするのをやめ、「ディレクトリ検索を再帰的に行う部分のみ同期的に行って.mdファイルをリスト化する」部分、「そのリストを使ってHTMLファイルに変換する」部分に分けてコードを書くようにした。

次に、このコードに

  1. 新たに.mdファイルをディレクトリに追加する処理
  2. .mdファイルを更新した時の処理
  3. .mdファイルをディレクトリから削除した時の処理

の処理を追加することにした。上記処理を行うにはディレクトリ内のファイルの追加・削除・変更を検知する必要がある。これを実現するためにnedbに作成したファイルの情報を保存しておき、次回実行した際にその情報を参照しながら追加・削除・変更することにした。このdbがあれば、追加・削除・変更があったファイルのみ変換・削除することもできる。これによって所望のものができたが、DBを全検索してディレクトリ中のファイルと照合する部分でどうしても時間がかかってしまう。データは4000個近くあるので差分を更新するようにしても30秒~1分程度かかってしまうのである。これはどうしたものか。。

よくよく考えると、.mdファイルの格納ディレクトリはgitで管理しているのでディレクトリ内のファイルの差分情報が得られる。わざわざ差分を検出するコードなど書く必要はないのである。そういうわけでgitを使って追加・削除・変更するように設計変更し、今コードを書き換えているところである。

Nodeクックブック

Nodeクックブック