create-react-appでTypeScript環境を構築したときにsourcemapを出力しない方法
Reactのテンプレートを作る方法にcreate-react-app
があります。
これを使うとcreate-react-app
コマンドで簡単にテンプレートを作って、初期設定されているnpm scripts
でサーバー起動やビルドといったことが簡単にできるようになっています。
React+TypeScriptの環境を構築したい場合はどうするのかというとプロジェクト作成時のオプションで--scripts-version=react-scripts-ts
を指定することでTypeScriptの環境を構築することができます。
github.com
プロジェクトのビルドをするために、初期で用意されているnpm scripts
を実行します。
$ yarn build
buildディレクトリ以下に成果物が作成されます。
jsディレクトリ配下を見るとsourcemap
も作成されています。
開発中は便利なのですが、本番環境にデプロイするときはsourcemap
は作成しなくていいので、作成しないためにはどうするのか調べてみました。
webpack.config.prod.js
にてsourcemapの出力を制御するために、nodeの環境変数を読み込んでいる部分がありました。
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
package.jsonのscriptsでビルド実行時に環境変数を渡してあげることでsourcemap
を出力せずにビルドすることができました。
npm scripts
を修正すると↓の用になると思います。
"build": "GENERATE_SOURCEMAP=false react-scripts-ts build"
Reactでシングルクリックとダブルクリックを区別して使いたい
先に今回の話のソースコードです。 github.com
Reactでは シングルクリックイベントとダブルクリックイベントが用意されています。
シングルクリック
<a href="javascript:void(0)" onClick={(e) => this.handleOnSingleClick(e)}> Single Click </a>
onClickイベントが用意されています。
ダブルクリック
<a href="javascript:void(0)" onDoubleClick={(e) => this.handleOnDoubleClick(e)}> Double Click </a>
onDoubleClickイベントが用意されています。
※ mobile safari だと動きませんでした。
それぞれを別々に使う分には期待した通りに動作します。
問題はSingleClickとDoubleClickを両方設定したときです。
<a href="javascript:void(0)" onClick={(e) => this.handleOnSingleClick(e)} onDoubleClick={(e) => this.handleOnDoubleClick(e)}> Single Click and Double Click </a>
こんな形で実装して、ダブルクリックしてみると、onClickイベントが2回とonDoubleClickイベントが1回発火します。
シングルクリックのときは onClickイベントだけ、ダブルクリックの時はonDoubleClickイベントだけと区別して使いたいことがあります。
そういったときどうするのかという話です。
ソースコードはTypescriptで書いてます。
import * as React from 'react'; interface IAppProps { } interface IAppState { } export class App extends React.Component<IAppProps, IAppState> { private clickCount: number; constructor(props: IAppProps) { super(props); this.clickCount = 0; this.state= {}; } /** * Single or Double Click */ handleOnSingleOrDoubleClick = (e: React.MouseEvent<HTMLAnchorElement>) => { this.clickCount++; if (this.clickCount < 2) { setTimeout(() => { if (this.clickCount > 1) { console.log('Double Click'); } else { console.log('Single Click'); } this.clickCount = 0; }, 200); } } render() { return ( <div> <a href="javascript:void(0)" onClick={(e) => this.handleOnSingleOrDoubleClick(e)}> Single Click or Double Click </a> </div> ); } }
の様に書くとシングルクリックとダブルクリックを区別することができます。
キモになるのはこの部分で、onClickイベントのみを使います。
/** * Single or Double Click */ handleOnSingleOrDoubleClick = (e: React.MouseEvent<HTMLAnchorElement>) => { this.clickCount++; if (this.clickCount < 2) { setTimeout(() => { if (this.clickCount > 1) { console.log('Double Click'); } else { console.log('Single Click'); } this.clickCount = 0; }, 200); } }
クリックイベント時に、クリック数をインクリメントして、setTimeoutで後からクリック後の動作を実行してあげることでシングルクリックとダブルクリックを別々に認識させることができます。
クリックして動作が見れるページを作ってみたので、興味がある人はクリックしてみて下さい。 React doubleclick sample
AWSで最初からCloudFormationでインフラを管理する方法を考えてみる
AWSで最初からCloudFormationを使ってインフラを管理していく方法について考えてみました。
個人でサービスを作ろうと思い、あまりお金もかけたくなかったのでサービス選定は
項 | サービス名 |
---|---|
クラウドサービス | AWS |
ソースコード管理 | Bitbucket |
CI/CD | CircleCI |
といった感じです。
参考にしたサイトです。
CloudFormation と CI の組み合わせをする上で必要な物
- IAMユーザー
- S3 Bucket
今回だとCircleCIからAWSへアクセスする為のIAMユーザーが必要になります。
また、CloudFormationのテンプレートを分割して管理する場合は、S3 Bucketも必要になります。
導入手順
先に今回の内容をコード化したものがコチラです。 github.com
導入手順としては
- 全サービスにアクセスできるIAMユーザーを作る
- 自分が作成したいサービス向けのIAMユーザーを作る
という流れにしようと思いました。
悩ましいのが、どうしても最初はWebのAWSコンソールからIAMユーザーとS3 bucketの作成が必要になることでした。
いろいろ考えて納得できる方法としては
- 手作業でAWSコンソールのCloudFormationから
setup/cfn.template.yml
を指定して awscli 用のIAMとCloudFormationのテンプレートを一時的に置くS3 bucket を作成します。 - 作成されたIAMユーザーのkey, secretを作成してAWS CLIにアカウントをセットアップします。
- AWS CLI でcloudformationのスタックを作成して新規作成するサービス向けのインフラを構築するための準備をします。
という手順です。
- の作成に使うテンプレートだけは別管理にした方がわかりやすいかなと思いました。
CloudFormationのスタックが2つに分かれてしまいますが、管理しやすくて良いかなと思いました。
- スタック1(setupディレクトリ配下)
- awscli用のIAMユーザーとテンプレートなどを保存するためのs3 bucketを管理するテンプレート
- スタック2(srcディレクトリ配下)
- 各サービスを管理するためのユーザーを管理するテンプレート
の形にCloudFormationのスタックを分けることでができるので良いかなと思いました。
理想としては1つのスタックで完結させたいです。
ディレクトリツリーで見るとこんな感じです。
要は全てのIAMを src/iam.template.yml
で管理したいです。
├── src │ ├── bucket.template.yml │ ├── cfn.template.yml │ └── iam.template.yml
ですが、 最初はWebのAWSコンソールからIAMユーザーとS3 bucketの作成 をしないといけないので
├── setup │ └── cfn.template.yml ├── src │ ├── bucket.template.yml │ ├── cfn.template.yml │ └── iam.template.yml
と分け
├── setup │ └── cfn.template.yml
ここで、最初のユーザーを管理します。
AWS CLI からのスタック作成について
デプロイするテンプレートは管理しやすくするために分割してます。
├── src │ ├── cfn.template.yml │ └── iam.template.yml
実際のところは下の様に実行すればCloudFormationで実行可能なテンプレートを作成することができる。
$ aws cloudformation package \ --template-file src/cfn.template.yml \ --s3-bucket artifact \ --output-template-file .cfn/packaged.yml \ --s3-prefix template
このコマンドでしていることは、 --s3-bucket
で指定したバケットの --s3-prefix
で指定したディレクトリ配下に src/cfn.template.yml
以外のテンプレートファイルをアップロードします。
--output-template-file .cfn/packaged.yml
で指定している部分に出力されるファイルは、 src/cfn.template.yml
の参照している他テンプレートファイルを指定するパスが追加されています。
TemplateURL: /path/to/bucket/fc2e4965584e9309b667bf9a6c1eb6f4.template
の様な物が追加されています。
.cfn/packaged.yml
が作成されたら、create-stack
や update-stack
を使って更新していきます。
ここまでやるとiam.template.yml
で作成したIAMユーザーをCircleCIに設定することで自動更新ができるようになります。
あとは、新しく作りたいサービスがでたらそのサービス向けに権限を絞ったIAMユーザーを作ることができます。 そうすることで、CIなどを使った際に誤操作で別サービスのデータを消してしまうなどが防げるのではないかと思いました。
課題
今回の構成で自動デプロイできるようにはなってますが、課題もあります。
update-stack
実行後のCloudFormationの状態を監視していないのでCircleCIでは Success になるが CloudFormation では失敗してロールバックする可能性がある- 連続でデプロイしたとき、現在のスタックの状態を確認していないため、スタックが更新中の場合不具合が起きる可能性がある
備考
テンプレートの検証に cfn-lint
を使っていて気づいたけど、 Type: AWS::CloudFormation::Stack
を使ってテンプレートを分けた場合は、子テンプレートまでチェックしてくれるわけではないみたい。
あくまで引数に渡したテンプレートファイル単体のテストをしてくれるツールみたい
CircleCI2.0でawscliを使いたいのでDockerイメージを作ってみた
CircleCI2.0で awscli を使いたいと思ったとき、毎回 .circleci/config.yml
で awscli
のインストールコマンドを書くのも面倒に感じます。
dockerhubを使うと特定のイメージの更新をトリガーにして、自分のイメージも更新することができます。
Dockerイメージはdockerhubで公開してます。
https://hub.docker.com/r/naughtldy/circleci-node-awscli/
ソースコードもgithubで公開してます。
github.com
phpでlocalstackのs3にファイルをアップロード
AWS周りのものをローカルで開発する場合 localstack を使うと便利だというのを聞いたので試してみました。
localstackは、GUIはあまり提供されていない用で、主にawscliコマンドを使います。
s3にファイルをアップロードしたい場合は、 --endpoint-url=http://localhost:4572
を付けてあげてアップロード先を変えることができます。
試しに awscli で test というバケットを作ってファイルをアップロードしてみます。
$ aws --endpoint-url=http://localhost:4572 s3 mb s3://test make_bucket: test $ aws --endpoint-url=http://localhost:4572 s3 ls s3:// 2006-02-04 01:45:09 test $ touch hoge.txt $ aws --endpoint-url=http://localhost:4572 s3 cp hoge.txt s3://test/hoge.txt upload: ./hoge.txt to s3://test/hoge.txt $ aws --endpoint-url=http://localhost:4572 s3 ls s3://test/ 2017-12-08 20:00:00 0 hoge.txt
localstack上のs3にファイルがアップロードされていることが確認できます。
phpを使ってlocalstackのs3にファイルをアップロードしてみます。
awscliのときと同様に、endpointを localstack のs3用エンドポイントを指定してみます。
aws-sdk-php は composer を使って入れてます。
put.php
<?php require_once 'vendor/autoload.php'; use Aws\S3\S3Client as S3Client; $s3 = S3Client::factory(array( 'endpoint' => 'http://localhost:4572', 'credentials' => array( 'key' => '', 'secret' => '', ), 'version' => 'latest' )); $pamans = array( 'Bucket' => 'test', 'Key' => 'hello.txt', 'Body' => 'Hello World', 'CacheControl' => 'max-age=1', ); $s3->putObject($pamans);
実行してみるとエラーが出ます。
Could not resolve host: test.localhost [url] http://test.localhost:4572/hello.txt
hostsが解決できないようなので、/etc/hosts
に
127.0.0.1 test.localhost
を追記します。
再度実行してみるとエラーが出ずに終了し、ファイルが作られていることが確認できました。
$ php put.php $ aws --endpoint-url=http://localhost:4572 s3 ls s3://test/ 2017-12-08 21:00:00 11 hello.txt
javascriptやgoを使う方法を参考にしていると
- s3ForcePathStyle
- endpoint
のオプションを設定してあげればいいと出るのですが、 aws-sdk-php には s3ForcePathStyle
というオプションが無いです。
s3ForcePathStyle
というオプションを設定しないでアップロードしようとすると、先ほどのエラーにも出てましたが、s3バケット名.localhost:4572
となり、hostsが解決できなくなってしまいます。
php と localstack を使って開発したい場合は hosts を書いてあげて localstack が提供する各URLを指定してあげないといけなさそうです。
追記
コメントで指摘頂いた通り、PHPでは use_path_style_endpoint
があり、そちらのオプションを有効にすることで hosts
を設定すること無くテストすることができました。
id:imunew さん、ご指摘ありがとうございます。
put.php を下記の様に 'use_path_style_endpoint' => true
とオプションを追加することでhostsの設定をすることなくテストが可能になりました。
<?php require_once 'vendor/autoload.php'; use Aws\S3\S3Client as S3Client; $s3 = S3Client::factory(array( 'endpoint' => 'http://localhost:4572', 'credentials' => array( 'key' => '', 'secret' => '', ), 'version' => 'latest', 'use_path_style_endpoint' => true )); $pamans = array( 'Bucket' => 'test', 'Key' => 'hello.txt', 'Body' => 'Hello World', 'CacheControl' => 'max-age=1', ); $s3->putObject($pamans);
参考
Testable Lambda: Working Effectively with Legacy Lambda // Speaker Deck
Testable Lambda|AWS Summit Tokyo 2017 - YouTube
docker exec で cd する方法
dockerを使っていて、普段は↓のコマンドでコンテナ内に入って作業してます。
$ docker exec -it {container-name} /bin/bash
最近はコンテナ内でお決まりの作業(特定のディレクトリに移動してコマンド実行など)をしたいときに
$ docker exec -i {container-name} "cd /path/to && ls"
の様な形で実行できると思っていたのですが、実際は違っていて上手くいきませんでした。
↓の様なエラーが出ました。
$ docker exec -i {container-name} "cd /var/log && ls" oci runtime error: exec failed: container_linux.go:265: starting container process caused "exec: \"cd /var/log && ls\": stat cd /var/log && ls: no such file or directory"
cd する方法
$ docker exec -i {container-name} /bin/bash -c "cd /path/to && ls"
という風に、 /bin/bash -c
としてその後に実行したいコマンドを書いてあげると動く。
参考
docker-exec failed: "cd": executable file not found in $PATH - Stack Overflow
ISUCON7予選に参加しました
id:Maco_Tasu に、ISUCONに一緒に出ませんかと誘ってもらえたのでいい機会だしと思い参加しました。
LabALICEというチームで、メンバーはid:Maco_Tasu, id:karia です。
全員のスコアと順位を出して頂けて運営の方には感謝です。
最終スコアは 79,590 で、43位でした。
当日私は、画像周りの修正をしたのでそのあたりについて記録を残しておこうと思います。
事前準備
チームで3回集まって過去問を解きました。
解いた過去問は
- isucon6予選
- isucon4予選
- pixiv社の社内向けisucon
です。
過去問を解くことで全体的な流れを掴むことができました。
そこで一番感じたのが、競技中にNginxの設定などを動作確認しながら作成してると時間がもったいないということでした。
NginxやMySQLなど、あらかじめ使うであろう物の設定ファイルはひな形を作っておこうとチームで話あって事前準備をしていました。
当日
最初の役割分担は id:Maco_Tasu がgithubへのソースコード登録、id:karia がインフラの整備となっていました。
私は、DBテーブルの確認をしてからアプリを触って動作確認をしていました。
今回のお題はチャットアプリで、画像の数が多いためNginxから返せれば速くなるのかなと思いながら触ってました。
一通りアプリを触ったので、ソースコードの確認に入りました。
ソースコードを見ると、pixiv社の社内向けisuconと同様に画像をMySQLに入れている部分が目に付きました。
予選の環境はDBサーバー1台とAPサーバー2台の構成でした。
画像はMySQLにいれるのを止めてローカルに保存してNginxから返す様にするというのはすぐに思いつきました。
問題はどのサーバーで保存するかというところで少し考えました。
- APサーバーからDBサーバーのMySQLに画像を保存するのを止める
- DBサーバーのローカルに画像を保存する
と考えたときに、DBサーバーのローカルに保存したいならDBサーバーをフロントにして、画像の保存はDBサーバーがやってそれ以外の処理は後ろのAPサーバーに流してあげれば良いんじゃないかと考えてインフラの構成を変更しました。
インフラの変更は id:karia にお願いをして、アプリの改修をしました。
改修内容は下の2つです。
- 画像の登録/更新があったらMySQLには保存せずにローカルに保存する
- 初期にDBに登録されている画像をローカルに書き出しておく
この後ベンチを回したりして動作を確認していたのですが、どうもDBサーバーの負荷が高くフロントと兼任させるのは良くないという事がわかってきました。
ここで、構成を変更することにしました。
次に試した方法は AP1台をフロントにして、フロント・AP・DBがそれぞれ1台づつの構成です。
- フロントサーバーは、追加された画像のローカル保存
- APは、画像処理以外の実行
に担当を分けてみました。
この構成だとDBに余裕ができたので、スコアを上げることができました。
その後は、GET /message
GET /history/:channel_id
のクエリを直したりということをしていました。
結果は予選敗退でしたが、気づきがとても多く参加して良かったと思います。
運営の皆様ありがとうございました。
id:Maco_Tasu, id:karia チームを組んでくれてありがとうございました。
学び/勘違いしていたこと
- DBの接続設定
リクエストごとにコネクションが作られる
def db return @db_client if defined?(@db_client) @db_client = Mysql2::Client.new(
コネクションの使い回しができる
def db return Thread.current[:isuconp_db] if Thread.current[:isuconp_db] client = Mysql2::Client.new(
- MySQLのコネクションがちゃんと閉じられていることを確認するのは大切