フロントエンド
私的リリース戦略史

【mediba社合同勉強会】
コネヒトマルシェオンライン

@tkdn

whoami

  {
    "name"  : "武田 諭",
    "from"  : "株式会社 mediba",
    "job"   : "フロントエンド開発者",
    "social": "tkdn",
    "career": {
      "2003-2013" : "役者",
      "2013-"     : "エンジニア"
    }
  }

[PR] 書籍紹介

担当したのは Part 1 導入編, Part 3 応用編

[PR] 書籍紹介

特定技術よりも解決する課題を扱う

  • フロントエンドを取り囲むエコシステムを浅く広く扱ってます
  • 例えば SSG(静的サイト生成)
    ※ SSG は書籍で扱ってません

    • DreamWeaver ⇢ Middleman ⇢ Next.js
  • 解決している根本課題は同じ
    (周回毎に解決していることは微妙に違う

[PR] 書籍紹介

特定技術よりも解決する課題を扱う

例)SSG 変遷

共通する解決したい課題は
✅ 画面を大量生成したい

[PR]書籍紹介 SSG 変遷:DreamWeaver

  • Web 制作におけるページ量産を効率化
  • Adobe デザイン・マークアップフルセット

✅ 手動で作るより
テンプレートに合わせて
画面を大量生成したい

SSG 変遷:Middleman, Jekyll, Hugo

世は SSG 戦国時代(だった(言語も多種多様

✅ 手動で作るより
プログラマブルに
画面を大量生成したい

SSG 変遷:Next.js, Gatsby, Nuxt

世は JavaScript 一強時代(バイアス

✅ フレームワーク(巨人)の肩の上で
画面を大量生成したい

フロントエンドに特化しているので
パフォーマンス💯、開発体験💯

本題

今日話すこと「リリースの話」

  • 💡「フロントエンドのリリースどうしてますか」
  • 💡「どういった戦略でデプロイにのぞみますか」

アジェンダ

  1. 私的リリース史
  2. リリース史観から戦略へ応用する
  3. まとめ

1.私的リリース史

私的リリース史

  • A. 静的ファイルがまとまっているパターン
  • B. バックエンド FW が中心のパターン
  • C. フロントエンド特化のパターン

私的リリース史 A

静的ファイルがまとまっているパターン

  • 200x-201x年初頭あたりの Web 制作・開発
  • Node.js を中心とした、ツールチェーン掛け合わせの爆発的増加
  • 成果物は SFTP, SCP でアップロード

💣 選択肢とデプロイが自由すぎる
💣 マニュアル操作で不穏な匂いがする

私的リリース史 B

バックエンド FW が中心のパターン

  • 201x年中頃の Web 開発
  • FuelPHP、Laravel、Rails、Yii などの
    バックエンドFW
  • フロントエンドは控えめに載っかっている
  • バックエンドFWのテンプレートエンジン

私的リリース史 B

バックエンド FW が中心のデプロイ

  • fabric, capistrano, deployer...
  • フロントエンドのソースは別でデプロイ

💣 フロントエンドが通常デプロイフローと区別される
💣 画面に必要なアセットファイルの存在有無が未確定

私的リリース史 B

mediba ジョイン時のあるデプロイ

私的リリース史 C

フロントエンド特化のパターン

  • 最近関わっている mediba のプロジェクト
  • Next.js SSR/SSG、React SPA

✅ フロントエンドに軸を置いているので
フロントエンド中心のメリットを享受できる

メリット1:Optimized Cache Busting

  • 中間キャッシュをパージする画面サブリソースの参照 URL 変更
<link rel="stylesheet" href="https://awsesome-app.com/css/style.css?cache=1604818365">
<script src="https://awsesome-app.com/js/app.js?cache=1604818365"></script>

メリット1:Optimized Cache Busting

  • 本当に大丈夫? 雰囲気でパラメータつけてない?
    • e.g. Cloudfront query string forwarding

メリット1:Optimized Cache Busting

昨今のフロントエンド事情
webpack でのコンパイルでフォローが可能

// webpack config...
output: {
    // 生成されるファイルのダイジェスト値により変わる
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
},
<link rel="stylesheet" href="https://awsesome-app.com/css/style.0cf28fbf.css">
<script src="https://awsesome-app.com/js/app.7847e54d.js"></script>

メリット2:Long Term Caching

  • 変更のないファイルはクライアントに中間キャッシュを提供
  • ムダな通信を発生させずキャッシュを最大限有効活用

メリット2:Long Term Caching

例)Next.js リリース 1回目

メリット2:Long Term Caching

例)Next.js リリース 2回目

メリット3:Valid Cache

  • メリット1:最適化されたキャッシュバスト
  • メリット2:長期的キャッシュ戦略
  • ゆえにアプリバージョン毎にユニーク

つまり?

リリース前後で参照するサブリソースが全く違うので
ローリングアップデート中に不幸がない

メリット3:Valid Cache

メリット3:Valid Cache

メリット3:Valid Cache

なんでそんなに気にするの?

  • ユーザーに届いてからの体験は正直何が起こるか分からない

実体験)
半年前リリースした資材になぜかまだリクエストがある…
リプレイスしているのに前の API にリクエストがある(なんで…)

C パターンのリリース戦略とはつまり…

🚸 ユーザーのためのキャッシュ戦略

2.リリース史観から戦略へ応用する

2. リリース史観から戦略へ応用する

  • 必ずしも C パターンだけではない
    💡 必ずしもフロントエンド特化ではない
  • B だって全然ありえるパターン
    💡 バックエンドFW中心ということも全然ありえる

再掲)mediba ジョイン時のあるデプロイ

👊 まずこの課題をクリアしたい

1.webpack のビルド時に manifest.json を作成

{
    "/assets/css/app.css": "/assets/css/app.640bde4c.css",
    "/assets/css/page-a.css": "/assets/css/page-a.201ed25f.css",
    "/assets/css/page-b.css": "/assets/css/page-b.b1b57f9f.css",
    "/assets/js/app.js": "/assets/js/app.39540668.js",
    "/assets/js/page-a.js": "/assets/js/page-a.9bf31e12.js",
    "/assets/js/page-b.js": "/assets/js/page-b.7b3b2b62.js"
}

2.バックエンドで json を読み出す

まあいろいろやって読む。

3. json からパス生成するビューヘルパー実装

json ファイルキーから実パスを作成
パスを参照した script タグを作る例(PHP

/**
 * @param string $url
 * @param array $attrs
 * @return HtmlString
 */
public static function script(string $url, array $attrs = []): HtmlString
{
    $filepath = self::asset($url);
    $attributes = self::attributes($attrs);
    return new HtmlString('<script src="' . $filepath . '" ' . $attributes . '></script>');
}

4. テンプレートディレクティブ

// ... なんやかんや
@script("/assets/js/app.js")
<!-- output: --><script src="/assets/js/app.39540668.js"></script>
</body>
</html>
こんなことをしなくても

昨今のバックエンドフレームワークは
準備が整っている

例)Laravel 5.8

  • laravel new app
  • ルートに webpack.mix.js がいる

webpack の小難しいところを
隠蔽したラッパーパッケージ

最適化を戦略へ応用

1.webpack.mix.js

const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .version(); // この行だけスキャッフォルド時点では入っていない

最適化を戦略へ応用

2.マニフェスト作成

ビルドコマンドはすでに用意されている

{
    "/js/app.js": "/js/app.js?id=2f472fa1cb8208a4b3f4",
    "/css/app.css": "/css/app.css?id=99bb964c34fbf0372f70"
}

最適化を戦略へ応用

3.テンプレートから利用

mix は付属のヘルパー関数

<link rel="stylesheet" href="{{ mix('css/app.css') }}" />
<script src="{{ mix('js/app.js') }}"></script>

例)Rails 6.0

  • rails new app
  • webpacker がデフォルトで入っている

webpack の小難しいところを
隠蔽したラッパーパッケージ

※ …と思ったが使い勝手に癖があって少し戸惑った

最適化を戦略へ応用

1.app/javascript/packs/ にファイル設置

最適化を戦略へ応用

2.マニフェスト生成

ビルドコマンドはすでに用意されている

最適化を戦略へ応用

3.テンプレートへ記述

<%= stylesheet_pack_tag 'app' %>
<%= stylesheet_pack_tag 'styles/another' %>
</head>
<body>
    <%= javascript_pack_tag 'app' %>
    <%= javascript_pack_tag 'scripts/another' %>
</body>

実装サンプル

tkdn/release-strategy-working

2. リリース史観から戦略へ応用する

適切なリリース戦略実現のために
バックエンドフレームワークとの協調で何が必要そうか?

  1. フロントエンドのあるべき姿を共有
  2. フロントエンドビルドパイプラインをデプロイフローへ合流させる
  3. リリース・キャッシュ戦略はチームで考える

3.まとめ

3.まとめ

昨今の JS フレームワークはフロントエンドに
最適化されたリリース・キャッシュ戦略が込み

  • メリット1:Optimized Cache Busting
  • メリット2:Long Term Caching
  • メリット3:Valid Cache

3.まとめ

最適化されたフロントエンドの戦略を
バックエンドフレームワークにも持ち込む場合

  • 昨今のフレームワークは用意がある
    (なければ自作して)
  • 開発チームの理解を得て
    リリースとフロントエンドビルドパイプラインを合流させよう

参考資料その1

  • テンプレート機能を使ってみよう パート1:「編集可能領域」と「オプション領域」の使い方 | デベロッパーセンター
  • Static Site Generators - Top Open Source SSGs | Jamstack

参考資料その2

  • Caching | webpack
  • ブラウザのキャッシュを活用する  |  PageSpeed Insights  |  Google Developers
  • Prevent unnecessary network requests with the HTTP Cache
  • Caching best practices & max-age gotchas - JakeArchibald.com

参考資料その3

  • クエリ文字列パラメータに基づくコンテンツのキャッシュ - Amazon CloudFront
  • S3 バケットのライフサイクルポリシーを作成する方法 - Amazon Simple Storage Service
  • medz/webpack-laravel-mix-manifest: 🐶A webpack plugin that generates Laravel framework compatible mix-manifest.josn file.
  • JeffreyWay/laravel-mix: The power of webpack, distilled for the rest of us.
  • rails/webpacker: Use Webpack to manage app-like JavaScript modules in Rails