MacOSで、ディレクトリ内部の.heicファイルを一括で.jpgファイルに変換する方法

このエントリーは 今すぐalias登録すべきワンライナー by ゆめみ① Advent Calendar 2018 の16日目のエントリーです。

僕には愛する愛する4歳の娘が居まして、たった4年間ではありますが、iPhoneで何気なく撮影した彼女の成長を捉えた写真が数えきれないほどあります。

そして、それらの写真はこれからもどんどん増えますw。

それほど愛しい愛娘の写真でも、流石にローカルへずっと保存しておけるほどの量では無くなってきたので、いつでもどこでも見られて、間違って消してしまうことも無いGoogle Photoにあずけることにしました。

しばらくは問題なかったのですが、しかし、事件はおきました。

iPhoneやブラウザでカワイイ可愛い娘の写真を見る分には全く問題が無かったのですが、Google Photoから写真をダウンロードしてみると(今はまだ)扱いづらい画像ファイル、そう、 .HEIC ファイルとなっていることに気づきました。

.HEIC ファイル自体はまあ良いのですが、まだ未対応のアプリも多く非常に不便だったりします。

HEICファイルとは

High Efficiency Image File Format (ハイ・エフィシエンシー・イメージ・ファイル・フォーマット、HEIF、ヒーフ) は、Moving Picture Experts Group (MPEG) によって開発され、 MPEG-H Part 12 (ISO/IEC 23008-12) で定義された画像ファイルフォーマットである。1枚の画像やイメージシーケンスに使用される。HEIF方式による画像ファイルにはheifやheicといった拡張子が使われる。

2017年6月、アップルはmacOS High SierraiOS 11でHEIFをサポートすると発表し、その秋に出荷した。HEVCのサポートにより、静止画/動画の圧縮率は最大2倍にまで高められる[4]。

Wikipedia - High Efficiency Image File Format

結論

そこで、 .HEIC ファイルをJPEGファイルに変換する処理をワンライナーで作ってみました。

時代に逆行する内容で本当に申し訳ないと思っていますが、世のお父様方の代表として、ここに公開したいと思います。

$ find . -name '*.heic' | xargs -IT basename T .heic | xargs -IT sips --setProperty format jpeg ./T.heic --out ./T.jpg;
~/Downloads/IMG_8738.heic
  ~/Downloads/IMG_8738.jpg

簡単に説明しますと、

  • findでダウンロードした .HEIC ファイルを一覧する
  • xargs で一つづつ受けとり、basenameを利用して *.heic の拡張子を一度除外
  • さらに xargs で受けとり、sipsコマンドで変換

ただし・・・

xargs -IT basename T .heic

を挟むのがやや納得いきません。

何故やっているのかというと、もともとはこんなふうに書けばいいかなと思ったのに

$ find . -name '*.heic' | xargs -IT sips --setProperty format jpeg T --out ${T%.*}.jpg;
~/Downloads/IMG_8738.heic
  ~/Downloads/.jpg

xargs内では変数展開(下記の ${T%.*}.jpg )が出来ない?っぽく、ファイル名が空になってしまったからです。

例えば、これだったら変数展開でできるのですが・・・

$ for target in ./*.heic; do
>   sips --setProperty format jpeg $target --out ./${target%.*}.jpg;
> done
~/Downloads/IMG_8738.heic
  ~/Downloads/IMG_8738.jpg

でも、やっぱりワンライナーで書きたいですよね。

以上です。

ここまでお読みくださいまして、ありがとうございました。

Nuxt.jsのSSRの動きをHeadlessBrowserで確認してみる

この記事は、Nuxt.js #2 Advent Calendar 2018 の15日目の記事です。

14日目は POPOPON - Qiita さんの Nuxt でストアから GraphQL を扱う方法 - Qiita でした。

16日目は taroodr - Qiitaさんお願いします。

SSR(サーバーサイドレンダリング)してますか?

SSRにまつわる意義などの詳しい話はネット上にいくらでも転がっているモノだと思いますので割愛しますが、Nuxt.jsはそれ自体がVue.jsを使う上でSSRをより扱いやすくするべく生まれてきたものと(勝手に)認識しています。

SSRはSPAな今の世の中において必須の技術だと思います。

そして、最近のNuxt.js(とVue.js)の勢い・人気からすると、Nuxt.jsがいかに良く出来たフレームワークかがわかります。

実際、最近一番触っていて楽しいのがNuxt.jsです。

しかし、そんな私自身「実際、SSRってどんな動きなの?」と理解不足に感じることが多かったため(つまりアドベントカレンダー関係なく自分で理解したいために)、実際に動かして試してみることにしました。

予めお断りしますが、基本的にNuxt.jsのオフィシャルドキュメントに記載の内容を確認しているだけですのであしからず。

ja.nuxtjs.org/guide

Nuxt.js/Vue.jsのドキュメントはかなり充実していてありがたいですね。

Nuxt.jsにおけるSSRの動き

こちらもドキュメントの通りなのですが、

https://ja.nuxtjs.org/nuxt-schema.png

引用 - (ja.nuxtjs.org/guide#図解](https://ja.nuxtjs.org/nuxt-schema.png)

私の理解では、Componentが呼び出されると(あるいは違う言い方:当該Componentが必要となるURLに直接リクエストが来ると)、当該Componentに定義した asyncData() メソッドというものがHTMLレンダリングされる前に実行され、そのタイミングでサーバーなどからデータを取得し、同じく当該Componentの data() に値をセットすることで、クローラーなどが巡回してきたときにはすでに仮想DOMが出来上がっているといった認識です。

もしこの表現(特に「仮想DOMが出来上がっている」は怪しい)が間違っていたらご指摘いただきたいです。

localhostにてNuxt.jsでSSRを確認する方法

localhostにおいてNuxt.jsでSSRを行う場合、特に難しい設定は一切ありません。

nuxt コマンドで起動するだけです。

package.jsonで定義しても良いのですが、Node.jsのバージョンが最新であれば npx (直接packageのコマンドが打てる)を利用できますので、下記のコマンドでOKです。

$ npx nuxt

これだけで基本的にSSRを伴う動きとなるようです。

nuxt コマンドを実行すると開発サーバーが起動します。このサーバーはホットリローディング及び Vue Server Renderer を備えており、アプリケーションが自動的にサーバーサイドレンダリングするよう設定されています。

引用 - ja.nuxtjs.org/guide#サーバーサイドレンダリング-ユニバーサル-ssr-

そして、SSRしたいComponentにて、値を data() へ設定する処理を行う asyncData() を定義するだけです。

  asyncData (context) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({
          title: "title from asyncData",
          async_data_time: new Date()
        })
      }, 5000);
    }).then((res) => {
      return res
    })
  }

setTimeout を利用し、少し長めに5秒ほどのサーバー通信を擬似的に行っています。

localhostにてNuxt.jsでSSRさせない方法

反対に、SSRをさせない単純なSPAとする場合は、 --spa オプションをつけて nuxt コマンドを実行するだけです。

$ npx nuxt --spa

もし何らかの理由でサーバーサイドレンダリングを使いたくない、あるいはアプリケーションを静的にホスティングする必要があるときは nuxt --spa を使って、シンプルに SPA モードを使うことができます。generate 機能と組み合わせて使うことで、Node.js ランタイムや特別なサーバー処理を利用する必要なしに、SPA のパワフルなデプロイを実現できます。

引用 - ja.nuxtjs.org/guide#シングルページアプリケーション-spa-

HeadlessBrowserを使って確認する理由

SSRの期待するところは、なんといってもSPAなアプリケーションであってもSEO効果を期待させるためだと認識しています。

また、通常のブラウザでアクセスした場合、SPAモードで起動していたとしても仮想DOMは生成されます。

したがって、擬似的にクローラーの動きを真似るという意味でHeadlessBrowserで確認した次第です。

今回使うHeadlessBrowserはGoogleChromeに搭載されているものを利用します。

詳しくは こちら - ヘッドレス Chrome ことはじめ を参照してみてください。

上記ページを参考に、下記のようなコマンドをCLIにて実行して確認していきます。

$ chrome --headless --disable-gpu --dump-dom http://localhost:3000/about

確認用に作成したアプリ

トップ画面とアバウト画面の2画面だけの簡素なアプリです。

f:id:omiend:20181214170622p:plain

mountedが実行されると mounted_time に、 asyncData() が実行されると async_data_time にそれぞれ new Date() を設定しています。

トップ画面はただの静的な画面です。

また、ソースは下記にあります。もしよかったらどうぞ。

github.com

SPAモードで起動し、仮想DOMが生成されないことを確認

では実際に確認していきましょう。

下記のコマンドを実行し、SPAモードで起動します。

$ npx nuxt --spa

HeadlessBrowserでアクセスします。

$ chrome --headless --disable-gpu --dump-dom http://localhost:3000/about
<!DOCTYPE html>
<html data-n-head=""><head>
    <title data-n-head="true">omiend_nuxt_advent_calendar_2018_2_day_one</title><meta data-n-head="true" charset="utf-8"><meta data-n-head="true" name="viewport" content="width=device-width, initial-scale=1"><meta data-n-head="true" data-hid="description" name="description" content="My glorious Nuxt.js project"><link data-n-head="true" rel="icon" type="image/x-icon" href="/favicon.ico"><link rel="preload" href="/_nuxt/runtime.js" as="script"><link rel="preload" href="/_nuxt/commons.app.js" as="script"><link rel="preload" href="/_nuxt/vendors.app.js" as="script"><link rel="preload" href="/_nuxt/app.js" as="script">
  <style type="text/css">
.__nuxt-error-page {
  padding: 1rem;
  background: #F7F8FB;
  color: #47494E;
  text-align: center;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  font-family: sans-serif;
  font-weight: 100 !important;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.__nuxt-error-page .error {
  max-width: 450px;
}
.__nuxt-error-page .title {
  font-size: 1.5rem;
  margin-top: 15px;
  color: #47494E;
  margin-bottom: 8px;
}
.__nuxt-error-page .description {
  color: #7F828B;
  line-height: 21px;
  margin-bottom: 10px;
}
.__nuxt-error-page a {
  color: #7F828B !important;
  text-decoration: none;
}
.__nuxt-error-page .logo {
  position: fixed;
  left: 12px;
  bottom: 12px;
}

/*# sourceURL=/Users/endo/my/omiend_nuxt_advent_calendar_2018_2_day_one/.nuxt/components/nuxt-error.vue */
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9lbmRvL215L29taWVuZF9udXh0X2FkdmVudF9jYWxlbmRhcl8yMDE4XzJfZGF5X29uZS8ubnV4dC9jb21wb25lbnRzL251eHQtZXJyb3IudnVlIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFtREE7RUFDRSxjQUFjO0VBQ2Qsb0JBQW9CO0VBQ3BCLGVBQWU7RUFDZixtQkFBbUI7RUFDbkIsY0FBYztFQUNkLHdCQUF3QjtFQUN4QixvQkFBb0I7RUFDcEIsdUJBQXVCO0VBQ3ZCLHdCQUF3QjtFQUN4Qiw0QkFBNEI7RUFDNUIsMkJBQTJCO0VBQzNCLCtCQUErQjtFQUMvQixvQ0FBb0M7RUFDcEMsbUJBQW1CO0VBQ25CLE9BQU87RUFDUCxRQUFRO0VBQ1IsU0FBUztFQUNULFVBQVU7Q0FDWDtBQUNEO0VBQ0UsaUJBQWlCO0NBQ2xCO0FBQ0Q7RUFDRSxrQkFBa0I7RUFDbEIsaUJBQWlCO0VBQ2pCLGVBQWU7RUFDZixtQkFBbUI7Q0FDcEI7QUFDRDtFQUNFLGVBQWU7RUFDZixrQkFBa0I7RUFDbEIsb0JBQW9CO0NBQ3JCO0FBQ0Q7RUFDRSwwQkFBMEI7RUFDMUIsc0JBQXNCO0NBQ3ZCO0FBQ0Q7RUFDRSxnQkFBZ0I7RUFDaEIsV0FBVztFQUNYLGFBQWE7Q0FDZCIsImZpbGUiOiJudXh0LWVycm9yLnZ1ZT92dWUmdHlwZT1zdHlsZSZpbmRleD0wJmxhbmc9Y3NzJiIsInNvdXJjZXNDb250ZW50IjpbIlxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cbi5fX251eHQtZXJyb3ItcGFnZSB7XG4gIHBhZGRpbmc6IDFyZW07XG4gIGJhY2tncm91bmQ6ICNGN0Y4RkI7XG4gIGNvbG9yOiAjNDc0OTRFO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBmb250LWZhbWlseTogc2Fucy1zZXJpZjtcbiAgZm9udC13ZWlnaHQ6IDEwMCAhaW1wb3J0YW50O1xuICAtbXMtdGV4dC1zaXplLWFkanVzdDogMTAwJTtcbiAgLXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OiAxMDAlO1xuICAtd2Via2l0LWZvbnQtc21vb3RoaW5nOiBhbnRpYWxpYXNlZDtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHJpZ2h0OiAwO1xuICBib3R0b206IDA7XG59XG4uX19udXh0LWVycm9yLXBhZ2UgLmVycm9yIHtcbiAgbWF4LXdpZHRoOiA0NTBweDtcbn1cbi5fX251eHQtZXJyb3ItcGFnZSAudGl0bGUge1xuICBmb250LXNpemU6IDEuNXJlbTtcbiAgbWFyZ2luLXRvcDogMTVweDtcbiAgY29sb3I6ICM0NzQ5NEU7XG4gIG1hcmdpbi1ib3R0b206IDhweDtcbn1cbi5fX251eHQtZXJyb3ItcGFnZSAuZGVzY3JpcHRpb24ge1xuICBjb2xvcjogIzdGODI4QjtcbiAgbGluZS1oZWlnaHQ6IDIxcHg7XG4gIG1hcmdpbi1ib3R0b206IDEwcHg7XG59XG4uX19udXh0LWVycm9yLXBhZ2UgYSB7XG4gIGNvbG9yOiAjN0Y4MjhCICFpbXBvcnRhbnQ7XG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbn1cbi5fX251eHQtZXJyb3ItcGFnZSAubG9nbyB7XG4gIHBvc2l0aW9uOiBmaXhlZDtcbiAgbGVmdDogMTJweDtcbiAgYm90dG9tOiAxMnB4O1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ== */</style><style type="text/css">
.nuxt-progress {
  position: fixed;
  top: 0px;
  left: 0px;
  right: 0px;
  height: 2px;
  width: 0%;
  opacity: 1;
  transition: width 0.1s, opacity 0.4s;
  background-color: #fff;
  z-index: 999999;
}
.nuxt-progress.nuxt-progress-notransition {
  transition: none;
}
.nuxt-progress-failed {
  background-color: red;
}

/*# sourceURL=/Users/endo/my/omiend_nuxt_advent_calendar_2018_2_day_one/.nuxt/components/nuxt-loading.vue */
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9lbmRvL215L29taWVuZF9udXh0X2FkdmVudF9jYWxlbmRhcl8yMDE4XzJfZGF5X29uZS8ubnV4dC9jb21wb25lbnRzL251eHQtbG9hZGluZy52dWUiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQTRKQTtFQUNFLGdCQUFnQjtFQUNoQixTQUFTO0VBQ1QsVUFBVTtFQUNWLFdBQVc7RUFDWCxZQUFZO0VBQ1osVUFBVTtFQUNWLFdBQVc7RUFDWCxxQ0FBcUM7RUFDckMsdUJBQXVCO0VBQ3ZCLGdCQUFnQjtDQUNqQjtBQUVEO0VBQ0UsaUJBQWlCO0NBQ2xCO0FBRUQ7RUFDRSxzQkFBc0I7Q0FDdkIiLCJmaWxlIjoibnV4dC1sb2FkaW5nLnZ1ZT92dWUmdHlwZT1zdHlsZSZpbmRleD0wJmxhbmc9Y3NzJiIsInNvdXJjZXNDb250ZW50IjpbIlxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cbi5udXh0LXByb2dyZXNzIHtcbiAgcG9zaXRpb246IGZpeGVkO1xuICB0b3A6IDBweDtcbiAgbGVmdDogMHB4O1xuICByaWdodDogMHB4O1xuICBoZWlnaHQ6IDJweDtcbiAgd2lkdGg6IDAlO1xuICBvcGFjaXR5OiAxO1xuICB0cmFuc2l0aW9uOiB3aWR0aCAwLjFzLCBvcGFjaXR5IDAuNHM7XG4gIGJhY2tncm91bmQtY29sb3I6ICNmZmY7XG4gIHotaW5kZXg6IDk5OTk5OTtcbn1cblxuLm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb24ge1xuICB0cmFuc2l0aW9uOiBub25lO1xufVxuXG4ubnV4dC1wcm9ncmVzcy1mYWlsZWQge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiByZWQ7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 */</style><style type="text/css">
html {
  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-weight: 300;
  font-size: 1rem;
  color: #35495e;
  letter-spacing: 1px;
  word-spacing: 1px;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  box-sizing: border-box;
}
*,
*:before,
*:after {
  box-sizing: border-box;
  margin: 0;
}
.button--green {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #3b8070;
  color: #3b8070;
  text-decoration: none;
  padding: 10px 30px;
}
.button--green:hover {
  color: #fff;
  background-color: #3b8070;
}
.button--grey {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #35495e;
  color: #35495e;
  text-decoration: none;
  padding: 10px 30px;
  margin-left: 15px;
}
.button--grey:hover {
  color: #fff;
  background-color: #35495e;
}
.container {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}
.title {
  display: block;
}

/*# sourceURL=/Users/endo/my/omiend_nuxt_advent_calendar_2018_2_day_one/src/layouts/default.vue */
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9lbmRvL215L29taWVuZF9udXh0X2FkdmVudF9jYWxlbmRhcl8yMDE4XzJfZGF5X29uZS9zcmMvbGF5b3V0cy9kZWZhdWx0LnZ1ZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBT0E7RUFDRTtnREFDOEM7RUFDOUMsaUJBQWlCO0VBQ2pCLGdCQUFnQjtFQUNoQixlQUFlO0VBQ2Ysb0JBQW9CO0VBQ3BCLGtCQUFrQjtFQUNsQiwyQkFBMkI7RUFDM0IsK0JBQStCO0VBQy9CLG1DQUFtQztFQUNuQyxvQ0FBb0M7RUFDcEMsdUJBQXVCO0NBQ3hCO0FBRUQ7OztFQUdFLHVCQUF1QjtFQUN2QixVQUFVO0NBQ1g7QUFFRDtFQUNFLHNCQUFzQjtFQUN0QixtQkFBbUI7RUFDbkIsMEJBQTBCO0VBQzFCLGVBQWU7RUFDZixzQkFBc0I7RUFDdEIsbUJBQW1CO0NBQ3BCO0FBRUQ7RUFDRSxZQUFZO0VBQ1osMEJBQTBCO0NBQzNCO0FBRUQ7RUFDRSxzQkFBc0I7RUFDdEIsbUJBQW1CO0VBQ25CLDBCQUEwQjtFQUMxQixlQUFlO0VBQ2Ysc0JBQXNCO0VBQ3RCLG1CQUFtQjtFQUNuQixrQkFBa0I7Q0FDbkI7QUFFRDtFQUNFLFlBQVk7RUFDWiwwQkFBMEI7Q0FDM0I7QUFFRDtFQUNFLGtCQUFrQjtFQUNsQixjQUFjO0VBQ2Qsd0JBQXdCO0VBQ3hCLG9CQUFvQjtFQUNwQixtQkFBbUI7Q0FDcEI7QUFFRDtFQUNFLGVBQWU7Q0FDaEIiLCJmaWxlIjoiZGVmYXVsdC52dWU/dnVlJnR5cGU9c3R5bGUmaW5kZXg9MCZsYW5nPWNzcyYiLCJzb3VyY2VzQ29udGVudCI6WyJcblxuXG5cblxuXG5cbmh0bWwge1xuICBmb250LWZhbWlseTogJ1NvdXJjZSBTYW5zIFBybycsIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJ1NlZ29lIFVJJyxcbiAgICBSb2JvdG8sICdIZWx2ZXRpY2EgTmV1ZScsIEFyaWFsLCBzYW5zLXNlcmlmO1xuICBmb250LXdlaWdodDogMzAwO1xuICBmb250LXNpemU6IDFyZW07XG4gIGNvbG9yOiAjMzU0OTVlO1xuICBsZXR0ZXItc3BhY2luZzogMXB4O1xuICB3b3JkLXNwYWNpbmc6IDFweDtcbiAgLW1zLXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7XG4gIC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTtcbiAgLW1vei1vc3gtZm9udC1zbW9vdGhpbmc6IGdyYXlzY2FsZTtcbiAgLXdlYmtpdC1mb250LXNtb290aGluZzogYW50aWFsaWFzZWQ7XG4gIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG59XG5cbiosXG4qOmJlZm9yZSxcbio6YWZ0ZXIge1xuICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICBtYXJnaW46IDA7XG59XG5cbi5idXR0b24tLWdyZWVuIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGJvcmRlcjogMXB4IHNvbGlkICMzYjgwNzA7XG4gIGNvbG9yOiAjM2I4MDcwO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIHBhZGRpbmc6IDEwcHggMzBweDtcbn1cblxuLmJ1dHRvbi0tZ3JlZW46aG92ZXIge1xuICBjb2xvcjogI2ZmZjtcbiAgYmFja2dyb3VuZC1jb2xvcjogIzNiODA3MDtcbn1cblxuLmJ1dHRvbi0tZ3JleSB7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBib3JkZXI6IDFweCBzb2xpZCAjMzU0OTVlO1xuICBjb2xvcjogIzM1NDk1ZTtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICBwYWRkaW5nOiAxMHB4IDMwcHg7XG4gIG1hcmdpbi1sZWZ0OiAxNXB4O1xufVxuXG4uYnV0dG9uLS1ncmV5OmhvdmVyIHtcbiAgY29sb3I6ICNmZmY7XG4gIGJhY2tncm91bmQtY29sb3I6ICMzNTQ5NWU7XG59XG5cbi5jb250YWluZXIge1xuICBtaW4taGVpZ2h0OiAxMDB2aDtcbiAgZGlzcGxheTogZmxleDtcbiAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbn1cblxuLnRpdGxlIHtcbiAgZGlzcGxheTogYmxvY2s7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 */</style><script charset="utf-8" src="/_nuxt/pages/about/index.js"></script></head>
  <body data-n-head="">
    <div id="__nuxt"><style>#nuxt-loading {  visibility: hidden;  opacity: 0;  position: absolute;  left: 0;  right: 0;  top: 0;  bottom: 0;  display: flex;  justify-content: center;  align-items: center;  flex-direction: column;  animation: nuxtLoadingIn 10s ease;  -webkit-animation: nuxtLoadingIn 10s ease;  animation-fill-mode: forwards;  overflow: hidden;}@keyframes nuxtLoadingIn {  0% {visibility: hidden;opacity: 0;  }  20% {visibility: visible;opacity: 0;  }  100% {visibility: visible;opacity: 1;  }}@-webkit-keyframes nuxtLoadingIn {  0% {visibility: hidden;opacity: 0;  }  20% {visibility: visible;opacity: 0;  }  100% {visibility: visible;opacity: 1;  }}#nuxt-loading>div,#nuxt-loading>div:after {  border-radius: 50%;  width: 5rem;  height: 5rem;}#nuxt-loading>div {  font-size: 10px;  position: relative;  text-indent: -9999em;  border: .5rem solid #F5F5F5;  border-left: .5rem solid #fff;  -webkit-transform: translateZ(0);  -ms-transform: translateZ(0);  transform: translateZ(0);  -webkit-animation: nuxtLoading 1.1s infinite linear;  animation: nuxtLoading 1.1s infinite linear;}#nuxt-loading.error>div {  border-left: .5rem solid #ff4500;  animation-duration: 5s;}@-webkit-keyframes nuxtLoading {  0% {-webkit-transform: rotate(0deg);transform: rotate(0deg);  }  100% {-webkit-transform: rotate(360deg);transform: rotate(360deg);  }}@keyframes nuxtLoading {  0% {-webkit-transform: rotate(0deg);transform: rotate(0deg);  }  100% {-webkit-transform: rotate(360deg);transform: rotate(360deg);  }}</style><script>window.addEventListener('error', function () {  var e = document.getElementById('nuxt-loading');  if (e) e.className += ' error';});</script><div id="nuxt-loading" aria-live="polite" role="status"><div>Loading...</div></div><!-- https://projects.lukehaas.me/css-loaders --></div>
  <script type="text/javascript" src="/_nuxt/runtime.js"></script><script type="text/javascript" src="/_nuxt/commons.app.js"></script><script type="text/javascript" src="/_nuxt/vendors.app.js"></script><script type="text/javascript" src="/_nuxt/app.js"></script>

</body></html>

なにやらうわ~っと出ていますが、最後の方に

<div id="nuxt-loading" aria-live="polite" role="status"><div>Loading...</div></div>

と出ているのがわかるかと思います。

おそらく、ここが仮想DOMに置き換わる場所だと思います。

chrome の HeadlessBrowserには screenshot を撮る機能がありますので、試しに撮ってみました。

まずはトップ画面。

f:id:omiend:20181214170912p:plain

静的な画面ですが、mountedは実行されるんですね。

次に、動的なアバウト画面。

f:id:omiend:20181214163656p:plain

真っ白です。

これではSEOもへったくれもありませんね・・・。

Universalモードで起動し、SSRされ、仮想DOMが生成されていることを確認

次に、Universalモードで起動してSSRがされるか試してみます。

$ npx nuxt

同じく、HeadlessBrowserでアクセスします。

$ chrome --headless --disable-gpu --dump-dom http://localhost:3000/about
<!DOCTYPE html>
<html data-n-head=""><head>
    <title data-n-head="true">omiend_nuxt_advent_calendar_2018_2_day_one</title><meta data-n-head="true" charset="utf-8"><meta data-n-head="true" name="viewport" content="width=device-width, initial-scale=1"><meta data-n-head="true" data-hid="description" name="description" content="My glorious Nuxt.js project"><link data-n-head="true" rel="icon" type="image/x-icon" href="/favicon.ico"><link rel="preload" href="/_nuxt/runtime.js" as="script"><link rel="preload" href="/_nuxt/commons.app.js" as="script"><link rel="preload" href="/_nuxt/vendors.app.js" as="script"><link rel="preload" href="/_nuxt/app.js" as="script"><link rel="preload" href="/_nuxt/pages/about/index.js" as="script">
  <style type="text/css">
.__nuxt-error-page {
  padding: 1rem;
  background: #F7F8FB;
  color: #47494E;
  text-align: center;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  font-family: sans-serif;
  font-weight: 100 !important;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
.__nuxt-error-page .error {
  max-width: 450px;
}
.__nuxt-error-page .title {
  font-size: 1.5rem;
  margin-top: 15px;
  color: #47494E;
  margin-bottom: 8px;
}
.__nuxt-error-page .description {
  color: #7F828B;
  line-height: 21px;
  margin-bottom: 10px;
}
.__nuxt-error-page a {
  color: #7F828B !important;
  text-decoration: none;
}
.__nuxt-error-page .logo {
  position: fixed;
  left: 12px;
  bottom: 12px;
}

/*# sourceURL=/Users/endo/my/omiend_nuxt_advent_calendar_2018_2_day_one/.nuxt/components/nuxt-error.vue */
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9lbmRvL215L29taWVuZF9udXh0X2FkdmVudF9jYWxlbmRhcl8yMDE4XzJfZGF5X29uZS8ubnV4dC9jb21wb25lbnRzL251eHQtZXJyb3IudnVlIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFtREE7RUFDRSxjQUFjO0VBQ2Qsb0JBQW9CO0VBQ3BCLGVBQWU7RUFDZixtQkFBbUI7RUFDbkIsY0FBYztFQUNkLHdCQUF3QjtFQUN4QixvQkFBb0I7RUFDcEIsdUJBQXVCO0VBQ3ZCLHdCQUF3QjtFQUN4Qiw0QkFBNEI7RUFDNUIsMkJBQTJCO0VBQzNCLCtCQUErQjtFQUMvQixvQ0FBb0M7RUFDcEMsbUJBQW1CO0VBQ25CLE9BQU87RUFDUCxRQUFRO0VBQ1IsU0FBUztFQUNULFVBQVU7Q0FDWDtBQUNEO0VBQ0UsaUJBQWlCO0NBQ2xCO0FBQ0Q7RUFDRSxrQkFBa0I7RUFDbEIsaUJBQWlCO0VBQ2pCLGVBQWU7RUFDZixtQkFBbUI7Q0FDcEI7QUFDRDtFQUNFLGVBQWU7RUFDZixrQkFBa0I7RUFDbEIsb0JBQW9CO0NBQ3JCO0FBQ0Q7RUFDRSwwQkFBMEI7RUFDMUIsc0JBQXNCO0NBQ3ZCO0FBQ0Q7RUFDRSxnQkFBZ0I7RUFDaEIsV0FBVztFQUNYLGFBQWE7Q0FDZCIsImZpbGUiOiJudXh0LWVycm9yLnZ1ZT92dWUmdHlwZT1zdHlsZSZpbmRleD0wJmxhbmc9Y3NzJiIsInNvdXJjZXNDb250ZW50IjpbIlxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cbi5fX251eHQtZXJyb3ItcGFnZSB7XG4gIHBhZGRpbmc6IDFyZW07XG4gIGJhY2tncm91bmQ6ICNGN0Y4RkI7XG4gIGNvbG9yOiAjNDc0OTRFO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICBhbGlnbi1pdGVtczogY2VudGVyO1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xuICBmb250LWZhbWlseTogc2Fucy1zZXJpZjtcbiAgZm9udC13ZWlnaHQ6IDEwMCAhaW1wb3J0YW50O1xuICAtbXMtdGV4dC1zaXplLWFkanVzdDogMTAwJTtcbiAgLXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OiAxMDAlO1xuICAtd2Via2l0LWZvbnQtc21vb3RoaW5nOiBhbnRpYWxpYXNlZDtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHJpZ2h0OiAwO1xuICBib3R0b206IDA7XG59XG4uX19udXh0LWVycm9yLXBhZ2UgLmVycm9yIHtcbiAgbWF4LXdpZHRoOiA0NTBweDtcbn1cbi5fX251eHQtZXJyb3ItcGFnZSAudGl0bGUge1xuICBmb250LXNpemU6IDEuNXJlbTtcbiAgbWFyZ2luLXRvcDogMTVweDtcbiAgY29sb3I6ICM0NzQ5NEU7XG4gIG1hcmdpbi1ib3R0b206IDhweDtcbn1cbi5fX251eHQtZXJyb3ItcGFnZSAuZGVzY3JpcHRpb24ge1xuICBjb2xvcjogIzdGODI4QjtcbiAgbGluZS1oZWlnaHQ6IDIxcHg7XG4gIG1hcmdpbi1ib3R0b206IDEwcHg7XG59XG4uX19udXh0LWVycm9yLXBhZ2UgYSB7XG4gIGNvbG9yOiAjN0Y4MjhCICFpbXBvcnRhbnQ7XG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbn1cbi5fX251eHQtZXJyb3ItcGFnZSAubG9nbyB7XG4gIHBvc2l0aW9uOiBmaXhlZDtcbiAgbGVmdDogMTJweDtcbiAgYm90dG9tOiAxMnB4O1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ== */</style><style type="text/css">
.nuxt-progress {
  position: fixed;
  top: 0px;
  left: 0px;
  right: 0px;
  height: 2px;
  width: 0%;
  opacity: 1;
  transition: width 0.1s, opacity 0.4s;
  background-color: #fff;
  z-index: 999999;
}
.nuxt-progress.nuxt-progress-notransition {
  transition: none;
}
.nuxt-progress-failed {
  background-color: red;
}

/*# sourceURL=/Users/endo/my/omiend_nuxt_advent_calendar_2018_2_day_one/.nuxt/components/nuxt-loading.vue */
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9lbmRvL215L29taWVuZF9udXh0X2FkdmVudF9jYWxlbmRhcl8yMDE4XzJfZGF5X29uZS8ubnV4dC9jb21wb25lbnRzL251eHQtbG9hZGluZy52dWUiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQTRKQTtFQUNFLGdCQUFnQjtFQUNoQixTQUFTO0VBQ1QsVUFBVTtFQUNWLFdBQVc7RUFDWCxZQUFZO0VBQ1osVUFBVTtFQUNWLFdBQVc7RUFDWCxxQ0FBcUM7RUFDckMsdUJBQXVCO0VBQ3ZCLGdCQUFnQjtDQUNqQjtBQUVEO0VBQ0UsaUJBQWlCO0NBQ2xCO0FBRUQ7RUFDRSxzQkFBc0I7Q0FDdkIiLCJmaWxlIjoibnV4dC1sb2FkaW5nLnZ1ZT92dWUmdHlwZT1zdHlsZSZpbmRleD0wJmxhbmc9Y3NzJiIsInNvdXJjZXNDb250ZW50IjpbIlxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cblxuXG5cbi5udXh0LXByb2dyZXNzIHtcbiAgcG9zaXRpb246IGZpeGVkO1xuICB0b3A6IDBweDtcbiAgbGVmdDogMHB4O1xuICByaWdodDogMHB4O1xuICBoZWlnaHQ6IDJweDtcbiAgd2lkdGg6IDAlO1xuICBvcGFjaXR5OiAxO1xuICB0cmFuc2l0aW9uOiB3aWR0aCAwLjFzLCBvcGFjaXR5IDAuNHM7XG4gIGJhY2tncm91bmQtY29sb3I6ICNmZmY7XG4gIHotaW5kZXg6IDk5OTk5OTtcbn1cblxuLm51eHQtcHJvZ3Jlc3MubnV4dC1wcm9ncmVzcy1ub3RyYW5zaXRpb24ge1xuICB0cmFuc2l0aW9uOiBub25lO1xufVxuXG4ubnV4dC1wcm9ncmVzcy1mYWlsZWQge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiByZWQ7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 */</style><style type="text/css">
html {
  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-weight: 300;
  font-size: 1rem;
  color: #35495e;
  letter-spacing: 1px;
  word-spacing: 1px;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  box-sizing: border-box;
}
*,
*:before,
*:after {
  box-sizing: border-box;
  margin: 0;
}
.button--green {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #3b8070;
  color: #3b8070;
  text-decoration: none;
  padding: 10px 30px;
}
.button--green:hover {
  color: #fff;
  background-color: #3b8070;
}
.button--grey {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #35495e;
  color: #35495e;
  text-decoration: none;
  padding: 10px 30px;
  margin-left: 15px;
}
.button--grey:hover {
  color: #fff;
  background-color: #35495e;
}
.container {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}
.title {
  display: block;
}

/*# sourceURL=/Users/endo/my/omiend_nuxt_advent_calendar_2018_2_day_one/src/layouts/default.vue */
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9lbmRvL215L29taWVuZF9udXh0X2FkdmVudF9jYWxlbmRhcl8yMDE4XzJfZGF5X29uZS9zcmMvbGF5b3V0cy9kZWZhdWx0LnZ1ZSJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBT0E7RUFDRTtnREFDOEM7RUFDOUMsaUJBQWlCO0VBQ2pCLGdCQUFnQjtFQUNoQixlQUFlO0VBQ2Ysb0JBQW9CO0VBQ3BCLGtCQUFrQjtFQUNsQiwyQkFBMkI7RUFDM0IsK0JBQStCO0VBQy9CLG1DQUFtQztFQUNuQyxvQ0FBb0M7RUFDcEMsdUJBQXVCO0NBQ3hCO0FBRUQ7OztFQUdFLHVCQUF1QjtFQUN2QixVQUFVO0NBQ1g7QUFFRDtFQUNFLHNCQUFzQjtFQUN0QixtQkFBbUI7RUFDbkIsMEJBQTBCO0VBQzFCLGVBQWU7RUFDZixzQkFBc0I7RUFDdEIsbUJBQW1CO0NBQ3BCO0FBRUQ7RUFDRSxZQUFZO0VBQ1osMEJBQTBCO0NBQzNCO0FBRUQ7RUFDRSxzQkFBc0I7RUFDdEIsbUJBQW1CO0VBQ25CLDBCQUEwQjtFQUMxQixlQUFlO0VBQ2Ysc0JBQXNCO0VBQ3RCLG1CQUFtQjtFQUNuQixrQkFBa0I7Q0FDbkI7QUFFRDtFQUNFLFlBQVk7RUFDWiwwQkFBMEI7Q0FDM0I7QUFFRDtFQUNFLGtCQUFrQjtFQUNsQixjQUFjO0VBQ2Qsd0JBQXdCO0VBQ3hCLG9CQUFvQjtFQUNwQixtQkFBbUI7Q0FDcEI7QUFFRDtFQUNFLGVBQWU7Q0FDaEIiLCJmaWxlIjoiZGVmYXVsdC52dWU/dnVlJnR5cGU9c3R5bGUmaW5kZXg9MCZsYW5nPWNzcyYiLCJzb3VyY2VzQ29udGVudCI6WyJcblxuXG5cblxuXG5cbmh0bWwge1xuICBmb250LWZhbWlseTogJ1NvdXJjZSBTYW5zIFBybycsIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJ1NlZ29lIFVJJyxcbiAgICBSb2JvdG8sICdIZWx2ZXRpY2EgTmV1ZScsIEFyaWFsLCBzYW5zLXNlcmlmO1xuICBmb250LXdlaWdodDogMzAwO1xuICBmb250LXNpemU6IDFyZW07XG4gIGNvbG9yOiAjMzU0OTVlO1xuICBsZXR0ZXItc3BhY2luZzogMXB4O1xuICB3b3JkLXNwYWNpbmc6IDFweDtcbiAgLW1zLXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7XG4gIC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTtcbiAgLW1vei1vc3gtZm9udC1zbW9vdGhpbmc6IGdyYXlzY2FsZTtcbiAgLXdlYmtpdC1mb250LXNtb290aGluZzogYW50aWFsaWFzZWQ7XG4gIGJveC1zaXppbmc6IGJvcmRlci1ib3g7XG59XG5cbiosXG4qOmJlZm9yZSxcbio6YWZ0ZXIge1xuICBib3gtc2l6aW5nOiBib3JkZXItYm94O1xuICBtYXJnaW46IDA7XG59XG5cbi5idXR0b24tLWdyZWVuIHtcbiAgZGlzcGxheTogaW5saW5lLWJsb2NrO1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGJvcmRlcjogMXB4IHNvbGlkICMzYjgwNzA7XG4gIGNvbG9yOiAjM2I4MDcwO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIHBhZGRpbmc6IDEwcHggMzBweDtcbn1cblxuLmJ1dHRvbi0tZ3JlZW46aG92ZXIge1xuICBjb2xvcjogI2ZmZjtcbiAgYmFja2dyb3VuZC1jb2xvcjogIzNiODA3MDtcbn1cblxuLmJ1dHRvbi0tZ3JleSB7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBib3JkZXI6IDFweCBzb2xpZCAjMzU0OTVlO1xuICBjb2xvcjogIzM1NDk1ZTtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICBwYWRkaW5nOiAxMHB4IDMwcHg7XG4gIG1hcmdpbi1sZWZ0OiAxNXB4O1xufVxuXG4uYnV0dG9uLS1ncmV5OmhvdmVyIHtcbiAgY29sb3I6ICNmZmY7XG4gIGJhY2tncm91bmQtY29sb3I6ICMzNTQ5NWU7XG59XG5cbi5jb250YWluZXIge1xuICBtaW4taGVpZ2h0OiAxMDB2aDtcbiAgZGlzcGxheTogZmxleDtcbiAganVzdGlmeS1jb250ZW50OiBjZW50ZXI7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbn1cblxuLnRpdGxlIHtcbiAgZGlzcGxheTogYmxvY2s7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 */</style></head>
  <body data-n-head="">
    <div id="__nuxt"><!----><div id="__layout"><div><section class="container"><div><a href="/" class="nuxt-link-active">to top</a> <h1 class="title">title from mounted</h1> <p>mounted_time : "2018-12-14T08:11:36.709Z"</p> <p>async_data_time : "2018-12-14T08:11:36.497Z"</p></div></section></div></div></div><script>window.__NUXT__={layout:"default",data:[{title:"title from asyncData",async_data_time:new Date(1544775096497)}],error:null,serverRendered:true};</script><script src="/_nuxt/runtime.js" defer=""></script><script src="/_nuxt/pages/about/index.js" defer=""></script><script src="/_nuxt/commons.app.js" defer=""></script><script src="/_nuxt/vendors.app.js" defer=""></script><script src="/_nuxt/app.js" defer=""></script>

</body></html>

下記の通り、sectionから始まるテンプレートが正しくマウントされてますね。

<section class="container"><div><a href="/" class="nuxt-link-active">to top</a> <h1 class="title">title from mounted</h1> <p>mounted_time : "2018-12-14T08:11:36.709Z"</p> <p>async_data_time : "2018-12-14T08:11:36.497Z"</p></div></section>

また、 <p>async_data_time : "2018-12-14T08:11:36.497Z"</p> もちゃんと出ました。

こちらも screenshot を撮ってみましょう。

$ chrome --headless --disable-gpu --screenshot http://localhost:3000/about

f:id:omiend:20181214171508p:plain

正しく表示されているようですね。

firebaseなどで確認するには?

$ npx nuxt --spa

と同様に

$ npx nuxt generate --spa

すれば同様に確認できると思います。

まとめ

SEOを気にする必要があり、サーバーとの通信如何でコンテンツが出来上がる画面においては、基本的に asyncData()SSRしたほうが良さそうです。

ざっくりとですが、個人的にはNuxt.jsにおけるSSRの理解が深まって良かったです。

また、試している最中に気づいたのですが、 asyncData() はその処理が終わるまでHTMLレンダリングを止めてくれる(すこし誤解を招きそうな表現)のですが、その分画面の表示は遅れました。重たい処理などはなるべく避けて、素敵なSSRライフを送りたいものです。

asyncData() を使わない静的サイトであれば、SPAモードで十分ですね。

以上です。

ここまでお読みくださいまして、ありがとうございました。

ブラックパンサー

「真っ黒が最高」でお馴染みのブラパンことブラックパンサー

というのは嘘で、MARVELヒーローズオーケストラきっての名作でした。

どことなくバーフバリに似たようなものを感じた。

祖国を守ることと、正義を守るはずが、その正義を守るために過ちを犯してしまう国王と、その真実に気づいた新たなる王の戦い。

すっごく面白かった。

この映画の新の主人公は、ブラパンことティ・チャラではなく、ライバル:キルモンガーだと思った。

自社サービスのRails4を、Rails5にアップグレードした話

会社のRails4.2.0で動いているプロダクトは、Rails5.2.0へとアップグレードしたので、メモがてら大変だったことなどを書いておこうと思います。

何故やろうと思ったのか

Railsをアップグレードすることで便利な機能が追加されたと、得られる嬉しい事がいろいろあると思います。

ですが、弊社はエンジニアの人数が限られた組織ですので、下記の理由からなかなかRails5へのアップグレードに踏み切れませんでした。

  • Rails5にすることで、影響範囲がわからない
  • Railsに詳しいわけではないので、そもそもRails4からRails5へのやり方を調べる必要がある
  • エンジニアの人数が少ないので、優先度としては
  • 調査時間や実際に対応する時間を捻出するのが難しい

しかし、フレームワークのバージョンアップはこの先もずっとついて回る事ですし、ここでやらないと一生やらないと思ったからです。

また、実はこれから春に向けて当該サイトのリニューアルを予定してます。

リニューアルは主にフロントエンド周りとなるのですが、そこで個人としても今一番注目している「Nuxt.js」を利用したいと考えています。

「Nuxt.js」は今をときめく「Vue.js」のフレームワークでして、「Nuxt.js」を使い今よりもよりモダンなフロントエンド開発にすることで、開発効率の向上となによりUI/UXをより向上させることができるのではないかと考えております。

そういった予定もあり、Rails5化はその前にやってしまいたかったというお話でした。

何が一番大変だったのか

まず、何よりも一番問題になったのが、サイトを開発したエンジニアがすでに居ない事でした。

さらに開発ドキュメントも存在せず、「ある機能が実装された経緯」一つとってもワカラナイ状態です。

Rails5にバージョンアップ後、各機能が正常に動作するかを確認していくのですが、エラーとなる箇所全てにおいてロジックを書き換えることになります。

そうすると、”知っている人”に聞くことができないまま、エラーになるたびに「この機能の意図する所はこうだ」とロジックを全て追って理解するという作業が必要となり、これがとにかく大変でした。

実際にやった作業

余談ですが、Rails5にアップグレードするにあたって、基本的に下記のページを参考にしました。

Upgrading Ruby on Rails

Gemの依存関係

さて、実際にRails4からRails5にアップグレードする上で、実際やった作業です。

まず、Gemfileのrailsをアップデートします。

- gem 'rails', '4.2.0'
+ gem 'rails', '~> 5.2.0'

すると、 bundle install する際にGemの依存関係でエラーとなりので、ひとつづつ依存を解決していく必要があります。

この依存関係をひとつづつ解決する作業がかなり大変で、しかも私自身がRuby on Railsを始めて1年ほど人間だったので、恐らく無理だろうと判断しましました。

そして、少しディストピア感はあるのですが、代わりに下記の様に対応しました。

  • 開発向けのGemは一旦すべて削除
  • 現在Gemfileに設定されているGemを一つづつ「何をする・しているGemなのか」調べる
  • 本当に必要なGemだけ残し、それ以外は一旦削除する
  • 各々のGemのアップデートで起きた依存解決エラーを解決

これで、なんとかRailsを立ち上げるところまでできました。

各々のGemのアップデートで起きたエラーを修正

・何故か mysql2 のinstallが通らなくなった

原因は結局ワカラズでしたが、とりあえず xcode-select --installXCodeをインストールしたら mysql2 をインストールすることができました。(必要なパッケージが無かったっぽい?)

gem 'therubyracer'gem 'mini_racer' に変更

詳しくは下記。

https://github.com/rails/rails/pull/29285

xxx_filterxxx_action に修正

  • before_filterbefore_action に変更
  • around_filteraround_action に変更

コレですね

DEPRECATION WARNING: before_filter is deprecated and will be removed in Rails 5.1. Use before_action instead.

・ scope でチェインするような書き方を修正

where{ (category_id == target_category_id) }

のように書かれていた箇所を

where({category_id: target_category_id})

の様に修正しました。

これは単純にエラーを解消させた感じです。

・ 画像表示周りで、 .url が無く、エラーになる

こちらも単純にエラーを解消させた感じです。

また、ついでなのですが、下記の様にリンク付き画像を定義されている、

= link_to(image_tag(record.image, alt: record.alt.to_s), record.link, target: "_blank")

といった書き方をされていた箇所を

= link_to record.link, target: "_blank" do
  = image_tag record.image.url, alt: record.alt.to_s

といった様に修正しました。

リンクはリンク、画像は画像で、見通しがよくなった様に思います。

SQLエイリアス

例えば、

employees belongs_to companies というAssociationがあったとして

class Emoloyee < ActiveRecord::Base
  belongs_to :company
end

とすると思いますが、Rails5から belongs_to は必須項目になったので

class Emoloyee < ActiveRecord::Base
  belongs_to :company, optional: true
end

に修正しました。

FactoryGirl から FactoryBot への修正

恐らくこの作業が一番大切だと考えています。

まず、アプリケーションのテストが全く書かれていませんでした。

なので、とりあえずRSpecで重要ロジックのテストを書いておこうと思ったのですが、その上で、FactoryGirlが FactoryBot に名前が変更されたので、ついでにFactoryBotにしました。

まとめ

作業内容をまとめると、

  • 「Gemの依存関係解決」難易度:★★★★
  • 「ちょっとしたロジックの修正」難易度:★★
  • 「(とりあえず)足りない重要ロジックのテストを書く」難易度:★★

といった感じでした。

とりあえずこのくらいの作業でRails5へのアップグレードは完了し、稼働させることができました。

やってしまえばなんてことはありませんが、手探り状態で1週間ほど対応に時間がかかりました。

その間新規機能の開発などが止まることを考えると、経営目線では「Rails5へのアップグレードは本当に必要な作業なの?」と問われることも多々あるかもしれません。

長い目で見ると、いつか爆発するかもしれない爆弾とも言えそうな「Gemの依存解決」、フレームワークの新機能を使ったスピード感ある「開発効率の向上」などなど、必ず良い事だらけだと思います。

はじまりへの旅

いやー泣いた泣いた。

こんなに泣けたのROOM以来だな。

ベン・キャッシュとその妻レスリーは、6人の子どもたちとワシントン州の森の奥深くに住んでいる。資本主義とアメリカ人の生き方に幻滅したベンとレスリーは、子どもたちにサバイバルの技術と哲学を教え込む。社会から離れ、ベンとレスリーは子どもたちを育てることに身を捧げる。批判的思考ができるよう教育し、肉体的に健康で活発であるように訓練をさせる。そして、森での生活を通じ、テクノロジーに頼らず自然と共生することの素晴らしさを身をもって体験させる。 しかしながら、レスリー双極性障害によって入院し、最終的には自らの命を絶ってしまう。ベンは妹のハーパーからこの事実を知らされる。葬儀の手配についての話し合いがもたれるが、義父のジャックとベンは言い争いを始めてしまう。レスリーの遺志に従いベンは火葬を執り行いたいが、ジャックは土葬にしたいと言う。結局土葬が行われることになり、ベンは葬儀への参列をしないことにし、子どもたちにそう伝えた。しかし、ベンは葬儀を台無しにしてやろうと決意し、子どもたちを連れ車での旅に出る。 そして、子どもたちは初めて森の外の生活を体験する。 はじまりへの旅 - Wikipedia

特殊といえば特殊なのだけど、そんな生活の中で本当に大切なことを全てを教え込んだはずなのに何も教えられていなかった父の葛藤と、ついには敗北してしまう様がとても切なく涙を誘ってきた。

この映画の登場人物のうち、大人たちに限って言えば全員が純粋な愛をもって行動しているのだけど、考え方や立場などからその愛を押し付けてしまうんだよね。

実際は、子どもたちから見てさえそれはエゴでしかなくて、大人としての、親としての辛さが凝縮されているように思った。

例えば、父は社会のくだらなさに子供たちを山で育て、自ら教育と肉体的な訓練、ときには鹿を狩って捌いて食すというサバイバルと食育(と書くと何故か安っぽくなるな)を叩き込む。

結果、子どもたちは全員アスリート並みの強さと、一流大学にたやすく合格してしまうほどの学力を身につける。

一見とても素晴らしいように思えるけれど、実際この世界で生きていくに必要なこと全てには足りなくて、そこを窘められてしまうというか。

そんな中でも、6人の子どもたちだけがどこまでもどこまでも純粋に生きていて、それが父である主人公の救いにもなるという。

あと、音楽も良い。

シガー・ロスが流れたりするのだけど、山の景色とか、ちょっとビビットな映像が妙にマッチしてた。

www.youtube.com

最近観た16本の映画の感想を簡単にメモ

この中だと、特捜部Qが推しかなあ。

イコライザーとジェーン・ドゥも良かった。

特捜部Q 檻の中の女(字幕版)

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp

特捜部Q - Pからのメッセージ

特捜部Q Pからのメッセージ(字幕版)

3作目。

これも面白かった。

ヒリヒリとド緊張する感じが素敵。

主人公が最後に言う、「子供ってのは馬鹿で無知で、でもそうでなくてはならない」的なセリフが最高。

omiend.hatenablog.jp

omiend.hatenablog.jp

omiend.hatenablog.jp