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

カック@ブロガー / k9u(@kakakakakku)さんにブログメンターについていただき8週間。隔週予定のふりかえりが遅れてしまいましたがまとめます。

最初のころのふりかえりは「日々学び」でしたが、最近は激動の1ヶ月になってきたように感じます。#ブログメンタリング、人生をも変えてしまうのか!?(大袈裟なw)

シリーズの記事

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

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

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

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

5週目

6週目

7週目

8週目

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

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

記事数

せっかくなので追加しました。(投稿数は目標設定カウント数のみ)

開始時1週目2週目3週目4週目5週目6週目7週目8週目
トータル212528313336384043
投稿数-32322223

週間 PV

Google Analitycs で取得しています。

開始時1週目2週目3週目4週目5週目6週目7週目8週目
全体232478355276305171309420700
/articles82241242199205138185181271
@lulzneko78203225174191126168160241

はてなブックマーク数

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

開始時1週目2週目3週目4週目5週目6週目7週目8週目
全体292930303030303131
/articles101011111111111212
@lulzneko101011111111111212

Twitter フォロワー数

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

開始時1週目2週目3週目4週目5週目6週目7週目8週目
@lulzneko158169170171172174176183184

気づき、学び

習慣化と継続性

ブログを書くというのはとても大変。だからこそ習慣化して継続することが大事なんだなと改めて気づきます。
そうした中で、最初に20個のテーマを考えて、また新しいことがあったらテーマを追加していくこと、リストを更新し続けることが大切です。それによって、いつでも書くことができる状態をキープできます。基本的なことですが、テーマリストを持っていないと書き始めるハードルが上がってしまい、書く手も止まりがちになるので重要なんだなと。

また、今月は仕事が忙しく、加えてハッカソン、発表とスケジュールが厳しい状況でした。そうした中で重量級な記事だとつらくなるので状況に合わせた記事を考えることなども教わりました。継続していくにはどうするのか、記事を書くだけでなく考えることも大事だと気づかされます。こういった状況に合わせた記事のテーマを考える宿題をいただいているのですが、まだ考え中。。。難しい。

また、お話(DM)していると自然と話を膨らませてくださり、テーマリストがスゴイ量になっていきます。このような発想の膨らませ方、考え方をもっと身に付けたいです。

プレゼンについても教わる!

カック@ブロガー / k9u(@kakakakakku)さん、登壇歴もスゴくプレゼンについても色々と教えてくれました。SPAJAM 2019 東京A予選に参加した際には少しは実践できたかなと思いますが、教えていただいた内容を振り返りつつ、この後の発表に臨みたいです。

教えていただいた資料

そして、カックさんのプレゼンが見れるのはこちら!

OSS 活動

これは前回までのふりかえりでも「コミュニティへの貢献」と書きましたが、OSS などのプロダクトを利用させていただいているなかで貢献していきましょうというのは、お話をしている中でとてもメッセージを感じます。

メンターについていただいた当初からプルリクを出してみようや、ブログで使っている Hexo の SNS 共有リンクに記事タイトルを入れるでは、記事の改修部分をオリジナルへ還元することの話も上がりました。

そうしたことから、せっかくなので OSS 活動について考えて行動すること始めてみようかなと。
日中に OSS の活動をすることは難しいので多くのことはできないかもしれませんが、まずは意識する日を明確に設けるということで Open Source Friday をやってみます。

書評をやってない

書評というと硬い感じですが、書籍紹介をやっていない。本を読むのは好きなのですが最近なかなか時間が取れていない。どんどん積読してしまっています。これはテコ入れをして、ブログメンティー中にしかりと書きたいです。カックさんより「書評は書く人によって全然良さが違いますもんね!」とのことで、しっかり見てもらいたいです。テーマは決まっているので「時間を作る」をしっかりやっていきます。


以上、8週目の振り返りです。

わずかとはいえ OSS 活動に手を出し始める宣言となり、まったく考えてなかったような活動まで始めるとは、まさに冒頭で書きました “#ブログメンタリング、人生をも変えてしまうのか!?” ですね。

新しいことに挑戦するきっかけをいただいたり、自分の可能性を広げたりとブログを通じて、とても良い変化が起きていると思います。引き続き良い変化を良い活動、良いアウトプットへつなげていけるように頑張ります。

ga-analytics に Node.js 10 以上対応のプルリクを送る

AWS Lambda が Node.js 10 に対応したので、Node.js 8 で作っていたプログラムの Node.js 10 対応を進めています。ところどころでエラーが出るので修正。このブログで使っているライブラリにも Node.js 10 で動かした時にエラーの出る部分があったのでプルリクを送りました。

2019年5月現在、Node.jsの最新バージョンは v12 です。AWS Lambda が v10 に対応。ようやくといった感じも受けますが、Node.js の LTS は v10 なので現行ともいえるでしょう。

Node.js のロードマップは、こちらhttps://nodejs.org/ja/about/releases/

AWS Lambda のバージョンアップに合わせて各プログラムのバージョンアップ対応をしています。その中で本ブログの Node.js バージョンを上げたところエラーが発生しました。せっかくなので修正とともにプルリクエストを上げます。
※ 本ブログは Static Site Generator のHexoを使っており、最近の Node.js v12 を使えますが、全体(= AWS Lambda を使ってるプロジェクト)に合わせて Node.js v8 を使っていました。今回も同様に v12 ではなく v10 にします。ただし検証と修正は v12 で動作するところまで確認。

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

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Visual Studio Code
  • Node.js 8.10.0 / 10.15.3 / 12.3.0
  • sfarthin/ga-analytics 0.0.7

プルリクを出すことになった背景

本ブログのプロジェクトを Node.js v10 で起動すると、以下のエラーが表示されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
username@pc:~/website$ yarn dev
yarn run v1.15.2
$ node -r dotenv/config node_modules/hexo-cli/bin/hexo server
fs.js:126
throw new ERR_INVALID_CALLBACK(cb);
^

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function.
at maybeCallback (fs.js:126:9)
at Object.writeFile (fs.js:1159:14)
at /home/username/website/node_modules/ga-analytics/module.js:89:6
at /home/username/website/node_modules/ga-analytics/module.js:69:7
at FSReqCallback.readFileAfterClose [as oncomplete] (internal/fs/.js:61:3)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

プロジェクト配下のnode_modules/ga-analytics/module.jsの 89行目でCallback must be a function.とのこと。

インストールされた npm モジュールga-sfarthin/ga-analyticsにトラブルがあるようです。今回、直接インストールしたものではありませんが、ブログの人気記事リストを作るtea3/hexo-related-popular-postsplugin が使っています。

詳しくは、こちらの関連記事をご参照ください。

問題を把握したところで重複レポートを避けるために GitHub Issue / Pull Request を確認します。どちらにも該当はありませんでした。

エラーの原因特定

では改修できるのか、プルリクを出せるか、ソースを確認します。(ファイル先頭のrequireと、後続処理も合わせて抜粋)
エラーログが指しているのは、下記抜粋の9行目fs.writeFile(sessionFile, JSON.stringify(result));です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var googleapis 	= require('googleapis'),
OAuth2 = googleapis.auth.OAuth2,
fs = require("fs"),
_ = require("lodash"),
moment = require("moment");

// ...(省略)

fs.writeFile(sessionFile, JSON.stringify(result));

oauth2Client.setCredentials({
access_token: result.access_token,
refresh_token: result.refresh_token
});

このfs.writeFile()は Node.js の基本 API で、ファイルにデータを非同期処理で書き込みをします。非同期処理なので、ファイル書き込み処理が始まると完了を待たずに後続処理、ここではoauth2Client.setCredentials({})へ進みます。
そしてファイルの書き込みが完了するとfs.writeFile()のコールバック関数が実行されます。

問題となっているエラーはCallback must be a function.です。ざっくり言うと「コールバックはfunction(関数)にして」です。

つまりfs.writeFile()にコールバックの関数を渡してほしいと。そして現状どうなっているかというとfs.writeFile(sessionFile, JSON.stringify(result))で、コールバック関数を渡していないです。
※ コールバック関数は第3または第4引数で渡します。

参考情報

検証

原因が分かったところで検証します。
今回は完全に独立したコードで検証できるのでTypeScript プロジェクト用サンドボックスで簡単コード検証の環境を使います。TypeScript プロジェクトですが、TypeScript は JavaScript のスーパーセットで JavaScript プロジェクトとしても動作します。検証は簡単に実行できれば、どのような環境でも大丈夫です。

index.tsに以下のコードを書きます。

1
2
const fs = require('fs');
fs.writeFile('/tmp/test.txt', JSON.stringify({ text: 'test'}));

まずは、最新の環境 v12 で試します。
エラー内容は TypeScript になっていますが、エラーメッセージが出ています。

1
2
v12.3.0
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function. Received undefined

私の環境 v10、メッセージが少し変わりましたがエラーです。

1
2
v10.15.3
TypeError [ERR_INVALID_CALLBACK]: Callback must be a function

これまでの環境 v8、動作します。
しかしながらwithout callback is deprecated、「コールバック無しは非推奨」の警告が出ています。

1
2
v8.16.0
(node:242) [DEP0013] DeprecationWarning: Calling an asynchronous function without callback is deprecated.

これでコールバック無しの呼出し方法が「v8 非推奨」が「v10 廃止、エラー」になったことがわかりました。
では、コールバック有りに修正すれば OK です。

検証用のindex.tsでは下記。

1
2
3
4
const fs = require('fs');
fs.writeFile('/tmp/test.txt', JSON.stringify({ text: 'test'}), function(err) {
console.error(err);
});

sfarthin/ga-analytics のコードでは下記(必要箇所のみ抜粋)

1
2
3
fs.writeFile(sessionFile, JSON.stringify(result), function(err) {
final_callback(err)
});

なお TypeScript で実装するとすぐにわかるのですが、型チェックエラーで引数が足らずコンパイルも実行もできないです。型チェック素晴らしいですね。

[補足]
sfarthin/ga-analyticsの最終更新は 2014-11-21 のバージョン 0.0.7 。当時は Node.js v6 (v8 のリリースは 2017-05-30)なので、非推奨もされてない時代です。
TypeScript のNode.js 定義writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException | null) => void)callback?なのでオプショナルです。

それから5年間メンテが止まりつつも、実行環境で問題はなかったのかもしれません。v8 は非推奨でも動作するし、LTS でサポートもまた続いているので大きな影響が出てなかったのかもしれません。

プルリクの作成

さっそくプルリクを作って送ります。
sfarthin/ga-analyticsのリポジトリから [Fork] で、自分のアカウントにフォークしてローカルへクローンします。
ボタンを押してクローンするだけなので今回は省略しますが、詳しい手順を確認したい場合はこちらをご参照ください。

今回も単発のプルリクなので、1つしかないmasterブランチへ直接コミットして上げてしまいます。
ただし、今回は注意点があります。

  • インデントがタブ
  • 空行もインデントされている
  • ファイル末尾に改行コードがない

これを壊さないように変更しコミットする必要があります。
私の環境の Visual Studio Code と Eclipse は、すべて反対の設定になっているため難しいです。今回はコンソール WSL Ubuntu 18.04.1 LTS からviで修正することにしました。しかしviでもファイル末尾に改行コードを自動的に入れてくるので、保存する前に:set binary noeolを実行してから保存します。(お使いの vi/vim によって異なるかもしれません)

フォークした自分のリポジトリから [New pull request] ボタンでプルリクを作り送ります。

取り込まれますように 🙏


Node.js のバージョンアップに伴いエラーが発生したのを修正するプルリクでした。今回はコーディング規約(明文化されてないけど読み取って)の部分で苦戦しました。普段のコミットも同じですが、本来変更したい部分と関係ないところに差分があると考えてしまいます。無用な差分が発生しないように変更してプルリクを出したいところです。(自分のプロジェクトだとつい許容してしまいますが、できればコミットを分けたいところ)

そして “オープンソース フライデー | 今週の金曜日は、利用しているお気に入りのソフトウエアを数時間助けてみよう。“ に登録してみました。(typo してる。。。 ⇒ ×始めて見る 〇はじめてみる)

直近3ヶ月、本記事を含めて月1つのペースでプルリクを出していました。そして、ブログメンターのカック@ブロガー / k9u(@kakakakakku)さんとお話をしている中で「自分が使っているプロダクトの Issues を修正するのは喜ばれることだし、自分も助かる。2倍嬉しい。」と勧められました。エラーの修正は必然なのでともかく、機能の追加改善などは自分のためだけでなくみんなのためにするべきです。前回の記事ブログで使っている Hexo の SNS 共有リンクに記事タイトルを入れるなども自分だけではなくプルリクで上げたほうが良かったです(後で上げます)。そうしたことから、”オープンソース フライデー” として、何かしらの OSS 活動を意識するような日にしたいと考えました。金曜日は OSS の取り組みを少しだけでもするようにします。

参考情報

ブログで使っている Hexo の SNS 共有リンクに記事タイトルを入れる

本ブログで使っている Hexo および、デフォルトのテーマ landscape。とても汎用的で便利ですが、汎用性が高い反面細かいところは自分で作りこむ余地が多くあります。記事の下にある SNS 共有リンクについて、記事タイトルを入れる強化をはかります。

これまで「SNS 共有リンクをダイアログから展開」したり、「はてなブックのリンクを追加」したりと強化してきました。
今回、ブログメンターのカック@ブロガー / k9u(@kakakakakku)さんから、ツイートボタンにタイトルが入ってないことを教えていただき、確認したところ URL だけが入っていることが確認できました。この部分を改善します。

現在の状態

まずは現在の状態を確認します。
記事の下まで行って [共有] リンクから [Twitter のアイコン] をクリックします。[リンクをあなたのフォロワーに共有する] ダイアログが表示されて、確かに URL だけが入っています。

このままでは、いくら URL が Twitter Card で展開されるとはいえ、ただの URL をツイートしただけになってしまいます。また一生懸命考えて付けたタイトルは(使ってくれるかは別として)できればツイートにのっけてもらいたいものです。要改修です。

改修する場所を探して修正

ソースの改修する場所を探します。Hexoの landscape テーマが生成している部分で、自分で書いてないので思い当たる箇所はありません。Chrome DevTools の力を借りることにします。

[共有] の [Twitter アイコン] を右クリックして [検証] を選択します。
Chrome DevTools が表示され<a>タグがハイライトされます。その中から全文検索用のキーワードを考えます。今回はclass属性のarticle-share-twitterで検索すると良さそうです。

Visual Studio Code で全文検索したところarticle.ejsがかかりました。(残りは自分の記事と、スタイルシート)

該当箇所を確認すると以下のコードになっています。

1
<a href="https://twitter.com/intent/tweet?url=<%- post.permalink %>" class="article-share-twitter" target="_blank" title="Twitter"></a>

https://twitter.com/intent/tweetに、パラメーターで記事の URL を渡しているだけのようです。ここにタイトルの文字列が入れば OK ということで、Twitter の URL の仕様を確認します。

Web Intent — Twitter Developersによると、クエリーパラメーターtextで、UTF-8 および URL エンコードされた文章を設定すれば良さそうです。また、表示されるダイアログではテキストが選択された状態になり、ユーザーが削除や編集しやすいようになっているとのこと。

他にもhashtagsなどがありますが、今回はブログ記事をツイートしてもらうためのボタンであり、何かのハッシュタグを付けてアクションするようなアプリではないので使わないことにします。この辺は投稿者さんの任意ですね。

参考情報

改修して、下記にします。
クエリーパラメーターtext=<%- encodeURI(post.title) %>で URL(URI) エンコードされたタイトルを入れています。

1
<a href="https://twitter.com/intent/tweet?text=<%- encodeURI(post.title) %>&url=<%- post.permalink %>" class="article-share-twitter" target="_blank" title="Twitter"></a>

できた!

よく見るような感じになりました。


今回は、ちょっとライトな感じの記事です。あえて書くまでもないような感じもありましたが、フレームワークやライブラリなどを使っている場合に、自分が作ってない部分の修正方法についての記事はあまりないのかなと思うと、せっかくだから残しておくことにします。(といっても、この記事をググるのは難しそうですが)

現在はデフォルトテーマに頼っていますが、ちゃんとしたボタンを記事の前後に配置したり、ボタンにカウントを表示したりしたいです。テーマもデフォルトでなく変えたいところですが、なかなか手が回らない。。。時間をとって、コツコツと改善ですね。

SPAJAM 2019 東京A予選 - ハッカソン戦記

モバイルアプリ・ハッカソンの SPAJAM 2019 東京A予選に参戦してきました。
2日間で開催され 24時間でのアプリ開発、本選をかけての熱いハッカソンの戦記です。

いろいろなアウトプットがある中で、ハッカソンはとても好きです。
アイデア、企画設計、開発、テスト・デバッグ、プレゼンテーション、文章、チームワーク、判断に決断などと、とても多くのスキルを限られた時間の中で全力投入して形を作り上げ、それを強力なライバルたちと競い、そして打ち解けあう。

自分が発想をできないようなアイデアを見せてもらったり、新しい技術を教えてもらったり、同じ技術でも使い方使いこなしを目の当たりにしてと、多くのことを学ばせてもらえます。

また、自分自身のスキル点検というかテストを受けるよう気持ちもあります。アイデアは衰えてないか、柔軟さを失ってないか、技術力はキープできてるか、新しいものを取り込めているかなど。

そんな楽しい時間を、今回は SPAJAM という全国の猛者が競い合うハッカソンで過ごさせてもらいました。どんなイベントなのか、どんな戦いだったのかを綴ります。

とても素晴らしいイベントなので興味を持っていただければと、そしてぜひ参加して熱い戦いを楽しみましょう。

SJAPAM 概要

SPAJAMとは “「温泉でハッカソン」を合言葉に、スキルを向上するための競技と交流の場を提供する国内最高峰のハッカソン -What’s SPAJAM“ です。

“国内最高峰” を謳うとおり、日本全国で予選を行い、予選を勝ち抜いたチームで本戦を行うという大規模なイベントです。2019年は全国6地域7会場で予選が行われています。

本戦入賞者には豪華な賞品が出ます!
2019年の最優秀賞は「深セン・上海スペシャルツアー」!!
副賞多数も多数で去年の実績は、こちら結果発表 | SPAJAM2018公式サイト – 温泉でハッカソン

ハッカソンのテーマは当日発表され、そこから24時間かけてのハッカソン勝負となります。主なスケジュールは下記。
1日目

  • 開場: 開会(挨拶・趣旨説明・テーマ発表)
  • 午前: アイデアソン
  • 午後: ハッカソン

2日目

  • 午前: ハッカソン
  • 午後: プレゼン、審査&表彰式

メンバーは1チーム5人までで、自分たちでチームを作って参戦します。
あらかじめチームを作って参加できるので安心です。

なお、本選出場とならないと「温泉」は楽しめません。
予選は協賛企業さんのオフィスを使わせていただいての開催となります。今回の東京A予選は株式会社gumiさん、新宿中央公園のお隣でした。

参戦経緯

もちろん、ハッカソンが好きで楽しみたいから!
というものありますが 2018年 SPAJAM 東京D予選で優秀賞を手にするも、本選出場ならずのリベンジ戦でもあります!!

その際の発表資料と作品は、こちら。

発表資料:リアルタイムの競演と参加型観戦で音楽を最高に楽しむ「ラップ、タップ、アップ🎶」

作品:ラップ、タップ、アップ 🎶

そしてなんと、2018 東京D予選で最優秀賞を取った方が、メンバー構成は違えど今年も同じ会場という偶然。
さらに、現在私が受けているブログメンタリングの同期さん(他のハッカソンで優勝経験あり)も同じ会場!
強敵すぎるライバルたちとの、熱いハッカソンとなりました。

そして我らがチーム。

2人。。。そう、2人、通称「ねこ」と「うさぎ」で参戦です。
ふざけてたり、あまくみてるわけでなく。年明けからチーム編成とか、とくにデザイナーさんを探そうとか、たくさん考えていました。ところが「イベントでの発表の連続」「仕事のピーク」「ブログメンティー開始」とか、いろいろしてたら〆切前日💦

でも「あきらめたらそこで試合終了だよ」ですよね。たとえ2人でも本戦、全国制覇の夢はあきらめられません!参戦です!!

テーマ


テーマ「NEWS」。毎度ざっくりではありますが、今回は硬い。硬いなぁ。
聞いた瞬間に一瞬眩暈がしました。難しい。

なお、テーマについての説明はありません。
どうとらえ、どのような作品にするのかは参加者にゆだねられています。審査項目の1つ “テーマ性(テーマに沿って利用者と共感できる価値を提供できているか)-予選概要 | SPAJAM2019“ がありますが、説明できればオーケーです。

今回、アイドルグループも連想されますが審査基準を満たせればオーケーだと思われます。実際に2018年本戦では “モビリティというテーマから、調味料が動くに発想を転換した点 -結果発表 | SPAJAM2018“ 他が評価され審査員特別賞を受賞されたチームがありました。

某アイドルグループの連想、やってみたかったですが詳しくないのでやめました。今考えても、いいアイデアが浮かばないので、ムリは良くないです。ハッカソン中の中間インタビューでは「某アイドルグループ」のアイデアを考えているチームもありましたが、最終発表では発表がなかったので断念してしまったのかな。見たかったので残念です。

アイデアソン

まずは全体でアイデアソンです。みんなで、いろいろなアイデアを出して思考の枠を広げます。こちらのフレームワーク、すごくいいですよね。

アイデアソンは各チームのテーブルで行い、時に他チームのアイデアを見て歩くようなスタイルで行いました。

最初に、この1日で心が動いたことについてのワーク。
あったことを書き、その時の感情を顔の絵文字キャラで表現し、気持ちを書く。何人か発表して共有。

私は「今朝、熊野神社でお参りをした。」「ハッカソンではやる気持ちが静まり落ち着いた。」でした。顔の絵文字キャラは、絵心がなさ過ぎてヤバいものを書いてしまいました。ヤバすぎるので貼るのはやめておきます。

続いてテーマ「ニュース」について、イメージや思いついたことなどを付箋で書き出し。その後、各チームのテーブルを回って共有。ニュース番組名や話題の人などいろいろ出ていました。

もう一度最初のワークのフォーマットで、テーマについて喜怒哀楽の表現。それをもって、4人の参加者とアイデア交換&インタビュー。皆さん色々な発想をされていてすごかったです。「協賛の株式会社エーアイさん、提供の高品質音声合成エンジンAITalk®+ Vtuber」といったアイデアなども出ていました、

そこから上記スライドのフォーマットで、より具体化。発表して共有という流れになります。

まずは「共創」の考えで、たくさんのアイデアが出ますのでアイデアが浮かばずに終了ということはないので、安心して参加できます。

去年はアイデアソンの時間はチームとばらけて作業し、できあがった資料や喜怒哀楽シートなどが張り出されていましたが、今年はチームでやり張り出しはなかったです。張り出したほうがいいのに、どうしたんだろう。聞けばよかった。(実際に去年の予選で優秀賞とったチームが、最後まで張り出しをみて悩んでギリギリでアイデアが作れたと発表してました)

ハッカソン!

アイデアソンが終わったらハッカソン開始。ここからは「競争」です。
ちょうど、お昼なので各チーム食事へ向かいながらアイデアを検討。私たちもファミレスに入ってアイデア練り。幸いにも昼食時点で方向性も決まり、会場に戻ってすぐにコーディングへ。2人だから、どんどん進めないと間に合いません。

午後はひたすらコーディングを進めましたが、何も準備してないし、人手も足りないのでベース部分の作成で時間をとってしまいました。基本的には慣れている Nuxt.js PWA と AWS Lambda で Web API の構成です。

開場は20時まで、それ以降はメンバーの家なり宿泊施設なりで作業です。複数日での開催は宿泊をどうするのか考えておく必要があります。また SPAJAM 本戦は会場が宿泊施設で主催者提供です。(♨ 温泉いいなぁ!)

予想されていたことではありましたが、結局徹夜。ちゃんと寝ないと体にこたえるので良くないのだけど。

意識高いポッドキャストで、意識(こっちの場合は睡眠不足による意識不明のほうの意識)を失わないよう高める感じで。とても面白かったです。夜間の開発とかはポッドキャスト(ラジオがやってるならラジオも)が良さそうです。

そして夜明け頃に問題発生!肝心の機能が AWS Lambda で処理しきれない😱
各種パラメーターを最大にしても時間内に処理が終わらない。急遽 DynamoDB Streams を併用する方向に転換するも、こちらも処理しきれず。

やりたかったことは形態素解析です。処理能力不足なのか、情報量が多いのか、設定が悪いのか、とにかく処理時間がかかって機能させられませんでした。タイミング的にも、人手的にも厳しい状況でした。悔しいな。(今後、ちゃんと調査して再実装します。お金の力でクラウドとかに逃げる手もありますが、それだとサービスの継続が厳しいので難しいところです。)

この時点でプレゼン資料は未着手なので、泣く泣く私はプレゼン側へ作業転換となりました。まだ他にも必要な機能が残っており「うさぎ」1人での実装では間に合わない部分が出てしまうことに。

発表!「📰NEWʑ Link」!!

できあがった作品は「パーソナルニュースの配信と交換によって爆速で仲良くなるアプリ「📰NEWʑ Link」」です!!

https://riotz.works/slides/2019-spajam-qualification

テーマ「NEWS」に対して、私たちは「個人がニュースを発信し、嬉しい楽しいの思いを広げてつなげていきたい」というコンセプトに設定しました。「1億総メディア時代」個人がニュースを発信、その中でもパーソナルで嬉しくなるニュースを扱いたいと考えました。

スライド2ページ目ですが、ここ1週間のことなのですが、見ててほっこり、見てるこっちに嬉しくなるような TL が流れてました。世の中には楽しいパーソナルなニュースがあふれています。

そのニュースをつなげ、嬉しい楽しいの輪が広がり、そしてまた新しいニュースが生み出され発信されていく。そんな世界観を作り出すアプリです。

そしてお互いの興味が重なる部分、それが共感であり、人は同じようなことに共感している人と仲良くなりやすいというのがあります。そこに着目し、お互いのニュースを交換し共通点を見つけ出して表示することで親密度を上げらられ、仲良くなるきっかけを作り出せるのではないかと考えました。

こちらは、まだ打ち解けきれてないところで使ったり、グループに初対面の人が入った時や、会議や研修のアイスブレイクなどを想定しています。このニュースの交換は当初Web NFCを使う予定でしたが、時間切れとなってしまい QR コードとなりました。
(ちょうど別件で Web NFC が欲しくて勉強する予定でしたが、ぶつけ本番となってしまった)

アーキテクチャは以下ですが、詳細記事を書きます。

  • Nuxt.js の PWA。JAMstack になります。
  • サーバーサイドは AWS で、サーバーレス(API Gateway, AWS Lambda, DynamoDB)。

審査&結果発表

残念ながら、受賞には至りませんでした。

「NEWS」というテーマにアイデアが追い付かなかったのと、技術の引き出し不足でした。

何とかアイデアを絞り出しアプリを作ったものの、こうして振り返ると「個人がニュースを発信したくなる」としているのに、その仕掛けが入っていないと、すぐに思いつくレベルで練り切れてないのがわかります。いろいろなテーマに対しても柔軟に対応できる発想力をもっと鍛えねば。。。

最優秀賞は、去年の同じ予選の覇者さん。もう3回連続で予選最優秀賞なのだとか。すごい!!

そしてブログメンティーの同期さんは、優秀賞。すばらしい!!さすがハッカソンでの優勝経験者。すごいです。

また、他の参加者さんもステキな作品ばかりで、アイデアも実装力も感心感嘆するばかりでした。またどこかのハッカソンでお手合わせしましょう。2日間、ありがとうございました。

東京A予選 -結果発表 | SPAJAM2019公式サイト – 温泉でハッカソン

写真


左上、新宿中央公園と三連ビル。
中央、勝利のビル!?見た時に勝利を確信したけど、みんな見てるって。
右上、会場案内。
下段、協賛のモンテールさん提供のお菓子。ごちそうさまでした!


最高に楽しい2日間!!。レベルの高い人たちと戦うことができ楽しかったし、賞は取れなかったけど、現在持てる力はすべて投入できました。

反省点は多々あるものの、総じて満足のいく結果でした。
また次のハッカソンで反省点を活かし、さらなる技術力をつけて来年の SPAJAM にも挑戦します!!

ぜひハッカソンの場で一緒に楽しみましょう♪

また、アプリは早めに改修を終えて公開できるようにします。
技術パートについては、ブログにします。


ところで、そもそも、このアプリ、ハッカソンへ参加する前に必要だったのではないだろうか 🤔
爆速で仲良くなって、SPAJAM のメンバーになってもらうために。。。 😇

TypeScript プロジェクト用サンドボックスで簡単コード検証

TypeScript を使うと JavaScript のコードに型を導入することができ、コードの安全性や生産性を高めることができます。いっぽうで TypeScript は JavaScript にコンパイルしてから実行する必要があり、ちょっとしたコードの確認をするにしても一手間かかります。
そこで検証を簡単にするため、プロジェクト内のファイル変更を監視し、ファイルの変更があったら自動的に再コンパイル&実行してくれる環境を作ります。

TypeScript では、ちょっとしたコードを試したいときでもtscでコンパイルしてnodeで実行してと、コンソールでの作業が必要となります。コードの動きを試したかったり、ネットで見つけたおもしろいコードを動かしたいといっただけでも手間がかかります。
今回は TypeScript 用のサンドボックス・プロジェクトを用意して、手軽にコードを試せるようにします。(サンドボックスというとセキュリティモデル的な感じもしますが、今回はお砂場遊び的な感覚。最近はプレイグラウンドと言う?)

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

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Visual Studio Code
  • Node.js 12.2.0
  • Yarn 1.15.2
  • TypeScript 3.4.5

TypeScript プロジェクトの作成

いろいろなモジュールが組み合わさるので、順を追って作っていきます。
まずは基本となる TypeScript プロジェクトを作成します。

プロジェクトのディレクトリを作成し/package.jsonファイルを作ります。(今回はsandboxという名前のディレクトリ)

  • devDependenciesは、常に最新版のモジュールを使いたいためにバージョンは"*"としています。
  • scripts: cleanは、最新のモジュールを使いやすいようにnode_modulesなどを削除します。OS に依存しないようnpx rimrafで削除しています。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "name": "sandbox",
    "private": true,
    "dependencies": {},
    "devDependencies": {
    "@types/node": "*",
    "typescript": "*"
    },
    "scripts": {
    "clean": "npx rimraf yarn.lock node_modules dist",
    "setup": "yarn clean && yarn install --ignore-optional"
    }
    }

同じくプロジェクト直下にtsconfig.jsonを作ります。
こちらは普段使う TypeScript の設定に合わせておくとよいでしょう。今回はesnextで最新仕様を使い、"strict": trueでチェックを厳しくしています。

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
{
"compilerOptions": {

/* Basic Options */
"target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"removeComments": true, /* Do not emit comments to output. */

/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"paths": { "~/*": [ "./src/*" ] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

/* Additional Options */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"newLine": "LF", /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix). */
"resolveJsonModule": true, /* Allows for importing, extracting types from and generating .json files. */

/* Specify library files to be included in the compilation. */
"lib": [
"esnext"
]
},
"include": [ "./src/**/*.ts", "./test/**/*.ts" ]
}

これで/srcディレクトリ以下の TypeScript ファイルをコンパイルして実行する環境ができました。
/src/index.ts

1
console.debug('Hello World !');

コンパイルして実行。

1
2
3
$ yarn tsc -p .
$ node dist/index.js
Hello World !

ts-node で TypeScript を直接実行

コンパイルの手間をなくして、TypeScript をコンソールから直接実行できるようにします。

1
$ yarn add -D ts-node@* tsconfig-paths@*

/package.jsonに以下のstartスクリプトを追加します。(scriptsのみ抜粋)

  • ts-nodesrc/index.tsを実行。(以降、プログラム実行の起点はindex.tsになります)
  • TypeScript の alias path をts-nodeで使えるように-r tsconfig-paths/registerを登録します。
    1
    2
    3
    4
    5
    6
    7
    {
    "scripts": {
    "clean": "npx rimraf yarn.lock node_modules dist",
    "setup": "yarn clean && yarn install --ignore-optional",
    "start": "ts-node -r tsconfig-paths/register src/index.ts"
    }
    }

実行!新たに1行追加しましたがtscなしで動作します。また先ほど作られたdistディレクトリは削除して大丈夫です。
/src/index.ts

1
2
console.debug('Hello World !');
console.debug('Hello TypeScript !!');
1
2
3
$ yarn start
Hello World !
Hello TypeScript !!

nodemon でファイルの変更を検知して TypeScript を直接実行

さらにファイルの変更を検出して、自動的にts-nodeを実行できるようにします。

1
$ yarn add -D nodemon@*

/package.jsonstartスクリプトを修正します。(scriptsのみ抜粋)

  • nodemon -w srcで、nodemonから起動しsrcディレクトリを監視します
  • -x ts-nodeで、nodemonから実行するプログラムts-nodeを指定しています
    1
    2
    3
    4
    5
    6
    7
    {
    "scripts": {
    "clean": "npx rimraf yarn.lock node_modules dist",
    "setup": "yarn clean && yarn install --ignore-optional",
    "start": "nodemon -w src -x ts-node -r tsconfig-paths/register src/index.ts"
    }
    }

実行!今回はyarn startから先に始めます。
先ほどまでの Hello World, TypeScript がコンソールに表示され、その後にnodemonが変更待ちをしている状態になります。

1
2
3
4
5
$ yarn start
...(省略)
Hello World !
Hello TypeScript !!
[nodemon] clean exit - waiting for changes before restart

ここで/src/index.tsを変更し、保存します。

1
2
3
console.debug('Hello World !');
console.debug('Hello TypeScript !!');
console.debug('Hello Sandbox Project !!!');

するとファイルの変更を検知して、コンソールに新しい結果が自動的に出力されます。

1
2
3
4
5
6
...(省略)
[nodemon] restarting due to changes...
[nodemon] starting `ts-node -r tsconfig-paths/register -r dotenv/config src/index.ts`
Hello World !
Hello TypeScript !!
Hello Sandbox Project !!!

コードの検証で利用

実際にコードの検証で使ってみます。

最近気になったのが、こちらソート可能なUUID互換のulidが便利そう - Qiitaの記事。
よく UUID v4 でランダムな ID 発行を使っています。それに対してソート可能なランダムな ID 発行できるというのは興味深いです。記事は Python ですが Node.js/TypeScript にもulid/javascriptモジュールがあります。

モジュールを導入し、サンドボックス・プロジェクトを起動しておきます。

1
2
3
$ yarn add uuid ulid
$ yarn add -D @types/uuid
$ yarn start

単なるコンソール出力ですがコードを書いて保存するだけで結果が見れます。

1
2
3
4
5
import { ulid } from 'ulid';
import * as uuid from 'uuid';

console.debug(`ULID: ${ ulid() }`);
console.debug(`UUID: ${ uuid.v4() }`);

ランダムな値を生成するので保存するたびに変わるのがわかります。
“また UUID との 128 ビット互換性” ですが、表現方法は異なるようです。ソートの必要があるかはシステム次第ですが、文字数が詰まるのは良さそうです。また TypeScript 対応されているのも Good!

1
2
ULID: 01DB2S8RCY1EM7XBJG6P6A2KFE
UUID: 67d6e72b-97d9-4a65-ac92-35c476e02f7b

もうひとつ、こちら日付時刻操作ライブラリをmomentからdayjsへ乗り換えた - Qiitaの記事。

普段、日時を扱う場合には Moment.js を使っていますが、もっと軽量のiamkun/dayjsがあるとのこと。
サーバサイドの開発では軽量に越したことはありませんがシビアに考えることは少ないですが、フロントでは大事なポイントです。

さっそくモジュールを導入し、サンドボックス・プロジェクトを起動しておきます。

1
2
$ yarn add dayjs moment
$ yarn start

ここではコードをまとめて書いていますが、保存しながら書き足している感じで作業しています。コーディングしながらCtrl + Sして、コンソールが流れている間に次のコードといった感じです。
デフォルトのタイムゾーンとフォーマットが異なるものの、通常は ISO 文字列で使ったり、フォーマットのテンプレートを指定したりするので問題はないでしょう。2KB の軽さは魅力的です。

1
2
3
4
5
6
7
8
9
10
11
import dayjs from 'dayjs';
import moment from 'moment';

console.debug(`Day.js: ${ dayjs() }`);
console.debug(`Moment.js: ${ moment() }`);

console.debug(`Day.js: ${ dayjs().unix() }`);
console.debug(`Moment.js: ${ moment().unix() }`);

console.debug(`Day.js: ${ dayjs().toISOString() }`);
console.debug(`Moment.js: ${ moment().toISOString() }`);

徐々にコンソール出力が増えていくのを確認しながら、動きの差異などを確認しています。

1
2
3
4
5
6
Day.js:    Fri, 17 May 2019 13:52:02 GMT
Moment.js: Fri May 17 2019 22:52:02 GMT+0900
Day.js: 1558101122
Moment.js: 1558101122
Day.js: 2019-05-17T13:52:02.637Z
Moment.js: 2019-05-17T13:52:02.637Z

以上、ちょっとしたコードの検証に使えるサンドボックス、お砂場プロジェクトでした。

このようなプロジェクトを1つ作っておくと、TypeScript で型を調べたり、新しいライブラリの使い方を確認したりといった際に便利です。

nodemon まで入ったところで Git にコミットしておくと、散らかってもクリーンな環境へ簡単に戻せます。
私は AWS Lambda で環境変数を使うことが多いので、さらにmotdotla/dotenvも追加しています。また、TSLint もかけています。また Strict なコーディングの確認や練習のためにTSLintも設定しています。(ESLint へ引っ越さないと💦)
ソースはこちらlulzneko/sandbox

Nuxt.js PWA のベースアプリを GitHub Pages へデプロイする

Nuxt.js を使うことで手軽に PWA なアプリを作ることができます。そして開発した PWA を世に出すためには、サーバー環境へデプロイする必要があります。Nuxt.js を SPA の JAMstack な作りにしている場合は、ウェブサイトをホスティングできる環境であれば、どこでも大丈夫です。今回は GitHub Pages でホスティングをします。

前回までの実装でNuxt.jsPWAwithTypeScriptができました。アプリとしての機能はまったくないですが、やはりデプロイして実際にスマートフォンからアクセスしてみたいところです。今回は簡単にデプロイできる GitHub Pages でホスティングします。また、一手間かかりますが CircleCI とも連携して、アプリのソース管理とホスティングを同じリポジトリで行い、ソースコードを変更したら自動的にビルド&デプロイする方法も紹介します。

シリーズの記事

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

GitHub Pages へデプロイ

GitHub Pagesは、ソースコード管理GitのホスティングサービスGitHubが提供する、ウェブサイト・ホスティングの機能です。
ユーザーや組織のウェブサイト、またはアプリなどのプロジェクト・ウェブサイトを公開できます。GitHub Pages はプログラムなどを実行できませんが、静的なサイトを手軽に公開できます。

まず GitHub にホスティング用のリポジトリを作成します。
[リポジトリ名] は、任意の名前です。 (今回はpwa-base-appとしました)
※ プロジェクトサイトを想定しています。ユーザーまたは組織サイト(リポジトリ名がusername.github.ioのパターン)の場合は設定が異なるので各項目で注釈を入れます。ご注意ください。

リポジトリをクローンしてローカルの開発ディレクトリに、アプリのソースをコピーしておきます。

GitHub リポジトリの [Settings] から GitHub Pages を有効にします。

  • [develop] はmaster branchを選択します

nuxt.config.tsに以下の設定を追加します。(必要箇所のみ抜粋)

  • build: publicPathは、CSS や JS などが置かれるディレクトリでデフォルトの_nuxt_始まりが GitHub Pages で使えないため変更します。(_で始まらなければ任意の文字列で大丈夫です)
  • router: baseは、GitHub Pages のプロジェクトサイトがサブパスのリポジトリ名に配置されるので[リポジトリ名]を設定します。
    ※ ユーザーまたは組織サイトの場合はドメイン直下に配置されるためrouter: baseの設定は不要です
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const config: NuxtConfiguration = {
    // ...(省略)

    build: {
    publicPath: '/static/',
    extend(config, ctx) {
    },
    },

    router: {
    base: '/pwa-base-app/'
    },
    }

GitHub Pages デプロイ用のモジュールを導入します。

1
$ yarn add -D gh-pages

※ npm の場合npm i -D gh-pages

デプロイの実行!
以下のコマンドでビルドとデプロイをします。

1
2
$ yarn build
$ yarn gh-pages -d dist -b master -m 'Update Site'

コンソールにPublishedが表示されたら GitHub Pages の設定で表示されていた URL にアクセスします。(ちょっとタイムラグがあるかもしれません。表示されなかったら少し待って再アクセスしてください。)

以上、簡単に GitHub Pages へデプロイでした。
毎回コマンドを打つ必要がありますが、GitHub Pages のリポジトリを用意するだけで簡単にホスティングできます。

CircleCI と連携した自動デプロイ

GitHub Pages へデプロイするのはコマンド1つで簡単ですが、開発中にちょくちょく実行するのは手間です。ソースがプッシュされたら自動でビルドしてデプロイしておいて欲しいです。これは CircleCI と GitHub を連携させることで実現できます。

ちょっと手間がかかりますが、設定を1回するだけで後はリポジトリにプッシュすると自動的にデプロイしておいてくれるので便利です。なにより「常にアクセス可能なプロトタイプ環境」が実現できるのでありがたいです。

CircleCI とは

CircleCIは Continuous Integration and Delivery と、サイトのタイトルにつけている通り CI(Continuous Integration)/CD(Continuous Delivery) の機能を提供するサービスです。
ソースコードの変更を受け、あらかじめ定義されたビルドやテスト、デプロイといったことを自動的に行ってくれます。無料で始められるので、CI/CD の入門や小規模なプロジェクトでも使いやすいですし、大規模なプロジェクトにもしかりと対応してくれます。

GitHub リポジトリにデプロイキーを追加

GitHub に CircleCI からデプロイできるようにデプロイキーを追加します。
まずssh-keygenで、パスフレーズなしの ECDSA キーペア(~/deploy_key~/deploy_key.pub)を作ります。
※ すべての作業が終わったらローカルのキーペアファイルは削除します

1
$ ssh-keygen -t ecdsa-sha2-nistp521 -C "" -f ~/deploy_key

※ ECDSA が使えない場合はssh-keygen -t rsa -b 4096 -C "" -f ~/deploy_key

そして~/deploy_key.pubの内容を、GitHub リポジトリの [Settings] - [Deploy keys] に貼り付けます。

  • [Title] は、あとでわかるようにします。(今回は CircleCI 用なのでCircleCIとしました)
  • [Key] は、~/deploy_key.pubの内容です。
  • [Allow write access]: チェックを付けます。これを忘れると CircleCI からプッシュできません。

追加されたキーのFingerprintをひかえておきます。

プロジェクトの npm スクリプトを設定

プロジェクト直下/package.jsondeployスクリプトを追加します。generateの行末にカンマを忘れないようにします。(scripts のみ抜粋)
これにより先ほどのデプロイコマンドをyarn deployだけで実行できるようになります。[ci skip]は CircleCI を動作させないためのコミットメッセージになります。masterブランチは GitHub Pages の公開用 HTML なので CI は使わないためです。

1
2
3
4
5
6
7
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"deploy": "gh-pages -d dist -b master -m 'Update Site [ci skip]'"
},

CircleCI の設定ファイルを作成

プロジェクト直下に.circleciディレクトリ、そのディレクトリにconfig.ymlファイルを追加します。
/.circleci/config.ymlに以下の部分を環境に合わせて変更したものを書きます。

  • 58行目 [fingerprints]: 上記 GitHub に追加したデプロイキーのFingerprint
  • 64行目 [user.name]: GitHub のmasterブランチへプッシュするユーザーの名前 (コミットログ用、私はCircleCIを使うことが多いです)
  • 65行目 [user.email]: GitHub のmasterブランチへプッシュするユーザーメールアドレス (コミットログ用、自分または開発チームのメーリングリストなど)
  • 21行目 [branches: only]: 後述の開発ブランチ名、今回はdevelopとしました
    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
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    version: 2.1

    executors:
    default:
    working_directory: ~/workspace
    docker:
    - image: circleci/node:12
    environment:
    TZ: /usr/share/zoneinfo/Asia/Tokyo

    commands:
    system_info:
    steps:
    - run:
    name: System information
    command: |
    echo "Node $(node -v)"
    echo "Yarn v$(yarn -v)"
    setup:
    steps:
    - restore_cache:
    name: Restore Yarn Package Cache
    key: yarn-packages-{{ checksum "yarn.lock" }}
    - run:
    name: Setup
    command: yarn install
    - save_cache:
    name: Save Yarn Package Cache
    key: yarn-packages-{{ checksum "yarn.lock" }}
    paths:
    - ~/.cache/yarn

    jobs:
    build:
    executor:
    name: default
    steps:
    - system_info
    - checkout
    - setup
    - run:
    name: Build
    command: yarn build
    - store_artifacts:
    path: dist
    - persist_to_workspace:
    root: ~/workspace
    paths:
    - ./*
    deploy:
    executor:
    name: default
    steps:
    - attach_workspace:
    at: ~/workspace
    - add_ssh_keys:
    fingerprints:
    - "[GitHub に追加したキーの Fingerprint]"
    - run:
    name: Deploy
    command: |
    mkdir -p ~/.ssh
    ssh-keyscan github.com >> ~/.ssh/known_hosts
    git config --global user.name "[your name]"
    git config --global user.email "[email@example.com]"
    yarn deploy

    workflows:
    build-deploy-flow:
    jobs:
    - build:
    filters:
    branches:
    ignore:
    - master
    - deploy:
    requires:
    - build
    filters:
    branches:
    only:
    - [開発ブランチ名]

ちょっと長いですがざっくり以下です。(個々の詳細は、こちらCircleCI を設定する - CircleCIをご確認ください)

  • executorsで、実行環境を定義しています
  • commandsで、再利用可能な実行ステップsystem_infosetupを定義しています
  • jobsで、ワークフローから実行するジョブbuilddeployを定義しています
  • workflowsで、GitHub のブランチに応じて実行するジョブの定義をしています

[開発ブランチ名]にプッシュするとbuildジョブに次いでdeployジョブが実行されます。
buildジョブの中心は、yarn installyarn buildの実行です。
deployジョブの中心は、yarn deployです。

空ブランチへプッシュ

プロジェクトのディレクトリで以下のコマンドを実行します。今回は [ブランチ名] をdevelopとしました。ブランチ名は、先に作成した/.circleci/config.ymlと合わせる必要があります。

1
2
3
4
$ git checkout --orphan [ブランチ名]
$ git add .
$ git commit -m "initial commit"
$ git push -u origin [ブランチ名]

git checkout --orphanは、これまでのブランチとは関係のない空のブランチを作るオプションです。これによりmasterブランチとはつながっていないdevelopの空ブランチが作られます。
ここに開発用のソースをコミットして GitHub へプッシュします。
GitHub でブランチが追加されていることが確認できます。

また [Insights] - [Network] では、masterブランチと途切れてdevelopができていることが確認できます。(masterブランチは GitHub Pages 用で基本的にみることがないので [Settings] - [Branches] からデフォルトをdevelopにしておくと便利です)

CircleCI にプロジェクトを追加

CircleCIhttps://circleci.com/dashboardへ GitHub のアカウントでログインします。
左のメニューから [➕ ADD PROJECTS] を選択します。

リポジトリが列挙されるので、今回のリポジトリ名の横 [Set Up Project] をクリックします。
設定と手順が書かれていますが、設定済みなので [Start building] をクリックします。

ワークフロー画面が表示され、しばらく待つとビルド失敗が表示されます。(デプロイキーを CircleCI に登録していないため)

ワークフロー画面の左メニュー、プロジェクト名横の [⚙] 歯車アイコンをクリックします。
左メニューから [SSH Permissions] を選び、画面右の [Add SSH Key] ボタンをクリックします。
SSH Key 追加ダイアログが表示されるので情報を入力し、[Add SSH Key] ボタンをクリックします。

  • [Hostname]:github.com
  • [Private Key]:~/deploy_keyファイルの内容

再ビルドするので、先ほどのワークフローのビルド失敗画面から [Rerun] - [Rerun from beginning] をクリックします。
(左メニューアイコン2段目の WORKFLOWS です)

以上で自動ビルド&デプロイ、「CI/CD」の完成です!
developブランチへプッシュするたびに CircleCI が自動的にビルドし、GitHub Pages へデプロイしてくれます。

ローカルのキーペアファイルのクリーンアップ

ビルドが通り、GitHub Pages でアプリが見れるようになったらローカルのキーペアファイル~/deploy_key~/deploy_key.pubを削除します。
とくに秘密鍵側~/deploy_keyは、リポジトリに書き込みができてしまうので注意してください。

ソースコード

今回作成した部分までのソースを GitHub へアップしました。
(プロジェクトのソース管理の都合上、リポジトリ名samples-pwa-base-appsourceブランチになります。)
https://github.com/riotz-works/samples-pwa-base-app/tree/0.0.3

GitHub Pages にホスティングもしています。
(公開サイトは1つのため記事公開に合わせて変わり、本記事の内容とは異なります)
https://riotz.works/samples-pwa-base-app/


Nuxt.js アプリを、GitHub Pages へデプロイできるようになりました。
ホスティング先はいろいろ選べますが、小規模で手軽に扱いたい場合は GitHub Pages を使うと簡単です。

今回 CircleCI 連携をしました。今回のようなサンプルアプリでも CI/CD を使うことで簡単にホスティングできるようになるので便利ですし、本格的に使う場合はテストも CI で実行して早期にビルドエラーやテストの失敗を気づけるようにします。

私は CircleCI を好んで使っていますが、他にもさまざまなサービスがあるので環境にあったものを使うとよいでしょう。

Nuxt.js PWA(Progressive Web Apps) のベースアプリをTypeScript対応する

Nuxt.js を使うことで簡単に PWA なアプリを作ることができます。そのアプリを作る際に TypeScript を使うとコードの品質を高めることができ、また複数人やチームでの開発生産性を向上させることができます。今回は Nuxt.js で作ったアプリに TypeScript を導入する方法を紹介します。

前回Nuxt.js で PWA(Progressive Web Apps) のベースアプリを作るで、Nuxt.jsを使ったPWAのベースアプリを作りました。そのまま JavaScript で開発を進めることができますが、TypeScript を導入することで変数や関数に型定義が行えるようになり開発生産性や保守性を向上できます。今回のベースアプリはゼロから作っているので最初から入れてしまうのがよいでしょう。また TypeScript の関連モジュールを導入したとしても、必ずしも TypeScript の利用が必須ではなく、JavaScript で書いていって、TypeScript にできるところから適用していく段階的な利用も可能です。そのような観点からも入れてしまうのがよいでしょう。

シリーズの記事

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

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Visual Studio Code
  • Node.js 12.1.0
  • Yarn 1.15.2
  • Nuxt.js 2.6.3

TypeScript とは

TypeScriptは JavaScript に型とクラスを導入する AltJS のプログラミング言語で、JavaScript のスーパーセットです。クラスは ES6 で導入されたので TypeScript 固有ではなくなりましたが、やはり「型」の導入が TypeScript のメリットといえるでしょう。

型の例で、たとえば JavaScript で以下のようなコードがあったとします。
単純な足し算を想定したコードですが、型がないので引数に任意の値を投入できます。その結果、戻り値が想定外になることもあります。

1
2
3
4
5
6
7
8
const add = (a, b) => {
return a + b;
}

console.log(add(1, 2)); // 3
console.log(add(1, "2")); // 12
console.log(add(1, [ 2 ])); // 12
console.log(add(1, { value: "2" })); // 1[object Object]

TypeScript では以下のようになります。
引数と戻り値に:numberで、数値型を明示します。それにより数値でない引数を渡している部分はコンパイルエラーとなり実行コード(JavaScript)が生成できません。

1
2
3
4
5
6
7
8
const add = (a: number, b: number): number => {
return a + b;
}

console.log(add(1, 2));
console.log(add(1, "2")); // error TS2345: Argument of type '"2"' is not assignable to parameter of type 'number'.
console.log(add(1, [ 2 ])); // error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'number'.
console.log(add(1, { value: "2" })); // error TS2345: Argument of type '{ value: string; }' is not assignable to parameter of type 'number'.

基本的には型定義が入っただけで書いていることに変わりはありません。またコンパイラや Lint の設定で、制約を強くしたり、甘くしたりできます。最初から使う場合はある程度強くできるでしょうし、途中から導入したり慣れていない場合などは徐々に強くしていくこともでき、状況に合わせて使えます。

このぐらいの関数でしたら型がなくてもとくに問題ないかもしれませんが、もっと大きなものや他の人が書いたコードなどになってくると話が異なります。関数の引数が何を期待しているのか、戻り値は何が返ってくるのか、型情報があるだけでだいぶ変わってきます。

そんな TypeScript ですが、最近はとくに注目されており Meetup イベントが東西で開催されます!抽選枠で、まだ応募可能です。
TypeScript Meetup #1 にエントリーしましたが、案内メールが来て一瞬で定数枠の 60 を超え、本記事執筆時点で 500 人を超えてます。スゴイ勢い!

Riotz.works は、TypeScript で開発するチームなのですが、実はずっと Java で開発をしてきたチームでした。あるトラブルがあって TypeScript に舵を切り、以来 TypeScript で開発するチームとなったのですが、その際の発表資料がこちらになります。もしよかったらご参照ください。(いつかの Meetup の機会でお話しできるといいな)

Nuxt.js のプロジェクトに TypeScript を導入

前回作成Nuxt.js で PWA(Progressive Web Apps) のベースアプリに TypeScript を導入します。

TypeScript サポートのモジュールを追加

まずは TypeScript サポートのモジュールを追加します。プロジェクトのディレクトリで下記コマンドを実行します。

1
$ yarn add -D @nuxt/typescript ts-node

※ npm の場合、npm i -D @nuxt/typescript ts-node

TypeScript の設定ファイルを生成

TypeScript の設定ファイルtsconfig.jsonを作成します。
※ このfeat(ts): auto generate tsconfig.json by kevinmarrec · Pull Request #4776 · nuxt/nuxt.jsプルリクがリリースされるとtouchしなくても済みそうですが、2019年5月現在、まだ事前に作成が必要です。

1
$ touch tsconfig.json

Nuxt.js 開発サーバーを起動し、tsconfig.json の定義を生成します。起動情報にTypeScript support is enabledが出ていることがわかります。

1
2
3
4
5
6
7
8
9
10
$ yarn dev
╭──────────────────────────────────────────╮
│ │
│ Nuxt.js v2.6.3 │
│ Running in development mode (spa) │
│ TypeScript support is enabled │
│ │
│ Listening on: http://localhost:3000/ │
│ │
╰──────────────────────────────────────────╯

tsconfig.jsonの内容が更新されます。JSON ファイルをモジュールとして扱える定義resolveJsonModuleを追加します。これは追加したほうが便利なのと、後述のnuxt.config.tspackage.jsonを扱えるようにするためです。

1
2
3
4
5
6
{
"compilerOptions": {
// ...(省略)
"resolveJsonModule": true,
}
}

この定義は追加するプルリクと削除するプルリクが流れているようで、日付的には消すほうが最後なので自分で追加が必要そうです。
“Remove resolveJsonModule as , after little thoughts, we should not force it to users -#4842“ のだそうで、フレームワークのポリシーや考え方ですね。(なぜなのかは知りたかった)

Nuxt.js の設定ファイルを TypeScript 化

続いて設定を TypeScript で書くためnuxt.config.jsファイルをnuxt.config.tsにリネームします。
ファイルの内容、先頭と最後を以下のように変更します。とくに'./package.json'.jsonをつけ忘れないように注意します。このpackage.jsonを読み込むのに先ほどのresolveJsonModuleが必要でした。

変更前

1
2
3
4
5
import pkg from './package'

export default {
// ...(省略)
}

変更後

1
2
3
4
5
6
7
8
import NuxtConfiguration from '@nuxt/config'
import pkg from './package.json';

const config: NuxtConfiguration = {
// ...(省略)
}

export default config

これにより設定ファイル内でCtrl + Spaceなどの入力補完が使えるようになります。

ページやコンポーネントを TypeScript 化

プロジェクトが TypeScript 対応したので、ページやコンポーネントを TypeScript 化します。

今回はスクリプトベースで実装します。基本的な構文は下記です。

  • <script>タグにlang="ts"を追加して TypeScript として認識させます
  • 全体に型定義を効かせるためにVue.extendを追加します
  • </script>タグ前の括弧閉じに ‘)’ を追加するのを忘れないよう注意します
    1
    2
    3
    4
    5
    6
    <script lang="ts">
    import Vue from 'vue'

    export default Vue.extend({
    })
    </script>

ベースアプリの/pages/index.vueは以下のようになります。

1
2
3
4
5
6
7
8
9
10
<script lang="ts">
import Vue from 'vue'
import Logo from '~/components/Logo.vue'

export default Vue.extend({
components: {
Logo
}
})
</script>

また、Visual Studio Code の Vue 拡張Veturを使っている場合/components/Logo.vueに以下の空実装コードを追加します。
(nuxtコマンドはエラーではないので、Vetur を使わない場合は追加しなくても大丈夫です)

1
2
3
4
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({})
</script>

Note
公式ドキュメントTypeScript サポート - Nuxt.jsでは、今回のようなスクリプトベースではなくクラスベースの “vue-property-decoratorを利用することを強くお薦め -公式サイト“ しています。
しかしながらクラス構文にすると、実装方法が Vue.js/Nuxt.js とまったく異なってしまいます。今回は、これまでの実装経験やサイト、書籍を活かすためにスクリプトベースとしました。まったくゼロから勉強するとのことでしたらクラス構文を使うのも手かもしれません。

カウンターを設置して TypeScript の実装を確認

TypeScript を導入したので実装を確認します。
トップページにボタンを設置し、クリックした数をカウントする簡単なアクションを追加します。

絵文字ボタンとカウンターの導入

/pages/index.vueの [Documentation] と [GitHub] の下、<div class="links"></div>ブロックの直後に以下を追加します。

1
2
3
4
5
6
7
<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>
<a @click="clear" class="button--grey">Clear</a>
</div>

カウンター表示用のデータプロパティを追加します。(dataのみ抜粋)

1
2
3
4
5
6
7
8
9
10
11
export default Vue.extend({
// ...(省略)
data() {
return {
tada: 0,
sparkles: 0,
thumbsup: 0,
heart: 0
}
},
})

@clickで呼び出されるメソッドを追加します。(methodsのみ抜粋)
各絵文字ボタンから呼び出されるメソッドは対応する変数をインクリメント、[Clear] は全変数を0に初期化します。各メソッドは TypeScript の型定義を導入し、戻り値:voidと明示しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default Vue.extend({
// ...(省略)
methods: {
addTada: function(): void {
this.tada++
},
addSparkles: function(): void {
this.sparkles++
},
addThumbsup: function(): void {
this.thumbsup++
},
addHeart: function(): void {
this.heart++
},
clear: function(): void {
this.tada = 0
this.sparkles = 0
this.thumbsup = 0
this.heart = 0
}
}
})

以上で、絵文字ボタンを押すと各ボタン横のカウンターが増える動作となります。
カウンターぐらいだと型のメリットが感じられませんが、たとえばclearの初期化で"0"のような文字列を入れるとコンパイルエラーとなります。しっかり TypeScript の型チェックが効いています。

カウンターのトータル回数、ロゴを回すアニメーションの導入

カウンターの値を使ったアクションを作りたいので、各カウンターのトータルを計算し、その値の回数ロゴを回してみます。

/pages/index.vueに、すべての絵文字カウンターの総計を持つ算術プロパティを追加します。(computedのみ抜粋)
counter()メソッドを作り、戻り値はnumber型、すべての絵文字カウンターの合計です。

1
2
3
4
5
6
7
8
export default Vue.extend({
// ...(省略)
computed: {
counter(): number {
return this.tada + this.sparkles + this.thumbsup + this.heart
}
},
})

ロゴを回転させるために、ロゴのコンポーネントへカウンターの値を渡します。
HTML テンプレートの<logo>タグにv-bind:counter="counter"属性を追加しcounterの値をロゴのコンポーネントで使えるようにします。

1
<logo v-bind:counter="counter" />

/components/Logo.vueで、カウンターの値を受け取りロゴを回転する CSS を追加します。
ロゴ全体を回転させるため、現在の HTML テンプレートの実装部分を<div :style="rotation">でラップします。
回転の CSS はstyle属性直接に、rotationプロパティ経由で設定します。

1
2
3
4
5
6
7
8
9
10
<template>
<div :style="rotation">
<div class="VueToNuxtLogo">
<div class="Triangle Triangle--two" />
<div class="Triangle Triangle--one" />
<div class="Triangle Triangle--three" />
<div class="Triangle Triangle--four" />
</div>
</div>
</template>

後はcounterの値を使って CSS を作るだけですが、CSS アニメーションを再実行できるようにするため、一手間かけています。(スクリプト全文)

  • 親コンポーネント/pages/index.vueで絵文字ボタンを押されると、props: counterに新しい値がセットされます。それをwatchで監視していて、変化があるとwatch: counterを呼び出します。
  • watch: counterは、dataプロパティにアニメーションの CSS をオブジェクトとして設定します。いったんアニメーション無し{ animation: 'none' }をセットし、10ミリ秒後に{ animation:rotate 1s linear 0s ${ value } forwards}をセットします。(アニメーション再実行のトリック)
  • 最後にcomputed: rotation経由で作られた CSS オブジェクトを HTML テンプレートへ渡します。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    export default Vue.extend({
    props: {
    counter: Number
    },
    data() {
    return {
    animation: {}
    }
    },
    computed: {
    rotation: function(): object {
    return this.animation
    }
    },
    watch: {
    counter: function(value: number): void {
    this.animation = { animation: 'none' }
    setTimeout(() => {
    this.animation = { animation: `rotate 1s linear 0s ${ value } forwards` }
    }, 10)
    }
    }
    })

だいぶ TypeScript の型が登場しました。
computed: rotationでは、戻り値がobjectで CSS の文字列ではないことがわかります。
watch: counterでは、引数がnumber型です。(フレームワークからの呼出し&テンプレートリテラルなので型明示のメリットはあまりないですが)

なお、CSS のアニメーション定義は下記です。

1
2
3
4
5
6
7
8
@keyframes rotate {
0% {
transform: rotateY(0);
}
100% {
transform: rotateY(360deg);
}
}

このくらいの規模だと型定義によるメリットはまだまだありませんが、アニメーションのためのトリックとは言え色々と取り回ししているので、他人の書いたこんなコードが急に出てきたと思うと型情報ぐらいあると嬉しいのではないでしょうか。

動作確認!

yarn devで開発サーバーを起動し、ブラウザでhttp://localhost:3000/へアクセスします。

※ 開発サーバーは常に立ち上げておいて大丈夫です。実際の開発に当たってはホットリロードを活用し、コードを保存するとブラウザが自動でリロードしてくれます。

ソースコード

今回作成した部分までのソースを GitHub へアップしました。
https://github.com/riotz-works/samples-pwa-base-app/tree/0.0.2

GitHub Pages にホスティングもしました。
(公開サイトは1つのため記事公開に合わせて変わり、本記事の内容とは異なります)
https://riotz.works/samples-pwa-base-app/


ベースアプリを TypeScript 化できました。
今回のカウンターボタンぐらいだとコーディング量が増えるだけでメリットが感じられませんが、他の人が書いた共通ライブラリを使うときなどに型があることで助かることが増えてきます。

型を導入できたところで Lint も入れたいところですが、こちらの話はまた重いので別途書きます。
もし使たい場合は、公式ドキュメントESLint を使った Linting - TypeScript サポート - Nuxt.jsをご参照ください。

Nuxt.js で PWA(Progressive Web Apps) のベースアプリを作る

ウェブの技術を使い、またウェブサイトとしてホスティングしながらも、モバイルやデスクトップのアプリとして振る舞うことができる PWA。最近では Google Play Store でも配信できるようになったり、iOS での対応強化など話題に事欠きません。
そんな PWA を簡単に高速に開発することができる Nuxt.js を紹介するとともに、今後さまざまな機能を試すためのベースアプリを作ります。

シリーズの記事

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

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Visual Studio Code
  • Node.js 12.2.0
  • Yarn 1.15.2
  • Nuxt.js 2.6.3

PWA とは

PWAは Progressive Web Apps の略称で、ウェブサイトをモバイルアプリとして使えるようにする仕組みです。さらに Google Chrome 67 からデスクトップアプリとしても動作させることができるようになりました。

アプリを PWA とすることで、以下のような機能が提供できます。

  • 端末にインストールしホームやデスクトップにアイコンを追加できる
  • ブラウザとは異なるネイティブアプリのような操作感
  • オフラインでも使えるように作ることができる
  • プッシュ通知を受け取ることができる
  • リンクやサイトへの埋め込みができる
  • 自身の運営サイト、および Google Play Store から配信できる

一方でデメリットもあります。

  • OS(ブラウザ) により使える機能のサポートレベルが異なる
  • ネイティブ実装に対してブラウザでサポートしている機能が足りない (e.g. NFC, 2019年5月現在)

主な PWA (アプリがない状態でブラウザを使いアクセスすると [ホームに追加] が表示される)

また私たち Riotz.works は、モバイルアプリのハッカソンSPAJAM2018(東京D予選) にて Nuxt.js
で PWA を作り優秀賞を獲得しました。「モバイルアプリ」の大会なので当然ですがネイティブアプリ開発のチームに対して、「唯一の PWA」として健闘し受賞に至りました。PWA スゴイ!

発表資料:リアルタイムの競演と参加型観戦で音楽を最高に楽しむ「ラップ、タップ、アップ🎶」

作品:ラップ、タップ、アップ 🎶
[バトルを募集する] ボタンから [対戦者] の [QRコード] または [URL] をスマホに渡しエントリー & カメラ付き PC は [対戦者] ラベル右の [入場] アイコンからエントリーで、ラップバトルの中継が行えます。PWA のため<iframe>で埋め込みもでき実際に動作させられます。スマホアプリなのにこういったことができるのも PWA のメリットになります。

Nuxt.js とは

Nuxt.jsは、Vue.js を使ったアプリの開発を支援するフレームワークです。”Vue.js アプリケーションの開発を楽しくするために必要なすべての設定が揃っています -Nuxt.js“ と謳う通り Vue.js で開発する際に必要となる、さまざまなライブラリや設定があらかじめ構築済みで、簡単にアプリ開発が始められるフレームワークです。

開発できるアプリは3パターンあります。

  • サーバーレンダリング - Universal SSR(Server Side Rendering)
  • シングルページアプリ - SPA(Single Page Applications)
  • 静的サイト(プレレンダリング)

SSR と JAMstack の両方を1つのソースから生成できるため、アプリ特性に合わせてモードを切り替えられます。

Vue.jsは、ウェブフロントを開発するためのフレームワークです。”The Progressive JavaScript Framework” と題しており、ウェブ UI に対して少しずつ適用でき、コンポーネントへの分離と再利用がしやすくなっています。

Vue.js は強力なフレームワークですが、あらゆるウェブサイトに対して柔軟に利用できるようコア機能に絞っています。そのため開発を支援するための周辺ライブラリが多数あります。
たとえば、ページのルーティング(URL のマッピング)はコア機能には含まれていません。独自に実装するか、公式ライブラリのVue Routerを別途導入します。

より踏み込んで高度なことの実装に耐えうる Vue.js ですが、アプリを高速に作り上げるには、ある程度のレールを敷いておいてほしい(たとえば Vue Router は設定済みにしてほしい)、そんなときに Nuxt.js を使うと手軽に開発できます。

Nuxt.js で、アプリ開発

さっそく Nuxt.js で PWA のベースアプリを作っていきます。
今回のアプリはベースなので以下の仕様としました。今後、このアプリをもとにさまざまな機能の検証などをします。

  • SPA の JAMstack として作成
  • TypeScript 化

今回は PWA アプリで、SPA の JAMstack で作成します。SSR(Universal) にするとサーバーサイドの処理を同時に実装できるので便利ですが、SEO 重視で CGM(Consumer Generated Media) や変化の激しいコンテンツでもない限り JAMstack にして、フロントとサーバーサイドは分離し Web API 経由でデータアクセスするほうが良いでしょう。
詳しくはJAWS DAYS 2019 で AWS x JAMstack な、サーバーレス Web Front について発表をしましたをご参照ください。

TypeScriptは JavaScript に型定義とクラスを導入する AltJS のプログラミング言語(正確にはスーパーセット)です。型定義を導入することで開発効率や不具合回避ができるため注目されており、最近で導入されるケースが多いようです。今回はゼロから作るので導入しておきます。
※ Nuxt.js は、バージョン 2.6 からオフィシャルに TypeScript 対応したのですが、2019年5月現在 CLI から簡単に導入できないため詳細は次回の記事で書きます。

2019年5月10日追記
書きました!
Nuxt.js PWA(Progressive Web Apps) のベースアプリをTypeScript対応する

プロジェクトの作成

公式のウィザード CLI (scaffolding tool) があるので、そちらを使います。
今回は最後の引数<my-project>pwa-base-appとしました。

1
$ npx create-nuxt-app <project-name>

※ yarn の場合yarn create nuxt-app <my-project>がありますがグローバル汚染回避のためにnpxを使いました

プロジェクト名と説明を聞かれます。必要に応じて入力します。

1
2
? Project name (pwa-base-app)
? Project description (My majestic Nuxt.js project)

サーバーのフレームワークを聞かれますが JAMstack でいくのでnoneを選択します。

1
2
3
4
5
6
7
8
9
? Use a custom server framework (Use arrow keys)
❯ none
express
koa
adonis
hapi
feathers
micro
(Move up and down to reveal more choices)

インストールする機能を聞かれます。

  • PWA Supportは、今回の目的なのでチェックします。
  • Axiosは、HTTP Client で Web API などにアクセスするのに使います。使うケースが多いので入れておきます。
  • Linter / FormatterPrettierは TypeScript との兼ね合いがあり CLI からは入れません。
    1
    2
    3
    4
    5
    ? Choose features to install (Press <space> to select, <a> to toggle all, <i> to invert selection)
    ◉ Progressive Web App (PWA) Support
    ◯ Linter / Formatter
    ◯ Prettier
    ❯◉ Axios

UI のフレームワークを選択します。ここはお好みで。
今回はnoneを選択し、本アプリを開発する際に別途導入します。(普段Vuetifyを使ってますがウィザードで入れると生成されるプロジェクトのサンプルコードが大きくなりすぎるので自分で入れてるのもあります)

1
2
3
4
5
6
7
8
? Use a custom UI framework (Use arrow keys)
❯ none
bootstrap
vuetify
bulma
tailwind
element-ui
buefy

テスティングのフレームワークを選択しまが、TypeScript にするので後から追加します。(このタイミングで追加するとbabel-jestになるため)

1
2
3
4
? Use a custom test framework (Use arrow keys)
❯ none
jest
ava

Nuxt.js のモードを選択します。JAMstack にするのでSingle Page Appを選択します。

1
2
3
? Choose rendering mode (Use arrow keys)
Universal
❯ Single Page App

Author を入力し、パッケージマネージャーを選択します。

1
2
3
4
? Author name ()
? Choose a package manager (Use arrow keys)
npm
❯ yarn

以上でウィザードが終了し、プロジェクトの生成とパッケージのインストールが行われます。
ウィザード終了時の案内に従いプロジェクトのディレクトリへ移動し、開発サーバーを起動します。
※ Yarn を選んだ場合にyarn run devを案内されますがrunは省略可能です

1
2
$ cd pwa-base-app
$ yarn dev

ウィザード終了時の案内に出ていた通りブラウザでhttp://localhost:3000/へアクセスすると、PWA な Nuxt.js アプリが表示されます。

Note
2019年5月現在、開発サーバーを起動するとERROR (node:515) DeprecationWarning: Tapable.plugin is deprecated. Use new API on .hooks insteadの警告が表示されますが@nuxtjs/pwaのバージョンが古いためです。ERRORと出ていますが問題なく動作します。気になる場合は下記コマンドでモジュールをアップデートします。

1
$ yarn upgrade --latest

※ npm の場合は、tjunnone/npm-check-updatesを使うとよいでしょう

デプロイ用のビルド

デプロイ用のビルドを実行するとdistディレクトリが作られます。
このdistディレクトリの内容物をウェブサーバーに配置するだけでデプロイ完了となります。

1
$ yarn build

※ 同じようなコマンドでgenerateがありますが、こちらは完全に静的化され HTTP Request が使えなくなるのでbuildを使います。各種サンプルなどでgenerateが出てくる場合はbuildに読み替えてください。たとえば下記デプロイの FAQ はgenerateで書かれていますがbuildを使います。

デプロイについては環境によって設定などが異なってくるので別途書きます。
試してみたい方は、公式 FAQをご参照ください。

また、現時点でデプロイしても PWA として機能します。

キャプチャ左側がスマートフォンでブラウザ表示時 [ホームに追加] が案内されます。中央がホームに追加後アプリとして起動した状態です。まだ何もないアプリですがブラウザのアドレスバーが消えてアプリっぽくなっています。アプリ内のボタンを押すとブラウザに戻るのは、アプリのドメイン外へ出たためです。

キャプチャ右側は Windows PC に、デスクトップ PWA としてインストールして起動したものになります。こちらもアドレスバーがなくアプリのような感じになります。

ソースコード

今回作成した部分までのソースを GitHub へアップしました。
https://github.com/riotz-works/samples-pwa-base-app/tree/0.0.1

GitHub Pages にホスティングもしました。
(公開サイトは1つのため記事公開に合わせて変わり、本記事の内容とは異なります)
https://riotz.works/samples-pwa-base-app/


Nuxt.js で PWA のベースアプリができました!

後はpagesディレクトリに表示するページの vue ファイルを追加していくことでアプリが作れますが、その前に TypeScript は入れておいたほうが良く次回の記事で、その辺のところを書きます。
TypeScript 対応しても、ページ単位で TypeScript/JavaScript を切り替えられますし、TypeScript の中に JavaScript が書けるので、TypeScript をやったことがなくても導入してしまい少しずつ使うことができます。

2019年5月10日追記
書きました!
Nuxt.js PWA(Progressive Web Apps) のベースアプリをTypeScript対応する

本文中で紹介しました SPAJAM は「2019 予選」が募集中です。本記事で PWA のアプリはできたも同然、ぜひハッカソンで試してみましょう!
Riotz.works は「東京A予選」でエントリー中です。チーム名「Riotz.works、進撃のPWA」と、今回こそ PWA で最優秀賞を目指します!!(エントリー結果が出てないので参戦できるかは決まってません)
予選一覧 | SPAJAM2019公式サイト – 温泉でハッカソン

2019年5月13日追記
SPAJAM 東京A予選に参戦が決まりました!
Gmail の迷惑メールに仕分けされてしまったようで気づいてませんでした😭 最近メール使わないからトラブったら迷惑メールフォルダーの確認を忘れてしまい。。。

SPAJAM 東京A予選の方、ご一緒いたします。ハッカソンを楽しみましょう!!
応募されてない方、まだ募集中です。本戦、箱根で会いましょう!!

Asana を Nativefier でアプリ化する

タスク管理の Asana、スマートフォンアプリはあるのですがデスクトップアプリが提供されておらずブラウザで使います。Google Chrome でアプリウィンドウ化ができますが、常駐化をするなどの高度な使い方をしたい場合は Node.js の Nativefier を使ってアプリ化をする方法があります。Node.js の実行環境とコマンドラインでの操作が必要となりますが、手軽にアプリ化ができます。

前回Google Chrome を使って Asana をアプリウィンドウ化をしましたが、ウィンドウを閉じてしまうとウェブアクセスから始まり起動に時間がかかります。常時使うようなサービスなので常駐化し、いつでも使えるようにしたいです。

公式フォーラムでもデスクトップアプリのニーズはWhat’s the status of a native Asana Mac app? - Product Feedback - Asana Community Forumのように 2017年と古くから上がっているようですが進展はなさそうです。

今回は、フォーラムでも紹介されている Nativefier を使いアプリ化し常駐、必要な時にすぐに利用できるようにします。

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

  • Windows 10 64bit
  • Node.js 12.1.0
  • Nativefier 7.6.12

Nativefier とは

Nativefierは、ウェブページをデスクトップアプリ化する Node.js のツールで、ウェブサイトを Electron でラップした Windows, macOS, Linux 用のアプリを作成します。

Electronは、ウェブの技術である HTML/CSS/JavaScript を使ってクロスプラットフォームのデスクトップアプリを作るツールで Atom や Visual Studio Code などのエディター、Slack や Discord などのチャットクライアント等、さまざまなアプリで使われています。

ただし Electron アプリはメモリ消費が大きく、1アプリで数百メガ (私の Asana のケースでは 250MB) 使うのでアプリの作りすぎには注意が必要です。

Nativefier は Node.js 6 以上で動作します。インストールされていない場合は、Node.js 公式サイトにしたがってインストールしてください。今回は Node.js 12.1.0 を使いました。

Nativefier で Asana をアプリ化

以下のコマンドで Asana をアプリ化します。

1
C:\Temp> npx nativefier -n "Asana" --internal-urls "accounts\.google\.com.*" --single-instance --tray "https://app.asana.com"

指定したオプションは下記となります。

  • -nは、アプリ名(=ディレクトリと実行ファイル名)
  • --internal-urlsは、Google 認証をウィンドウ内で行えるようするため
  • --single-instanceは、アプリの起動を1つだけにし、ウィンドウを複数開かないため
  • --trayは、システムトレイに常駐させます

Google 認証については、GitHub に Issues が上がっています。いくつかパターンがありますが、今回は一番狭いスコープで設定する#282 @tianhuil さんのを使わせてもらいました。情報ありがとうございます!

オプション最後のhttps://app.asana.comは、アプリ化する URL で、アプリ起動時に表示したい URL たとえば Asana マイタスクなども指定できます。システムトレイに常駐させるならば起動後は最後に見た状態を覚えているのでトップページの URL がよいでしょう。

また Node.js の npm モジュールのグローバル汚染回避と、使う頻度が少く常に最新版を使いたいためnpxで実行しました。npxはローカルに存在しないモジュールを実行する場合はダウンロードして実行し、実行後はダウンロードしたファイルを削除します。そのためグローバルにモジュールを追加するよりもクリーンですが、毎回モジュールをダウンロードすることになるので試行錯誤したりする場合は、グローバルに追加して実行した方が効率的です。
グローバルに追加して実行するには下記のコマンドとなります。

1
2
C:\Temp> yarn add --global nativefier
C:\Temp> nativefier -n "Asana" --internal-urls "accounts\.google\.com.*" --single-instance --tray "https://app.asana.com"

※ npm を使う場合はnpm -g nativefier

Asana アプリ

Nativefier を実行すると、実行したディレクトリにアプリが作られます。
ディレクトリ名がAsana-win32-x64になっていますが変更したり、別の場所に移動しても大丈夫です。

ディレクトリ内のAsana.exeをダブルクリックするとアプリが起動します。
ログイン画面から始まるので、使っているアカウントでログインします。

システムトレイにアイコンが配置され常駐していることがわかります。ウィンドウを閉じても常駐されているので、システムトレイのアイコンから最後の状態で表示されます。
今回はシングルインスタンスにしているのでAsana.exeを繰り返しダブルクリックしたり、ショートカットから起動させてもウィンドウは1つで、最初に起動したウィンドウが手前に表示されます。

終了するにはシステムトレイのアイコンを右クリックして [Quit] を選びます。
常駐型のアプリと同じように扱うことができます。

Nativefier の、その他のオプション

Nativefier には、たくさんのオプションがあり柔軟に設定を行うことができます。
主なオプションを紹介します。(詳しくは--helpを参照ください)

プラットフォームを指定してクロスビルドできます。

  • -p, --platform <value>は、プラットフォームで ‘osx’, ‘mas’, ‘linux’, ‘windows’ から指定できます
  • -a, --arch <value>は、CPU アーキテクチャで ‘ia32’, ‘x64’, ‘armv7l’ から指定できます

macOS の場合はバッジを付けることができるようです。(macOS がないので試せない & Asana は何がバッジされるんだろう🤔)

  • --counterは、ドックでカウンターのバッジを付けます
  • --bounceは、カウンターが増えた時にバウンドします

アプリの挙動を柔軟に設定できます。

  • --tray start-in-trayは、システムトレイ常駐で初回起動時にウィンドウを表示させません
  • --always-on-topは、ウィンドウを常にトップに表示します

Nativefier を使うことで簡単にウェブサイトをアプリ化できます。Asana に限らず、よく使うサービスでデスクトップアプリがない場合に Nativefier を使うと手軽にアプリ化できるのでとても便利です。

一方であくまでもウェブサイトをラップしているだけなので、アプリらしい機能はありません。やはり公式でアプリをリリースされることが望ましです。Asana アプリ、リリースされないかな。。。

Asana を Google Chrome でアプリウィンドウ化する

タスク管理の Asana、スマートフォンアプリはあるのですがデスクトップアプリが提供されておらずブラウザで使います。ブラウザで使っているとタブの中に埋もれてしまい欲しいときにすぐ使えなかったり、ブラウザごとまとめて閉じられてしまうことなどがあります。もっと快適に利用できるよう Google Chrome でアプリのように扱う方法を紹介します。

今回は Google Chrome の機能を使ってアプリウィンドウ化します。この機能について一時期使えなくなっていたこともあり、環境によってはうまく動作しないかもしれません。今回の動作確認環境は下記となります。(Google Chrome にメニューがなかったりなど今回の方法でできなかったり、Google Chrome を使っていない場合は、次回投稿予定のアプリ化の方法をご検討ください)

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

  • Windows 10 64bit
  • Google Chrome 74.0.3729.108

Google Chrome でショートカットの作成

まずは Google Chrome で Asanahttps://app.asana.com/へログインします。
現在ブラウザで表示しているページが、アプリのトップになるので [マイタスク] やプロジェクトなど、最初に出てほしいページを表示しておきます。今回は [マイタスク] としました。

右上の [メニューアイコン] - [その他のツール] - [ショートカットを作成] を選びます。

ショートカットの作成ダイアログが表示されます。
[ショートカットの名前] を入力し、[ウィンドウとして開く] にチェックを付け [作成] をクリックします。

ブラウザが終了し、アプリウィンドウとして Asana が表示されます。
あとはアプリとして独立したウィンドウのため、ブラウザを閉じても巻き込まれることはありません。

スタートメニューとタスクバーへの登録

Google Chrome のアプリとして登録されているので、起動はchrome://apps/またはブックマークバーの [アプリ] からとなります。
※ ブックマークバーは [Ctrl + Shift + B] で表示できます

起動をもう少し簡単にするため、スタートメニューへ登録します。
Google Chrome の [アプリ] にあるアイコンを右クリックし [ショートカットを作成] を選びます。

ショートカット作成のダイアログが表示されるので、必要なショートカットを選び [作成] をクリックします。
今回は両方作成しました。

デスクトップまたはスタートメニューのアイコンの右クリックからタスクバーにも追加できます。
これによりタスクバーからもいつでも起動することができるようになります。


タスク管理の Asana をデスクトップアプリ風にしました。これにより簡単に起動できますし、ブラウザのウィンドウやタブに紛れて探す必要もなくなります。なによりブラウザの閉じるでまとめて消えてしまわないのが助かります。

この方法は Asana だけではなく、ほとんどのウェブサイトで同様のことができます。よく使うサイトをアプリウィンドウ化することで便利に使うことができるようになります。

次回は Google Chrome の機能ではなく、アプリ化する方法を紹介します。