JAWS-UG 公式サイトリニューアルの技術的な話

JAWS-UG 仙台勉強会 [6/23夜] でお話しした「運用がつらすぎたJAWS-UG公式サイトをリニューアルした話」のフォローアップになります。
このセッションではWebサイト運営者やWebディレクター向けにサイトリニューアルに関するディスカッションやどう改善したのかという部分を中心に技術面よりも手法や考え方をお話ししました。
この記事では JAWS-UG 公式サイトリニューアルでWordPressを捨てて実装したJekyll+GitHub+Travis CI+Netlifyなどの自動化に関する技術部分を紹介します。

(やばい、ブログ書く詐欺だ!何本ネタたまってるんだろ。)

当日のスライド

特にスライドの内容について補足事項はないです。見てもらうとわかるのですが、JAWS-UGのようにたくさんの支部があって多人数が関わる場合、その全員がブログツールに精通してるわけではない=WordPress辛いになってました。
かといって少人数に負担がかかるのも良しとせず、どうやったらみんなが「楽」に運用できるのかという部分に焦点をおいて技術の選定は行われました。

サイトデザイン(CSSフレームワーク)

JAWS-UG

新しいデザインはこんな感じで。アメリカ政府関連サイトで使われてるという「U.S. Web Design Standards」がベースです。
変えたところといえばSassベースで

  • 基本カラーをJAWS-UGの色(ゴールドとか)に変更
  • 基本書体を Noto Sans Japanese に変更

ぐらいであとはコンテンツに合わせて埋め込んでいきました。

静的サイトジェネレーターと承認ワークフローと自動デプロイ

1. 静的サイトジェネレーター Jekyll

Jekyll

Rubyで動きます。MarkDownのドキュメント+テンプレートになるので、文章とデザインを完全に分離できるというのもJAWS-UGのように多様な人が運用に関わる時に良いのかもしれません。
Jekyllは単体でローカルサーバーを起動しサイトの確認もできるのですが、いろいろ機能が含まれていてSassのコンパイルもJekyllで賄ってくれるのは余計なnpmやタスクランナーが不要というのも良いです。

また、SEO関連もプラグインで実装されてるのでゼロから構築する手間も省けます。

2. ホスティング Netlify+GitHubによる自動デプロイ

Netlify

通常、Jekyllで作成されたサイトはそのディレクトリ内にある _site の中身だけをサーバーにアップロードする必要があります。
Netlify はサーバー側でJekyllをビルドする機能が備わっているためサイトの元となるファイル群をアップロードするだけで自動でよしなにやってくれます。
その他にも

  • Gitリポジトリとの連動が可能(指定したブランチにコミットがあると自動でデプロイ)
  • HTTPS、HTTP/2をサポート。しかも独自ドメインOK
  • でこれら諸々の機能が無料

という面が AWS S3 や GitHub Pages ではなく Netlify を選定した理由です。

また、リニューアルで大量のコンテンツが不要となったわけですがそのあたりのリダイレクトの設定もテキストファイルで書くだけで済みました。

3. GitHub+Travis CIを利用した承認ワークフロー

Jekyll と Netlify の間に入るのが GitHub のリポジトリで、前述したように Netlify と GitHub のリポジトリを紐づけておけばコミットのあったタイミングで自動デプロイされます。
Travis CIも含めたワークフローは以下のようになります。

  1. プルリクエスト作成 or 作業ブランチを GitHub に push
  2. 1に対してTravis CIでテストが走る
  3. Travis CI のテストOKかつ内容に問題がない場合のみ master ブランチにマージする ※ここは目視での確認が必要なため承認作業となる
  4. master ブランチに対して Travis CI でテストが走る
  5. master ブランチの Travis CI 上でテストOKの場合のみ release ブランチに master ブランチの内容がマージされ GitHub に push される。
  6. release ブランチは master ブランチと同一のため(5の状態) Travis CIのテストは除外する
  7. release ブランチにコミットがあった場合(5の状態)、Netlify でビルドとパブリッシュが実行され、サイトに反映される

自動デプロイを維持するため、GitHub で master ブランチと release ブランチを保護しておきます。

  • masterブランチの保護
    • 他ブランチやプルリクエストは master ブランチへの merge 前に travis-ci のテストを通しOKである必要があります。
    • このブランチにpushできるのはリポジトリのオーナー権限のあるユーザーのみ
  • releaseブランチの保護
    • このブランチにpushできるのはリポジトリのオーナー権限のあるユーザーのみ

また、プルリクエストの内容を Netlify 経由でプレビューすることもできます。

GitHub
Detailsをクリックすると発行された一時URLで確認できる

この辺りは Qiita に詳細を記載してますので参照にしてください

それ以外のコンテンツの自動化と属人性の排除

JAWS-UG で各支部ごとに情報が上がってくるコンテンツとして

  • 支部一覧
  • イベントカレンダー

があります。
これらも公式サイトのHTMLなどを触らずに更新できるよう、工夫しました。

支部一覧 Googleスプレッドシートの活用

https://jaws-ug.jp/act/ に掲載されている支部一覧はGoogleスプレッドシートにマスターがあって、その内容(必要な情報のみ)をJSONで配信し、サイト上ではAjaxで読み込んでいます。

まず、Googleスプレッドシートの master になっているシートから、必要な情報のみ別シートに抽出します。
これはmasetrになっているシートには各支部連絡先などコミュニティの運営上必要な情報も記載されていますが、サイト掲載には不要な情報もあるからです。
シートへのコピーはスプレッドシートの関数を使っており、コピーされたシート自体は保護をかけて編集できないようにしてあります。

関数自体はこんな感じで「masterシートのA列(支部名)が記入され、C列(エリア別か目的別か)が指定のものでB列(活動中、休眠中など)が活動中の行の、1, 5, 6, 7, 8 列を抽出」という意味になります。

=query(FILTER(master!A:I,master!C:C="エリア別",master!B:B="活動中"), "select Col1, Col5, Col6, Col7, Col8")

コピーされたシートの内容をJSONに変換するのはGoogle Apps Scriptを利用しています。
GoogleスプレッドシートのURLの ~/d/{ID}/edit{ID} が必要となります。

function getData(sheetName) {
  var sheet = SpreadsheetApp.openById('{ID}').getSheetByName(sheetName);
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {}
    row.map(function(item, index) {
      obj[keys[index]] = item;
    });
    return obj;
  });
}
function doGet(e) {
  var data = getData(e.parameter.sheetName);
  return ContentService.createTextOutput(JSON.stringify(data, null, 2))
  .setMimeType(ContentService.MimeType.JSON);
}

sheetName が変数扱いになってるのは JAWS-UG の場合、エリア別・目的別でシートが分かれているからです。
作成したGoogle Apps Scriptは「公開」で「ウェブ アプリケーションとして導入」としてWebサイト(外部)からのアクセスを許可しておきます。

GAS

これをサイト側から Ajax で「ウェブ アプリケーションの URL」から取得しよしなに表示します。

$.ajax({
    url : '{ウェブ アプリケーションの URL}?sheetName=area',
})

イベントカレンダーの表示 Googleカレンダー

以前からイベントカレンダー自体はGoogleカレンダーをそのまま埋め込んでました。
今回のリニューアルではサイトトップでも直近のイベントを紹介したかったので、「FullCalendar」 というJavaScriptのライブラリを使用しました。

JAWS-UG カレンダー

Googleカレンダーと連動させるためには Google Calendar API KeyとカレンダーのIDが必要なので、ドキュメント参考に設定します。

日本語対応もv3以降は言語が設定されたjsファイルにリンクを貼るだけでOKです。

イベントカレンダーの登録とSNS配信 Googleフォーム+Googleカレンダー+IFTTT

以前はイベント情報を登録したい場合、各支部から上がってきた情報を少人数で手作業でGoogleカレンダーに登録してました。
当然ボランティアベースなので、手間や時間がかかりました。
今回のリニューアルで

  1. Google フォームからイベント情報を登録(情報自体はスプレッドシートにも保存)
  2. その内容をGoogle カレンダーに反映
  3. Google カレンダーにイベントが登録されたらIFTTT経由で、Twitter、Facebookページ、Slackへ投稿

まで自動化しました。

Google フォーム(カレンダーに必要な情報+登録者名など)から内容が送信されると、Googleカレンダーに自動で登録されるようここでも Google Apps Scriptを使います。
登録されるカレンダーのIDも必要です。
この辺りは検索すればソースは転がっていると思いますので、フォームから送信された内容がカレンダーのどこに入るかなどはよしなにしてください。

function myFunction(e){
  //初期設定
  var calendar      = CalendarApp.getCalendarById('{カレンダーID}');
  var itemResponses = e.response.getItemResponses();
  var cCity   = '';
  var cTitle  = '';
  var message = '';
  var place   = '';

  //入力項目の解析
  for (var i = 0; i < itemResponses.length; i++) {
    var itemResponse = itemResponses[i];
    var question     = itemResponse.getItem().getTitle();
    var answer       = itemResponse.getResponse();
    
    if( question == "開催地域名" && answer.length > 0 ){
      cCity = "[" + answer + "] ";
    } else if( question=="勉強会名" ){
      cTitle = cCity + answer;
    } else if( question=="日付" ){
      var cDate = answer.replace(/-/g,'/');
      var cEDate = cDate;
    }else if(question=="開始時刻"){
      var cDate = cDate + " " + answer;
    }else if(question=="終了時刻"){
      var cEDate = cEDate + " " + answer;
    }else if(question=="場所"){
      place = answer;
    }else if(question=="イベントURL"){
      message = answer;
    }
  }

  var objEvent = calendar.createEvent(cTitle,new Date(cDate),new Date(cEDate),{description:message, location:place}).setGuestsCanSeeGuests(false);
}
GAS トリガー

最後に「編集」>「現在のプロジェクトのトリガー」 でフォーム送信時に実行されるようにしておきます。

Googleカレンダーに登録があった場合にTwitterやFacebookページ、Slackに情報を投稿するのはIFTTTを利用しました。

IFTTT

IFTTT での設定方法なども検索すれば山のように出てくるのでここでは割愛します。

過去のイベントサイトのCSV読み込み

(2017.11.8 追記)
https://jaws-ug.jp/previous-events/

JAWS-UG 過去のイベント

過去のイベントのサイトへのリンクですが、同じフォーマットでの繰り返しになるので Jekyll のデータファイルの読み込み機能を使いました。
サイト名、URL、画像、日付が記載されたCSVを用意してループで回してます。
これによりHTMLタグはコピペですらいらない=触らずに画像のアップロードとCSVの更新だけでリンクのリストを更新できます。


ざっとリニューアルしたJAWS-UG 公式サイトの技術面での紹介でした。
詳しく紹介しなかったサイト自体のソースはGitHubのリポジトリで公開されてますので、合わせて参考にどうぞ。

現場からは以上です。