AWS で静的ウェブサイトをホスティングするための構成(S3 + CloudFront)

ここ最近、SPA(シングルページアプリケーション)や SSG(静的サイトジェネレーター)を利用してウェブサイトやアプリケーションを構築するケースが増えており、静的サイトを配信する仕組みについても興味を持つようになりました。便利なサービスとしては Netlify や Firebase Hosting, GitHub Pages など色々ありますが、ウェブサービスやシステム開発を行う上で、AWS を使っているならば AWS で完結したいということもあるかと思います。

この場合、Web サーバーを立てて実現することもできますが、AWS が提供するサービスをうまく活用することで、より簡単に実現でき、費用を抑え、運用の手間を減らすことができます。定番のやり方としては、例えば以下の AWS サービスが挙げられます。

  • AWS Amplify コンソール
  • S3 の静的ウェブサイトホスティング(Static website hosting)

この記事では、一見クラシックなやり方ではありますが、CloudFront を利用することで高速かつ柔軟性を持つ配信方法の一例を紹介します。AWS WAF や Lambda@Edge と組み合わせることで、単体のウェブアプリケーションだけでなく、社内利用を含め色んなシーンに対応できることが利点になります。

S3(ストレージ) や CloudFront(CDNサービス) といった AWS のサービスについての基本的な前提知識が必要となります。

この記事で紹介する構成

ポイントとしては、

  • Origin Access Identity を使用することで、外部から直接 S3 へアクセスすることはできない
  • CloudFront の各種機能をフル活用できる
  • アクセス制御や流量制限など、WAF や Lambda@Edge を利用して柔軟に対応できる
  • Origin を変更する場合でも、クライアントに提供されているエンドポイントを変更することなく対応できる
    ※ Origin の S3 をクロスアカウントにすることも可能

といった点です。

構築方法

上記の構成で静的サイトをホスティングするためには、大きく以下の2ステップに分けられます。

  • Origin となる S3 バケットを作成する
  • CloudFront ディストリビューションを作成する

Origin となる S3 バケットを作成する

どのリージョンでも問題ないので、普段良く使っているリージョンを指定してバケットを作成します。
バケット名をサイトのFQDNにする必要はありません。

設定はデフォルトで問題ないかと思います。
アクセス許可については、パブリックアクセスができないことを確認します。

CloudFront ディストリビューションを作成する

Web ディストリビューションを作成します。

Origin Settings


  • Origin Domain Name をクリックして、先程作成したS3バケットを選択
  • Restrict Bucket Access: Yes を選択
  • Origin Access Identity: Create a New Identity を選択
  • Grant Read Permissions on Bucket: Yes, Update Bucket Policy を選択

これで Origin Access Identity が作成され、この Identity からのアクセスのみ許可するように S3 のバケットポリシーが更新されます。構築後、CloudFront コンソール左側メニューの Security > Origin access identity を開くと名前と ID が確認できます。

Default Cache Behavior Settings


  • Viewer protocol policy: Redirect HTTP to HTTPS にするとリダイレクトしてくれます。
  • Object Caching については、とりあえず 全て 0 を設定します。
    • Minimum TTL: 0
    • Maximum TTL: 0
    • Default TTL: 0
  • Compress Objects Automatically: Yes を選択することで、リクエストヘッダーに Accept-Encoding: gzip が含まれている場合、圧縮してくれます。

Distribution Settings


  • AWS WAF Web ACL: WAFのACLをここで適用させることができます。
  • Default Root Object を設定しないと、ルートのURLパスで index.html を見てくれません。

ホスティングするサイトのトップページを表示するためには、Default Root Object に index.html を指定する必要があります。
ここは色々ハマりポイントがあるので、後日、別途紹介したいと思います。

ディストリビューションの作成

その他、独自ドメインや証明書、ログの設定などは一旦置いといて、
とりあえず Create Distribution ボタンを押します。そうすると、下記のようにディストリビューションが作成されます。
初期作成は 20 分 - 40 分ほどかかります。コーヒータイムですね。

サイトをリリース(更新)する

キャッシュを設定していなければ、Origin の S3 バケットのファイルを更新するだけですぐに反映されます。
例えば、AWS CLI を利用して CI やデプロイスクリプトに下記のようなコマンドを利用することで簡単にリリースできます。

1
aws s3 sync {リリース対象のディレクトリ} s3://{バケット名} --region {リージョン名}

特に問題なければ、下記のように表示されます。

また、S3 に入っていないパスやファイルを指定すると、下記のようなエラーページ(403) が表示されます。
カスタムエラーページを指定することで、この画面を見せないようにすることができます。

まとめ


  • 一見クラシックなやり方ですが、S3 + CloudFront の組み合わせは高速かつ柔軟性が高くおすすめです。
  • CI連携も兼ね揃えた AWS Amplify コンソールを使用するのも良い選択です。
  • AWSに限定しなくていい・もっと手間を省きたい場合、Netlify か Firebase か GitHub Pages 等、選択肢は多くあります。

いかがだったでしょうか。

引き続き、このパターンを用いた

といった方法や、AWS Amplify についても紹介していきたいと思います。

「ドリルの王様 1,2年のたのしいプログラミング」、自然とプログラミング的思考が身につく問題集

「鉛筆で学ぶ小学生用プログラミング教材」という、おもしろい見出しで紹介されているドリルがあります。「ドリルの王様 たのしいプログラミング」シリーズです。プログラミングを学ぶのにパソコンなしで鉛筆とはどういうことなのか、どうやって学習できるのか、本書を読んだ中から良かった点を紹介します。ドリルのターゲットである小学生はもとより、年齢に関係なくプログラミングへ興味ある方は一度手に取ってみるとよいでしょう。

紹介する書籍はこちら、「ドリルの王様 1,2年のたのしいプログラミング」

ドリルの王様は、新興出版社が出している小学生向けの教材シリーズです。
漢字や計算をはじめ、図形、時計、理科、社会、英語などと幅広く出版されている中、新学習指導要領対応でプログラミングが追加されました。2020年からはプログラミング教育が小学校でも必修化されるため同社ではいち早く対応し出版したとのことです。

こちらのドリルがネット記事で取り上げられはじめたときから気になっていました。思い返せばプログラミング自体ちゃんと習った覚えがなく(そもそもなかったと思いますが)。見よう見まね、いわゆる写経で覚えてきたのでプログラミングを教えるってどういったことなのか、さらにパソコンを使うことなく教えるとは!? 何気なく眺めたらおもしろく、せっかくなのでしっかり読みブログで取り上げることにしました。

主な内容(目次より抜粋)

  • じゅんじょ
  • くりかえし
  • ぶんき
  • イベントしょり
  • コンピュータの かんがえかた
  • へんすう
  • こたえ と おうちの方へ

コンピューターの動作は決められた手順と繰り返しが基本

まずは基本動作の手順(= じゅんじょ)と、反復(= くりかえし)について、積み木やシールの重ね張りとイメージしやすい問題から入ります。手順通りに作業をするというのを身の回りにあるものや、イメージしやすい形で表現しているのがわかりやすいです。これは本の中でわからなかったら、実際に積み木なりでやってみればよいので入門に良いのではないでしょうか。

また反復では交互の色塗りをしたり、いくつかの絵を1セットとしてセットを繰り返すとどのような順番になるかなど、こちらもイメージしやすくなっています。そのセットも、セットとして理解した後に1つ1つへ分解して繰り返しの一連の処理となるように発展するので、いつの間にかプログラミング的な処理の作り方を勉強していることになります。

繰り返しの概念を分かったうえで、その繰り返される部分へ最初に学んだ「じゅんじょ」を持ってくることで着実な理解が図られるようになっており、勉強の道筋ができていることに感心させられます。

最終的にはロボットを「1マス進む」「向きを変える」という手順と、繰り返しでどのように動いたかという応用問題になります。これまでの単純な手順が繰り返されるではなく「ロボットの向き」が入ってくるので手順がしっかり頭に入っていないとハマるのではないでしょうか。

条件分岐は複雑になりがち、ドリルで頭の整理にも

プログラミングしていると出てくる複雑な条件式。致し方ないケースもありますが、多くの場合は整理ができていないだけです。

簡単な分かれ道の右左分岐でイメージを増幅させてから「〇=〇」「△=△」「◇=◇」の形が同じ条件、またったく異なる形同士での条件マッチと、徐々に複雑化します。さらには算数を混ぜた計算のある条件式と、気づけば実際のプログラミングでも使うような形へ自然とつながっていきます。ここでもプログラミング的な思考力がいつの間にか勉強できています。

なによりも感心したのは状態が組み重なっていく問題。以下、問題文より抜粋 (実際には絵も入っててわかりやすいです)

あおいさんは 上がった はたを 見て、きまった ポーズを とります。

☆ の はたなら、かた手を 上げる。
□ の はたなら、かた足を 上げる。
☆ □ と ちがう はたなら、えがおになる

□、☆、△ の じゅんで はたが 上がりました。
あおいさんが さいごに とった ポーズは どれですか。

[#21 ぶんき ⑦ - P.43 より抜粋]

選択肢としてポーズの絵がありますが本ブログでは省略します。どのようなポーズになったかわかりますでしょうか。この問題は前回の状態を意識して次の条件の結果を加えていく状態変化でもあります。

このように「条件から導き出されること」「状態がどのように変わるのか」、ちょっと考えると簡単な問題ですが、実際のプログラミングとなると、いつの間にか複雑な条件に加えて難しい状態管理となってしまいます。本ドリルで今一度整理するのもよいでしょう。また問題をプログラムで表現するとどうなるのかを思い浮かべてみるのも楽しみ方のひとつと言えるでしょう。

プログラミングやコンピューターに詳しくなくても安心の解説

答えの章には「おうちの方へ」という解説もしっかり入っており、プログラミングやコンピューターに詳しくなくても解説から理解できるように作られています。

たとえば「変数」についての説明なども、下記抜粋のようにシンプルな説明と出題の意図が解説されています。

変数はプログラムで使用する値を一時的に保存することができます。必要な時に値を参照したり、値によって処理を変更したりするときなどに使われます。1つの変数に保存できるのは1つの値だけです。

今回の問題では、ロボットが最後に伝えられた言葉を覚えることを通して、「変数の値を1つしか覚えることができない」ことや、「値は上書きされることがある」などの変数のしくみについて考えることができます。

① ロボットは最初に「キャラメル」を覚え、次に「わたあめ」を、その次に「クレープ」を覚えます。ロボットは1つしか覚えることができないので、「キャラメル」と「わたあめ」は忘れてしまいます。

[こたえ #34 へんすう ① - P.94 より抜粋]

プログラミングに興味ある方にも手に取ってもらいたいと思ったのは、こちらの解説がシンプルで分かりやすいからです。もちろん小学生向けドリルの解説なので、この部分だけでプログラミングできるようになるわけではないですが、上記「変数」のように機能と使われ方がわかりやすく説明されており、ふりかえって問題を見てみるとさらに理解が深まるのではないでしょうか。

紙のドリルであることの良さ

最近ではScratch - Imagine, Program, Shareをはじめ、学習用のプログラミング環境やサービスがあります。それらに比べて「紙」であることは、どのようなメリットがあるでしょうか。

1つには、試行錯誤の過程を書き表せることではないでしょうか。
たとえば商品情報 - ドリルの王様楽しいプログラミングにて、以下の問題が公開されています。

ロボットの動きを考える問題ですが、鉛筆で書いて動きを考えることもできますし、説明する際にも必要な情報を書いてあげることもできます。もちろんパソコンなどを使う場合もディスプレイに加えてノートを使うことで同じようなこともできますが、やはり問題そのものに直接書けるのは紙のドリルならではでしょう。

もう1つには、Try & Error も同様に書いて試せることでしょう。
Scratch などでは実際に動かせるので、手軽に何度も Try & Error できますし実際に動くのでわかりやすいというメリットがありますが、パッと動いておしまいなので過程が飛ばされがちとも言えます。基礎段階としては、過程が残る形で繰り返し試すことは理解の助けになるといえるでしょう。

一方でデジタル・ネイティブ(インターネット・ネイティブ?)としてはどのように感じるのかは聞いてみたいです。生まれた時から YouTube などがあり、あらゆるものがデジタルでネットにつながっている。その様な環境で育ってくると、同じような問題をタブレットなどでやる方が分かりやすいのでしょうか。ぜひ聞いてみたいところです。

まとめ

1~2年生向けとのことでプログラミングの初歩かと思うと、意外と深くしっかりした考え方が身につく本だと思います。

とくに解説である「おうちの方へ」は、シンプルでわかりやすい表現で用語や使い方が解説されており必読です。プログラミングに興味ある方には、ぜひ解説を読んでいただきたいと思いますし、またプログラムを作る方にもこのぐらいシンプルに説明できるかを確認するという観点で見ていただくとよいのではないでしょうか。

今回は1~2年生向けのドリルを紹介しましたが、3~4年生向け、5~6年生向けと3冊でており、アルゴリズムやデータ活用などより発展的内容になっています。すべて解けるか、そして解説まで踏み込んで読み込むとおもしろいでしょう。

参考情報

紹介した書籍「ドリルの王様 1,2年のたのしいプログラミング」


デブサミ 2019 で開催されていた「こどもプログラミング本大賞」に足を運んだことがきっかけで、子供向けのプログラミング本というのに興味を持ちました。子供向けということですが、子供向けであるからこそ説明がシンプルで逆に本質がわかりやすいのだなと感心しながら手に取り、楽しませてもらいました。

今回取り上げたドリルはイベント後の発売なので会場にはありませんでしたが、書店で見かけた際にプログラムを書く身として興味を持ち手に取ったのがきっかけであり、またブログのメンターについていただいているカック@ブロガー / k9u(@kakakakakku)さんが書評を書いていたのが決め手となり本ブログで初書評として取り上げてみました。

本ブログ執筆時点では、またカックさんの書評を読んでおらず同じ本で挑戦してみて、どのような違いがあるかブログの勉強とともに、カックさんの記事を楽しみたいと思います。

カックさんの書評はこちら。

共有:

OS や IDE 固有ファイルの .gitignore はどこにする?

Git のソースコード管理の除外指定する .gitignore。ときどき気になるのが Thumbs.db や .DS_Store などの指定がプロダクトの .gitignore ファイルに含まれているケース。開発者固有の設定、どこにしたらよいでしょう?

開発者固有のファイルはプロダクトのソースコード管理からは除外したいです。とはいえ OS が自動で作ったThumbs.db.DS_Storeなどは、いつの間にかソースコード管理に紛れ込んでしまうことも。それを避けるために .gitignore があるのですが、Thumbs.dbなどの除外指定がプロダクトの.gitignoreファイルに書かれているといつもモヤっとします。

プロダクトとしてThumbs.db.DS_Storeが生成物であり、かつ管理不要だとしたらわかります。しかし、多くのケースはプロダクトの生成物ではありません。開発者固有といえるでしょう。このようなファイルの除外指定はどうするのが良いのか調べたのでまとめます。

gitignore.io での扱い

以前、本ブログで紹介した「.gitignore は、生成サービス gitignore.io を使って作ろう!」のgitignore.ioは、さまざまな環境の .gitignore に対応しています。

たとえばWindows の定義では[Dd]esktop.iniなどが設定されています。もちろんmacOS 版もありますし、Linux 版もあります。また Java のビルドツールMavenや、IDE のVisual Studioなどといったツールに関するものもあります。

ただし、これらをどのように使うのかにつて言及したドキュメントはありませんでした。

GitHub の gitignore リポジトリ

gitignore.io 発祥の元となる GitHub のgithub/gitignoreリポジトリを見てみます。このリポジトリは GitHub で新しいリポジトリを作成する際の [Add .gitignore] で使われるテンプレートです。

このリポジトリで気になるのがGlobalディレクトリです。OS や IDE/エディター の設定が、このGlobalディレクトリにあります。

そして、以下のドキュメントへのリンクがあります。色々説明がありますが OS 固有のファイルなどは global .gitignore を作り、そちらに設定するようにとのことです。

つまり、このGlobalディレクトリにあるような定義はプロダクトの.gitignoreではなく、グローバル側(ユーザーのホームディレクトリ以下) に設定するとしています。

Git のドキュメント

GitHub で答えが出たように思いますが、最後に Git 公式では、どのように考えられているのか確認します。
.gitignore に関するドキュメント「Git - gitignore Documentation」があります。

まず.gitignore自体について以下のように書かれています。
“Patterns which should be version-controlled and distributed to other repositories via clone (i.e., files that all developers will want to ignore) should go into a .gitignore file. -gitignore Documentation

そして、もうひとつ。
“Patterns which a user wants Git to ignore in all situations (e.g., backup or temporary files generated by the user’s editor of choice) generally go into a file specified by core.excludesFile in the user’s ~/.gitconfig. -gitignore Documentation

ざっくり意訳すると「すべての開発者が無視したいファイルをバージョン管理して、クローン先の他リポジトリへ配布するパターンとして.gitignoreファイルを使うべき」で「ユーザーのエディターが生成したバックアップや一時ファイルは、~/.gitconfigcore.excludesFileで指定したファイルに入れる」といった感じでしょうか。

全開発者で共有する内容に.gitignoreを使うべき(should be)で、ユーザー固有の設定はcore.excludesFileで指定したファイルにするのが一般的(generally)であると。

ここでも OS 固有の設定は.gitignoreではなく、グローバル側(ユーザーのホームディレクトリ以下) に作るとしています。

まとめ

Git 公式ドキュメントや、GitHub の説明から考えるとThumbs.db.DS_Storeなどの OS 固有の設定は、~/.gitconfigcore.excludesFile指定ファイルがよいといえるでしょう。

ただし使っている単語がshould be / generallyであり、mustではないので絶対ダメまではいかずとも、固有設定を.gitignoreに入れるのだとしたら、よく考えてからやりましょう、といったところでしょうか。(RFC ではないし、全大文字でもないですがRFC 2119 - IPAに倣って)

core.excludesFileのデフォルトは、以下の順で見つけたファイルを使います。そのためcore.excludesFileは明示的に設定せず、下記2番目の~/.config/git/ignoreファイルを作って使うのが簡単です。

  • $XDG_CONFIG_HOME/git/ignore
  • $HOME/.config/git/ignore(=~/.config/git/ignore)

これで、モヤっとしていたのがスッキリしました。
OS 固有の設定は~/.config/git/ignoreにしていきたいと思います。

では IDE/エディターの設定は?
“すべての開発者が無視したいファイル” か?、と考えると、やはり固有の設定といえるので~/.config/git/ignoreですね。OSS ではなく、クローズドなプロジェクトの場合は.gitignoreでよいかもしれませんが、いつ「すべての開発者」が変わるかもしれないと考えると~/.config/git/ignoreがよいと思います。

サーバレスな天気レポートの Slack ボット、「空鏡」

「空模様を図表に写して、天気をお伝えする「空鏡」」と題して、Slack で簡単に現在の天気情報を得られる Slack Slash Commands を作りました。そのサーバーレスなアーキテクチャを紹介します。

梅雨入りシーズン、天気予報を確認したり、お守り傘を持ったりしているかと思います。天気予報を確認するのは、いまの梅雨シーズンに限らず、次のゲリラ豪雨シーズンにも必要でしょう。普段はアプリやウェブサイトで確認しますが、Slack を使っている場合は Slack 上で確認できるとさら便利なのではないでしょうか。それを実現するのが「空鏡」です。

「空鏡(Sora-Kagami)」は Slack の Slash Commands として動作する天気レポートのサービスです。Slack で/sora 東京のような簡単な入力で天気情報が得られます。下図のような「現在の天気」「今後1時間の降水強度グラフ」「降水レーダー地図」で天気情報をお伝えします。(デモ環境については後述)

アーキテクチャ

「空鏡」は AWS 上に AWS Lambda と Amazon S3 を活用したサーバーレスなサービスとして構築されています。

以下、各パートについての概要です。別途詳細に解説する記事を書きたいと思います。今回はアーキテクチャとしての概要説明にとどめます。(いっぺんに書ききれない)

Slack Slash Commands のリクエスト受付

Slack Slash Commands は 3秒以内に応答する必要があります。最近でこそ Lambda の初期起動(コールドスタート)は速くなってきましたが、それでも外部 API 連携をしているサービスではかなり厳しい条件です。今回は遅延応答、Sending delayed responses - Slash Commandsを使ってレスポンスしています。これは Slack からのリクエストにいったん200 OKをレスポンスし、リクエストに含まれていたresponse_urlへ JSON を HTTP POST して返すというものです。この形式なら仕様上 30分まではレスポンスを遅らせることができます。(30分後にレスが突然来てもビックリかもしれませんが)

Serverless Framework による「Amazon API Gateway」と「非同期 AWS Lambda」の構成

Slack Slash Commands 遅延レスポンスを使うには、最初に200 OKを返すため、HTTP レスポンスとは別に非同期で本処理を行う必要があります。それを Lambda で行うには Amazon SNS や Amazon SQS へ処理を積むなどの工夫が必要だったのですが、Amazon API Gateway による非同期 Lambda の呼出しがサポートされました。X-Amz-Invocation-TypeEventの Lambda です。今回はこちらを使い200 OKの即時レスポンスと、非同期の本体処理を実現しています。

ただしX-Amz-Invocation-TypeEventの Lambda は、ちゃんと設定すると大変みたいです(途中で挫折した)。ここはServerlessを使うと簡単です。以下は Serverless での空鏡の定義抜粋ですがasync: trueと属性を設定するだけですべてを構成してくれます!

1
2
3
4
5
6
7
8
9
module.exports = {
functions: {
Command: {
name: '${ self:custom.names.lambda.command }',
handler: 'src/aws-lambda-handler/sora-kagami-command.handler',
events: [{ http: { path: 'sora-kagami', method: 'post', cors: true, async: true }}]
}
}
}

非同期 AWS Lambda での処理

非同期な Lambda で構成されているとはいえ、基本的な Lambda の実装に変わりはありません。
以下は Lambda のメイン処理部分の TypeScript 実装です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export const handler: Handler<APIGatewayProxyEvent, void> = async (event: APIGatewayProxyEvent): Promise<void> => {
console.debug('Starting Lambda handler: event=%s', JSON.stringify(event));

// tslint:disable-next-line: no-any - 'cus to parse non-JSON, JavaScript object literal like strings by Slack
const command = event.body as any as SlashCommand;
if (Env.SLACK_TOKENS.length !== 0 && !Env.SLACK_TOKENS.includes(command.token)) {
return console.error('Forbidden: team=%s, command=%s', command.team_domain, JSON.stringify(command, undefined, 2));
}

try {
const geo = await getGeometry(command.text);
if (!geo) { return apis.slack.response(command, { text: `場所の検索に失敗しました。\`${command.command} [郵便番号 または 地名]\` を入力してください。` }); }

const place = await getPlace(geo);
const weathers = await getWeathers(geo);

const filenames = Config.FILENAMES(geo, weathers.current);
await Promise.all([
apis.map.get(Config.REQUEST_MAP(geo)).then(async (value: Buffer) => {
await apis.aws.s3PutObject(Env.S3_IMAGES_BUCKET, filenames.map, value, Config.CONTENT_TYPE_MAP);
}),
Config.CHART_CANVAS().renderToBuffer(createChartOps(weathers.data), Config.CONTENT_TYPE_CHART).then(async (value: Buffer) => {
await apis.aws.s3PutObject(Env.S3_IMAGES_BUCKET, filenames.chart, value, Config.CONTENT_TYPE_CHART);
})]
);

const message = createMessage(place, weathers, filenames, geo);
await apis.slack.response(command, message);
} catch (err) { await handleError(err as object, command); }
};

一番大きな違いは、1行目のメソッドの戻り値void(またはレスポンスを返すcontext/callbackを使わない点)です。API Gateway が200 OKをレスポンスしているため、Lambda からはレスポンスは返せません。そのためレスポンスにかかわる処理はありません。

変わってawait apis.slack.response(command, message);で Slack Slash Commands の遅延レスポンスresponse_urlへ HTTP POST して終了しています。また12行目のエラーレスポンスも同様に Slack へ返しています。

天気データは、Yahoo!デベロッパーネットワーク YOLP(地図)

天気や地点の検索は Yahoo! のYOLP(地図) APIを使っています。地図に関する API が豊富に用意されていて、利用方法も簡単です。
API の利用制限は 1日 50,000回までです。今回1リクエストで4回 API を使うので、デモ環境がもしかしたら辛そうです(このブログの PV から考えると十分耐えられると思いますが)。なお商用利用する場合は「お問い合わせください」とのことです。

今回は以下の API を使っています。

空鏡での機能YOLP API 名
郵便番号が入力された場合の地点検索郵便番号検索API
郵便番号でない場合の地点検索場所情報API
地点からのエリア名、周辺情報取得コンテンツジオコーダAPI
天気情報気象情報API
降水レーダー地図の生成スタティックマップAPI

グラフの描画は Chart.js

天気レポートの中段にある、今後1時間の降水強度予報グラフはChart.jsを使っています。

Chart.js は簡単なコードで綺麗なグラフを描画してくれます。以下はグラフ作成部分のコードです。(必要箇所のみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const createChartOps = (weathers: Weather[]): ChartConfiguration => {
const data = weathers.map((value: Weather) => value.Rainfall);
const borderColor = createChartColor(weathers, Config.ALPHA_BORDER);
const backgroundColor = createChartColor(weathers, Config.ALPHA_BACKGROUND);
return {
type: 'bar',
data: {
labels: weathers.map((value: Weather) => dayjs(value.Date).format('H:mm')),
datasets: [{ data, borderColor, backgroundColor }]
},
options: {
legend: { display: false },
scales: { yAxes: [{ ticks: { beginAtZero: true }}]}
}
};
};
const createChartColor = (weathers: Weather[], opacity: number): string[] =>
weathers.map((value: Weather) => hexToRgba(WeatherForecastApi.getLevelColor(value.Rainfall), opacity));

dataに表示するグラフの配列を渡します。YOLP のレスポンスからRainfallの値を取り出して配列化しただけです。borderColorbackgroundColorは、雨雲レーダー - Yahoo!天気・災害の降水量の凡例からグラフの色を付けるための処理で、これも配列です。
基本的に配列を渡して、その配列群のインデックスが合うところを使う形です。

今回は1つしか描画しませんがdatasetsに複数の配列セットを渡すことで複数のグラフを描画できます。

Chart.js のグラフをサーバーサイドで画像化して Amazon S3 へ

Slack は、画像 URL を渡すと自動的に展開してくれます。逆に言うと URL がないと画像が表示できないということです。

画像をウェブサイトとしてホスティングするには S3 を使うのが AWS を使う上での常套手段でしょう。しかしながら、Chart.js が描画する Canvas を画像化するところがハマりました。

今回はSeanSobey/ChartjsNodeCanvasを使い、Node.js 上で Canvas を扱えるようにしています。
実態はAutomattic/node-canvasなのですが、こちらと Lambda の実行ランタイム環境との組み合わせの問題か、 Node.js 10.x では動作しませんでした。いったん Node.js 8.10 で動かしています。組合せとかもいろいろあるので、今後しっかりと調べたいと思います。

CircleCI

CI/CD にCircleCIを使っています。
基本的に Serverless がすべてをやってくれserverless deployだけなので難しいビルドプロセスはありません。

1つだけポイントがあります。デプロイ・ステージとブランチをマッピングしています。

今回、実行環境として Develop, QAS, Production の3つを用意しています。Develop は言わずもがなですが、デモ環境が必要なので分離して QAS を使っています。Production は使う予定ないですが、ソース公開にあたって設定しています。

それに応じてステージのキーワードを3文字でdevqasprdに。対応するブランチをmasterstableproductionとしています。

各ブランチにプッシュされると自動的にビルドが走り、ステージに応じた環境へデプロイされます。
継続的に開発を行うには CI/CD は必須であるとともに、今回のように複数環境へ対応できるようにすると便利です。

Slack へ、画像をポストする際のテクニック

Slack で、画像や URL をポストするときのテクニックを2つ。

画像 URL はランダムにする
URL の最後に?ランダムな値を付けます。パラメーター名が必要な場合は適当なものを付けて構いませんが、とにかくランダムな値(= URL) を作ってポストします。

これは Slack が同じ URL の文字列の場合、折り畳みで表示するためです。今回は日時と地点を使ってファイル名を作っていますが、YOLP が 10分おきのデータを作るため、たとえば同じ時間ゾーンだと画像 URL が同じなります。そうすると画像が展開されません。それを防止するためにランダムな値を付けます。

メッセージフォーマットを使う
これは公式ドキュメントにもあるのでテクニックまではいかないですが、使うと便利です。
画像などの URL をポストすると、生の URL が投稿されて、続いて画像が表示されます。ちょっと見た目が良くないので<URL|表示文字列><|>形式でポストします。

これにより、生 URL が表示されるのを避けられます。もっと言ってしまうと<URL| >と半角スペースにしてしまうと何も表示されません。(画像サイズの 129kB のような文字列はどうしても残ってしまいますが)

今後の展望

ゲリラ豪雨シーズンに向けて、着実にバージョンアップしていきたいと思います。

  • 指定地点で1時間以内の降水予報があったら自動通知
    現在は自分で Slash Commands をたたかないと表示されませんが、自動通知してくれるようにしたいです。
    ハードコードでよければ Amazon CloudWatch Events で cron 処理からの、Slack Incoming Webhooks で簡単ですが、地点は登録できるようにしたいですし、複数ユーザーに対応したい。そうなると結構複雑で今回は落としました。

  • Slack 以外のツールへ対応
    あまり需要がないかもしれませんが、何か他のツールにも対応してみたいです。
    ただアプリとウェブを使わないが、そのツールを使うというシーンが難しいです。ゆえに Slack として作りました。

  • ボット化
    今回「Slack ボット」と名乗っていますが、実態は Slash Commands です。ちゃんと会話の中から天気のキーワードを拾って返せるようにしたいです。これは WebSocketクライアントを作る必要があり、サーバーレスにしにくいというハードルがあります。サーバーレスの形態は崩したくないので、チャレンジですね。

その他、組み込んだらおもしろい機能などがあったら、ぜひ Issues や Pull Request をいただけたら幸いです。(もちろん Twitter DM@lulznekoでも)

デモ環境 & My 空鏡のセットアップについて

「空鏡」はデモ環境があります。空鏡デモを利用するには、お使いの Slack チームへSlash Commandsを導入します。

[コマンドを選択する] を/soraで、以下の Slack Slash Commands を設定します。

項目設定値
[URL]https://4tvr294f4h.execute-api.ap-northeast-1.amazonaws.com/qas/sora-kagami
[アイコンをカスタマイズ]任意の画像/絵文字 (※ もご利用いただけます)
[名前をカスタマイズ]任意の文字列 ( e.g.空鏡)
[説明]任意の文字列 ( e.g.現在の空模様をレポートします)
[使い方のヒント]任意の文字列 ( e.g.[郵便番号 or 地名])

以降、設定した Slack で /[コマンド名] [郵便番号 or 地名] を入力すると、空鏡デモから天気レポートが返信されます。

また「空鏡」は MIT ラインセスの元 OSS として公開しています。
利用している AWS の環境へデプロイすることもできます。

詳しくはリポジトリの README を、ご参照ください。

参考情報

Slack 関連

Yahoo! JAPAN 関連


非同期 AWS Lambda は Serverless から設定すると属性1つなので、とても簡単です。HTTP Request からの非同期処理はあまり作らないかもしれませんが、Serverless のこの機能は覚えておくとよいでしょう。

「空鏡」によって少しでも雨をよけられたら幸いです。

.gitignore は、生成サービス gitignore.io を使って作ろう!

Git のソースコード管理から特定のファイルやディレクトリを除外するのに .gitignore を使います。その .gitignore ファイル、手作りしていませんか?
gitignore.io のサービスを使うことでベストプラクティスを盛り込んだ .gitignore を手軽に作ることができます。素晴らしいサービスを活用して快適な .gitignore ライフを送りましょう!

gitignore.io とは

gitignore.ioは、”Create Useful .gitignore Files For Your Project” をタイトルに掲げている .gitignore 生成サービスです。

Java や Python, Node.js といった各種プログラミング言語や実行環境から、フレームワーク、開発環境、OS とさまざまな環境に応じた .gitignore を生成できます。先人たちの知恵が終結した、まさにベストプラクティスな .gitignore を簡単に手に入れることができるサービスです。

また生成元のテンプレートもGitHub で公開されているので、公式に追加してもらうこともできます。

ブラウザを使って生成

まずは使い方を確認するためにブラウザでアクセスして .gitignore を生成してみます。

ブラウザでhttps://www.gitignore.io/へアクセスします。
画面中央に入力フォームがあるので、生成したい環境を入力します。

インクリメンタルサーチが効いているので入力しながら確認ができます。またJavaScriptNodeなど、別名や集約されているケースがあるので、見つからない場合は、いくつか試してみてください。(どうしても見つからない場合や全候補が見たい場合はhttps://www.gitignore.io/api/listへ)

入力できると候補が確定します。欲しい組み合わせがある場合は検索ワードを追加し、完成したら [Create] ボタンをクリックします。

.gitignore が生成されます。
よくよく眺めてみると手入力では追加し忘れがちなyarn-error.logなども入っていて助かります。また逆に使っていないものも入っていますが、私は気にせずマルっと入れて使っています。

コマンドラインから生成

ブラウザからコピペもいいですが、コマンドラインからサクッと作ってしまいたいところです。
先ほどのブラウザで生成した .gitignore の URL を確認するとhttps://www.gitignore.io/api/node,nuxtのようになっています。https://www.gitignore.io/api/に続いて検索ワードをカンマ切りで追加しています。この URL を使うことで好みの .gitignore をcurlなどのコマンドから取得できます。

1
2
3
4
5
6
7
8
9
10
11
12
$ curl https://www.gitignore.io/api/node,nuxt

# Created by https://www.gitignore.io/api/node,nuxt
# Edit at https://www.gitignore.io/?templates=node,nuxt

### Node ###
# Logs
logs
*.log
npm-debug.log*

...(省略)

直接.gitignoreファイルへ書きだしてしまえば完成です。

1
2
3
$ curl -s https://www.gitignore.io/api/node,nuxt > .gitignore
$ ls -l .gitignore
-rw-rw-rw- 1 lulzneko lulzneko 1573 6月 18 11:14 .gitignore

コマンドラインからはインクリメンタルサーチの補完が効かず、また正確なキーワードが必要となります。キーワードがわからない場合はhttps://www.gitignore.io/api/listから全リストを取得して確認します。

1
2
3
4
5
6
$ curl https://www.gitignore.io/api/list
1c,1c-bitrix,a-frame,actionscript,ada
adobe,advancedinstaller,agda,al,alteraquartusii
altium,android,androidstudio,angular,anjuta
ansible,apachecordova,apachehadoop,appbuilder,appceleratortitanium
... (省略)

コマンドラインからも使えることで、プロジェクトの初期設定時に手軽に実行して .gitignore が作れるので助かります。


使っている環境に合わせて .gitignore を作れるのが嬉しいですね。新しいプロジェクトを作る時にサクッと生成して、すぐ次の作業や検討に入れるのがとてもよいです。

またプロジェクト固有の除外指定は、生成後に自分で追加できるので運用もあまり変わりなく、最初にプロジェクトメンバーの知見を集めるか、先人の知恵を借りるのかの違いといえるでしょう。むしろ率先してプルリクを出し先人の知恵に参加していきたいところです。

Shell の作業ディレクトリごとに自動で環境変数を設定する

さまざまなプロジェクトのソースコードを扱っていると、プロジェクトに応じて環境変数を切り替えたいことがあります。もし現在のディレクトリに応じて環境変数が自動的に切り替わったら?
それを実現してくれる direnv を紹介します。

The Twelve-Factor App (日本語訳)などでも取り上げられているように、アプリやシステムの設定に環境変数を使うことが多いです。

その環境変数を毎回設定するにしても手間ですし忘れがちです。また.bashrcなどに全部設定しロードしておくこともできますが、プロジェクトが増えてくると煩雑になり困ります。

そのような時に使えるのがdirenvです。対応しているのが “bash, zsh, tcsh, fish shell and elvish -direnv“ とのことなので、Linux/Mac そして WSL(Windows Subsystem for Linux) が中心となりますが、環境変数を自動的に切り替えてくれる素晴らしい機能を提供してくれます。

環境
本記事の開発環境は以下となります。

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Bash 4.4.19 (WSL Ubuntu)
  • direnv/direnv 2.20.0

参考情報

direnv とは

Shell 用の環境スイッチャーで、現在のディレクトリに応じて環境変数をロードまたはアンロードしてくれます。

まさに “~/.profile ファイルを乱雑にすることなくプロジェクト固有の環境変数を使用できます -direnv“ とのこと。

もう、この一文で語りつくされているのではないでしょうか。(私の序文は長くて冗長だった。。。)

direnv の導入

WSL Ubuntu にはデフォルトで入っていないのでインストールします。
※ 各環境に合わせたインストールおよび設定はInstall - direnv/direnvをご確認ください

1
$ sudo apt install direnv

~/.bashrcに以下の設定をします。
※ ただし WSL Ubuntu の場合は~/.bashrcがデフォルトで読み込まれないので、今回は~/.bash_profileに設定

1
2
3
# direnv configuration
export EDITOR=vi
eval "$(direnv hook bash)"

設定ファイルを記述するためのエディターを環境変数EDITORに設定します。今回はviとしました。続いてeval "$(direnv hook bash)"で Bash のフックに direnv を登録します。

上記設定を読み込みます。

1
$ source ~/.bash_profile

利用するプロジェクトごとに環境変数を設定

環境変数を設定したいディレクトリで.envrcファイルを作り、exportで環境変数の定義を書きます。利用しているシェルの種類にかかわらず、このファイルは Bash の書式で記述します。

direnv edit [path]でエディターを起動し、設定を保存します。

1
2
3
$ cd ~/my-brilliant-project
$ direnv edit .
export MSG=Hello

エディターを保存して終了すると、そのディレクトリに.envrcファイルが作られます。また以下のように設定を読み込んだとのメッセージが出力されます。環境変数を確認すると、確かに読み込まれています。

1
2
3
4
5
direnv: loading .envrc
direnv: export +MSG

$ echo $MSG
Hello

ディレクトリを出ていくと、以下のようにアンロードしたとのメッセージが出力され、また入ると読み込まれます。ディレクトリに応じて自動的に環境変数が切り替わっているのがわかります。

1
2
3
4
5
6
7
8
9
10
11
12
$ cd ../
direnv: unloading

$ echo ${MSG-unloaded}
unloaded # アンロードされている

$ cd ~/my-brilliant-project
direnv: loading .envrc
direnv: export +DEBUG +MSG

$ echo ${MSG-unloaded}
Hello # .envrc が読み込まれている

あとは、必要なディレクトリごとに.envrcを用意します。

なおdirenv edit以外の方法で.envrcが作られたり変更されると、はじめて.envrcを読み込む際にエラーが表示されます。これはアーカイブやgit cloneなどで.envrcが作られた場合も同様ですし、他のユーザーが.envrcを編集した(そのユーザーがdirenv editを使ったとしても)もエラーが表示されます。

これは自分が明示的に編集した.envrcではない場合に、自動で読み込むと危険だからです。
エラーが表示された場合は.envrcに問題ないことを確認し、読み込む場合はdirenv allowを実行します。
※ 一度direnv allowしても、.envrcが自分のdirenv edit以外で書き変わると再エラー&確認が必要となります

1
2
3
4
5
6
$ cd ~/my-splendid-project
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.

$ direnv allow
direnv: loading .envrc
direnv: export +MSG

注意
.envrcに秘匿情報がある場合は、Git などのソースコード管理の対象にしないよう注意が必要です。


Shell のディレクトリ移動に応じて環境変数の定義を切り替えてくれる direnv、一度設定すれば後は無意識に切り替えてくれるので助かります。

Nuxt.js で Vue コンポーネントの利用とコンポーネントの相互呼出しをする

Nuxt.js を使うことで JAMStack なアプリを高速に構築できます。それに加えて Vue.js のフレームワークであるため Vue コンポーネントが使えるというメリットがあります。UI のパーツをコンポーネントとして切り出すことで実装をシンプルにし、また再利用性を高めることができます。

これまで、Nuxt.jsPWAwithTypeScriptとして、ベースとなるアプリを作ってきました。このままページを追加していくことで簡単にアプリ画面を増やしていけますが、その前に Nuxt.js (というか Vue.js 系フレームワーク) の、強力な機能である「Vue コンポーネント」を使い実装をシンプルにしたいと思います。

説明の都合により下記シリーズのソースを使いますが一般的な Vue コンポーネントの使い方ですので、下記 “開始時のアプリソース” ではなくても大丈夫です。

シリーズの記事

環境
本記事の開発環境は以下となります。

絵文字ボタンを Vue コンポーネントにする

以下はページ(/pages/index.vue) へ追加した絵文字ボタンに関連するコードです。絵文字に応じたtadaなどの文字列が異なるだけで、ほぼ同じコードの繰り返しとなっています。

/pages/index.vue(該当箇所のみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<div class="actions">
<a @click="addTada" class="button--action">🎉 {{ tada }}</a>
<a @click="addSparkles" class="button--action">✨ {{ sparkles }}</a>
<a @click="addThumbsup" class="button--action">👍 {{ thumbsup }}</a>
<a @click="addHeart" class="button--action">🧡 {{ heart }}</a>
</div>

<script lang="ts">
export default Vue.extend({
data() {
return {
tada: 0,
sparkles: 0,
thumbsup: 0,
heart: 0
}
},
methods: {
addTada: function(): void {
this.tada++
},
addSparkles: function(): void {
this.sparkles++
},
addThumbsup: function(): void {
this.thumbsup++
},
addHeart: function(): void {
this.heart++
}
}
})
</script>

これを Vue コンポーネント化して、再利用可能な絵文字ボタン・コンポーネントにします。
まず、ボタン1つとして表現される/components/EmojiButton.vueを作ります。ボタン1つ分なので<a>タグが1つ、汎用的なメソッド名、変数名にします。絵文字は、親コンポーネントから渡された絵文字を使いたいのでprops: { emoji: String }として受け取るようにします。

/components/EmojiButton.vue(コード全文)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<a @click="add" class="button--action">{{ emoji }} {{ counter }}</a>
</template>


<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
props: {
emoji: String
},
data() {
return {
counter: 0
}
},
methods: {
add: function(): void {
this.counter++
}
}
})
</script>


<style scoped>
.button--action {
display: inline-block;
border-radius: 4px;
border: 1px solid #3b70d0;
color: #3b70d0;
text-decoration: none;
margin: 0 2px;
padding: 10px 10px;
cursor: pointer;
}

.button--action:hover {
color: #fff;
background-color: #3b70d0;
}
</style>

絵文字ボタンを利用するページ側(/pages/index.vue) では、ハードコードしていた絵文字ボタンをコンポーネントの利用に変更します。コンポーネントのタグは<emoji-button>で、data() { emojis }の配列を使った v-forの繰り返しで作ります。また古いハードコードしていた絵文字ボタンのコードは削除します。

/pages/index.vue(該当箇所のみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<section class="container">
<div>
<div class="actions">
<emoji-button v-for="e in emojis" :key="e" :emoji="e" />
<a @click="clear" class="button--grey">Clear</a>
</div>
</div>
</section>
</template>


<script lang="ts">
import Vue from 'vue'
import EmojiButton from '~/components/EmojiButton.vue'
import Logo from '~/components/Logo.vue'

export default Vue.extend({
components: {
Logo,
EmojiButton
},
data() {
return {
emojis: [ '🎉', '✨', '👍', '🧡' ]
}
},
computed: {
counter(): number {
return 0
}
},
methods: {
clear: function(): void {
}
}
})
</script>

これにより絵文字ボタンがコンポーネント化され、再利用可能なボタンとなりました。
それぞれのボタンに絵文字が表示され、クリックするとクリックされたボタンのカウンターが増えます。似たようなコードがなくなりスッキリしました。

子コンポーネントから、親コンポーネントにイベントを送る

絵文字ボタンのコンポーネントはできましたが、トータルのカウンターは機能しなくなりました。絵文字ボタン・コンポーネントのカウンターの合計だけロゴ(/components/Logo.vue) コンポーネントを回転する機能がありましたが、現在は回転しません。

これまではページ側(/pages/index.vue) に、すべての情報があったのでボタン・クリック数の合計をロゴ・コンポーネントに送れました。しかし各カウンターが絵文字ボタン・コンポーネントへ移動してしまったため、カウンターの合計がページ側では作れなくなりました。

そこで、絵文字ボタン・コンポーネント(子コンポーネント)はクリックされたことを、ページ(親コンポーネント)へ伝えるようにします。ページ側では、その各絵文字ボタン・コンポーネントから伝えられたクリックの回数をカウントして合計としてロゴ・コンポーネントへ送るようにします。

そのクリックされたことを伝えるために「イベント」を使います。絵文字ボタン・コンポーネント(子コンポーネント)から、ページ(親コンポーネント)へ「イベント通知」をすることで、クリックされたことを伝えます。

子コンポーネント「絵文字ボタン・コンポーネント」を修正

絵文字ボタン・コンポーネントのカウントアップ処理にイベント通知this.$emit('countup')を追加します。

/components/EmojiButton.vue(該当箇所のみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
12
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
methods: {
add: function(): void {
this.counter++
this.$emit('countup')
}
}
})
</script>

$emitは、Vue コンポーネント間でイベントを通知するための仕組みです。今回は絵文字ボタン・コンポーネントが子コンポーネントなので、親コンポーネントであるページへイベントを通知します。

イベント通知は$emit('イベント名', [...引数])の書式です。引数は可変長配列で、イベントの通知先へ渡されます。今回はcountupという名前のイベント名で引数無しの通知を行っています。

参考情報

親コンポーネント「ページ」を修正

親コンポーネントであるページ側で、子コンポーネントである絵文字ボタン・コンポーネントからのイベント通知を受け取ります。

/pages/index.vue(該当箇所のみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<section class="container">
<div>
<div class="actions">
<emoji-button v-for="e in emojis" :key="e" :emoji="e" v-on:countup="add" />
<a @click="clear" class="button--grey">Clear</a>
</div>
</div>
</section>
</template>


<script lang="ts">
export default Vue.extend({
data() {
return {
total: 0,
emojis: [ '🎉', '✨', '👍', '🧡' ]
}
},
computed: {
counter(): number {
return this.total
}
},
methods: {
add: function(): void {
this.total++
},
}
})
</script>

<emoji-button>タグにv-on:countup="add"属性を追加し、絵文字ボタン・コンポーネントのcountupイベントを受け取ります。属性値addは、メソッド呼出しでadd()メソッドを呼び出すという定義です。これにより子コンポーネントのイベントを受け取って、親コンポーネントで処理をするという形が作れます。

上記定義で呼び出されるメソッドを<script>に追加します。これは通常のメソッド定義と同様です。今回はdata() { total }をインクリメントするメソッドとします。

これにより各絵文字ボタン・コンポーネントがクリックされると、ページ側にcountupイベントが伝わります。そのイベントの回数をtotalにインクリメントしていくことで、全絵文字ボタン・コンポーネントのカウンター合計が作れます。

そしてtotalcomputed: { counter() }につなぐことで、これまで機能していたロゴの回転が復旧します。

参考情報

親コンポーネントから、子コンポーネントにイベントを送る

最後に [Clear] ボタンを復旧します。[Clear] ボタンは絵文字ボタンと異なり1つだけですし、すべての絵文字ボタン・コンポーネントのカウンターをリセットする特別な役割があります。そのため親コンポーネントに残しました。

[Clear] ボタンの機能であるリセットですが、親コンポーネント内のtotal0にリセットすることはできます。それによりロゴの回転を止めて、カウンターのリセットもできます。

しかしながら子コンポーネントのカウンターは直接リセットできません。こちらもイベント通知を使いリセットします。先ほどと逆なり、親コンポーネントから子コンポーネントへイベントを通知します。

親コンポーネント「ページ」を修正

ページ側はイベントを通知する先の子コンポーネントである、全絵文字ボタン・コンポーネントを特定してイベント通知します。今回は絵文字ボタン・コンポーネントしかないので(正確には、ロゴ・コンポーネントも子コンポーネントですが)、送り先を意識しにくいですが多数のコンポーネントを抱えている親コンポーネントであるからこそ、どのコンポーネントへイベント通知するか考える必要があります。

/pages/index.vue(該当箇所のみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<section class="container">
<div>
<div class="actions">
<emoji-button v-for="e in emojis" :key="e" :emoji="e" v-on:countup="add" ref="EmojiButtons" />
<a @click="clear" class="button--grey">Clear</a>
</div>
</div>
</section>
</template>


<script lang="ts">
export default Vue.extend({
methods: {
clear: function(): void {
this.total = 0;
(this.$refs.EmojiButtons as Vue[]).forEach((value: Vue) => value.$emit('clear'))
}
}
})
</script>

<emoji-button>タグのref="EmojiButtons"属性は、EmojiButtonsの文字列でタグを参照されるための設定です。ref属性は通常の HTML タグと、子の Vue コンポーネント・タグのどちらにも使用できます。

参照する側は$refs.[ref 属性の値]で行います。今回はEmojiButtonsで登録したので$refs.EmojiButtonsでアクセスします。clear()メソッドではEmojiButtonsで登録された全絵文字ボタン・コンポーネントを取得し、$emit('clear')clearというイベント名で引数無しの通知を送っています。$emit()の使い方は、子コンポーネントからイベントを送った時と同じです。

参考情報

子コンポーネント「絵文字ボタン・コンポーネント」を修正

絵文字ボタン・コンポーネントで clearイベントの通知を受けます。

/components/EmojiButton.vue(該当箇所のみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
created: function() {
this.$on('clear', () => {
this.counter = 0;
})
},
})
</script>

Vue コンポーネントのライフサイクルcreatedで、$onを使い、イベント受け取りの登録をしておきます。今回は、イベント名clearで引数無しです。イベントを受けたらコンポーネント内のcounterをリセットします。これにより、各絵文字ボタン・コンポーネントのカウンターがクリアできます。

参考情報

ソースコード

今回作成した部分までのソースを GitHub へアップしました。(Tag:0.0.5)

GitHub Pages にホスティングもしました。動作している状態を確認する場合は、こちらへアクセスしてください。
(公開サイトは1つのため記事公開に合わせて変わり、本記事の内容とは異なることがあります)


似たようなコードをコンポーネント化し再利用できるようにしました。Vue コンポーネントを使うことで、画面内のコードもスッキリさせることができます。(同じようなものは集約してプログラムで処理したいところですよね、0.0.4 までのソースはサンプル用とはいえ心苦しかった)

一方でコンポーネント間の連携は意外と難しいところがあります。まずは親子間のイベント通知が基本です。

今回のサンプルは、スタータープロジェクトにボタンを付けたり、ロゴを回したりと地味な感じではありますが、2種類の子コンポーネント間を親コンポーネントがつなぐ形となっています。まさにコンポーネント間連携の基本なので、実装を確認いただければと思います。

[Clear] ボタンが子コンポーネントになると、兄弟間の連携となり、また一歩踏み込むことになりますが次の機会に!

httpstat.us で、簡単 HTTP クライアントのテスト

Web API などを利用するコードを書くとき、異常系の動作確認はどのようにしていますでしょうか。
サービス側でテスト用のエンドポイントが用意されているとよいのですが常にあるとは限りませんし、想定していないステータスコードもあるかもしれません。何よりレスポンスが遅い場合の確認などは難しいでしょう。そんな時に便利なサービス httpstat.us を紹介します。

httpstat.us とは

httpstat.usは、多様な HTTP Status Code をレスポンスしてくれるだけのシンプルなサービスです。

https://httpstat.us/200のように httpstat.us の URL に欲しいステータスコードの番号を付けてアクセスするだけで、そのステータスコードのレスポンスを返してくれます。https://httpstat.us/200なら200 OKのステータスコード。https://httpstat.us/404なら404 Not Found

3桁の数字であれば001999のように、Hypertext Transfer Protocol (HTTP) Status Code Registryに定義されていないコードも画面表示こそUnknown Codeですが、ステータスコードはしっかり返してくれます。

なお対応しているのは3桁のみで、2桁以下や4桁以上でリクエストするとナイスなティーポット画像が楽しめます。(ステータスコード418 I’m a teapot - HTTP | MDNのジョークにかけているのかな?https://httpstat.us/418418 I'm a teapotと、あっさりな実装だから)

一応最後に “WE DON’T CAPTURE OR STORE ANY DATA ABOUT THE REQUESTS YOU MAKE.” とメッセージされていますが、大事なデータは送らないようにしましょう。

遅延応答

クエリーパラメーターsleepで、ミリ秒単位の遅延応答をリクエストできます。プログラム側のタイムアウトを先に起こさせたい場合などに使えます。(遅延なしでもレスポンスに2秒かかってる)

1
2
3
4
5
6
7
$ SECONDS=0; curl https://httpstat.us/200; run_time=$SECONDS; echo -e "\n"$run_time
200 OK
2

$ SECONDS=0; curl https://httpstat.us/200?sleep=5000; run_time=$SECONDS; echo -e "\n"$run_time
200 OK
7

JSON レスポンス

ヘッダーにAccept: application/jsonを付けると JSON でレスポンスを返してくれる、らしいのですが"200 OK"とダブルクォーテーションで囲ってくれるだけのようです。({ "status": 200, "message": "OK" }などを期待したかった)

1
2
3
4
5
$ curl https://httpstat.us/200
200 OK

$ curl -H 'Accept: application/json' https://httpstat.us/200
"200 OK"

簡単ながら HTTP Status Code をいろいろと試せるサービスの紹介でした。

実開発のテストに組み込むことはさすがにできないでしょうが、新しいライブラリの利用方法の確認だったり、エラーハンドリングの確認などを行う場合に便利です。

JSON レスポンスは期待値と異なるのでOSS-Friday 案件としてプルリクエストを出したいと思います。

共有:

ブログメンティふりかえり10週目

カック@ブロガー / k9u(@kakakakakku)さんに、ブログメンターについていただき10週間。ふりかえりの中間ポスト最終回です。いろいろ下書きしているうちに思いが詰まってきて、卒論(論文ではない)っぽくなりそうだったので、今回は軽めのサマリーにして最終回へ回します。

いよいよブログメンティー、最終月となりました。まだやり切れていないことが多々残っていますが、悔いのないブログメンタリングの日々を過ごしたいと思います。

前回のふりかえりで書きましたが5月は激動でした。6月に入ったところでいったん落ち着きを取り戻したところですが、感動!?のフィナーレを目指してテンションを上げていきます!

シリーズの記事

ブログメンティーとしての目標設定

技術系記事を以下のペースで公開する(※ このシリーズの記事は記載するがカウント外)

  • 週1回を必達とする
  • 週2回を目標として設定する

以下の記事を書き目標を達成することができました。

9週目

10週目

各種メトリクスの推移と考察

毎週日曜日に以下のメトリクスを取得しています。こちらは達成数値のノルマとかではなく、推移を見ていただいています。

記事数

ブログメンタリング開始時からの記事数の推移です。

開始時1週目2週目3週目4週目5週目6週目7週目8週目9週目10週目
トータル2125283133363840434649
投稿数-3232222323

週間 PV

Google Analitycs で取得しています。

開始時1週目2週目3週目4週目5週目6週目7週目8週目9週目10週目
全体2324783552763051713094207001,772958
/articles82241242199205138185181271785439
@lulzneko78203225174191126168160241660397

はてなブックマーク数

サイト内の合計はてブ数を表示するツール|はてブチェッカーで取得しています。

開始時1週目2週目3週目4週目5週目6週目7週目8週目9週目10週目
全体2929303030303031314042
/articles1010111111111112121618
@lulzneko1010111111111112121618

Twitter フォロワー数

lulzneko (ラルズネコ)(@lulzneko) | Twitterを見ています。

開始時1週目2週目3週目4週目5週目6週目7週目8週目9週目10週目
@lulzneko158169170171172174176183184199206

OSS-Friday 活動

GitHubから、活動をピックアップしました。
こちらは少ないので月単位でまとめています。(一部金曜日でないけど OSS 貢献ってことで)

プルリクをたくさん出している場合は、メンターのカックさん記事「2018年のプルリクエストを振り返る - kakakakakku blog」に検索の方法があります。
また、金曜日に限定してよい場合は「オープンソース フライデー」に登録すると、ユーザーサマリーを「lulzneko | オープンソース フライデー」みたいな感じで作ってくれます。(自分リポジトリへの push も含まれてしまいますが)

開始時4月5月6月
プルリク1134

気づき、学び

タスク管理、大事

「活動量が増える=タスクも増える」ということ。やることが増えると、どうしても管理が難しくなります。
私はこの管理がすごく苦手で、ここまで増えてくると厳しくなって「Asana を導入」しましたが、まったく管理できてない💦
ToDo 管理としては、やりたかったことが列挙できているので初歩にはなったと思います。でも、まったくなってなくてツライ。

たとえば、先日書いた「Git の設定をリポジトリごとに自動で使い分ける」の記事は、思いついて設定しているうちにブログを書きたくなって、すぐに書いた流れでした。予定では Nuxt.js の続きでしたが。。。(そして次回作も、新たな思い付きに流れそうな予感)

この思い付きに振り回されていて管理できてないなぁというのを改めて気づきました。気づいたところで、どう管理するのか。。。

何事も続けるにはムリなく着実にとは思います。そのためにもスケジュールやタスク管理が必要なのだなと。とはいえ「いいこと思いついた!」的な幼児の発想力は私の原動力であり、何事にも挑戦する力になっているのでバランスしていきたいと思いました。

まだまだチャレンジすることが、たくさん

まだできていないこととしては大きく2つ。「書籍紹介、書評」と「Tips 的な記事」。

「書籍紹介、書評」はブログメンタリング期間中に確実に挑戦したいです。6月初めに挑戦したのですが、ちょっとブログを書くには難しい本(自分のレベルとして)で別の本に切り替え中です。自分のためだけのインプットとして読むのとは異なり、アウトプットを考えて読むのは難しいですね。時間切れにならないように気を付けねば。(何を読んで断念したのかは、初書籍紹介ができた時に書きます)

「Tips 的な記事」は、コンパクトな記事をイメージした表現です。ブログを書いてて文章が長いなぁと自分で思っていたりもします。その中でも伝えたいことをが増えてしまって脱線している部分もあると思うので、Tips 的な小さくて目的をはっきりした内容を作れるようになりたいです。そうすることで、どうしても長くなる記事でもスッキリできるのかなと考えています。(その Git 記事は短くしようとして結果迷走しカックさんのチェックが入りました。難しい。)

たぶん、これで技術関連ブログとしてのジャンル的にはひとおりブログに書くことの体験をできたと思うけど、どうかな🤔

IoT というか、ハードウエアやってないかな。初夏のJavaScript祭の発表で、マイクロビットをいただいたので、こちらは調べてしっかり書きたいと思います。


残り3週間、良い習慣を身につけることの最終化と、良いアウトプットを出す品質を上げていくことをしっかりやっていきたいです。

Git の設定をリポジトリごとに自動で使い分ける

Git で開発をしている中でリポジトリによって設定を変えたいことがあります。たとえばコミット時の名前やメールアドレスなどを使い分けたいといったケースです。このような場合に有用な Git の Conditional includes を使った自動切り替えの方法について紹介します。

Gitでコミットする際の名前やメールアドレス、多くの場合は全体設定git config --globalにしているかと思います。通常は問題ないのですが、リポジトリによって設定を変えたいこともあります。そのような場合はgit config --localを使いリポジトリ単体の設定ができます。

しかしながら、そのようなリポジトリが増えてくるとクローンするたびごとに設定が必要となり手間です。場合によっては設定忘れでエラーになったり、悪い場合はアカウント名を間違えてコミットすることもあり得ます。できればリポジトリをまとめるディレクトリ単位などで設定したいところです。

今回は特定のパスに配置したリポジトリへ設定をインクルードするConditional includesを使い、設定をリポジトリごとに自動で使い分けるような仕組みを紹介します。

環境
本記事の開発環境は以下となります。

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Git 2.21.0.windows.1 (Windows 10 64bit)
    Git 2.17.1 (WSL Ubuntu 18.04.1 LTS)

参考情報

Git Conditional includes とは

バージョン 2.13.0 から追加された機能で、条件へマッチした場合に指定された設定ファイルをインクルードします。

条件はgitdirgitdir/iで指定します。
どちらも glob パターンでパスを指定して、現在のディレクトリがパターンにマッチするかで判定されます。違いはgitdir/iが大文字小文字を区別せずに判定します。

自動切り替えの設定例

設定は git config –global includeIf.”<条件>”.path “<設定ファイル>” のコマンドを実行します。
(とはいえコマンドは分かりにくいので、設定ファイルを直接編集してます)

たとえば、ホームディレクトリ以下のreposworkossを作り、その下に関連するリポジトリを配置しているとします。

1
2
3
4
5
6
~/repos
|-- work
| `-- wondrous-product
`-- oss
|-- astounding-software
`-- local-config-service

それぞれのディレクトリ名をインクルードするファイル名の一部に使い以下のようにしました。
※ 末尾が/で終わる場合は、暗黙的に/**の glob パターンになります

1
2
$ git config --global includeIf."gitdir:~/repos/work/".path ".gitconfig_work"
$ git config --global includeIf."gitdir:~/repos/oss/".path ".gitconfig_oss"

上記コマンドを実行した際の設定ファイル~/.gitconfigは以下のようになります。(必要箇所のみ抜粋)
※ 今回はユーザー名とメールアドレスを切り替える想定なのでuseConfigOnly = trueでデフォルトのユーザー名を無効にしています

1
2
3
4
5
6
7
[user]
useConfigOnly = true

[includeIf "gitdir:~/repos/work/"]
path = .gitconfig_work
[includeIf "gitdir:~/repos/oss/"]
path = .gitconfig_oss

続いてインクルードされるファイル~/.gitconfig_work~/.gitconfig_ossを作ります。

~/.gitconfig_work

1
2
3
[user]
name = Formal account name used at work
email = my-work@example.com

~/.gitconfig_oss

1
2
3
[user]
name = Cool account name used at OSS activities
email = my-oss@example.org

自動切り替えの確認

それぞれのリポジトリ用ディレクトリ~/repos/work/~/repos/oss/の下にリポジトリを配置して設定を確認します。どの設定ファイルが使われたかわかるようにgit config --show-origin --get user.nameを実行します。

~/repos/work

1
2
~/repos/work/wondrous-product$ git config --show-origin --get user.name
file:/home/username/.gitconfig_work Formal account name used at work

~/repos/oss

1
2
~/repos/oss/astounding-software$ git config --show-origin --get user.name
file:/home/username/.gitconfig_oss Cool account name used at OSS activities

設定が切り替わっていること、また参照しているファイルも異なることが確認できます。
今回はユーザー名とメールアドレスを切り替えましたが、他の設定項目も同様に切り替えることができます。

注意点

  • ローカルの設定が優先される
    設定ファイルの優先度は、そのまま適用されるのでgloballocalではlocalが優先されます。今回の設定はglobalにしているので、localの設定がある場合はそちらが優先されます。

    1
    2
    ~/repos/oss/local-config-service$ git config --show-origin --get user.name
    file:.git/config Hot account name configured in local
  • シンボリックリンクの場合は両方設定しておく
    現在のディレクトリのパスが判定されているので、シンボリックのパスと、オリジナルのパスで有効にするには両方設定しておきます。

    私の環境は Windows 10 64bit + WSL Ubuntu 18.04.1 LTS で、Visual Studio Code のターミナルは WSL です。
    しかしながら Windows の Eclipse でも作業することがあり、リポジトリの実態は Windows 側にあり/mnt/c/develop/repos/~/reposにシンボリックリンクして使っています。(特殊なケースではありますが。。。)

    Visual Studio Code でターミナルを開くとオリジナルパスの/mnt/...が使われますが、自分でコマンドを打っているときはcd ~/repos/...のようにシンボリックリンクから移動することが多いです。このような場合にマッチするパスが変わるので両方指定しておかないと切り替えが機能しません。

    余談ですが、Eclipse は 2019年6月現在543171 – Support for includeif in git configが上がっており includeif に対応していません。

  • Windows の場合は、パスの大文字小文字を区別したくないのでgitdir/iを使う
    これはドライブレターのC:c:のレベルですでに大文字小文字の違いが出てしまうので区別しないようにします。またパスの区切りは Windows の\ではなく、Linuxt などで使われる/です。

    1
    2
    3
    4
    5
    6
    7
    [user]
    useConfigOnly = true

    [includeIf "gitdir/i:C:/develop/repos/work/"]
    path = .gitconfig_work
    [includeIf "gitdir/i:C:/develop/repos/oss/"]
    path = .gitconfig_oss

これで作業環境ごとにgit config --localで設定を追加する必要がなくなりました。
とくにアカウント名やメールアドレスは、複数の作業グループがある時に間違えコミットしないように注意が必要です。そのためにuseConfigOnly = trueを設定しますが、そうするとリポジトリごとに設定が必要となります。その際に、今回の設定をしておくと特定のディレクトリ以下に配置しておくだけで設定が反映されるので便利です。

最近は「OSS-Friday 活動 - 2019年5月まとめ」に書きましたように、わずかながら OSS への貢献活動も始めたのでリポジトリのクローンをすることが増え、今回のようにディレクトリで自動に切り替わってくれるのは助かります。