Apollo ServerとNowで作るGraphQLモックサーバ #3 デプロイ編
前回の続き
ZEIT Nowにデプロイする
Deploying Node.js Apps - ZEIT Documentation
詳しい方法は公式ドキュメントに譲るとして...
一般的なnode appは特に設定をしなくてもプロジェクトディレクトリで now
コマンドを実行すればデプロイされ、利用可能な状態になる。
npm scriptsの "start" がエントリーポイントになるので、以下のように設定しておく。
"scripts": { "start": "ts-node src/index.ts" },
now.json
また、設定なしでデプロイした場合URLにプロジェクトディレクトリ名が勝手に入ってしまうため、 now.jsonの "name" で明示的にプロジェクト名を設定しておくとよい。
{ "name": "graphql-server", "env": { "NODE_ENV": "production" } }
これにより https://graphql-server-foobarbaz.now.sh
といったURLにデプロイされるようになる。
GitHubへのpushにフックしてデプロイさせる
手順は以下の通り ZEIT – Now + GitHub
Pull Requestをopenしてブランチをpushするとnow.jsonの設定にしたがってデプロイが始まる。
デプロイが完了すると下記のようなメッセージが表示され、 commit messageの横にある✓のアイコンからデプロイされた環境にアクセスできるようになる。
URLを固定する
通常 now
でデプロイするとURLが毎回変わってしまう。
デプロイする度にAPIのURLが変わってしまうのは困るので、URLを固定するためにaliasを使う。
Aliases and Domains - ZEIT Documentation
{ "name": "graphql-server", "alias": "graphql-server-foobar.now.sh", "env": { "NODE_ENV": "production" } }
now.jsonに "alias" として、[任意の文字列].now.sh
を追加し、
now alias
を実行すると、現在のdeploymentが指定したaliasでアクセスできるようになる。
モニタリング編に続く
Apollo ServerとNowで作るGraphQLモックサーバ #2 実装編
モックサーバの実装
前回
1. 型の定義を行う
import { ApolloServer, gql } from 'apollo-server' const typeDefs = gql` type Person { id: ID name: String phone: String } type Query { people: [Person] } ` const mocks = { Query: () => ({ people: () => new MockList([2, 5]), // 2~5件の[Person]が返る }), } const server = new ApolloServer({ typeDefs, mocks, playground: true, }) const port = 4004 server.listen(port).then(({ url }: { url: string }) => { console.log(`Server ready at ${url}`) })
型定義をしただけでQueryの中身(resolver)を書いていないのだから このままでは動かないんじゃないか...と思うが動く。
Apollo ServerはMockingの仕組みによってresolverが設定されていなくてもそれらしい値を返すようになっている。
2. devサーバを起動
package.jsonに以下のscriptを追加して yarn dev
で起動する
"scripts": { "dev": "ts-node-dev --respawn src/index.ts" },
ts-node-devの --respawn
オプションにより、ファイルが更新されると実行されているサーバが自動的に再起動する
3. Playgroundで確認
起動したサーバの /
にアクセスするとPlaygroundが表示される。
こういうのが最初から用意されているのもapollo-serverのいいところ。
一応値は返ってきているが、nameやphoneが全て "Hello World" なのが気になる
4. Mockを調整
nameは名前っぽい、phoneは電話番号っぽい値が返ってほしいので、 mockの設定をもう少し弄ってみる
「っぽい」値を得るためにfakerを使う
import * as faker from 'faker' const typeDefs = gql` scalar Name scalar Phone type Person { id: ID name: Name phone: Phone } type Query { people: [Person] } ` const mocks = { Name: () => faker.name.findName(), Phone: () => faker.phone.phoneNumber(), Query: () => ({ people: () => new MockList([2, 5]), // 2~5件の[Person]が返る }), }
それっぽい値が返ってくるようになった
デプロイ編に続く
Apollo ServerとNowで作るGraphQLモックサーバ #1 準備編
GraphQLのモックサーバを作る際に使うものをメモ
使うもの
TypeScript
- 型があったほうが書きやすい
- 最近はクライアント(React)も全てTypeScriptで書いてるし...
- apollo-serverのパッケージには型情報が含まれている
ts-nodeとts-node-dev
- 本番はともかく、パフォーマンスを必要としないdevでいちいちコンパイルしてから実行するのはめんどくさい
- ts-nodeを使えば.tsをそのまま実行できる
- とある案件のdev環境でしばらく使っているが安定稼働している
- ローカルで開発を行う際にはts-node-devを使う
--respawn
オプション付きで起動するとファイルの更新を検知して再起動する
Apollo Server
apollographql/apollo-server: GraphQL server for Express, Connect, Hapi, Koa and more
- GraphQL APIサーバを立てるのに必要なものがだいたい揃ってる全部入りパッケージ
- とある案件ではgraphql-yogaというものを使っているが、これもapollo-serverとだいたい同じもの
- apollo-serverのほうがメジャーなので近々乗り換えようと思っている
Apollo Engine
Apollo Engine | The GraphQL Gateway with essential features like caching and performance tracing.
- apollo-serverに追加することでクエリのキャッシュやモニタリングが可能になる
- ユニークなのはフィールドごとにリクエスト回数が可視化されるところ
- People.bloodTypeは1回もリクエストされてないからdeprecatedにしよう...といった判断に使える
- Free版はカスタムアラートが使えない。有償版は$99/mo~ とややお高め
- 既存サーバへの組み込みも容易
ZEIT Now
続く
TypeScriptにおける、既存クラスの拡張
TypeScriptでAudioParam.cancelAndHoldAtTime()
のコンパイルを通したかったが、本家の型定義には追加されていないので、自分で拡張を行ったときのメモ。
declare global { interface AudioParam { cancelAndHoldAtTime(cancelTime: number): void } }
Declaration Merging · TypeScript
compiler merges two separate declarations declared with the same name into a single definition.
自分でAudioParam interfaceを追加すると、元々存在するAudioParam interfaceにマージされる。
You can also add declarations to the global scope from inside a module:
AuidoParamがglobal scopeにいるのでGlobal augmentationを使う
参考
create-react-appで絶対パスによるimportをする
src/Components/App.jsx
を src/index.js
からimportする場合、create-react-appでは
import App from './Components/App';
と書くが、これを、
import App from 'Components/App';
こう書きたい。
が、デフォルトではパスの解決ができず、 Module not found: Can't resolve 'Components/App’
エラーとなる。
NODE_PATHを設定する
プロジェクトの .env
に NODE_PATH=src
を設定することで解決する。
※ dev serverの再起動が必要
NODE_PATH=src
Same as NODE_PATH in Node.js, but only relative folders are allowed. Can be handy for emulating a monorepo setup by setting NODE_PATH=src.
余談だが、create-react-appのカスタム環境変数は、上記のAdvanced Configurationに含まれるものと、REACT_APP_
プレフィクスを持つもの、それに NODE_ENV
のみが有効となる。
Adding Custom Environment Variables
You must create custom environment variables beginning with REACT_APP_. Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name.
TypeScriptによるStateless Functional Componentの定義
環境構築は create-react-app
とMicrosoft/TypeScript-React-Starterでサクッと行う
$ npx create-react-app my-app --scripts-version=react-scripts-ts $ cd my-app $ npm run start
以前、TypeScriptでReact app作ろうとしてセットアップに手間取ったときとは隔世の感がある。
type React.SFCを使う
@types/react
に React.SFC
という型が用意されているので、これを使う
DefinitelyTyped/index.d.ts at master · DefinitelyTyped/DefinitelyTyped
type SFC<P = {}> = StatelessComponent<P>; interface StatelessComponent<P = {}> { (props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null; propTypes?: ValidationMap<P>; contextTypes?: ValidationMap<any>; defaultProps?: Partial<P>; displayName?: string; }
type SFC<P = {}>
とあるので、全てのpropsはoptionalにする必要がある。
そのため、requiredで定義すると以下のコンパイルエラーが発生する。
error TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & AppProps & { children?: ReactNode; }'. Type '{}' is not assignable to type 'AppProps'. Property 'name' is missing in type '{}'.
コンポーネントの書き換え
App.tsxをStateless Functional Componentに書き換えるとこのようになる
import * as React from 'react'; import './App.css'; const logo = require('./logo.svg'); interface AppProps { name?: string; } const App: React.SFC<AppProps> = ({ name }) => { return ( <div className="App"> <div className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h2>Welcome to {name}</h2> </div> <p className="App-intro"> To get started, edit <code>src/App.tsx</code> and save to reload. </p> </div> ); }; App.defaultProps = { name: 'React.SFC', }; export default App;
defaultPropsの定義をコンポーネント定義と分けたくない場合は、
const App: React.SFC<AppProps> = ({ name = 'React.SFC' }) => { return ( <div className="App"> ...
とも書ける
React Sketch.app
フロントエンドのデザインと実装のワークフローをスムーズにできないか、というのを考えているときにReact Sketch.appというそれっぽい名前のツールを見つけた。
React Sketch.appとは
そもそもSketch使ったこともなかった(UIに特化したIllustratorくらいの認識)ので、チュートリアルを触りながらどんなものか確認してみた。
Introduction · react-sketchapp
jsxのコードを変更して保存するとSketchにリアルタイム反映される。
これだけだとdev-serverで開発してるのとさして変わらんという印象を受けたので、どういうケースにハマるのかドキュメントを読んでみる。
Managing the assets of design systems in Sketch is complex, error-prone and time consuming. Sketch is scriptable, but the API often changes. React provides the perfect wrapper to build reusable documents in a way already familiar to JavaScript developers.
Sketchでデザインシステムのアセットを管理する手間を軽減する。 SketchのAPIがころころ変わるので、Reactでラッパーを作ったと。
できること
- デザインシステムを実コード(React components)で実装できる
- React Componentの実物を使って画面デザインができる
- 実データを反映したデザイン成果物を作れる
つまづきポイント
チュートリアルのコードを動かしている最中、突然コケるようになった
- saveするたびにPageが作られる
TypeError: MSAttributedString.alloc().initWithAttributedString is not a function.
というエラーを吐いてコードをSketchに反映しなくなる
踏んでいたのは以下のバグ
最新のSketch(48.2)を使っていたのが原因。47を使えとのこと
チームで使う場合は事前にSketchのバージョン合わせておく必要がありそう
まとめ
エンジニア主導の現場かつ、大きめのデザインシステムを導入しているプロジェクトではワークフローをシンプルにできそう。
一方で、JSX(React Sketch.appのAPIも加わるのでさらに難易度上がる)をデザイナーに書いてもらうのは現実的なのか、という課題は残る。
また、Sketchのバージョンアップに伴ってAPIが変更された場合に動作しなくなるケースがあるため、Sketchのバージョンを少し古いものに揃えなければならない。