樹脂が固まる前に

Web Frontend / Android / Designが好きな人のメモ

Riot.js から Next.js に移行した話

マジで年 1 ぐらいでしか個人ブログ更新してないなと気付き反省しています。

はじめに

最近、 自分の Portfolio サイトを Riot.js から Next.js に移行しました。 理由としては、 大きく以下の 2 点があります。

  • Riot.js のメジャーアップデート(v3 -> v4)による API の変化が個人的にしっくりこなかった
  • Next.js の SSG について勉強したいと思っていて、ちょうどよい題材が欲しかった

長年お世話になった Riot への感謝とともに、移行の手順について簡単に残しておきたいと思います。

※ Riot.js は今でも開発が続けられてますし、dis る意図はありません。

Riot.js との思い出

Riot は私にとって青春でした。 私がお世話になったのは v3 で、初めて知ったのは 2016 年の冬でした。 v3 のページは現行のバージョン(https://riot.js.org/ja/)とは別になっています ↓

https://v3.riotjs.now.sh/ja/

今でこそ Web フロントエンドエンジニアとして仕事をしていますが、当時は少しだけ React を触ったことがある程度で、ただの Kotlin / Android 大好き学生でした。 そんな私にとって、初めて出会った時は凄まじい衝撃が身体を駆け巡りました。

<todo>

  <!-- layout -->
  <h3>{ opts.title }</h3>

  <ul>
    <li each={ item, i in items }>{ item }</li>
  </ul>

  <form onsubmit={ add }>
    <input ref="input">
    <button>Add #{ items.length + 1 }</button>
  </form>

  <!-- style -->
  <style>
    h3 {
      font-size: 14px;
    }
  </style>

  <!-- logic -->
  <script>
    this.items = []

    add(e) {
      e.preventDefault()
      var input = this.refs.input
      this.items.push(input.value)
      input.value = ''
    }
  </script>

</todo>
  • 非常にシンプルでミニマルな文法(API
  • HTML や CSS、JS をひとまとめに書ける単一ファイルコンポーネント
  • バンドルサイズの小ささ

は衝撃的でしたし、その美しい文法とは相反する内なる思想の強さみたいなものが Riot (暴動)という名前にふさわしいと思いました。 Vue も似たような立ち位置でしたが、特に JS 周りの文法はかなり記述量が少なく、Minimalism という観点で個人的にかなりしっくり来ていました。 HTML 部分や CSS 部分、JS 部分もカスタマイズが可能で、Pug + SCSS + ES6 という当時のお気に入りのセットが実現できたのも嬉しかったです。

すぐに虜になった私は、Riot でたくさんの Web アプリケーション(SPA)を作りました。 特に自分しかフロントエンドの開発をせず、規模が大きくならなそうなものに関しては、積極的に使っていきました。 例えば、アルバイト先で開発を担当した新規のサービスのうち、半分ほどは Riot を使いました。 また、研究で使うデモ用の Web アプリケーションを作成するときも Riot を使いました。 ミニマルな構文は、生産性に直結し、かなりスピーディに開発をできていた気がします。 Riot が少し日本で有名になった頃には勉強会なども開かれ、参加したりもしました。 その学習の容易さはエンジニアだけでなくデザイナにも受け入れられている様子で、なぜか私自身も嬉しくなってしまいました。 他にも、就活をしていて好きな FW について聞かれたときはいつも Riot の話をしていました。 Web フロントエンドの FW としては初恋だったと思います。 知れば知るほど奥深く、本当に Riot が大好きでした。

ただ、徐々に疎遠になっていきました。 私自身はあまり感じたことなかったのですが、パフォーマンスでの問題やバグなどがあったらしく、そういうツイートをちょいちょい見かけるようになりました。 それを見てから私も自信を持って Riot を布教することがだんだん難しくなってきたように思います。 人に流されるなと言われればそうなのですが、頭の片隅でやはり気にしてしまう自分がいました。

そして、それらの多くが解決された Riot v4 が誕生したとき、 私の好きだった Riot のミニマルな文法は失われ、より Vue っぽいものへと変わってしまいました。

<todo>
  <!-- layout -->
  <h1>{ props.title }</h1>

  <ul>
    <li each={ item in state.items }>{ item }</li>
  </ul>

  <form onsubmit={ add }>
    <input name="todo">
    <button>Add #{ state.items.length + 1 }</button>
  </form>

  <!-- style -->
  <style>
    :host {
      padding: 16px;
    }
  </style>

  <!-- logic -->
  <script>
    export default {
      state: {
        items: []
      },
      add(e) {
        e.preventDefault()
        const input = e.target.todo

        this.state.items.push(input.value)
        this.update()

        input.value = ''
      }
    }
  </script>
</todo>

完全に個人の趣味なのですが、この JS 部分がどうにも、受け入れられず、そのあたりからリタイアしてしまいました。 実際使いこなすだけであれば、全く問題ないと思います。 ただ、思いを寄せていた相手がガラッとイメチェンしてしまった感じです。 このなんとも言えない行き場のない思いは、興味の衰退という形であらわれていきました。

Riot との思い出はここまでです。 また、気になったら「初めまして」から始めたいと思います。

Next.js への興味

話は変わって React ベースの SSR, SSG の FW である Next.js に移ります。 Next.js にはフロントエンドのパフォーマンスを高めるための様々な工夫が盛り込まれています。 実際に勉強がてら触ってみたいと思い、題材として自分の Portfolio サイトを選択しました。 Portfolio サイトは Riot v3 で作られており、v4 への移行はかなり根本から変えなければならなそうだったため、それなら勉強したい Next.js にしようということです。 中身のコンテンツはめちゃめちゃ古いままなのですが、FW を刷新した感じです。

移行

ここでは移行の手順を説明します。 今考えると遠回りしていた感じもありますが、実際の作業どおりに書いていきます。 まず、現状の状況を整理します。

  • Riot v3 による SPA
    • Pug
    • SCSS(Scoped CSS)
    • ES6
  • webpack でビルド(バンドル)
  • GitHub Pages で配信
    • master ブランチの root に手動でビルドして Push

ここから以下に変更しました。

  • Next.js v9.5
    • React
    • SCSS
    • TypeScript
  • Next.js で SSG して HTML ファイル生成
  • GitHub Pages で配信
    • GitHub Actions で自動ビルド(エクスポート)

URL を変えたくなかったので、GitHub Pages を使うことは継続しました。 一方でビルドは、手動から GitHub Actions での自動ビルド(エクスポート)に変更しました。 その方がリポジトリソースコードだけになってきれいそうだったので。 あとは、型が欲しかったので TypeScript に移行しました。 ちなみに古いリポジトリなので main ではなく master ブランチです。

移行の仕方としては、現状のコードやビルドされた生成物には触れず、Next.js の設定をして完全に同じサイトが再現できたタイミングで GitHub Pages の設定を切り替えました。

Pug -> HTML

サイト作成時は Pug というテンプレートエンジンにハマっていたので、Riot でも Vue でも Pug を使っていました。 ここから React(TSX)にする必要があるので、まずは HTML に近づけるところから始めました。 Riot の標準のテンプレートはほぼ HTML なので、Pug を HTML に変換するサイトなどを使って変換しました。

Scoped CSS -> Global CSS

Riot ではコンポーネント内にスタイルの定義を閉じる、Scoped CSS:scopedが独自実装されているので、それをグローバルでも衝突しないように変更しました。 具体的には SCSS のネスト機能を使っていたので、:scopedで囲まれていたスタイルを.component-fooのような一意なクラス名で囲む形にしていきました。

ルーティング

GitHub Pages で SPA を実現するため、riot-route を使い、以下のような Hash によるルーティングを行っていました。 github.io/#projects/foo

個人的にはこの Hash は好きではなかったので、Next.js の SSG で以下のパスに変更しました。 github.io/projects/foo

詳細ページのリンクを貼ることはあまりなかったので、ルート以外のパスが変わってしまうのは許容することにしました。

Next.js 導入

そろそろ Next.js のセットアップを行っていきます。 普通に Next.js のドキュメント通りに作っていくだけだったので特に書くことはないです。 まずは開発用サーバで SPA が良い感じに動くようにし、ルーティングできるようにしていきました。 中身はまだ空っぽです。 CDN をいくつか使っていたので、_app.tsxで NextHead コンポーネントを使って対応しました。 あと、モダンブラウザ以外を考える気はあまりなかったのですが、Babel の設定をするのが面倒だったので polyfill.io をとりあえず入れておきました。 IE でチェックしてないのでもしかしたら動いてないかもしれません。 最後に、next export して SSG したファイルでも問題ないかを確認しました。

CI

GitHub Actions で自動ビルドするようにしました。 以下の記事を参考にしました。

GitHub Pages に Next.js をデプロイする | Qiita

master への Push を検知し、gh-pages ブランチに SSG の生成物を Push する形です。 yarn を使ったりコマンド名を変えたり多少変えましたがほぼ同じ設定を書きました。 記事中でも言及がありますが、GitHub Pages の仕様で _ で始まるディレクトリがデフォルトでは見えないようになっているらしく、.nojekyll ファイルをリポジトリのルートに置くというのが勉強になりました。 これで GitHub Pages の配信設定を master ブランチではなく、gh-pages ブランチに切り替えれば Next.js で作成したページが見られるようになりました。

コンポーネントを Next.js 側に移行

あとは気合です。 Next.js 側にコンポーネントを作成しまくり、Riot に書かれたロジックを移していきました。 特に Riot 特有の記法(each や if など)を JavaScript に書き換えるのは単純作業ですが、楽しかったです。

SCSS

スタイル定義にはこれまで通り SCSS を使っています。 正直なんでも良かったのですが、移行前がそうなのでそのままです。 各コンポーネントの隣に同名の SCSS ファイルを置き、それを import しています。 global の定義は_app.tsxで読み込んでいます。

GitHub Pages の設定変更

配信設定を master ブランチではなく、gh-pages ブランチに切り替えたあと、実際に問題がないか確認しました。

Riot 削除

家に帰るまでが遠足です。移行は掃除まで含めて移行完了です。 ということでバッサバッサと Riot のビルドで使っていたツール群を消していきました。 webpack や loader、コンポーネントの tag ファイルなどです。 これは気持ちよかったですね。

移行完了して

何年も放置していた Portfolio サイトを久しぶりに触って、ひどい作りだったのでこれから技術的にもデザイン的にもコンテンツ的にもアップデートしていきたいなと思いました。 今までありがとう Riot。 また会う日まで。