create-react-appはなぜmanifest.jsonを作るの?
スマートデバイスで「ホームに追加」を出すためだった。
manifest.jsonて拡張機能作るときに書くけど、なんでSPA作るcreate-react-appがmanifest.jsonを作るのか気になった。
reactjs - What is public/manifest.json file in create-react-app? - Stack Overflow
このSOのアンサーで参照されているウェブアプリマニフェスト | MDNにこうある。
ウェブアプリマニフェストは、 JSON 形式のファイルでアプリケーションについての情報 (名前、作者、アイコン、説明など) を提供するものです。マニフェストは端末のホーム画面にインストールされたウェブサイトの詳細を通知し、ユーザーによりすばやいアクセスと、より豊かな使い勝手を提供します
create-react-appはService Workerもサポートしているから、その一環でmanifest.jsonが作られるのだな、と解釈した。
kustomizeのTimeoutについて
注意: kustomize1系の話。2系を使う場合以下は参考にならない。
背景
k8sのyamlを書くのにkustomizeを使っている。kustomizeの secretGenerator.commands で時間のかかる処理(GCSに置いてある秘匿情報を gsutil cat で取得するなど)をしたところ以下のエラーメッセージを吐いて終了してしまった。
Error: NewResMapFromSecretArgs: NewResMapFromSecretArgs: commands map[password:gsutil cat gs://myproject/mysql/password username:gsutil cat gs://myproject/mysql/username]: signal: killed
secretGenerator.commands に書かれた処理はデフォルトでは5秒で終了する
わからないことはググる。「kustomize "timeout"」でググった。timeoutをダブルクオーテーションでくくらないと有用な情報に当たらなかった。timeoutに完全一致させて検索すると以下のPRが一番上に来た。
secretGenerator.commands に書かれた処理は5秒でタイムアウトするらしい。このPRでは kustomize build に --command-timeout というオプションを付けることを提案している。しかし、「 kustomize build にはオプション作りたくないんだ」と言われて却下されている。
TimeoutSeconds オプションができる
同じ課題を抱えていることはわかったので誰かが何とかしてくれているはずである。PRの検索欄にtimeoutと入れて探すと以下のPRが見つかる。
TimeoutSeconds オプションができている。
結論
TimeoutSeconds オプションを付けるだけ。
secretGenerator:
- name: db-secrets
commands:
username: "gsutil cat gs://myproject/mysql/username"
password: "gsutil cat gs://myproject/mysql/password"
TimeoutSeconds: 30
NginxでCORSの設定を入れるときはOPTIONSの処理を忘れない
server {
listen 80;
...
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD";
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Expose-Headers x-total-count;
if ($request_method = "OPTIONS") {
return 204;
}
...
}
OPTIONSにNGINXが返答しない場合、裏側のアプリケーションサーバーまで通信が行くが、そこで処理できない場合適当なエラーが帰る。
おまけ: Railsの場合
rack-corsがOPTIONSリクエストに対して200番を返してくれている。
Reduxを使ってるときのAuth0の使い方
背景
ログイン処理にAuth0を使っている。Auth0はサンプルが豊富でReactを使ったサンプルも在るが、そのサンプルではReduxは使わずPure React構成のため、Reduxを使っている場合の使い方がイマイチ分からなかったので、考えて実装した結果をまとめておく。
どこで悩むか
基本的には Auth0 React SDK Quickstarts: Login を見てまず実装した上でRedux化していくのが良い。その上で悩みポイントは2つ。
- Authオブジェクトで直接historyAPIを叩いているが、react-router-reduxを使っている場合それはできない
- Authオブジェクトのインスタンス化のタイミング
AuthオブジェクトからhistoryAPIを叩く方法
react-router-reduxを使う場合historyAPIもActionを発行しReducerに処理させなければならない。そうなると store.dispatch をAuthオブジェクトは使わざるを得ない。
つまり公式のサンプルでは hitsoty.replace('/home') とやっているところを、 dispatch(push('/home')) とすれば良い。コードは以下のようになる。
Auth.js
import auth0 from 'auth0-js' import { push } from 'react-router-redux' export default class Auth { auth0 = new auth0.WebAuth({ domain: AUTH0_DOMAIN clientID: AUTH0_CLIENT_ID, redirectUri: AUTH0_REDIRECT_URI responseType: 'token id_token', scope: 'openid' }) constructor (dispatch) { this.login = this.login.bind(this) this.handleAuthentication = this.handleAuthentication.bind(this) this.isAuthenticated = this.isAuthenticated.bind(this) this.logout = this.logout.bind(this) this.dispatch = dispatch // constructorの引数でdispatchをもらってthisに束縛する } login () { this.auth0.authorize() } handleAuthentication () { this.auth0.parseHash((err, authResult) => { if (authResult && authResult.accessToken && authResult.idToken) { this.setSession(authResult) this.dispatch(push('/home')) // ここが公式のサンプルでは history.replace('/home') になってる } else if (err) { console.log(err) this.dispatch(push('/hoe')) } }) } setSession (authResult) { let expiresAt = JSON.stringify( authResult.expiresIn * 1000 + new Date().getTime() ) localStorage.setItem('access_token', authResult.accessToken) localStorage.setItem('id_token', authResult.idToken) localStorage.setItem('expires_at', expiresAt) this.dispatch(push('/home')) } isAuthenticated () { let expiresAt = JSON.parse(localStorage.getItem('expires_at')) return new Date().getTime() < expiresAt } logout () { // Clear Access Token and ID Token from local storage localStorage.removeItem('access_token') localStorage.removeItem('id_token') localStorage.removeItem('expires_at') this.dispatch(push('/home')) } }
ディレクトリ構成
Authにstore.dispatchを渡さなければならないため、Authオブジェクトのインスタンス化とStoreのインスタンス化のタイミングは同時のタイミングのほうが都合が良い。よってディレクトリの構成は以下のようにした。
- App.js
- ページ全体のテンプレート 且つ ルーティング設定
- index.js
公式のサンプルだと routes.js というのがあって、ルーティングの設定を切り出しているが、 App.js で全てやってしまった。コードは以下のようになる。
index.js
import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' import { ConnectedRouter } from 'react-router-redux' import createBrowserHistory from 'history/createBrowserHistory' import App from './App' import Auth from './auth' import { createStore } from './store' const history = createBrowserHistory() const store = createStore(history) const auth = new Auth(store.dispatch) // authのdispatchを渡す ReactDOM.render( <Provider store={store}> <ConnectedRouter history={history}> <App auth={auth} /> </ConnectedRouter> </Provider>, document.getElementById('root') )
App.js
handleAuthenticationの引数にauthを追加しているのに注意。公式のサンプルだとauthがスコープにいたが、この構成だと明示的に渡してやる必要がある。
import React from 'react' import { Route, Switch, Redirect } from 'react-router-dom' const handleAuthentication = (nextState, replace, auth) => { if (/access_token|id_token|error/.test(nextState.location.hash)) { auth.handleAuthentication() } } export default ({ auth }) => ( <Switch> <Route exact path='/login' render={() => <LoginPage auth={auth} />} /> <Route exact path='/callback' render={props => { handleAuthentication(props, null, auth) return <p>ログイン中</p> }} /> </Switch> )
ログイン処理はこうなる
handleAuthentication()の処理の完了を待たず "ログイン中" がレンダリングされるhandleAuthentication()でログイン処理が終了すると push('/') がstoreにdispatchされ、react-router-reduxのRouterReducerがページ遷移を実行する

改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/30
- メディア: 大型本
- この商品を含むブログを見る

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)
- 作者: 穴井宏幸,石井直矢,柴田和祈,三宮肇
- 出版社/メーカー: 翔泳社
- 発売日: 2018/02/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る

「Auth0」で作る!認証付きシングルページアプリケーション (技術書典シリーズ(NextPublishing))
- 作者: 土屋貴裕
- 出版社/メーカー: インプレスR&D
- 発売日: 2018/08/31
- メディア: Kindle版
- この商品を含むブログを見る
Prettierとstandardを併用する
背景
今のプロダクトにlinterを入れたかった。今までは雑にググってヒットしたPrettierのみ使っていた。
Linterも入れたほうがいいらしい
PrettierはFormatterであってLinterではない。だからPrettierはコードの「この部分が良くないよ!」とは教えてくれず問答無用で整形だけしてくれる。「この部分が良くないよ!」を教えてもらうにはLinterが必要。JavascriptならESLintがデファクトスタンダード。
eslint --fix で良くない?
おれもそう思ったが、2つの理由でPrettierも一緒に使ってもよいかなと思う。
まず、WebStormがPrettierに公式で対応していて使うのが簡単だから。Prettierがインストールされていれば何の設定も無しに Alt-Shift-Cmd-P を押せば整形してくれる。これは以前記事にも書いた。
ふたつめが、横長過ぎるコードをESLintの --fix では整形できないが、Prettierはこれができる。なぜかというと、Prettierは書かれているコードをパースしてASTにして整形するから、らしい(What is Prettier? · Prettier)。だから横長過ぎるコードも妥当な位置で改行したりインデントを入れることができるようだ。
というわけでESLintとPrettierを併用することにした。
standard
ESLintには、Javascriptのコーディング規約に則っているかチェックしてもらいたい。Javascriptのコーディング規約は世の中にいくつかあるが、Javascript Standard Styleに従うことにした。
Standard Style用にESLintの設定を書くのは骨が折れれるが、Standard StyleはGitHub - standard/standard: 🌟 JavaScript Style Guide, with linter & automatic code fixerというツールを提供している。これはESLintの設定をまるっとやって standard コマンドにラップしている。だからインストールして $ standard --fix と叩けばStandard Styleに則ったコードに整形される。素敵。
prettier-standardでPrettierとstandardを併用する
standardを使うと、今度困るのがPrettierで、PrettierのコードフォーマットもJavascript Standard Styleに従ってもらう必要がある。しかし、Prettierの設定でStandard Styleになるよう書くのもやはり骨が折れる。そこで調べているとprettier-standardというのがあった。
GitHub - sheerun/prettier-standard: (✿◠‿◠) Prettier and standard brought together!
これは $ prettier-standard ''/path/to/file' でstandardとPrettierを両方適用してくれる。素敵!!!
WebStormからprettier-standard呼べるの?
残念ながらWebStormから呼べるのはPrettier単体のみ。ただし、あとで説明するようにgit commit時にprettier-standardが走るように設定しておけば、書いている最中はどんな風に整形しても問題はない。だからPrettier単体で呼び出して使っています。ただし、セミコロンの設定だけは気になったから .prettierrc を作って ; は補完されないようにしている。
{ "semi": false }
git commit時にprettier-standardを走らせるようにする
commit hookはcloneしてから各自で設定する必要があり微妙に使いづらいが、huskyというライブラリを使うと、インストール時にcommit hookの設定を書き込んでくれる。git commit時に具体的にどのようなコマンドを実行するかは package.json に書けるようになるという素晴らしいライブラリ。これとlint-stagedというライブラリを併用すると、git commit時にコードフォーマットを当ててからgit commitするということが可能になる。この記事が詳しい。
コミット前に Lint を強制するなら lint-staged が便利
結局どうなったの
package.jsonに以下のように書き足すことでgit commit時にprettier-standardが走って全JSファイルをフォーマットしてくれる。
{ "scripts": { "format": "prettier-standard 'src/**/*.js'" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.js": [ "yarn format", "git add" ] }, }
関数の引数にスプレッド構文を使った場合
背景
React書いていると以下のような...propsのような記述がたまに見られるが、どういう振る舞いになるのか少し混乱したから整理する。
const DeleteButton = ({className, ...props}) => ( <span className={[style.root, className].join(' ')} {...props}> <TrashCanIcon /> <Baloon>削除する</Baloon> </span> );
スプレッド構文
ドットを3つつなげたこれはスプレッド構文と呼ばれている。Iterableなオブジェクトの中身を展開してくれるようだ。
これらは以下の3つの場面で使うことができる。 1. 関数呼び出し 2. Arrayリテラル 3. Objectリテラル
2,3は想像が付くんだが関数呼び出しのときの振る舞いがうまく想像できない。
実験
とりあえず使ってみる。
const hage = ({...props}) => {console.log(props)}; hage({a:1}); // => {a: 1}
const hage=(props) => {console.log(props)};と変わらないっぽい?
propsの中身を一度展開し、再度Objectにしたものをpropsという名前で束縛しているのか?
そもそも({a, b}) => {}って引数に渡されたObjectからaとbのプロパティだけを抽出する構文だよね?なんかおかしくね?
const hige = ({...props}) => {console.log(a)}; hige({a: 1}); // => ReferenceError: a is not defined
これでaに1が束縛されるんじゃないかと思ったんだけどな…。
propsの中身を一度展開し、再度Objectにしたものを
propsという名前で束縛している?
これが正解っぽい
const huge = ({a, ...props}) => {console.log(a, props)}; huge({a: 1, b: 2, c: 3}); /// => (1, {b: 2, c: 3})
なるほど?...を付けたら問答無用で残りは引き渡ってくれるのかー。
結論
というわけで最初の例はなんでもごちゃごちゃに渡されてくるpropsからclassNameだけをチュ出し、残りの要素は再度propsに束縛するという感じか。納得はしてないが振る舞いはわかった。
再掲
const DeleteButton = ({className, ...props}) => ( <span className={[style.root, className].join(' ')} {...props}> <TrashCanIcon /> <Baloon>削除する</Baloon> </span> );
storybookに階層構造を追加する
背景
デフォルトでは1階層しかパスが切れない。
例えばAtomsのButton、MoleculesのFormがあったとして、理想的には以下のように整理したい。
- Atoms
- Button
- with text
- with some emoji
- Button
- Molecules
- Form
- ContactForm
- MailAddressForm
- Form
しかしデフォルトでは以下のようにするしかない。
- Button
- with text
- with some emoji
- Form
- ContactForm
- MailAddressForm
@storybook/addon-chaptersを使うと実現できるらしい。おれは以下の本を読んでこのアドオンの存在を知った。
Atomic Design ~堅牢で使いやすいUIを効率良く設計する | 五藤 佑典 | コンピュータ・IT | Kindleストア | Amazon
公式のaddon galleryでも紹介されている。
https://storybook.js.org/addons/addon-gallery/#chapters
階層構造と読んでいたがChapterと言うようだ。
@storybook/addon-chapters
install
npmを見る。
@storybook/addon-chapters - npm
storiesOf('Atoms', module) .addChapter('Button', chapter=>chapter .add('with text', () => <Button>with text</Button>) .add('with som emoji', () => <Button>with some emoji</Button>) ); storiesOf('Molecules', module) .addChapter('Form', chapter=>chapter .add( ...
というような感じで階層構造を追加出来た。
storybook-directory-chapters
components以下のdirectory構造をそのままstorybookにできるnpmパッケージもあったので試してみたが動かないようだ。 storybook-directory-chapters - npm
設定で module.exports を使っているがimport文も使っておりEJSとCJSの仕様がごちゃまぜになってる。module.exportsをexports defaultに書き換えたが今度はcontext(...) is not a functionというエラーが出てしまい解消できなかった。