JavaScript じゃなくても GitHub Pages で動かしたい


この記事は TSG Advent Calendar 2019 の 4 日目として書かれました。枠が空いていたので全く関係ないもので埋めてしまいましたが許してください……

動機

Web というのは魅力的で、何か動くものを作ってぴゃっと公開したいとき、 URL を見せればダウンロード不要で動かしてもらえます。大抵の PC やスマホにはブラウザがついているので、環境によらず同じように(たまに同じでないけれど)動いてくれます。

さて、自分のサーバを持っていないとき、これをどうやって公開しましょうか。いわゆる静的サイトのホスティングですが、 GitHub Pages を使うというのは割とある選択肢です。 GitHub にソースコードを push するだけで、 hogehoge.github.io みたいな URL で動かしてくれるアレです。

ただ多少コードが増えてくると、素の JavaScript を書くのが嫌になってくることがあります。あるいは JavaScript でない、いわゆる AltJS で書いてみたい、という場合もあります。 GitHub Pages はファイルをそのまま配信しますが、 AltJS をブラウザで動かすにはビルド処理が必要です。 AltJS でなくても、複数ファイルをまとめる(バンドル)など、最近の JavaScript まわりは事前のビルド処理が前提になってきています。

そこで最近使えるようになった GitHub Actions を使って、このあたりの構築を勝手にやってくれるようにしようというのが本記事の目的です。

他の選択肢として「ローカルでビルドして gh-pages でデプロイする」「そもそも GitHub Pages ではなく Netlify のようなサービスを使う」「 CircleCI でも自動化できる」とかあると思いますが、プロジェクトごとに URL が与えられる Netlify は小規模のプログラムに対してそこまで必要なかったり、できれば管理する範囲を狭めたくて GitHub だけで完結すると嬉しかったり、まあとにかく GitHub Actions 使ってみたかっただけなので許してください。

需要があるのかは知りませんが、自分の備忘録も兼ねています。あとこの記事自体フロントエンド初心者向けなのか慣れてる人向けなのかだいぶブレてますが許して……。

共通手順

GitHub ユーザ名を ${USER} 、リポジトリ名を ${REPO} とします。

  1. プロジェクトを作ります。ここはなるべく各言語でシンプルにできる方法を選んでいますが、多少準備が面倒なものもあります。それぞれ筆者が作ってみたリポジトリをおいておくのでそちらを参考にしてくれるとよいです。あるいはリポジトリをフォークしてくれても良いです。

  2. GitHub でリポジトリを作成し、ローカルのものをプッシュします。

    git init
    git add -A
    git commit -m "Initial commit"
    git remote add origin https://github.com/${USER}/${REPO}.git
    git push -u origin master
    
  3. GitHub の Personal Access Tokens でトークンを発行します。スコープは repo で十分です。

  4. 発行したトークンをリポジトリの Settings > Secrets に貼り付けます。名前は PERSONAL_TOKEN とします。

  5. /.github/workflows/ にワークフローを追加します。リポジトリの Actions タブからワークフローを設定するページに行けるので、記事中に貼った URL の YAML ファイルをペッと貼ってください。ファイル名はなんでも良いです。
    デプロイ部分には GitHub Actions による GitHub Pages への自動デプロイ - Qiita を使わせてもらっています。

3 ~ 4 の処理は本来は省けるのですが、 問題があってまだ実現できていないらしいので残念……。
GITHUB_TOKEN が使用可能になり、3 ~ 4 の処理は不要になりました。

Vue 編

ここでは Vue CLI を使用します。

  1. プロジェクトを作ります。

    vue create ${REPO}
    cd ${REPO}
    

    ただしこのままだと諸々のデフォルトのパスが / なので、変更します。

    vue.config.js
    module.exports = {
      publicPath: './',
    };
    
  2. 共通

  3. 共通

  4. 共通

  5. 以下を参考にしてください。
    https://github.com/n4o847/template-vue-pages/blob/master/.github/workflows/deploy.yml

ワークフローの実行にかかった時間は 1 分 4 秒でした。

https://${USER}.github.io/${REPO}/ を開くと「Welcome to Your Vue.js App」と表示されます。

ちなみに Vue がどんなフレームワークかというと、コンポーネント(=部品)ごとに .vue ファイルを作って、中には Vue ファイルに HTML (構造) と CSS (見た目) と JavaScript (動き) を書きます。また状態を「モデル」として持っておき、それが変更されると勝手に描画が更新されます。初心者によくおすすめされているイメージがあります。

TypeScript + React 編

ここでは Create React App を使用します。

  1. プロジェクトを作ります。

    npx create-react-app ${REPO} --typescript
    cd ${REPO}
    
  2. 共通

  3. 共通

  4. 共通

  5. 以下を参考にしてください。
    https://github.com/n4o847/template-react-pages/blob/master/.github/workflows/deploy.yml
    PUBLIC_URL に注意しましょう。

ワークフローの実行にかかった時間は 1 分 7 秒でした。

https://${USER}.github.io/${REPO}/ を開くと「Edit src/App.tsx and save to reload.」と表示されます。

ちなみに React がどんなフレームワークかというと、 Vue はどちらかというと HTML に JavaScript をくっつける方式だったのに対し、 React は JavaScript の中に HTML を書きます(JSX という特殊な記法を使っています)。

TypeScript は JavaScript に型をつけた漸進的型付け言語です。静的型付き言語で育った人が聞いてもまあって感じでしょうが、 JavaScript で育った人からすると JavaScript で踏んでしまう実行時エラーというのはだいぶつらくて、型があるとバグが減らせてとても良いです。

Elm 編

ここでは Create Elm App を使用します。

  1. プロジェクトを作ります。

    create-elm-app ${REPO}
    cd ${REPO}
    

    構成は以下のとおりです。

    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   ├── logo.svg
    │   └── manifest.json
    ├── src
    │   ├── Main.elm
    │   ├── index.js
    │   ├── main.css
    │   └── registerServiceWorker.js
    ├── tests
    │   └── Tests.elm
    ├── .gitignore
    ├── README.md
    └── elm.json
    

    Create Elm App で生成されるものから変更する必要は特にありません。

  2. 共通

  3. 共通

  4. 共通

  5. 以下を参考にしてください。
    https://github.com/n4o847/template-elm-pages/blob/master/.github/workflows/deploy.yml
    PUBLIC_URL に注意しましょう。

ワークフローの実行にかかった時間は 1 分 9 秒でした。

https://${USER}.github.io/${REPO}/ を開くと「Your Elm App is working!」とは表示されるものの、画像が表示されていないと思います。これは Create Elm App で生成される src/Main.elm でロゴのパスが /logo.svg となっているせいなので ./logo.svg に変えてやれば動きます。

ちなみに Elm がどんな言語かというと関数型言語なんですが、モナドとかが(明示的には)出てきません。型クラスとかもありません。言語仕様が小さいため、コードが多少冗長になる代わりに、単純・簡潔に書けます。筆者はこの言語のおかげで関数型言語の考えを知って好きになったので、関数型言語を始めたい人にはおすすめです。もうひとつの特徴として DOM 更新含め設計の仕方が固定されているというのがあって、フレームワーク・設計どうしよう、みたいな悩みがなくなります。語りたいのですがこの辺にしておきます。

PureScript 編

PureScript については Create *** App みたいなものが見つからなかったので、自前で環境を作ります。パッケージマネージャは Spago 、バンドラは Parcel を使います。

  1. プロジェクトを作ります。

    mkdir ${REPO}
    cd ${REPO}
    spago init
    

    構成は以下の通りです。フレームワークとして Halogen を使ってみました。詳しくは下のリポジトリを参考にしてください。

    ├── src
    │   ├── Button.purs
    │   ├── Main.purs
    │   ├── index.html
    │   └── index.js
    ├── test
    │   └── Main.purs
    ├── .gitignore
    ├── package-lock.json
    ├── package.json
    ├── packages.dhall
    └── spago.dhall
    
  2. 共通

  3. 共通

  4. 共通

  5. 以下を参考にしてください。
    https://github.com/n4o847/template-purescript-pages/blob/master/.github/workflows/deploy.yml

ワークフローの実行にかかった時間は 1 分 21 秒でした。

https://${USER}.github.io/${REPO}/ を開くと On と Off が切り替わるボタンが出てきます。なんか Parcel と Spago の組み合わせが微妙だったので webpack のほうが良かったな……と思っています。でも設定が増えるしうーん……。

ちなみに PureScript がどういう言語かというと、ほぼ Haskell です。 Haskell よりも Haskell らしいとまで銘打っています( Haskell のレガシーな部分が改善されている)。型レベルプログラミングでぶいぶい言わせて JSON 扱ったりできるらしい。この辺バリバリ使って Web アプリ書いたらどうなるんでしょうね、試してみたい……。

Rust 編

Parcel を使おうとも思ったのですが、今のところ wasm-bindgen との組み合わせに難があるらしいので webpack + wasm-bindgen でやります。

  1. これに関しては設定する事項がそこそこあったので、用意したテンプレートを見てくれると良いです。構成は以下のようになっています。

    ├── crate
    │   ├── src
    │   │   └── lib.rs
    │   ├── .gitignore
    │   ├── Cargo.lock
    │   └── Cargo.toml
    ├── public
    │   └── index.html
    ├── src
    │   ├── index.js
    │   └── style.css
    ├── .gitignore
    ├── package-lock.json
    ├── package.json
    └── webpack.config.js
    

    設定事項は多いものの、今回特有のことはそんなにありません。 webpack の方で @wasm-tool/wasm-pack-plugin を、 Rust の方で wasm-bindgen を使っているので、主にそちらの使い方が基本になると思います。

  2. 共通

  3. 共通

  4. 共通

  5. 以下を参考にしてください。
    https://github.com/n4o847/template-rust-pages/blob/master/.github/workflows/deploy.yml

ワークフローの実行にかかった時間は 6 分 24 秒でした。かなり遅いですね。 wasm-pack を自動でインストールしてくれるんですが、このインストールに時間がかかっているようです。キャッシュすべきですね。

https://${USER}.github.io/${REPO}/ を開くと Rust から呼び出したアラートが出てきます。

ちなみに Rust がどんな言語かというと、安全性と速度を極めた言語です。フロントエンドの視点から言うと、 WebAssembly にコンパイルできるので、 Web アプリやゲームで速度の必要な大量の演算をさせるのにもってこいです。所有権を始めとして、安全なプログラムを書くことを強制し、低レベルな処理をゼロコストで実行する工夫がところどころにあります。

Rust + Yew 編

上のは Rust を使っているものの、結局 webpack で nantoka-lodaer を設定するのが面倒だったり、根本は JavaScript だったりします。(一部の重い計算を Rust に任せる、みたいな使用には適していますが。)

そこで全部を Rust で書けるフレームワーク Yew を使ってみます。

  1. プロジェクトを作ります。

    cargo new --bin ${REPO}
    cd ${REPO}
    

    構成は以下の通りです。シンプルで良いですね。

    ├── src
    │   └── main.rs
    ├── .gitignore
    ├── Cargo.lock
    ├── Cargo.toml
    └── index.html
    
  2. 共通

  3. 共通

  4. 共通

  5. 以下を参考にしてください。
    https://github.com/n4o847/template-yew-pages/blob/master/.github/workflows/deploy.yml

ワークフローの実行にかかった時間は 9 分 52 秒でした。これも遅いですね。 cargo-web のインストールに 8 分 24 秒、プロジェクト自体のコンパイルに 1 分 14 秒かかっているようです。キャッシュすべきですね。

https://${USER}.github.io/${REPO}/ を開くとボタンが出てきます。 JavaScript が 0 行で動くのは感動しますね。

ちなみに Yew がどんなフレームワークかというと、知りません……
頻繁に JavaScript とやりとりすると WebAssembly のパフォーマンスが損なわれるような気はしますが、 Rust で書けるというのが大きいんでしょうか。

Go + Vugu 編(失敗)

Go 言語も Rust と大体同じ流れです。ただ Go をそのまま使うと JavaScript の API 叩きまくって見た目が微妙、みたいなところがあるので、最初からフレームワークの Vugu を使います。

……と思ったのですが、 Vugu が思うように動きませんでした

色々なバージョンで試したのですが、最新( 2019/11/30 時点での master )だと distutils で怒られが発生する、 v0.1.0 だと github.com/vugu/vugu/domrender がないと怒られる、みたいな感じで Vugu 自体がちゃんとバージョン管理されてないのが悪そうなんですが、これ動かせた人はいるのでしょうか

Vugu 自体今のところ不安定なフレームワークらしいので、まあ気長に待ちます。(ググったり Twitter 調べても最近話題にしている人が少ないように感じますが大丈夫なのでしょうか……)

ちなみに Go 言語をフロントエンドの視点で見ると、生成される WebAssembly にガベージコレクションとか Go 自体の処理系がひっつくのでサイズはでかくなるそうです。この辺は Rust が流石という感じですね。とはいえ Go は言語仕様が比較的小さく、 Go をよく知らない自分がコードを見ても何をしているかがなんとなく分かるし、プログラムを単純に書きやすそうな言語に思えます。

その他

Opal という Ruby から JavaScript へのコンパイラの v1.0 が出ていることに気づいて、せっかくだからやりたかったけどいい感じのビルド方法がわからず挫折しました。

あと最初この記事に「github-pages GitHubActions Vue.js React Elm PureScript Rust Yew Go WebAssembly」みたいなタグをつけたら「5つ以内にしろ」と怒られました。

まとめ

GitHub Pages で JavaScript 以外のものが動くと楽しいですね。

今回は実験的に用意しただけなのでやっていませんが、実際使うときはインストールに時間がかかる部分をキャッシュすべきですね。

あなたはどの AltJS が好きですか?

更新履歴

  • 2021-11-30
    • GitHub Actions で GITHUB_TOKEN が使用可能になったので、トークンの用意に関する記述を取り除きました。
    • GitHub Actions でキャッシュが使用可能になったので、各アクション内でキャッシュを利用しました。
    • その他微修正