トップ 追記

2018-12-10 [長年日記]

_ Decoupled CMSのメリットと難しさ

単なるメモ。

Decoupled CMSの世界ではそれぞれ別々に道具を選べる

Jekyllの外の世界からdataを作る準備 - あーありがち(2018-12-09) で書いたような仕組みさえできてしまえば、逆にユーザー向けの HTML の生成が Jekyll でなければならないということもなく、バックエンドの CMS もユーザー向けの HTML を生成するジェネレータも要件に合わせて選択し直せばよい。もはや我々は

  • CMS 上での作業者の作業しやすさ
  • コンテンツを提供する仕組み
  • コンテンツを通じたユーザー、ビジネスへの価値提供
  • ユーザーの体験からどんなデータを得たいか(ビジネス価値など)

などを独立して考えることができる。

「○○だからこれができません」も「○○でないと作業担当が困ります」もない世界へ。上に挙げた要件がそれぞれ明確であれば CMS が一体ですべてを担う必要はない。これぞ Decoupled CMS のメリット。

それぞれの道具がマッチするかどうかをどう判断するのか

これらの要件を明らかにするためには一体のアリモノ(Coupled CMS)でまず始めてみて、ここがこうなっていた方がよい、いやこのままでよいといった学習が大事なんだけど、ここの線引きができるかどうかが実際にはかなり難しいと思う。

技術的には Decoupled CMS はそこまで難しい話ではない。しかし「『目先の道具が使いにくい』という視点から離れられるかどうか」は難しい問題だ。それぞれの価値を定義し、それを共有できて始めて分離後の姿を考える話に進むことができる。*1

例えば Prismic.io は "Headless API CMS for both developers and marketers" と謳っている。

Headless API CMS for both developers and marketers - Prismic

マーケターにとって大事なのは細かいデザインの調整ではなく、商品価値が潜在ユーザーに伝わり、それが顧客へコンバージョンされることであり、そのために役に立たないことが CMS で実現されていても別に嬉しくもなんともない。逆にいろんな SNS と手軽に連携するための何かは即時機能追加されてほしいところだろう。

これは「マーケター」という言葉があるから考えやすくなっているのであって、恐らく同様の定義が個々の Decoupled CMS 案件であるべきなのだろう。

  • コンテンツを publish する主体者は誰なのか?
  • その目的はなんなのか?
  • 提供する価値は何か?
  • 何が成果なのか?

という話があって初めてそれぞれの道具が果たすべき役割を明確にできる。何のことはない普通の開発の話っちゃ話なんだろうけど、CMS とコンテンツということになると、その目的はそんなにきれいに整理されていないことが多そうだ。

例えば publish する主体が仮に「ライター」と定義されてしまった場合はどうだろう? 途端に要件がボヤけてしまったはずだ。では「SEOライター」ではどうだろうか? こうなると例えば検索エンジンのアドバイス(人気の検索ワードとか)にしたがってコンテンツをリアルタイムに採点するような仕組みがあると作業が早くなるはずだ。*2

もちろんエンジニアが試してみるだけなら面白ければおっけー

いろいろ書いたけど、エンジニア一人がエンジニア一人のために分離するのはそんなに難しい話はないので、なんでもやってみるのがよさそう!(時間があれば)

Tags: CMS

*1 それ以前に Web 制作では「なんとなくイケてない問題」とかあるわけだけど、それは置いておく。

*2 もしかしたらこれは広義のマーケターに含まれるかもしれないが、ここには深入りしないでおく。


2018-12-09 [長年日記]

_ Jekyllの外の世界からdataを作る準備

※ 実際には外の世界へのアクセスの部分は何も扱ってません。あくまで Reader が仕事する前に data を作ることができた、ってだけ。また Assetを外に置く話 とも別の話。

My first Jekyll GeneratorのためにJekyllを読んでみて分かったんだけど、Jekyll::Hooks を利用すると Jekyll の外からデータを持ってきたあとに reader が仕事するようにすることは割と簡単にできそうだ。

Jekyll 自体はあくまで local の file を相手にする静的サイトジェネレータだが、これを活かして plugin 的なもので外部からデータを持ってきて file を作ってしまえば そのデータはどこからやってくるものであろうと構わない、つまりデータの生成、置き場所は自由ということになる。

ということは Jekyll 以外の何かでコンテンツを作れる、言い換えると Decoupled CMS のできあがりだ。*1

とりあえず毎回のprocessのたびに何かする

Jekyll::Site#process をもう一度読むと

   def process
     reset
     read
     generate
     render
     cleanup
     write
     print_stats if config["profile"]
   end

のように毎回 reset が走っているので、適当な plugin を作って*2、その最後に

Jekyll::Hooks.register :site, :after_reset do |site|
  MyFirstResetPlugin.new(site)
end

みたいなコードを書いておくと、自動でサイトの内容が空っぽの状態で Plugin が実行されるので、そこで site.config['data_dir'] の中に対してなんらかのデータを吐いてあげれば、Generator からは自動的に site.data として取得できるようになる。

例えばこんな感じ。

class ExternalData < Jekyll::Plugin
  def initialize(site)
    @dir  = File.join(site.config['source'], site.config['data_dir'])
    @file = File.join(@dir, 'external.yml')
  end
  attr_reader :dir, :file

  def fetch_from_api
    FileUtils.mkdir_p(dir)
    open(file, 'w') {|f|
      f.puts YAML.dump(foo: 'bar')
    }
  end
end

Jekyll::Hooks.register :site, :after_reset do |site|
  ExternalData.new(site).fetch_from_api
end

とすると、build のたびに API からデータを取得するような仕組みが可能だ。

※ ここでは API から取得する処理は何も書いてないので data を吐いているだけ。

after_resetなのかafter_initなのかそれとも

ただ上のやり方だとちょっと現実的じゃない。

少なくとも実際に本物の API へアクセスするような仕組みを毎回の process のたびに走らせるのは開発環境では高コストすぎる。だからたぶんこんな感じにして、

Jekyll::Hooks.register :site, :after_init do |site|
  if ENV['FORCE_FETCH']
    ExternalData.new(site).fetch_from_api
  end
end

みたいに環境変数で切り替えて、必要な時にだけ API アクセスするような仕組みがあるとよさそう。*3

これを応用して data でも collection でもなんでも作って、その後に Generator に渡してや れば Decoupled CMS への第一歩を踏み出せたと言ってよさそう。

残りはCMSとビルドサーバの連携(例えばWebhook)

CMS を Jekyll や静的サイトジェネレータ側で担わないようになると、

  1. コンテンツの更新
  2. コンテンツの更新から kick されたサイトのビルド
  3. ビルドのサイトの deploy

の 1 と 2 をどうやって繋ぐのかという問題になる。

例えば CMS が WordPress だったら WordPress の更新を Webhook でビルドサーバに送り、ビルドサーバ上で WP REST API からコンテンツを取得してビルド、といった具合。

これが可能になれば CMS としては WordPress を活かしたまま、表側はエンジニア、デザイナが余計なこと*4を気にせずに、かつ存分にモダンな作りで改善するために WordPress から分離するといったことが可能になる。*5

感想

今後静的サイトジェネレータを評価する機会があったら、簡単に始めることができることも速さも大事だけど、Asset も Data も分離しやすい、という視点での評価を少なくとも個人的には大事にしいきたいなぁと思いましたとさ。

Tags: CMS Web Ruby

*1 実際には site オブジェクトにさえつっこめればいいので、site に ActiveRecord をぶらさげる、みたいなやり方でもいいけど、今回は扱わない。

*2 plugin でなくてもいいけど、plugins_dir 以下は自動で require されるし、乗っておいた方が混乱がなくてよい。

*3 本気でコンテンツの量がすごいことになったらCI的な意味ではなくgenerate用のビルドサーバを用意してそこにストレージも用意して差分でfetchするようなものが必要かもしれないが、その辺は今回の範疇外。

*4 セキュリティの問題はあるけどバージョンアップでプラグインが動かなくなってしまったらどうしよう?とか

*5 「プレビュー」をどのレベルまで求めるのか?といった問題はあるが。


2018-12-08 [長年日記]

_ My first Jekyll GeneratorのためにJekyllを読む

先日Jekyll と Middleman の比較をして、Jekyll の方が拡張はやりやすそうという結論を出したわけだけど、実際に書くとなったら公式のドキュメントを読んでるだけだとピンとこない部分があって、それが Generator.

Generators | Jekyll • Simple, blog-aware, static sites

いきなり Generator に site オブジェクトを突っ込んで、そこから Page を生成する例が出てくるんだけど、なんでこれでうまくいくのかがよく分からない。

この理由はそもそも全体の流れが説明されていないから。ということでコードを読んでみることにする。

対象バージョンは現行の Jekyll 3.8.5

まずはcommandから

jekyll の操作は

$ jekyll <command>

から始まる。これを担うのが commands/ 以下のコマンド群だ。

exe/jekyll

を読むと command 群は init_with_program が呼ばれるのが分かるので、例えば build.rb で追っていくと

        def init_with_program(prog)
          prog.command(:build) do |c|
            c.syntax      "build [options]"
            c.description "Build your site"
            c.alias :b

            add_build_options(c)

            c.action do |_, options|
              options["serving"] = false
              Jekyll::Commands::Build.process(options)
            end
          end
        end

中で process を呼び、process では

        def process(options)
..
          site = Jekyll::Site.new(options)
..
            build(site, options)

build を呼んでて、

        def build(site, options)
..
          process_site(site)

process_site は superclass の

      def process_site(site)
        site.process

で、Jekyll::Site オブジェクトにたどりつく。

すべてを司るJekyll::SiteオブジェクトからGenerator#generateが呼ばれる

Jekyll::Site#process はこれだけ。

    def process
      reset
      read
      generate
      render
      cleanup
      write
      print_stats if config["profile"]
    end

これを見るとピンとくるのが

Hooks | Jekyll • Simple, blog-aware, static sites

ですね。ここでようやく全体の流れが分かる。これを見ると Generator は Reader と Renderer の間で仕事をしていることが分かる。

generate の中身は

    def generate
      generators.each do |generator|
        start = Time.now
        generator.generate(self)
        Jekyll.logger.debug "Generating:",
          "#{generator.class} finished in #{Time.now - start} seconds."
      end
    end

登録された Generator それぞれの generate メソッドに Jekyll::Site そのものが渡され、site を書き換えることでコンテンツを増やし、その後 render, write が仕事をすることで実際のファイルが生成される、という流れになる。

なるほど。

Generator間の依存関係はpriorityで調整

generator の初期化は site.rb の #setup の中の

      self.generators = instantiate_subclasses(Jekyll::Generator)

からの

    def instantiate_subclasses(klass)
      klass.descendants.select { |c| !safe || c.safe }.sort.map do |c|
        c.new(config)
      end
    end

で、ここの sort で plugin.rb の

   def self.<=>(other)
     PRIORITIES[other.priority] <=> PRIORITIES[self.priority]
   end

が呼ばれて、priority の高いものから並ぶようになっている。

※ ちなみにこの setup が実行され終わる時に after_init に register した hook が走るようになっている。

したがって、例えばカテゴリやタグといった情報をもとにイマドキのリッチなスライドっぽいリンクを並べた一覧ページを作りたいということになったら、

  1. Page をなめて content から data ( Front Matter ) の中に最初の heading や paragraph, img など、リンク生成時に使いたいデータをまとめ直す処理を行う
  2. 1 のデータをもとにカテゴリやタグの Page を追加する

この際に 1, 2 の順になるように priority を調整する、という感じで利用することができる。

※ これくらいならひとまとめでもよさそうだけど、他の generator で生成したコンテンツも利用できるようになっていた方が都合がよいかもしれない。分かってくると Ganerator は応用範囲が広い感じがする。

MiddlemanのproxyのようなものもGeneratorで解決できる

考え方は以下のような感じ。

  1. site.data, site.pages などから必要なデータを取得して
  2. 取得したデータをもとに Jekyll::PageWithoutAFile を継承した Page を生成
    • この際、Page#initialize とはリスコフの置換原則を破るようにしないと page 独自の data は渡せないことに注意が必要。
   def initialize(site, base, dir, name)

適当に keyword args 足せばいいと思う :p

layout はいちいち指定してもいいけど、_config.yml の defaults で一気に指定するようにした方がたぶん他のものと整合性が確保できてよい感じになる。

Front Matter Defaults | Jekyll • Simple, blog-aware, static sites

Tags: CMS Web Ruby

2018-12-04 [長年日記]

_ 2018年にもなってMiddlemanとJekyllを比較してみたよ

先に結論

独自に拡張していく、外部のデータと連携することを重視するなら Jekyll の方がよさそう。

Rails の view に慣れてて何も設定せずにいきなり使う場合の初速の速さは Middleman の方が上になる。

きっかけ

2016 年に『マイクロサービスアーキテクチャ』を読み、それとは別に Decoupled CMS という概念を知って以来、古くて評判のよくない CMS を使うことや自分で中途半端な CMS もどきを作ることに対して今まで以上に「このままじゃいかんよな」と思うことが増えていて、改めて静的サイトジェネレータをもうちょっとちゃんと試してみようと思っていたのが一つ。

もう一つは Rails の Webpacker gem など、バックエンドや CMS に Ruby を使っていてもフロントエンド周りはフロントエンドの作法に則った方がよさそうなので、その組み合わせ方を知っておかないといけないなと思ったこと。

これまで

Jekyll は GitHub Pages に必須という考え方で触っていて、Liquid が面倒くせえなぁ、ERB で素直に書かせてくれよくらいの気持ちだった。

一方で Middleman は Rails の View を触ったことがあれば違和感なく使えた。特に AssetPipeline のようなものが最初から使える部分でも非常に Rails によく似ており、Rails から大きく考え方や手順を変えることなく、Rails の不要なサイトを作る際に重宝するなぁ、くらいの捉え方だった。

設定

  • Jekyll は YAML
  • Middleman は独自の Config オブジェクトになる config.rb

ここだけ見るとどちらでもよさそうに見える。しかし実際はかなり違う。

なんでもかんでも YAML にするのもどうかとは思うが、一方で Middleman の Config オブジェクトは中に書いたコードの context を変更してしまうので気軽に分割できない。独自の DSL ならまだしも、pure Ruby で context が変わってしまうのはとてもやっかい。正直言うと多少でかく複雑になっても YAML の方がマシに見える。

ERB vs Liquid

ぱっと見て最初に気づく違いだと思うのはテンプレートとして ERB を使えるか否かだ。

個人的には Rails でも Haml や Slim には賛成できない派で、ERB で見にくくなるんなら何かを間違えてるんやで派なんだけど、Jekyll では ERB を使わせてくれない。

Liquid template language

Liquid を通してしか仕組みを提供できないので、一見すごくまどろっこしく感じる。適当に Helper っぽいものを足して気軽に Ruby のコードを call することはできない。ちゃんと Liquid Tag として register して使うように手配しないといけない。

対して Middleman は ERB を書けるので、Rails のようにある意味雑にどんどん Ruby のコードを書いていける。

GitHub Pages など制限環境ではそもそも該当するコードを呼べるかどうかという問題はあるものの、どの程度 adhoc に Ruby のコードを呼びたいか によって、特に最初のもどかしさは変わってくるだろう。なれるまでは Liquid::Tag の register は確かにとてももどかしい。

ただ、最近のフロントエンド向けのテンプレートなどは基本的に Liquid のような制限を持っているものが多いので、フロントエンドから入ってくる人にはそれほど大きな違和感はないかもしれない。もう Rails の View を基本に考える時代ではないのかもしれない。

基本の「構成」はよく似ている

  1. Asset Pipeline 的な Sass, CoffeeScript コンパイル機能を持つ
  2. Front Matter, Data Files, Collection を備える
  3. plugin ( extension ) 機構がある

Asset Pipelineの「外」の実現方法は異なるが考え方は似てる

Asset Pipeline 的なものの機能は似ているが、ではこれらを使わずにモダンフロントエンドの力を使う場合はどうしたらよいか。

  • Middleman v4 には External Pipeline があり、割と賢く asset を管理してくれる
  • Jekyll v3 標準では何も支援機構がないので以下のように自分で設定する
    1. フロントエンド → Jekyll の順に build が走るようにする
    2. 開発中はそれぞれ依存するコードを監視していい具合に動作する

といった具合で、機能の有無で差はあるが、考え方が分かればなんとかなる。ただし cache busting も Jekyll には存在しない機能なので、cache busting にこだわるなら Jekyll Assets など外部の何かを頼るなり、Web サーバの設定に頼るなり、設計が必要になる。

参考

Collectionは大きく違う

Front Matter, Data Files には特に言うことはないのだが、Collection には大きな違いがある。

Jekyll の Collection はものすごく乱暴に言うと Data Files のようにドキュメントを扱えるもの、である。collections_dir 以下に置いたドキュメントの固まりをページとは別に自由に取り出せる。Front Matter も使える。

対して Middleman の Collection は Extension の一形態を取っている。これが曲者で、

  • Middleman でコードを書くのは Contract との戦い
  • Middleman には本体外のコードで使える hook が少ない
    • (before|after)_(configuration|build) しかない

の二重の意味で使いにくくなってしまっている。

collection のデータ形式はこうですよ、こういうデータを作ってください、のような Jekyll 的なアプローチではなく、あくまでどの class のインスタンスを受け取るかというプログラミング言語レベルでのインターフェイスが基本になっていて、collection に対して適当な Array を与えるといったことができない。

Collection というよりは Extension の話になってしまっているし、サンプルに引きずられすぎて可能性を狭めているだけかもしれないが、今のところ collection を設定とデータで実現しやすい Jekyll の方が扱いやすそうに見える。

Plugin vs Extension

自分でそれぞれを拡張しようと思った際に選択できる手法について。

Plugins | Jekyll • Simple, blog-aware, static sites

Jekyll は Plugin であり、その Plugin に種類がある。

  • Generators
  • Converters
  • Commands
  • Tags
  • Filters

Generator は site 全体に対して何らかの追加コンテンツを生成する、Converter はコンテンツを変換するもの、Tags, Filters はそれぞれ Liquid に対する追加 といった具合に役割が整理されている。

Middleman: Custom Extensions

対して Middleman は Extension という仕組みであり、Extension がどんな役割を担うのかは割と自由に決められる。Tag かもしれないし、resources を加工するものだったりする。extension から configuration, template などへメソッドを expose して利用する。

Hookはかなり違う

Jekyll は

Hooks | Jekyll • Simple, blog-aware, static sites

site, page, document に対してそれぞれ細かく hook が設定されており、割といろんなことができそうな感じがする。

Middleman: Custom Extensions

対して Middleman は Extension に対して提供されると決まっているうえで、コア機能以外に解放されているのは

  • before_configuration
  • after_configuration
  • before_build
  • after_build

しかない。

Rack

Middleman は標準で Rack アプリだが、Jekyll は独自に WEBRick 上で動いている。Jekyll を Rack で mount したい場合は rack-jekyll が必要。

adaoraul/rack-jekyll: Transform your Jekyll app into Rack application!

情報量

圧倒的に Jekyll の方が多い。

その他 - MiddlemanのContractに気をつけろ

独自に拡張するコードを書こうと思った場合、ごく単純に言うと Jekyll には Liquid, Middleman には Contract という制約がある。

extension のところでの話のくり返しになってしまうが、Middleman の extension を軽く書いてみた*1ところ、Contract がヒジョーにつらいという感想しかなかった。

やったことは単に独自の Collection を生成したかっただけなのだが、ドキュメント不足とあいまって中のコードをひたすら追うことになってしまい、一応動くのは動く状態になったがまったく納得のいくデキにならなかった。

Contract とはごく乱暴に言うと Ruby のコードに対して型を定義できるようなものである。

egonSchiele/contracts.ruby: Contracts for Ruby.

型定義をメソッド定義以外に書けるならよい、と自分は思っていたし、その考え方自体は今もあまり変わらないんだけど、実際には「型だけあってもその型通りのデータを外からどう与えるかという API がないとただツライだけ」だなということがよく分かった。

もともと Web プログラミングのように外から文字列でデータを与えることが前提になっているコードに型だけあってもつらいよねというのは思っていたんだけど、ドキュメントや周辺のツール、そして よい設計 が揃って始めて Contract ベースのプログラミングはスムーズに行えるんだなぁということを痛感したのでした。

※ Contract を外して build するオプションもあるんだけど

正直言うと Contract ベースで書くよりは Liquid のレールに慣れる方がドキュメントも事例も見つかるし、たぶんかなり簡単。

まとめ

ERB と Middleman 標準の asset digest にこだわる必要がないなら Jekyll の方がよさそう。

特に Decoupled CMS やモダンフロントエンドなど、外部とどう組み合わせるかという点を重視した際の拡張しやすさは Jekyll の方に軍配が上がると思う。

Tags: Web Ruby

*1 実際には必要なかった