Chienomi

Chienomi リニューアル! 〜 裏話編

開発::web

計画

PureBuilder Simplyのデビューは2017年の年末。 当初WordPressでスタートしたMimir Yokohamaのサイトを「ほぼそのままに」移植するのが目標だった。

実はこの時期にChienomiはWordPressデザインを変更している。

PureBuilder Simply以前にはPureBuilder2を使用していた。 PureBuilder2はMimir Yokohamaで制作サイトを販売・サポートしていたので、商用利用に耐えるプロダクトではあったのだが、 個人的には「めんどくさい」プログラムだった。たくさん設置したいとは思えない。

ドキュメントそのものは、PureBuilder2になってKramdownによるMarkdownサポートが追加されており、Markdownで書いていたのだが、サイト構築がめんどくさい。

そこでこれを改善するのがPureBuilder Simplyであった。 Simplyの名の通り、複雑な方法で機能を提供していたPureBuilder2と違い、Pandocを使用して非常にシンプルなアプローチを実現している。

そして、このソフトウェアがとてもよかった。これこそ商用利用に適しているし、広くオープンソースとして展開できると思った。 超急ピッチで開発された新しいMimir Yokohamaのウェブサイト(原稿執筆、WordPressでの展開からPureBuilder Simplyの開発、PureBuilder Simplyでの展開まで含めて12月に、というか1週間程度である)だが、この感触の良さから他のサイト(PureBuilder2, またはWordPressで構築されている)の置き換えが検討された。

だが、新しいMimir Yokohamaのサイトは既にMarkdownで書かれており、置き換えが容易だったのに対して、それ以外のサイトは多くの問題があった。 仕事を含めいくつかのサイトをPureBuilder Simplyに置き換えたし、元がWordPressサイトだったものもあるが、「正攻法で」…というか、汎用性のある方法で移行したものはなく、あまりノウハウとしての蓄積がなかった。

現在私のサイトとしては5つあるが、「旧サイトからの移行」がなされたのは、Harukamy’s Memorandaだった。 つまり、実行はごく最近である。 Harukamy’s MemorandaはWordPressからの移行ではあるが、記事の選定でごく一部の記事しか残らなかったため、手作業で移植している。

そして、最も移行「したかった」のがChienomiだ。 Chienomiは実用的なニーズが高く、アクセス数も非常に多い。ここ数ヶ月は月間PVは約15万ほどである。 だからこそ、軽量化したいし、負荷にも耐えられるようにしたい。また、ヴィジターをトラッカーに晒す事態からも避けたい。 これだけ負担のあるサイトで、広告をつけることも避けているのだから、それを台無しにするような要素をなくしたい。

だが、なかなか実行できないまま、2年が経とうとしていた。

作業は2日

着手したのは金曜日の夜だった。この時点では何もしていない、まっさらの状態だ。 そして、新しいChienomiをお披露目したのは日曜日の夜中。およそ2日の作業である(2回寝ているので、体感的には3日)。この間

  1. 「HTMLを抜き出してアーカイブとして設置し、テンプレートにはPureBuilder Simplyから “生成した” HTMLファイルを使う」という発想に至る
  2. WordPressのXMLを眺める
  3. 余り使ったことのないREXMLライブラリを試し、うまくいかずに挫折する
  4. XML処理について調べ、ActiveRecordに行き着く
  5. WPImporterを開発する
  6. Chienomiをデザインする
  7. Chienomiのテンプレートを書く
  8. ChienomiのCSSを書く
  9. サイドバーのデザインにおいて、「タブ切り替え」を試すも、Dilloで正しく表示されず、試行錯誤の末リンク切替式に変更する
  10. WordPressのXMLが本文はHTMLでないことに気づき、「中途半端なHTML」を含めHTMLに変換するコードを追加する
  11. WPImporterのドキュメントを書いてGitLabで公開
  12. ConoHa WINGに新しいChienomiのドメインを登録
  13. 検索機能の開発を開始し、エクストラクタを書く
  14. 検索用ライブラリを書く
  15. 検索用ライブラリを使ったCGIをとりあえず書く
  16. ConoHa WINGにアップロードして、CGIが動くまで試す
  17. CGIを汎用化したものを書く
  18. ドキュメントを書く
  19. GitLabで公開する
  20. テンプレートにWordPressにあった案内を追加する
  21. WordPressサイトに案内を掲載する
  22. WordPressサイトでこの1年で人気の高い記事を確認し、テンプレートに追加する
  23. 変換した記事をビルドドキュメントに追加する
  24. Twitterのシェアリンクの仕様を調べ、素材を用意してシェアリンクを追加する
  25. 現状の仕様だとシェアリンクが作れないことが分かったので、PureBuilder Simplyを改良する
  26. wp-content/uploadsをソースドキュメントに追加する
  27. リニューアルの記事を書く
  28. ビルドしてリポジトリ化し、ConoHa WING上でテストする
  29. CGIのデバッグ
  30. 公開
  31. 旧サイトのVPS側でサーバーリダイレクトを設定

という作業である。

眠かったり、低気圧もあり具合が悪かったりした中であったため、「だらだら作業していて遅々として進まない」という印象だったのだが、 こうしてみると結構がんばっている。いつもよりかなり多めの家事をこなし、台風の対応もしつつであったことを考えると割と褒め称えられてもいいと思う。

ちなみに、ドキュメントはコードを書きながらちゃんと書いている

一番きつかったのはREXML

REXMLのXPathが目的のノードを全く見つけてくれなくてとても困った。

例えば

<a>
  <b>
    <c>
    </c>
  </b>
  <b>
    <c>
    </c>
  </b>
  <b>
    <c>
    </c>
  </b>
</a>

というドキュメントに対して

require 'rexml/document'

doc = REXML::Document.new(ARGF.read)
elem = doc.elements

elem.each("//b") do |child|
  p child["//c"]
end

の結果が

nil
nil
nil

である。c要素が見つけられていない。 これがなぜか全くわからず、悪戦苦闘した。さらにいえば、#each_elementも通らない。

実はこれ、REXML::ElementsREXML::Elementがあるという罠であった。つまり、

require 'rexml/document'

doc = REXML::Document.new(ARGF.read)
elem = doc.elements

elem.each("//b") do |child|
  p child.elements["//c"]
end

なら良い。

結局、サーバーサイドで動かすようなものではないので、ActiveRecordを採用したのだけれども。

速くするはずが…

実は検索のコードは、「改善」としてもっと無駄がなく速そうなこんなコードも書いていた。

#!/usr/bin/ruby
# -*- mode: ruby; coding: UTF-8 -*-

require 'yaml'
require 'shellwords'

class PureBuilderSearch
  def initialize(config)
    @config = YAML.load(File.read(config))
    @andsearch = false
  end

  attr , true

  def search(words)
    cond = Shellwords.shellsplit words.downcase
    hits = []
    Dir.glob("**/*", @config["destdir"]).each do |stfile|
      stfile = File.join(@config["destdir"], stfile)
      next unless File.file? stfile
      doco = File.read(stfile)
      doc = doco.downcase
      if ( andsearch ? cond.all? {|i| doc.include? i} : cond.any? {|i| doc.include? i} )
        if @config["incl_digest"]
          doc = doc.each_line.to_a[1 .. -1]
          doc.each_with_index do |i, index|
            if cond.any? {|c| i.include? c}
              lines = doco.each_line.to_a[(index + 1), 3]
              doco =~ /\A(.*)\n(.*)\n/
              hits.push({stfile, lines.compact, $2, $1})
            end
          end
        else
          doco =~ /\A(.*)\n(.*)\n/
          [$1, stfile, $2]
        end
      end    
    end
    hits
  end
end

実際のライブラリでは一旦リストを作ってから改めてファイルを読み直すという方法をとっている。 downcaseしたことのある文書をもう一度downcaseしており、しかも後半は1行ずつdowncaseしているのでかなり効率が悪いように見えた。

#!/usr/bin/ruby
# -*- mode: ruby; coding: UTF-8 -*-

require 'yaml'
require 'shellwords'

class PureBuilderSearch
  def initialize(config)
    @config = YAML.load(File.read(config))
    @andsearch = false
  end

  attr , true

  def search(words)
    cond = Shellwords.shellsplit words.downcase
    hits = Dir.glob("**/*", @config["destdir"]).select do |stfile|
      stfile = File.join(@config["destdir"], stfile)
      next unless File.file? stfile
      doc = File.read(stfile).downcase
      if andsearch
        cond.all? {|i| doc.include? i}
      else
        cond.any? {|i| doc.include? i}
      end
    end

    if @config["incl_digest"]
      rv = []
      hits.each do |hf|
        doc = File.foreach(File.join(@config["destdir"], hf)).to_a
        doc.each_with_index do |i, index|
          idc = i.downcase
          if cond.any? {|c| idc.include? c}
            lines = doc[(index + 1), 3]
            rv.push({hf, lines.compact, doc[1], doc[0].strip})
            break
          end
        end
      end
      return rv
    else
      hits.map do |i|
        title = nil
        File.open(File.join(@config["destdir"], i)) do |f|
          date = f.gets
          title = f.gets
        end
        [date, title, i]
      end
    end
  end
end

そこでできるだけ処理をまとめ、無駄なループが発生しないように書き直した…のだが、結果的には同条件計測で0.179秒から0.221秒へと悪化してしまった。 なんで遅くなったのかさっぱり分からない。 パフォーマンスチューニングは想像でやってもうまくいかないことを見せつけられた。

ハイアクセシブル

私の伝統である「Dilloとw3mでチェック」がChienomiにも適用された。

加えて、Chienomiは印刷のニーズも高いため、普段より入念にprinting CSSを練ってある。

主には

  • 余計な要素の除去 (サイドカラムだけでなくヘッダーやシェアアイコンもなくなる)
  • ページをフルに使うレイアウト設計
  • グレースケールのカラーテーマ
  • 画像への折返しの配慮
  • ソースコードの表示のされ方の調整
  • セリフ体フォントの指定

である。

デザイン的にはデフォルトを尊重するようなものでそんなにいじってはいないが、余計な要素を適用させないようにしつつ、 より見やすいように微調整を加えている形である。

「印刷しやすいテック系サイト」というのは、魅力のひとつになるかな、と思っている。