Nuxt.jsでEmojiをfaviconに設定する
適当なサイトを作るとき、サイト自体のレイアウトやインタラクションはコンポーネントライブラリやCSSフレームワークがあって開発者一人でも割と迷いなく作業を進められたりする。
最近自分はVueを触ることが多いので、前者だと vuetify、後者だと、tailwindcssを使ったりしている。
折角公開するなら、主要なmetaタグは一通り設定しておきたいと思い、最終的にどうするか迷うのがfaviconだったりする。
何も指定しないよりは、適当なEmojiを指定しておいたほうが見た目が映えるだろうとうのがことの発端。
faviconにはSVGが使える
faviconを用意するとき、適当な.png画像を.icoに機械的に変換していたが、Chromeだと80以降ではsvg画像をfaviconとして指定できるらしい。
faviconにEmojiを指定する
「favicon emoji」とかでググると以下のツイートが出てくる。
このツイートがそのまんま答えで、faviconにSVGを指定できることを利用してインラインのSVGにEmojiを埋め込むことで、faviconにEmojiを表示させることができるとのこと。
Now that all modern browsers support SVG favicons, here's how to turn any emoji into a favicon.svg:
— Lea Verou (@LeaVerou) March 22, 2020
<svg xmlns="https://t.co/TJalgdayix" viewBox="0 0 100 100">
<text y=".9em" font-size="90">💩</text>
</svg>
Useful for quick apps when you can't be bothered to design a favicon! pic.twitter.com/S2F8IQXaZU
Nuxt.jsのfaviconを固定でEmojiにする
NuxtはSSRの場合、文字列結合で、SPAの場合 vue-meta
を使って、head内のmetaタグを構築するようになっている。
どちらにせよ、nuxtConfigのhead内にmetaタグやlinkタグに関する設定を記述してあげれば良い。今回のfaviconの設定も例にもれず、head.link
に書けば良い。
この例では、共通の設定になっているが、ページごとに指定したい場合には同様に、pageの head
に設定すればよさそう。
create-nuxt-app
で作成した直後のプロジェクトでは以下のようになっているはず
const nuxtConfig: NuxtConfig = { head: { // 略 link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } ] }, // 略 }
それを、こんな感じで使いたいEmojiを含むSVGをインラインで指定する
const nuxtConfig: NuxtConfig = { head: { // 略 link: [ { rel: 'icon', type: 'image/x-icon', href: 'data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>' } ] }, // 略 }
Nuxt.jsのfaviconを動的にEmojiにする
runtimeで切り替えるのもページからmetaの操作がきでれば特に特別なことはない。
以下に例のgistを貼った。この例では、 composition-apiを使っていて、useMetaでmetaの操作をしている。
忘れていた頃に届いたMoonlander Mark I
久々に新しいキーボードを買った。
とりあえず最低限のセットアップが終わって使い始めたときのツイート。
moonlander、やっと届いた
— Daiki (@daikiojm) November 14, 2020
ErgoDox EZからの乗り換えだと全く違和感ないのと、パームレストが一体型なのは机の上でキーボードをよく移動する自分には嬉しい変更 pic.twitter.com/4q0kzHnBAi
そんなにキーボードいらなくね?
当時の会社の先輩に即発されて、2年ぐらい前からErgoDox EZを使っている。
もともと肩こりとかはあんまりしない方だったけど、その前に使っていたHHKBに比べて圧倒的に疲れが出なくなった印象がある。
1年ぐらい前からオフィスに出社する機会が減り、そのタイミングで2台持っていたErgoDoxのうち1台は手放してしまっていた。
オフィスに出社して仕事する機会が増えそうだったので、それぞれに置くために追加のErgoDoxを買おうと思ったところ、ErgoDoxのサイトでMoonlanderの広告を見つけ、スパークジョイしてしまった。
親指周りのキー配列以外はErgoDoxEzとそれほど変わらないのと、USB Type-C対応していたのが決め手だった。
Moonlanderが届くまで
ErgoDox EZと同じ Kailh Silver軸で白のモデルを発注した。
- 8月22日 注文
- 11月5日 発送
- 11月12日 受け取り
もともと発送までに時間がかかることはわかっていたけど、9月末ぐらいになって国内で受け取った人のツイートを見るまで注文していたことを忘れていた。
関税は着払いで支払う感じだった。
使ってみて
ErgoDox EZと比較して感じたいこととか。
パームレスト一体型は便利
外出するときにもと出すことは無いけど、机の上で少し移動したときとかにパームレストがあると微妙に位置がずれていたり、机にへばりついていて移動しづらかったり結構ストレスを感じていた部分だった。
パームレストが一体化されたこともあってか、折りたたまれた状態でかなりコンパクトな箱で届いた。ErgoDox EZを買ったときはパームレストと合わせるとバカでかい宅配ピザみたいな箱で届いた記憶がある。
キーを覆う枠がないのはゴミが溜まりにくそう
キーを覆う枠(これがないタイプの名称とかあるのかな?)があるとキーの間に髪の毛やホコリとかのゴミが溜まって、時々ひっくり返して叩きまくるみたいな掃除が必要だった。 Moonlanderの場合枠自体が無いので、ゴミが溜まりにくそう。
(親指部分の赤いキーが)日の丸弁当っぽい
他のキーと同じ色の予備キーキャップを付けてくれたら嬉しかったかもしれない。
焦りポイント
セットアップ中、いくつか焦りポイントがあったので書き残しておく。
バックライトの消し方がわからなくて焦る
ErgoDoxのときはバックライトの有無を注文時に指定できたが、今回のMoonlanderは標準仕様(というか選べるのは色だけ)でもRGBバックライトがついている。
ファームウェアを書き込んだ状態でもこのバックライトが消えず、このまま使い続けなきゃいけないのかとちょっと焦った(ゲーミングキーボード並みのギラギラ)。
焦ってググってみると、以下の回答の通り適当なキーにバックライトのtoggle操作をアサインすればいいだけだった。
https://www.reddit.com/r/ergodox/comments/jnzvv1/help_my_moonlanders_rgb_lights_just_turned_off/
Karabiner-Elementsが動かなくて焦る
USキーのキーボードで、日本語配列と同様に日本語IMEの切り替えを行う&システム全体でviっぽいカーソル操作をするために、Karabiner-Elementsを使ってる。
ErgoDoxのときはときに設定も必要なく、Max本体のキーボードと同様に認識されたが、今回のMoonlanderの場合はそうは行かなかった。
Karabiner-Elements > Preferences に表示されている「Moonlander Mark Ⅰ (ZSA)」の両方にチェックを入れると使えるようになった。(接続した直後は画像2番目のみにチェックが入った状態になる)
総評
最高なのでもう一台買ってしまいそう。
前から使っているErgoDox EZのキーアサインをベースに同じ設定にして使っているけど、親指周りのキーや角度の付け方とかが完全に同じとはいかないので、結局全く同じものに揃えた意欲が出てきてしまいそう。
多分、Moonlander Mark2が出たら即買いすると思う。
Nuxt.jsでvuejs-datepickerを使うとdocument is not definedが発生する問題
前提として
Nuxt.js
以下は、Nnux.jsの公式から引用
Nuxtは、モダンな web アプリケーションを作成する Vue.js に基づいたプログレッシブフレームワークです。Vue.js 公式ライブラリ(vue、vue-router や vuex)および強力な開発ツール(webpack、Babel や PostCSS)に基づいています。 Nuxt の目標は、優れた開発者エクスペリエンスを念頭に置き、Web 開発を強力かつ高性能にすることです。
フレームワークによって設計やコーディングルールを矯正される面が大いにあるため、PureなVue.jsの導入を検討する際にも引き合いに出されることが多い印象。
最近開発を手伝っている案件でも、Nuxt.js(TypeScript)が採用されており、ちょくちょく触ることがある。
vuejs-datepicker
Vue Componentとしてdatepickerを提供するnpmモジュール。
Vue Component提供されるdatepickerの中ではダウンロード数も多く優勢。
個人的にも以前別のプロジェクトで使ったことがあり、シンプルなAPIが使いやすく気に入っていたこともあり、今回もNuxt.jsと組み合わせて導入することにした。
ただし、デフォルトのUIが結構簡素なものなので、カスタムCSSを当てて見た目は自前で作り込んで上げる必要がある。
document is not defined
何も考えずvuejs-datepickerのドキュメントに沿って、対象のComponentにdatepickerをimportとして動作確認しようとすると、次のようなエラーが発生する。
どうでもいいが、Nuxt.jsのエラー画面はさすが優れた開発者エクスペリエンスを念頭に置き開発されたアプリケーションフレームワークと歌っているだけのことがある。
これは、クライアントサイドJavaScriptで動作することを前提としたモジュールをサーバーサイドレンダリングで動作させようとして、ブラウザオブジェクト(今回の場合documentオブジェクト)にアクセスしようとするも、失敗したことに起因するエラーっぽい。
大抵のVue Componentを提供するnpmモジュールではこの点は考慮されていないので、次で説明するように、Nuxt.jsのpluginを定義したうえで、一手間加えてやる必要がある。
解決方法
現状、上記の問題を解決する方法は次のような手順でdatepickerを使用すればよい。 最初から順を追って説明する。
インストール
yarn add vuejs-datepicker
Nuxt.jsプラグインの定義
/plugins/vue-datepicker.client.js
を作成する。
次に、 vue-datepicker.client.js
の内容を次のように定義する。
import Vue from 'vue'; import Datepicker from 'vuejs-datepicker'; Vue.component('date-picker', Datepicker);
コンポーネントで使用する
コンポーネントでdatapickerを使用する際には、次のように <client-only>
コンポーネントを利用する。
<client-only> <date-picker placeholder="MM/DD/YYYY" format="MM/dd/yyyy" v-model="date" /> </client-only>
こうすることで、意図的にクライアントサイドレンダリングでdatapickerを利用するようにすることができる。
参考
https://github.com/charliekassel/vuejs-datepicker/issues/147
Slack Appの認証にAPI GatewayのCustom Authorizerを使おうと思ったら使えなかった話
Serverless Frameworkを使うと、API Gateway + Lambdaを使ってCustom Authorizerを簡単に実装することができる。
Slack のSlash Commandsの認証にこれを使おうと思ったけど使えなかった話と、Custom Authorizerを使わないで実装した話。
Slack Appの認証について
最新のSlack API(2019/09/23 現在)では、リクエスト署名方式が使われており、SlackからのPOSTリクエストを受ける側のアプリケーションではこの署名を検証する必要がある。
実際に送信されるリクエストには、 X-Slack-Signature
というHTTPヘッダーが付加されていて、この内容が署名になっている。
詳しい検証方法はドキュメントにもあるので触れないが、検証には次の情報が必要になる。
- APIバージョン番号 (v0)
- Slack Appの設定ページから取得できる、共有シークレット
Signing Secret
- リクエストbodyの内容
- リクエスト時のタイムスタンプ (
X-Slack-Request-Timestamp
HTTPヘッダーに付加されている)
Custom Authorizerを使おうと思った...
注意: 以下で紹介する方法は動作しません
serverless.yaml
functions: hello: handler: handler.slashCommand events: - http: method: post path: slashCommand cors: true authorizer: name: slashCommandAuthorizer identitySource: method.request.header.X-Slack-Signature, method.request.header.X-Slack-Request-Timestamp type: request slashCommandAuthorizer: handler: handler.slashCommandAuthorizer
handler.ts
export async function slashCommandAuthorizer( event: APIGatewayEvent & CustomAuthorizerEvent, context: Context, ): Promise<CustomAuthorizerResult> { const result: CustomAuthorizerResult = { principalId: '*', policyDocument: { Version: '2012-10-17', Statement: [ { Action: 'execute-api:Invoke', Effect: 'Deny' as Effect, Resource: 'arn:aws:execute-api:*:*:*/*/*/', }, ], }, }; const timestamp = event.headers['x-slack-request-timestamp']; const signature = event.headers['x-slack-signature']; const body = event.body; // verify slack secret. const isValidSecret = verifySlackSignature(timestamp, signature, body); if (isValidSecret) { result.policyDocument.Statement[0].Effect = 'Allow' as Effect; } else { result.policyDocument.Statement[0].Effect = 'Deny' as Effect; } return result; }
serverless.yamlのfunctionsの設定をしているときにドキュメントを見ながら、identitySourceにbodyを設定できないことを知ったあたりから薄々怪しさを感じていたもの、
念の為、 slashCommandAuthorizer
が受け取っているevent内容をconsole.logで出力してCloudWatchLog上から覗いてみることに...
しかし、この時点でCustom Authorizerで扱えるのはheaderのみと認識し軌道修正。
どうやら、aws-sdkのインターフェースにも定義されている通り、Custom Authorizerに設定したLambdaには次のようなイベントが渡ってきており、その中にbodyは含まれないようです。
// API Gateway CustomAuthorizer "event" export interface CustomAuthorizerEvent { type: string; methodArn: string; authorizationToken?: string; resource?: string; path?: string; httpMethod?: string; headers?: { [name: string]: string }; multiValueHeaders?: { [name: string]: string[] }; pathParameters?: { [name: string]: string } | null; queryStringParameters?: { [name: string]: string } | null; multiValueQueryStringParameters?: { [name: string]: string[] } | null; stageVariables?: { [name: string]: string }; requestContext?: APIGatewayEventRequestContext; domainName?: string; apiId?: string; }
代替案
Custom Authorizerを使わずにそれぞれのhandlerで verifySlackSignature
メソッドを呼び出す実装にした。
// handler.ts export async function slashCommand(event: APIGatewayEvent, context: Context): Promise<any> { try { // ここで authorizeRequest を直接呼び出す authorizeRequest(event); // メインのロジック // ... } catch (e) { if (e instanceof AuthError) { // 認証エラーのハンドリング } // サーバーエラーのハンドリング } }; // authorize-request.ts export function authorizeRequest(event: APIGatewayEvent): void { try { const timestamp = event.headers['X-Slack-Request-Timestamp']; const signature = event.headers['X-Slack-Signature']; const body = event.body; // verify slack secret. const result = verifySlackSignature(timestamp, signature, body); if (!result) { throw new AuthError('Authentication failed.'); } } catch (e) { throw e; } }
まとめ
- bodyの内容が含まれているタイプの署名の検証にはCustom Authorizerは使えない
- おとなしくメインのhandlerから署名の検証に必要な情報を取得するのがよい
- Custom Authorizerが今後対応してくるかは未知(そもそも今回のようなユースケースは想定されていなそう?)
参考
Nest.jsでgRPCサービスのハンドリング
Nest.jsでは microservices
パッケージの1機能として、gRPCによる通信をサポートしている。
Nest.jsでgRPCのサーバ実装をされてる方がいて、この記事がすごく参考になった。 https://qiita.com/jnst/items/27b6a0cd3813b34f98e4
microservices
パッケージにはクライアント実装のラッパーも含まれてるようなので、今回はこれを試してみる。
Nest.js公式の sampleの実装 をみるとなんとなく雰囲気はわかる。
サーバ
上記で挙げたQiitaの記事のサンプルリポジトリがGitHubに公開されていたので、こちらを使わせてもらった。
以下の .proto
に定義されたサービスをハンドリングする前提で進める。
https://github.com/jnst/x-nestjs-grpc/blob/master/protos/rpc/rpc.proto
プロジェクトの雛形作成
$ node -v v10.14.1 $ yarn global add @nestjs/cli
必要なパッケージをインストール
$ yarn add @nestjs/microservices @grpc/proto-loader grpc protobufjs
今回はNest CLIでプロジェクトを作成し、rest moduleというモジュールを定義した。
$ nest new x-nestjs-grpc-client $ nest g mo rest
main.ts
でhttp待受ポートを変更
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Serverに使うプロジェクトが3000を使っているのでずらした await app.listen(3001); } bootstrap();
ClientOption
@Client
Decoratorに渡すconfigは以下の通り。
import { ClientOptions, Transport } from '@nestjs/microservices'; import { join } from 'path'; const protoDir = join(__dirname, '..', 'protos'); export const grpcClientOptions: ClientOptions = { transport: Transport.GRPC, options: { url: '0.0.0.0:5000', package: 'rpc', protoPath: '/rpc/rpc.proto', loader: { keepCase: true, longs: Number, enums: String, defaults: false, arrays: true, objects: true, includeDirs: [protoDir], }, }, };
options.loader
以下のオブジェクトは protoLoader.loadSync
の Options
オブジェクト内容として扱われる。
gRPCサーバの機能をREST APIとして提供するコントローラー
今回のサンプルでは、Nest.jsで作成したWebサーバから別のgRPCサーバを呼び出して、REST APIとしてその機能を提供するというユースケースを想定している。
以下は、ControllerからgRPCをハンドリングするサービスの機能を呼び出している部分。
@Client
DecoratorでgRPCクライアントを取得して、ライフサイクルフック onModuleInit
のタイミングで、実際に使用するサービスを動的に取得している。
(このライフサイクルフックを使わずにConstructor内でサービスの取得を行おうとしたところエラーが発生したので、おとなしく使ったほうが良さそう)
import { Controller, Get, Query, OnModuleInit } from '@nestjs/common'; import { Client, ClientGrpc } from '@nestjs/microservices'; import { RpcService, Empty, GetChampionResponse, GetChampionRequest, ListChampionsResponse, GetBattleFieldResponse, } from '../../types'; import { grpcClientOptions } from '../grpc-client.options'; @Controller('rest') export class RestController implements OnModuleInit { @Client(grpcClientOptions) private readonly client: ClientGrpc; private rpcService: RpcService onModuleInit(): void { this.rpcService = this.client.getService<RpcService>('Rpc'); } @Get('champion') async getChampion( @Query() request: GetChampionRequest, ): Promise<GetChampionResponse> { return this.rpcService.getChampion(request); } @Get('champions') async getChampions(@Query() request: Empty): Promise<ListChampionsResponse> { return this.rpcService.listChampions(request); } @Get('battle_field') async getBattleField( @Query() request: Empty, ): Promise<GetBattleFieldResponse> { return this.rpcService.getBattleField(request); } }
実際の利用場面では、gRPCサーバから取得した情報をREST サーバー側で加工してからレスポンスするパターンなどが想定される。
その場合、今回のように直接コントローラーでgRPC Clientを扱うのではなくService層で扱うことになるかと思う。
動作確認
次のように、gRPCサーバーの内容をREST APIとして呼び出せるようになっていることがわかる。
$ curl -s 'localhost:3001/rest/champion?champion_id=1' | jq { "champion": { "champion_id": 1, "type": 3, "name": "Akali", "message": "If you look dangerous, you better be dangerous." } }
$ curl -s 'localhost:3001/rest/champions' | jq { "champions": [ { "champion_id": 1, "type": "ASSASSIN", "name": "Akali", "message": "If you look dangerous, you better be dangerous." }, { "champion_id": 2, "type": "MAGE", "name": "Kennen", "message": "The Heart of the Tempest beats eternal...and those beaten remember eternally." }, { "champion_id": 3, "type": "FIGHTER", "name": "Tryndamere", "message": "Rage is my weapon." } ] }
$ curl -s 'localhost:3001/rest/battle_field' | jq { "battle_field": { "battle_field_id": 2, "name": "The Twisted Treeline", "description": "" } }
終わりに
Nest.jsの microservices
パッケージ自体、まだ発展途上な感じられる(インターフェースは定義されいるが実装がされていないなど)が、Nest.jsライクな操作(Decoratorでサービスのメソッドを定義するなど)でgRPCのマイクロサービスを構築できるのはかなりのメリットだと思うので、今後も使っていきたい。Server, Clientともにnodeの grpc
パッケージのインターフェースを事前に触っていると取っつきやすいように感じる。
TypeScriptでgRPCのstreaming RPCを使ったチャットのサンプル
はじめに
Node.jsのgRPCサンプルコードを解説されている方がいたのですが、この記事で触れられているのはUnaryなリクエストのみで、Streem通信に関しては触れられていなかったのでChartを例に試してみた。
Node.jsは現時点(2018/12/23)でのLTSv10.14.2を使っています。 gRPCの通信方式の違いに関しては、公式ドキュメントの RPC life cycleの項目が参考になります。
Protpcol Buffersの定義
gRPC公式サンプルの中に含まれているstreamを使った例 route_guide.proto
を参考にchartアプリケーションを想定した簡単なProtpcol Buffersの定義を作成しました。
https://github.com/grpc/grpc/tree/master/examples/protos
// proto version. syntax = "proto3"; // package name. package example; service Chat { rpc join(stream Message) returns (stream Message) {} rpc send(Message) returns (Message) {} } message Message { string user = 1; string text = 2; }
上記.protoファイルのmessageと対応するTypeScriptのInterfaceも作成しました。
// .protoファイルの message定義に合せてtypescriptのinterfaceを用意 export interface Message { user: string; text: string; }
Server
今回は、Node.jsの grpc
パッケージのインターフェースを一通り眺めながら進めて行きたかったので、protoファイルからコードを自動生成を行うことはしていません。protoLoaderに.protoファイルを渡してサービス定義を読み込みます。
今回のように実行時に動的にコード生成を行う方法を dynamic codegen
といい、事前にコード生成(静的にコード生成)をおこなう方法を static codegen
と呼ぶようです。
import * as grpc from 'grpc'; import { ServerWriteableStream } from 'grpc'; import * as protoLoader from '@grpc/proto-loader'; import { Message } from './types'; const server = new grpc.Server(); const serverAddress = '0.0.0.0:5001'; // .protoファイルを動的に読み込み const proto = grpc.loadPackageDefinition( protoLoader.loadSync('./chat.proto', { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, }) ); const users: ServerWriteableStream<Message>[] = []; function join(call: ServerWriteableStream<Message>): void { users.push(call); notifyChat({ user: 'Server', text: `new user joined ...` }); } function send(call: ServerWriteableStream<Message>): void { console.log(`send message from ${call.request.user}: ${call.request.text}`); notifyChat(call.request); } function notifyChat(message: Message): void { // すべてのユーザーにメッセージを送信 users.forEach((user) => { user.write(message); }); } // .protoファイルで定義したserviceと上記実装をマッピング server.addService(proto.example.Chat.service, { join: join, send: send }); // Serverのconfig設定 & 起動 server.bind(serverAddress, grpc.ServerCredentials.createInsecure()); server.start();
Client
Client側もServer側と同様に、protoLoaderに.protoファイルを渡してサービス定義を読み込みます。
import * as grpc from 'grpc'; import * as protoLoader from '@grpc/proto-loader'; import * as readline from 'readline'; import { Message } from './types'; const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); const proto = grpc.loadPackageDefinition( protoLoader.loadSync('./chat.proto', { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, }) ); const remoteServer = '0.0.0.0:5001'; let username = ''; const client = new proto.example.Chat( remoteServer, grpc.credentials.createInsecure() ) function startChat(): void { let channel = client.join({ user: username }); channel.on('data', onDataCallback); // 標準入力に新しい行が入力されるごとにServerに送信 rl.addListener('line', (text: string) => { client.send({ user: username, text: text }, () => {}); }); } function onDataCallback(message: Message): void { if (message.user === username) { return; } console.log(`${message.user}: ${message.text}`); } rl.question('User name: ', (answer: string) => { username = answer; startChat(); });
動作確認
今回は、REPLで実行する。
まずはServer側を実行しておく
$ ts-node server.ts
その後、Client側(1)を実行
ユーザー名を入力しEnterを押すと入力待受状態になる
$ ts-node client.ts User name: daikiojm Server: new user joined ...
Client(2)も同様に実行
$ ts-node client.ts User name: test-user Server: new user joined ...
その後は双方向の通信が確立される
$ ts-node client.ts User name: test-user Server: new user joined ... daikiojm: はらへった me too
終わりに
gRPCのstreaming RPCはClient - Serverで真意を発揮しそうなので、機会があればgRPC-webでも試してみたい。
従来httpとwsを併用していたような箇所をgRPC1本にまとめつつ型安全を保てるのはかなり実用的な印象がある。
Node.jsでのServer実装の例は少なそうなので、そのうちもう少し大きめのサンプルを作ってみたいかもしれない。
参考
https://tomokazu-kozuma.com/run-grpc-sample-code-with-nodejs/
https://grpc.io/docs/guides/concepts.html
https://github.com/improbable-eng/ts-protoc-gen
フロントエンドエンジニアのAWSとの付き合い方について
この記事は、ex-handslab Advent Calendar 2018 16日目の記事です(大分遅れています)。 私が以前在籍していたハンズラボはAWS関連技術に強みを持った会社だったので、今回はAWS関連の小ネタを書きたいと思います。
対象の読者
ざーっと、書いてみて以下のような方が対象の読者になるのではないかと思っています。
※ 普段からAWSを使って開発している人にとっては当たり前の内容ばかりかもしれないです
この記事を書こうと思ったきっかけ
フロントエンドエンジニアと一口に言ってもいろいろなバックエンドを持つ人がいる
ここでは、言葉の定義を深掘りすることはしないが、以下のようにフロントエンドエンジニアと言ってもいろいろなバックエンドを持つ人がいるなぁという印象を受けている。
- サーバーサイドインジニア
- Webコーダー
- デザイナー
- プログラミングスクールを経てフロントエンドエンジニアとして働き始めたばかりの人
- などなど
自分自身も、新卒で入社した会社ではWindowsのネイティブアプリケーションの開発/インフラの保守のような仕事を行っていた後、exしたハンズラボでは、AWSのマネージドサービス/アプリケーションサービスを使い、主にフロントエンド仕事をしてきた経緯がある。
フロントエンド/バックエンドともに専門性が増している
フロントエンド(特にSPA)フレームワーク/ライブラリのが普及していることや、ツールチェーン周りのエコシステムの発達などで、フロントエンドエンジニアの備範囲が広がっている印象がある。 一方サーバーサイドインジニアもまた、マイクロサービスアーキテクチャ、AWSなどのクラウドサービスに依存するアプリケーションサービスのキャッチアップなど、それぞれの領域で求められる専門性が増している印象がある。 専門性と同時に多少性もましている気がしていて、これに関してはすごく共感できる記事を書いている方がいた。
今回は、上記を踏まえてAWS上に構築されるWebアプリケーションを開発するフロントエンドエンジニアが最低限知っておきたいAWSサービスに関する内容をざっくりまとめていきたいと思う。
関わりが深そうなAWSサービス一覧
フロントエンドエンジニアと関わりが深そうなAWSサービス一覧とユースケースを並べていく。
S3
Cloud Front
- Webフロントエンド配信CDNとして
- エッジキャッシュサーバーを利用してオリジンからのレスポンスをキャッシュする
- オリジンのアクセス削減
- エッジサーバーを利用するため地理的に離れた環境でも高速な読み込みを実現
- 上記で挙げたS3と合わせて使われることが多い
参考
Lambda (Lambda@Edge)
- SSRのレンダリングサーバとして使う
- Lambda@Edgeでコンテンツヘッダーのカスタマイズ
- CloudFront の Behaviorに対してLambda@EdgeをトリガーすることでCloudFrontのレスポンスヘッダーを操作することができる
- セキュリティ関連ヘッダー(CSPなど)の不可やキャッシュコントロールなども行える
参考 https://dev.classmethod.jp/cloud/aws/lambda-edge-design-best-practices/ https://tech-blog.abeja.asia/entry/front-engineer-re-invent
Route53
- S3 + CloudFrontで配信するコンテンツに独自のDNS名を付ける必要がある際に利用する
Cognito
- フロントエンドアプリケーションとAWSだけで完結する認証/認可
API Gateway
Code Build
- フロントエンドアプリケーションのビルド・テスト
circle CIを使ったことがあれば、同じ感覚でyamlを書けばAWS上でCodeのビルド・テスト(デプロイ)が可能 ソースコードのホスティングまで含めてAWSで行っている場合、AWSに閉じた環境で一貫したフロントエンドアプリの開発・デプロイができるので便利っぽい
AppSync
Amplify Console
- WebフロントエンドアプリケーションのCI/CDを簡単に構築出るサービス
- 今年のre:Inventで発表されたらしい
参考
https://dev.classmethod.jp/cloud/aws/amplify-console/
フロントエンド周りのAWS構築/運用は誰がやるのか問題
上記で挙げたようなフロントエンドアプリケーションを構築するために利用するAWSサービスは様々。 チームの規模感なんかによっても、変わってくるとはいもうけど、AWS特有のアプリケーションサービスが多いので、インフラの管理を行うチームでは対応しきれずフロントエンドエンジニアがここらへんの面倒を見るパターンも多そうというという印象。 AWSに限らず、ある程度の規模感をもって開発しているチームでフロントエンド配信CDNの運用とかをどうやっているかは単純に興味がある。
最後に
ここまで挙げてきたように、AWSではフロントエンド開発にも欠かせないツールを数多く提供している印象。 上記で挙げたようなアプリケーションサービスはサーバーレスアーキテクチャの文脈で話に出ることも多い。 サーバーサイドの開発経験がないフロントエンドエンジニアでもこのようなAWSサービスを組み合わせることで簡単にWebアプリケーション全体を構築することができたりする。
(とりあえず書いてみた感が否めないのとかなり遅れてしまったけど、書けたのでほっとしている😇)