トップ 最新 追記

2018-12-03 [長年日記]

_ Jekyll 3 + JekyllAssets + Parcelの環境を作ったけど注意が必要だった

先日の Middleman 4.2.1 + External Pipeline + Parcel 1.12環境を作れた - あーありがち(2018-11-23) に続いて Ruby の静的サイトジェネレータと Parcel を組み合わせる実験。

Middleman の評価については別に話があるんだけど、それは置いておく。

Jekyll には Middleman の External Pipeline のような仕組みは存在せず、各位で頑張って webpack を使う boilerplate が何個もできているような状態だったりする。

Search · jekyll webpack

これはこれで参考になるんだけど、できるだけ生で Webpack を触りたくないということで今回は前回に引き続いて parcel を使ってみることにした。

素材

ディレクトリ構成

前回と同様に

+- bin/
+- assets/     parcelが処理するもの
+- source/     jekyllが処理するもの
    +- assets/ parcelの処理結果かつjekyll-assetsのサーチパス

とした。

npm scriptsとnpm-run-allで処理を制御

Jekyll には External Pipeline のような賢い仕組みは存在しないので、build の際には

  1. parcel
  2. jekyll

の順番でコマンドが実行されるようにしてあげないといけない。

serve の際には npm-run-all で parallel に実行してあげれば両方ともいい具合に watch して rebuild してくれるのでそんなに気にする必要はない。

assets/ 以下を編集 → parcel が処理して source/assets/ に反映 → jekyll が処理して _site/assets/ に出力

が自動的にグルグル回ってくれる。

なぜJekyllAssetsか

サーバに依存しない cache buster のため。

Jekyll 自体には cache busting を支援する仕組みはなく、いろんな組み合わせがあり得る。例えば Web サーバを直接設定できるのであれば、revision を適当に URL に埋め込んで処理するという方法もある。

しかしせっかく静的サイトを生成するのであれば Web サーバには依存したくないというのが正直なところで、例えば S3 + CloudFront で似たようなことをやろうと思うと Lambda 使ってくださいみたいな話になっちゃって、静的サイトでサーバ要らずとはなんだったのか?という気持ちになってしまう。

注意すべきはjekyll-assetsのファイル名の扱い、特にparcel watchと組み合わせた時

parcel にも HTML からまとめて build させるとすべて同じディレクトリにフラットにぶちまけられてしまう問題があったが、jekyll-assets には

assets/ 以下のパスをいい具合に検索する機能

があるおかげで、

同じ名前のファイル名があるとどっちが正なのか分からなくなる

という致命的な問題がある。

どういうことかというと、例えば

assets/js/site.js
       css/site.js

があった場合に

{% asset site.js %}

と書くとどっちの site.js を読み込んでよいのか分からなくなってしまうという問題だ。

上の例だと「そんなことやるわけじゃないじゃん?」と思うかもしれないが、parcel は CSS に対しても HMR 用の JS を吐くので、意図せず同じ名前の JS が生成されてしまうことはあり得るのですよ。

ということで、

parcel + jekyll-assets を使う場合はファイル名のルールをちゃんと整えるべし

と思ったのでした。

ただ、そこさえ考えておけば必要な設定がほとんどないというのはやはり魅力。そこは JekyllAssets も Parcel も優秀だと思う。

例によって成果物

wtnabe/jekyll-v3-example-with-jekyll-asset-and-parcel

こんな感じ。


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 実際には必要なかった


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-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-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-15 [長年日記]

_ 失敗を認めて頑張るべきところと失敗のない安全性を重視すべきところ

あるいは Kanazawa.rb meetup #76 に行ってきたよ の話。

割といつも言ってることと同じなんだけど、まぁせっかくなので。

エンジニアが集まると

なんでそんなものになってしまったのか?

みたいな話で割と盛り上がれるわけですよ。

それ要る!?

みたいなやつ。

そういうのを避けるために自分は「インフラは持つべきではない、全部PaaSかフルマネージドにしろ」という選択を数年前にしたんだけど、最近は「バックオフィス業務で独自に開発はすべきでない、そもそも開発を外部に依頼するのは難しいし、独自に作らなきゃいけないものなんてほとんどない」と思っている。

で、これって、結局「余計な失敗をしない」ということなのだろうなぁと思っている。

何か作れば、何かやれば、それには失敗がつきものだ。避けようがない。そしてその失敗をなくすためには実はかなり大きな、時に測定不能なコストが発生する。

なぜか人は何かをやる時にこの「余計な失敗を抑え込むためのコスト」を甘く見積もり、結果として恒常的にリソース不足になっていたりする。そして自分がやってきた自動化は要するにこれらをなくすためのものなんだなぁと思うようになってきた。deploy の自動化、provisioning の自動化、テストの自動化。*1

これらはそれ単体では実はそんなに価値はない。でもここで余計な失敗をしないことで何回でも安心して実行できる。上記の何かを「実行する」ということは「何かを新しく作ったり変更したりしている」ということだ。

その「何かを新しく作ったり変更した」結果、それが失敗だったり成功だったりするかもしれない。例えば思ったより全然便利じゃなかったり、思ったより面白くなかったり、そういうことだ。しかし成功だった場合はそれが「価値」になる。この「価値」を生んだり大きくしていくことが事業としての「開発」において最も大切なことだが、その「価値」を見つけるのは実は容易なことではない。

2C なら Consumer, Customer が喜んで使ってくれる、遊んでくれる何かが「成功」だが、それを見つけるには何回も何回もチャレンジが必要だ。そのためには新機能はすぐに追加できてすぐに取り外せた方がよい。

2B なら目先の課題がただの全体の歪みの一部が表出された何かでしかない場合がよくある。本当は全体の歪みの原因を見つけるまではやはり同じように何回でも変更できるようにしたり、まずは既存のシステムに乗せて「実際にやってみることで課題を洗い出す」ということが必要だったりする。既存のシステムに載せるということは、その部分で「開発そのものの失敗をなくし」「本当の要件の抽出の失敗をなくす」ことである。

やってみたこともないのに何が必要で何が不要かを決めるのは実はとてつもなく高度なことで、それそこ失敗して当然である。だからその失敗を許容するために、その他の要らぬ失敗を排除することが大切になる。

で。

最近よく思うのは、こういう話、実際に作り手になったことがない人には通じないんだよなぁということ。結果として、「いやいや、できた結果ダメだったってなったら単に余計なコストが上乗せになりますよ?」という話、全然想像できないどころか、相手の話を真剣に聞いていない場合は想像する必要すら感じてなかったりする。

でもさ、余計な開発は余計なランニングコストを生みますよ。見て見ぬふりしたいかもしれないけど、作っちゃったらもうゼロ円は無理なんですよ。それよりはアリモノを Subscription で使った方が結果安かったりするわけです。

ということで、これからも仕事では「価値を生まないモノ、価値を生まない作業」にコストを掛けすぎてはいけない、それが「本当に価値を生むモノや価値を生む行為」を阻害しないようにすることを意識して、コードを書いたり書かなかったりしていこうと思いましたとさ。

ま、当たり前の話ですね。

Tags: 日々

*1 あの頃は、単に自分を守るために必死にやっていたけれど。


2018-12-20 [長年日記]

_ 超今さらS3 + CloudFront + CloudFormationでWebサイトを公開する設定についてOriginとProtocolの注意点

※ 独自ドメイン関係の話は一切ないです。あくまで公開できる状態になるまでで S3 Web Site Hosting と CloudFront の Origin の設定と Protocol の話のみです。

S3はWeb Site Hostingにすべし

最近は AWS 側で不必要に S3 Bucket を公開してしまわないように Web UI でいろいろ警告してくれるというか分かりやすくしてくれていて、「もしかして S3 の Web Site Hosting って実は使ってほしくないのかな?」という雰囲気を感じていたんだけど、どうもそういう意図はないらしい。うっかりを防ぎたいだけっぽい。

というのも、

Web Site Hosting にしておかないと IndexDocument などの機能が効かないので / で終わる Directory へのアクセスは不可能

だから。

※ 逆に、 / で終わる URI が世界に共有されないのであれば Web Site Hosting でなくてもよい。

cf. ウェブサイトエンドポイント - Amazon Simple Storage Service

CloudFrontからS3へCustom Originで繋ぐ

CloudFront から S3 を見にいく場合、S3 Origin としてアクセスしにいくか Custom Origin としてアクセスしにいくかを選べる。

結論から言うと上と同じ理由で

Web サイトとして S3 を使う場合は Custom Origin にする

のが正解。S3 Bucket としてではなくただの Web サイトとしてアクセスしにいく方式だ。

そのため、DomainName の設定は

<bucket-name>.s3-website-<region>.amazonaws.com

にしておく必要がある。

S3 Origin としてアクセスする方式でも asset や upload 済みの「ファイルだけにアクセスさせる」場合は問題ないのだが、「HTTP でリソースにアクセスできることと Web サイトとしてアクセスできることの意味は異なる」ところがポイント。

例えば上のように / で終わる URI に対して CloudFront にアクセスがあった場合、CloudFront は S3 に対してもまったく同じように / というリソースを要求するのだが、これは S3 Object としては存在しない。あくまで S3 上では Object として存在しているのではなく Prefix として機能しているだけなので、これにアクセスすることはできないのだ。

※ トップページ / に対してだけは DefaultRootObject を設定できるのがまた事態をややこしくしている。これは本当にトップの / に対してだけしか効かない。

もし / で終わる URI にアクセスできているとしたら、それは Web Site Hosting 機能の IndexDocument のおかげである。そして Web Site Hosting に対して CloudFront からリクエストするためには Custom Origin にしておく必要がある。

ウェブサイトエンドポイント - Amazon Simple Storage Service

仕上げにCloudFrontからS3へはhttp-onlyで繋ぐ

CloudFront は Origin を複数設定できるのだが、CloudFormation の設定を YAML で書くとすると、

Origins:
  - Id: <Name>
    CustomOriginConfig:
      OriginProtocolPolicy: http-only

としておくのが正解。

CustomOriginConfig では OriginProtocolPolicy が required なのでこれを http-only にしておく。

順番に見ていくと、

  • Origins の設定のところには S3OriginConfig か CustomOriginConfig のいずれかが required
  • S3 Website Hosting に接続するには CustomOriginConfig が required
  • CustomOriginConfig を設定するためには OriginProtocolPolicy が required

という寸法。

S3のWeb Site Hostingはhttp-only

ウェブサイトエンドポイント - Amazon Simple Storage Service

S3 の Web Site Hosting の機能で提供されるのは上の「ウェブサイトホスティング」に該当する。

ということは「SSL 接続をサポートしません」なので、上のように

Origins:
  - Id: <Name>
    CustomOriginConfig:
      OriginProtocolPolicy: http-only

で http-only にしておくのが正解。これで CloudFront へのアクセスが http でも https のどちらであっても、S3 へは http で接続しにいく。

これを指定しておかずにうっかり https でリクエストすると謎の timeout に苦しむことになる。

cf.

おまけ

CloudFormation を使えば確かに設定をコードで管理できるようにはなるが、決してお手軽に設定できるわけではない。

その設定になる理由はすべて資料と現象の観測と収集できる情報から掘り下げていくしかない。

例えば CloudFront で /path/to/resource/ へアクセスすると timeout するが /path/to/resource/index.html へアクセスすれば ok 、http ではアクセスできて https でアクセスできない、といったことは順番に試して現象を観測して分類、整理して解決していくしかない。

そうたくさんくり返すわけでもないこの手の作業こそ基本が大切。

Tags: AWS Web

2018-12-23 [長年日記]

_ NetlifyとNetlifyCMSがものすごい

ほんとは去年から気になっていたんだけど、CMS や静的サイトジェネレータから距離を置いていたので今年になってしまった。言い訳まじりに少し触ってみた感想など。

Netlifyがすごい

Netlify: All-in-one platform for automating modern web projects.

確かもともと静的サイト専用ホスティング x 1 は無料で使えるものとして登場してて、フォームとかは有料扱いだったような気がするんだけど、今見ると

Plans and Pricing | Netlify

Site password や Team 管理が不要なら Free のアカウント1つでも、

  • 複数のサイトが作れる
  • 5人まで Identity Free という add-on で共同編集する人を招待できる*1
  • Form も 100 POST まで無料
  • Functions ( Lambda ) も 125k invoke 100 時間まで無料

ということでえーなにこれどこで儲けるの?という感じになっている。

話が前後してしまったが、Netlify の特徴としては

  • Git Backend と協調して自動でサイトに deploy することができる
  • build system が揃っており、Ruby, PHP, Node.js, Go など基本的には設定なしで静的サイトジェネレータを動かせる
  • もちろん以前の revision に revert するのも簡単
  • Custom Domain, DNS, HTTPS, CDN など欲しいものはまず揃っている
  • main ( master ) branch 以外の branch を preview URL に deploy してそこで preview できる*2
  • Incoming Webhook も Outgoing Webhook もある

という、静的サイトジェネレータが動かせてホスティングできるだけでなく、

ちゃんとイマドキのエンジニアが歓喜する機能もサイト制作の workflow も押さえにきてる

非常によくできたシロモノになっている。

NetlifyCMSがやばい

Git-backend + 静的サイトジェネレータの構成で最大の泣き所は CMS である。じゃあ普通の人に GitHub 上で Markdown 編集してくださいで通用するかっていうと、まーそうはいかない。

そして Netlify さんはとっくにそんなものお見通しである。

Netlify CMS | Open-Source Content Management System

NetlifyCMS は

  • サイト上にポンと設置できる(もちろん localhost でも動く部分は動く*3
  • GUI でファイルの内容を編集できる
  • もちろん Git Backend と通信してファイルを更新できる
  • 上で挙げた branch と preview の workflow を支援する
    • 自動で pull-req ができるので GitHub Flow とも矛盾しない

特に最後の workflow 支援が とんでもない ことになっている。従来からよく Web サイトに設置できる GUI エディタというものはあるが、そんな程度ではない。branch で preview の課題をクリアした Netlify には造作もないことなのかもしれないが、これはすごい。やばい。

さらにこの CMS はカスタマイズでき、

  • 静的サイトジェネレータの設定とのすり合わせ
  • Collection という NetlifyCMS 側のコンテンツのカタマリの定義
  • Widget の組み合わせで様々なデータを扱える
    • Boolean, Date, Image, Select, Markdown, String, Text など
    • 頑張れば Custom Widget も作れる

さらに

branch -> pull-req -> preview の流れを Workflow というメニューで隠蔽しつつ支援できる*4

その画面は GitHub Projects や Trello のようである。NetlifyCMS の Workflow で In Review にしている様子

なにこれ。

NetlifyCMSと静的サイトジェネレータの設定のすり合わせが意外と大変

NetlifyCMS は静的サイトジェネレータとは異なる概念で設定を要求するので、一部は静的サイトジェネレータ側との二重管理、一部は NetlifyCMS 独自、みたいな設定になる。

ここはいろいろ各ツールの概念の整理と試行錯誤が必要。

  • 今のところ NetlifyCMS には Data Files という概念はなくてすべて Collection 扱い
  • NetlifyCMS で編集しやすい Widget と Collection の組み合わせが必ずしも静的サイトジェネレータ側で標準的な構成とは合わない

特に Collection は 2018-12-23 時点では folder 一つか、一つ一つの files を個別に指定するかのどちらかしか選べないので、静的サイトジェネレータ側で複数のフォルダ、階層構造になっているものは NetlifyCMS 上で一つの Collection として扱うことができない。

ということは既存のサイトの構成をそのまま NetlifyCMS で編集できるようにしたいと思うとなかなか大変だと思う。

ただ、CMS の考え方としては十分シンプルかつ Workflow まで考慮されていて、これをベースにコンテンツを作っていくのは十分可能な感じがする。仕組みや概念がシンプルに整理されていれば、サイト規模自体は問わない感じだし、どうしても1サイトで扱うのが難しい場合は

Netlify 側の Reverse Proxy の機能を使ってサイトを分割してしまえばよい*5

なんでもあるな、Netlify.*6

しかもですよ、実は Enterprise Plan だと SAML SSO が可能なんですよ。こいつらビジネスよー分かってるわ。

ほんとは Decloupled CMS でなければならない!みたいな気持ちが半分くらいはあったんだけど、ある程度は Netlify にロックインされちゃった方がいいんじゃねーかという気すらしてきた。少なくとも API-only の Headless CMS よりははるかに早く始めることができてすぐに成果が目に見えるようになる。これはめちゃくちゃ強いぞ。

Tags: CMS Web

*1 Git Backend 側で共同作業するならそれも不要

*2 URLは推測しにくいという前提ですね

*3 認証に注意が必要だし、更新するとたぶんややこしい

*4 2018-12-23時点で対応してるのはGitHubのみ

*5 アカウントを増やさずにサイトは分割できる

*6 後日、中の人に聞いたらこのサイト分割の考え方と Identity が per-site add-on であるところに矛盾があって per-account add-on の要望もあるそうだ。確かになぁ。


2018-12-29 [長年日記]

_ 北陸ディベロッパーズ忘年会'18に参加してきた

北陸ディベロッパーズ忘年会 '18 - 北陸ディベロッパーズ交流会 | Doorkeeper

久しぶりに参加してきた。めちゃくちゃ濃い話ができた。

幹事のみなさんありがとうございます!

以下、覚えてる範囲で何喋ったっけ。

  • Netlify の中の人と Netlify の Feature, Pricing などの deep な話と NetlifyCMS の改善案の話*1
    • Tokyo は meetup あったけど、北陸でも採用を考えている人が自分以外にもいた
  • 個人のブログ静的サイトジェネレータで書くの割とストイックだよね話
  • WordPress 強すぎ問題
  • Ruby は PHP や JavaScript よりはずっとカタイんだよ話 vs Java 勢
    • アンサーでアセンブラ派の話
  • スプラトゥーンつらいっすね話
  • DBのからむテストの話
  • やっぱ SaaS 使いまくりになるよね話
  • Dart 面白そうだよ話
  • あいつどうなってんだよ話
  • 北陸で北陸以外の仕事してる人ばかりになってきてるなー話

超久しぶりに樽の酒飲んだりしてた。いやー悪いことたくさんしたわ! また今度!

Tags: 日々

*1 i18nとか


2018-12-30 [長年日記]

_ Dart + webdev動かしてみた

Flutter の注目度が上がって Dart 2 の話が再び注目を浴びるようになってきて、

Googleが「Dart 2」発表、Dartを再起動。iOS/Android用ライブラリ「Flutter」と共にWebとモバイルのクライアント開発にフォーカス − Publickey

の記事の意味をちゃんと理解していなかったことが分かってきたので、ちょっと試したくなった。

Dart programming language | Dart

本当は repl があればいちばんよかったんだけど、どうも Dart 2 で安定して動く repl コマンドは 2018-12 時点ではないっぽいので、再現性が安定しているであろう webdev の方を試してみた。

Dartの雑なメモ

  • コンパイラ言語 ( ARM と x86 のネイティブを吐く )
  • JavaScript も吐く
  • すべて Object
  • type は optional ( null安全という意味じゃなくてあってもなくてもよい )
  • mixin が普通(2.1 からは mixin 宣言を導入して class から分離できる)

全部 Object っていうところに Rubyist としてはいちばん興味が引かれてる。

  • デフォルト entry point は main()

C だ。でも決まってるのははっきりしていいかもね。実際、Ruby でもモノによってはそういう書き方してるし。

インストール

https://github.com/dart-lang/homebrew-dart

より

For web developers, we highly recommend Dartium and content shell:

$ brew install dart --with-dartium --with-content-shell

これは 2018-12-29 時点で古い。インストールの時点でドキュメントが古いので、これ以降もそこかしこで「話が見えない」話題がいくつか出てくる。

Dart 2 では裸でインストールするのが正義。その後 pub global などでツールを足していく。

実際にやったのは

brew tap dart-lang/dart
brew install dart@2
brew link --force dart@2

pubで必要なものをインストール

pub は依存パッケージを管理するもの。Node.js の npm のように言語インストール段階で標準で使える。

  • package.json のように CLI で追加、変更はできない
    • Gemfile のように先に pubspec を編集する必要アリ
  • package.json のように name を要求する
  • インストールは pub get
  • global にインストールされる*1
    • 言語のバージョンが上がった時の扱いとかどうするんだろ。LLじゃないから考える必要ないのか?
  • 実行は各パッケージの bin/ 以下に入っているものを pub run
    • bin/ 以下がないなら実行できない
  • 独立コマンドがあるものは pub global activate で

このうち、いちいち pubspec.yaml を書くのが面倒だなぁと思って den などを使う方法があるらしいのだが、公式のツールではないので、2018-12-30 時点で dart 2.1 には対応していなかった。

pubspecのバージョン指定の落とし穴

dev_dependencies:
  build_runner: >=0.8.10 <2.0

これは build_runner 自身が出力した「こう書け」に従ったのだが、

  • まず YAML の文法として意味があるのでダメ
  • "" で囲んでもみたが、それでも以下のようなエラー
Error on line 3, column 17 of pubspec.yaml: Invalid version
constraint: Expected version number after "<" in ">=0.8.10 <2.0", got "2.0".
 build_runner: ">=0.8.10 <2.0"

いろいろ試したのだが、Semantic Versioning で言う <major>.<minor>.<patch> の patch level までちゃんと書けという意味だった。

そう言ってくれい。

webdev package

によると

  • Dart 2からは DDC を使え 
  • dart2js を個別に使う必要はない
  • Dartium は古い。webdev server を使えば普通のブラウザで動作する。
pub global activate webdev

webdev serve
webdev build

が使えるようになる。ただし、これらは依存パッケージとして build_runner と build_web_compilers を要求する。これは別途 pubspec.yaml を用意して pub get する必要がある。*2

とりあえず動くものを書く

https://www.dartlang.org/tools/pub/package-layout

を参考に、

web/index.html
web/main.dart

を置く。実行は

webdev serve web:8000 --hot-reload

みたいな感じ。*3

コツは HTML 上で

<script src="main.dart.js"></script>

と書きつつ、実際に用意するのは .dart のままにする、ということっぽい。

source map も以下のように自動で生成される。

webdev serve で dart の source map が表示されている様子

import

import の書き方は

import "package:<package_name>/path/to/lib"
import "path/to/lib"

いずれか。

Web フロントエンド開発で webdev を利用する場合、import のパスが飛ぶ(lib/ 以下と web/ 以下に分離する)ので、package:<package_name> を付けると lib/ 以下を見てくれる。

成果

wtnabe/dart-practice

参考

Flutterだけじゃない! Dart × Webフロントエンドの現状と未来

*1 —packages-dir 指定しても global への symlink になるし、obsolete

*2 なんで? なんで分かれてる? 一緒に入れればええやん。

*3 デフォルト port は 8080


2018-12-31 [長年日記]

_ 2018年ふりかえり

時間配分

ここで書く最も大事なことは今年は日記書いただと思う。

去年までも増やそう増やそうと思って頑張っていたんだけど、明らかに増えたぞーと言える。

過去5年分を比較するとこんな感じになる。

投稿数グラフ
2014 9 *
2015 8 *
2016 20****
2017 19***
2018 63************

明確。これでも6日に1回ペース。まだ増やせそうではある。

理由はいくつかあるが、

  1. 使える時間が増えた
  2. まとめる役回りが増えた
  3. 調べる役回りが増えた
  4. まとめやすいことをやっている

ということで、まぁ喜んでいいんじゃなかろうか。

4 についてはあんまりここで満足しすぎてはいけないかもしれないが。

技術的に

今年は日記的には概ね

  • モダンフロントエンドの JavaScript
  • FaaS の JavaScript
  • Infra as Code
  • セキュリティ、特にパスワードおよび鍵管理
  • BigQuery 向けの何か
  • 静的サイト(ジェネレータ以外も)

をやっていた。

あと書いていないのは業務系の SaaS API やそれを扱う何かとの戦い*1みたいな感じ。

扱うものが単に多岐に渡っているだけでなく、フロントエンドをモダン化するとアーキテクチャ全体もちゃんとモダン化して責務を分離しないとダメっすね、みたいなことを思ったりしながら、仕様化テストからアンチレガシーも同時にやっちゃうぜ、みたいなところがやっぱ得意領域っぽいです。はい。

もともとバックエンドからインフラは見る人がいないので PaaS 丸投げにしてどんどん負荷を下げつつ、テストの自動化で変更をガードしつつ、できることを増やす作戦だったんだけど、近年こっち方面はちょっと移譲できそうな感じになってきているのと、現在の環境的には重点的に扱えた方がよい*2ということで徐々にフロントエンドにステ振りを変えつつある。(気づかれてないかもしれないけど)

その他お仕事的には

最近は

  • それコストに合わないっす
  • それ「引き出せ」てないっす

みたいなツッコミ役と壁打ちの壁役みたいなのが多い。これ、割と汎用的な役割なので、なんかそこら中に首つっこめた方がいんじゃね? みたいな気がしてきている。

イカ的には

今年はヒーローモード、ナワバリ、オクト、ガチマッチと順番に丁寧にやってきましたが、ガチマ始まったらストレスが爆上がりなので、目標切ってどこかでバッサリいかないとまずいかもという気がしている。あ、スプラトゥーン2っていうゲームの話なんですけどね。

Ingressの時も思ったけど、ゲームね、やっぱ向いてないっすね。ストレスになる(笑)

Tags: 日々

*1 だいたいアーキテクチャ含む設計と現実のデータや振る舞いとの戦いに主にレビュー方面から

*2 UI的な意味と分析的な意味の両方