サーバレスな天気レポートの 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.jsPWA with TypeScript として、ベースとなるアプリを作ってきました。このままページを追加していくことで簡単にアプリ画面を増やしていけますが、その前に 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 への貢献活動も始めたのでリポジトリのクローンをすることが増え、今回のようにディレクトリで自動に切り替わってくれるのは助かります。

OSS-Friday 活動 - 2019年5月まとめ

2019年5月中頃に OSS-Friday を始めてみます宣言から最初の1ヶ月が経ちました。初月の活動についてまとめます。

ブログのメンタリングを受けている中で、メンターの カック@ブロガー / k9u(@kakakakakku)さん から、使っている OSS への貢献について話がありました。使わせてもらっている以上、プロダクトやコミュニティへしっかり貢献しましょうということですね。もちろん「同意!」です。
(本件とは関係ないけど「ブログのメンタリング」って、ブログに限らず幅広く見ていただいているので実態としては何と言うとマッチするんだろ🤔)

そうした中から、どのような活動をしようかと考えていたところ、以前 IT 関連の記事で「オープンソース フライデー | 今週の金曜日は、利用しているお気に入りのソフトウエアを数時間助けてみよう。」が取り上げられていたことを思い出し、せっかくなので金曜日を OSS について考える日にしてみることにしました。

サマリー

OSS-Friday のページで GitHub のアカウント連携すると自分のページが作られます。

このページの中に GitHub での「過去3ヶ月間&過去1,000イベント」から金曜日の活動をサマリーしたものがあります。(下図、重複を取り除いてキャプチャ)

自分のところの push も出てますが、メインは以下の3つです。

ということで、最初の OSS-Friday 活動は3件でした!
以降、プルリクエストを出した背景など。

gridsome/gridsome-starter-default #10

こちらのプルリクは、2019年6月1日の「初夏のJavaScript祭 in メンバーズキャリア」で Gridsome のデモアプリを作る際に Lighthouse のスコアをとったところ <meta name="description" /> がないだけでオール 100点にならなかったので出しました。

せっかく[SEO-friendly - Gridsome]を特徴として謳っているので 100点にしておきたいところです。これは設定ファイルにデフォルト値をセットしておくだけで 100点になります。

なにより設定にデフォルトが入っていないと、設定箇所がないように見えるので開発者が気づかない、または調べるのを後回しにして設定忘れが放置される可能性もありえます。デフォルト値は意味のない値とはいえ設定が明示的に入っていることで開発者に意識を向けさせることができるのではないでしょうか。

本質的ではないですが、気になったのでプルリクを出すことにしました。

関連コンテンツ

hexojs/hexo-theme-landscape #134

こちらのプルリクは、Hexo のデフォルトテーマ landscape の Twitter 共有リンクを開いたときに URL のみだったのを、タイトルも含めるようにしたものです。

カック@ブロガー / k9u(@kakakakakku)さん に指摘されるまで気づきませんでした。。。自分が使っているものをしっかり把握しておかないとですね 😓

この共有リンクはテーマのデフォルトではダイアログに入っているのですが、本ブログでは展開して記事の一部に表示する改造をしています。そのため単純な差分が取れずに、よくコードを確認する必要がありましたが勉強になりました。また最新のコードを本ブログのテーマに取り込むきっかけにもなりよかったです。(さりげなく本家からマージした LinkedIn の共有ボタンが入ってる)

テーマ周りでも Issues が上がっていたりするので、引き続き貢献ポイントを探して活動していきたいです。

関連コンテンツ

sfarthin/ga-analytics #2

こちらのプルリクは、本ブログの開発環境を Node.js v8 から v10 にバージョンアップしたところエラーが発生したので修正したものになります。

この sfarthin/ga-analytics は、Google Analytics からレポートデータを取得するもので、本ブログでは Hexo で人気記事のリストを実現する tea3/hexo-related-popular-posts Plugin が使っています。エラーが発生しているので修正が必然だったものになります。

関連コンテンツ


5月は3件のプルリクを出せました。

まだ、どれもマージされてないのが寂しいですが、最新の2件はプロジェクトテンプレートやテーマですし内容も優先度が高くないので、じっくり待ち。

3つ目の sfarthin/ga-analytics #2 は、エラー修正なのでマージされると嬉しいな。最終更新が 2014年と5年前なのがちょっと心配。。。

気づいたところを少し修正しただけですが、結構楽しいです。
今後とも OSS へ貢献する活動をしていきたいと思います。

🚲 Let’s enjoy OSS-Friday !!

OSS-Friday 参考情報

初夏のJavaScript祭にて発表した Gridsome のサンプルアプリ実装解説

2019年6月1日の「初夏のJavaScript祭 in メンバーズキャリア」で『Gridsome で作る JAMStack なサーバーレス Web Front』の発表をした際にデモしました Gridsome のアプリについて実装を紹介します。

今回の発表用に作ったのは Gridsome を使って、Instagram から画像をとってきて並べるだけの簡単なサンプルアプリです。実装が小さいながらも GraphQL を使い、入力ソースが簡単に扱えて、それでいて画像が出るので表現がわかりやすいかと思います。具体的な実装について聞いてくださった方がいらっしゃったので実装手順を紹介します。

今回 Instagram からの入力に使っている Gridsome の Plugin @zefman/gridsome-source-instagram は、”Currently only supports grabbing the latest photos from a user’s public instagram profile. - README.md“ とのことで、ちょっとデータを取ってこれるだけのアプリになります。ご注意ください。

発表資料で扱ったサンプルアプリの紹介スライド

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

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Visual Studio Code
  • Node.js 12.2.0
  • Yarn 1.15.2
  • Gridsome 0.6.3
  • @zefman/gridsome-source-instagram 0.1.2

Gridsome プロジェクトの初期構築

まずは @gridsome/cli でプロジェクトの初期構築をします。今回は [project name] を samples-gridsome-instagram としました。

1
$ npx @gridsome/cli create [project name]

この時点でローカルサーバーを起動して、初期状態のアプリを確認できます。
下記コマンドを実行し Gridsome が起動したら、ブラウザで http://localhost:8080/ へアクセスします。

1
2
$ cd samples-gridsome-instagram
$ yarn develop

シンプルな初期画面が表示されます。[Home] と [About] の間で高速にページの表示切替ができます。

この時点で Lighthouse(Simulated throttling (faster)) は以下のスコアを出します。(キャプチャは、GitHub Pages へデプロイして計測しています)

SEO が 91点なのは <meta name="description" /> タグがないからで、プロジェクト直下 /gridsome.config.js ファイルに siteDescription を追加することで 100点です。サイトのタイトルは siteName なので、あわせて適切な名前と説明を設定します。(以下、設定項目の全文を抜粋)

1
2
3
4
5
module.exports = {
siteName: 'Gridsome',
siteDescription: 'My Gridsome project',
plugins: []
}

デフォルト値を入れていおくプルリクを出しましたが、本質的ではないのでマージされるのは難しいかな。。。

Gridsome Plugin の導入とクエリー構築

続いて @zefman/gridsome-source-instagram を導入します。
プロジェクトのディレクトリで下記コマンドを実行します。

1
$ yarn add -D @zefman/gridsome-source-instagram

/gridsome.config.js ファイルにプラグインの設定をします。(以下、設定項目の全文を抜粋)

  • username は、取得したいインスタグラムのユーザー名です。(現在のところタグは対応していないのでユーザー名が必要で、今回は公式の instagram さんにしました)
  • typeName は、GraphQL のクエリーで、このプラグインと設定を示すのに使う文字列です。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    module.exports = {
    siteName: 'Gridsome',
    siteDescription: 'My Gridsome project',
    plugins: [
    {
    use: '@zefman/gridsome-source-instagram',
    options: {
    username: 'instagram', // Instagram username
    typeName: 'InstagramPhoto' // The GraphQL type you want the photos to be added under. Defaults to InstagramPhoto
    }
    }
    ]

この状態で yarn develop でローカルサーバーを再起動します。
起動したら http://localhost:8080/___explore へアクセスします。
GraphQL の Playground が表示されるので、左側のクエリーエディターに以下を入力して画面中央の [▷ (再生)] ボタンをクリックします。

  • photos: allInstagramPhoto は、Plugin で指定した typeName の文字列 allInstagramPhotophotos の名前で使うという指定です。
  • iddisplay_url が Instagram から取得した ID と、画像の URL です。
  • text は、Instagram のコメント(一番最初のだけ)です。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    query {
    photos: allInstagramPhoto {
    edges {
    node {
    id
    display_url
    edge_media_to_caption {
    edges {
    node {
    text
    }
    }
    }
    }
    }
    }
    }

右にある [DOCS] や [SCHEMA] タブを確認しながら欲しいデータが取得できるクエリーを作っていきます。(たとえば shortcodehttps://www.instagram.com/p/[shortcode の値] でページを表示するための文字列が返ります。)

ページの作成

/src/pages ディレクトリにページ用のファイルを作ります。URL に対応するアッパーキャメルケース(パスカルケース)の文字列でファイル名 + .vue を作ります。
今回は InstagramPhotos.vue としました。URL としては slug 化されて /instagram-photos となります(Pages - Gridsome)。
ここからはローカルサーバーを立ち上げておき http://localhost:8080/instagram-photos へアクセスした状態で作業を進めます。ホットリロードでブラウザが自動更新するので確認が容易です。

コードは下記で、基本的に Vue.js です。<page-query> タグが Gridsome 特有の GraphQL のクエリーを書くための場所になります。
また画像も Gridsome 特有の <g-image> タグで、これにより最適化されます(Images - Gridsome)。

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
<template>
<Layout>

<h1>Instagram Photos</h1>
<span v-for="edge in $page.photos.edges" :key="edge.node.id">
<a :href="'https://instagram.com/p/' + edge.node.shortcode" target="_blank" rel="noopener">
<g-image :src="edge.node.display_url" :alt="'Instagram Photo: ' + edge.node.edge_media_to_caption.edges[0].node.text" class="photo" />
</a>
</span>

</Layout>
</template>

<page-query>
query {
photos: allInstagramPhoto {
edges {
node {
id
shortcode
display_url
edge_media_to_caption {
edges {
node {
text
}
}
}
}
}
}
}
</page-query>

<style>
.photo {
width: 144px;
margin: 0 4px;
}
</style>

レイアウトにリンクを追加

ページが作れたら、そのページへアクセスするためのリンクを作ります。(以下、テンプレート部分のみ抜粋)
[Home] と [About] のリンクがあるのは /src/layouts/Default.vue です。
サイト内のページ遷移は Gridsome 特有の <g-link> タグを使います。これによりプリフェッチが効いてページ遷移を高速化できます(Linking - Gridsome)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="layout">
<header class="header">
<strong>
<g-link to="/">{{ $static.metaData.siteName }}</g-link>
</strong>
<nav class="nav">
<g-link class="nav__link" to="/">Home</g-link>
<g-link class="nav__link" to="/about">About</g-link>
<g-link class="nav__link" to="/instagram-photos">Instagram Photos</g-link>
</nav>
</header>
<slot/>
</div>
</template>

完成!

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

また、今回作成した部分までのソースを GitHub へアップしました。

発表資料

当日の発表資料と、サマリーの記事はこちらです。
もしよかったら、ご参照ください。

Gridsome で作る JAMStack な サーバーレス Web Front』の発表資料はこちらになります。(下記、スライド埋め込み)

また発表のサマリー記事は、こちらになります。


Gridsome アプリの実装例になります。

GraphQL で簡単にデータソースの内容にアクセスできるのを感じていただけたでしょうか。このように Gridsome は Source 系 Plugin を使うことでさまざまなデータソースに容易にアクセスすることができ、GraphQL として画一的に扱うことができます。いろいろなデータソースを扱っておもしろいサイトが作れそうです。

初夏のJavaScript祭 in メンバーズキャリアにて「Gridsome で作る JAMStack なサーバーレス Web Front」の発表をしました

2019年6月1日に開催された「初夏のJavaScript祭 in メンバーズキャリア」で『Gridsome で作る JAMStack なサーバーレス Web Front』と題して、JAMStack の世界と、Gridsome 導入について発表しました。その発表サマリーです。

“JavaScript祭” 概要

発表したイベント 初夏のJavaScript祭 in メンバーズキャリア は、Web デザイナー、フロントエンドエンジニア、サーバーサイドエンジニアまで JavaScript に関わるすべての人を対象にした JavaScript 好きのコミュニティー、Javascript祭り が開催するイベントです。初夏と秋の年2回開催されています。

会場は メンバーズキャリア さん。”もう迷わない!五反田駅からメンバーズキャリア本社への推奨ルート | メンバーズキャリアNOW“ が公開されていて安心の道筋です。が、迷いました。ぐるりと一周して、サイトを見直して、ようやくたどり着きました。なんと。。。

発表資料

Gridsome で作る JAMStack な サーバーレス Web Front』の発表資料はこちらになります。(下記、スライド埋め込み)

サマリー

発表の主旨は「JAMStack は JavaScript 祭り!、JavaScript から JAMStack を広めよう!!」です。

「JAMStack、好きだよね」と言われそうなほど JAMStack で発表させていただいていますが、前回の Serverless Meetup Tokyo 12 にて「サーバーレスなウェブフロントを実現する JAMStack」について発表 でふれました通り、「フロントも含めて、まるっとサーバーレスにしたい」のです。それを実現できるのが JAMStack です。

JAMStack とは

では、JAMStack とは何でしょうか。

「クライアントサイド JavaScript」「再利用可能な API」「構築済みのマークアップ」で構築されたサイトのことで、ざっくり言うと HTML に静的化されたされたサイトで、動的要素はブラウザ上の JavaScript から Web API を呼び出す形にしましょうというものになります。

これによって、すべてを CDN に配置できるので、素晴らしいパフォーマンスとスケーラビリティを手に入れることができます。そして HTML が CDN にあるだけなので攻撃対象を局所化できセキュリティを高めることができます。
また開発者の視点として、サーバー内のロジックとテンプレートエンジンが分離できます。これは表示部分とデータについて役割を明確に分離できます。役割が明確なのは開発において重要です。

そんな JAMStack なサイトを作るには「構築済みのマークアップ」を用意します。これは Static Site Generator(SSG) を使います。メジャーな Static Site Generator は StaticGen | Top Open Source Static Site Generators から探すことができます。さまざまな Static Site Generator があるので、開発言語やフレームワークに合わせて選べます。

その中でも私たちがよく使っているのは以下です。

  • Nuxt.js - Vue.js ベース、アプリのフレームとして使いやすい
  • Gridsome - 同じく Vue.js ベースで、完全に静的化した情報発信サイトに向いている

JAMStack の “J“ は、JavaScript の “J”。そして API も、Markdown も、JavaScript 作ることができるので、まさに JAMStack = JavaScript だったりします。そんなお話から、JavaScript 好きの皆さんと JAMStack で盛り上がりたいとのことで今回の発表に至りました。

アーキテクチャ紹介

Gridsome で、情報発信サイトを作るパターンです。

ソースコードのセットにある Markdown ファイルを記事として取り込む情報発信サイト、ブログのようなサイトを作る場合のアーキテクチャになります。ホスティングは AWS の CloudFront/S3 を使うことでスケールさせています。これは完全に静的なサイトとして作られているので、他にも NetlifyGitHub Pages などにもデプロイできます。

また、このアーキテクチャ図は Markdown ファイルをソースとしていますが、Gridsome はさまざまなソースを取り扱えるので、ここを変えていくとおもしろいシステムが作れます。Gridsome の Plugin は、こちら Plugins - Gridsome で、source とついているものが入力を担う Plugin です。WordPress や、ヘッドレス CMS の Contentful、Google 系などさまざまな入力ソースに対応しています。

ここのでのポイントは、HTML とは異なるソースを Static Site Generator と組み合わせることで HTML 化してデプロイするという点です。あらかじめ HTML 化してしまうので、実行時に変換する必要がないので高速に動作させることができます。

Gridsome で作る JAMStack サイト

アーキテクチャ図で出てきた Gridsome について、具体的な使い方を紹介します。

Gridsome は、Vue.js ベースのモダンで高速なウェブサイトを構築するためのフレームワークです。
とにかく高速で “builds ultra performance automatically”, “almost perfect page speed scores by default.” と公式で謳っているほどです。ウルトラ・パフォーマンスやパーフェクト・スコアと明言してくるあたりスゴいです!

そして大きな特徴として GraphQL をデータ管理の中心として持ってきているところです。これにより、さまざまなデータソースを画一的に取り扱うことができます。いろいろな組み合わせを作ることができますし、異なるデータソースを簡単に組み合わせてページを構築できることが魅力です。

サイト構築の開発も簡単です。
npx @gridsome/cli create <project name> で、スタータープロジェクトを使って初期構築。
プロジェクトのディレクトリで yarn gridsome develop でローカルサーバーを起動して、http://localhost:8080/ へアクセス。あとは Vue.js のページやコンポーネントを追加していくだけです。ホットリロードが効いているのでコードを変更するたびにブラウザがリフレッシュされるので確認も容易です。

重要な GraphQL も Vue.js と組み合わせて簡単に扱えます。何よりも http://localhost:8080/___explore へアクセスすることで GraphQL のクエリーやスキーマを確認できるのが素晴らしいです。これによりデータソースの確認が容易に行えます。

下図は Instagram をソースとして取り扱う zefman/gridsome-source-instagram と、その GraphQL のクエリーの例です。こんな感じで簡単にソースが取り扱えますし、クエリやスキーマの確認は http://localhost:8080/___explore から確認できます。

※ 猫の写真は アメショっす! さんの Instagram をお借りしました。私の SNS アイコンの猫さんも アメショっす! さんに許可をいただいて使わせて頂いているものになります。ありがとうございす 😸

JAMStack の可能性

基本的には、どのようなサイトでも JAMStack で作れると考えています。
ただし、どうにもならないのが1つ、SEO/OGP 重視で変化の激しいコンテンツ。これは JAMStack では、どうにもならないです。SEO/OGP は HTML で <meta> タグを設定する必要があります。JAMStack では HTML を事前ビルドする必要があるので、変化が激しいとビルドが回り切らない。この場合は Server Side Rendaring(SSR) にしざるを得ないです。
とはいえ、すべてがすべて SEO/OGP 重視で変化が激しいわけではないので、まずは JAMStack で作ることを考えるとよいでしょう。

まとめ

QA

Nuxt.js でも SSG できるし、また他にもツールがあるが一番早いのは Gridsome でしょうか

「どれが最速か?」となると、ベンチマークしないと、ちょっとわからないです。すみません。

ただ、たとえば Gridsome はリンクのプリフェッチの機能があります。Nuxt.js は最近導入されました。ちょっと前であれば Gridsome のが速いといえたでしょう。しかしながら、よい技術はどのツールにも取り込まれていくので「最速が必須!」とかでなければ、スキルセットなどに合わせるとよいと思います。
それは Vue.js / React のベースをどちらにするかといったところなどがあるかと思います。

GraphQL のサーバーは別途立てますか

Gridsome は GraphQL を内蔵しているので別途サーバーを用意する必要はありません。
gridsome develop を起動すると GraphQL サーバーも同時に起動するので http://localhost:8080/___explore へアクセスするだけで利用できます。また GraphQL サーバーを使うのはビルドするまでで、ビルドしたら HTML ができるので GraphQL のサーバーは不要です。

他に確認したいことなどありましたら、ぜひ[lulzneko | Twitter]へ DM 他、気軽にいただけましたら幸いです。


JavaScript のコミュニティで、お話させていただきました。

さまざまなツールやライブラリが登場する中で、JAMStack の話は緊張しました。とくに Gridsome の話は、コミッターさんの前の時間で発表という(それでいてブログメンティーの先輩でもある)💦

そんな大先輩 もっと@GatsbyJSとNetlify推し(@mottox2)さんSSRを検討する際にSSGも検討しませんか? / ssr or ssg - Speaker Deck の発表。より具体的に SSG の紹介と、SSG で困ったときに “Gatsby => Next.js、Gridsome => Nuxt.js” と移行のパスも説明されています。
こちらのスライドも必見です!

そして Javascript祭り | Doorkeeper。今回、はじめて参加させていただきましたが、登壇者さんの発表がスゴイのに加えて LT が熱い!とてもすごいイベントです。次回は秋。また参加したいです。