Apollo ServerとNowで作るGraphQLモックサーバ #3 デプロイ編

前回の続き

nomusiclife.hatenablog.jp

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の設定にしたがってデプロイが始まる。

f:id:nishaya:20180824115112p:plain

デプロイが完了すると下記のようなメッセージが表示され、 commit messageの横にある✓のアイコンからデプロイされた環境にアクセスできるようになる。

f:id:nishaya:20180824115035p:plain

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でアクセスできるようになる。


モニタリング編に続く

nomusiclife.hatenablog.jp

Apollo ServerとNowで作るGraphQLモックサーバ #2 実装編

モックサーバの実装

前回

nomusiclife.hatenablog.jp

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のいいところ。

f:id:nishaya:20180821172302p:plain

一応値は返ってきているが、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]が返る
  }),
}

f:id:nishaya:20180821172316p:plain

それっぽい値が返ってくるようになった


デプロイ編に続く

nomusiclife.hatenablog.jp

Apollo ServerとNowで作るGraphQLモックサーバ #1 準備編

GraphQLのモックサーバを作る際に使うものをメモ

使うもの

  • TypeScript
  • ts-node, ts-node-dev
  • apollo-server, apollo-engine
  • Zeit Now

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


続く

nomusiclife.hatenablog.jp

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.jsxsrc/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を設定する

プロジェクトの .envNODE_PATH=src を設定することで解決する。
※ dev serverの再起動が必要

NODE_PATH=src

Advanced Configuration

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-appMicrosoft/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/reactReact.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に反映しなくなる

踏んでいたのは以下のバグ

github.com

最新のSketch(48.2)を使っていたのが原因。47を使えとのこと
チームで使う場合は事前にSketchのバージョン合わせておく必要がありそう

まとめ

エンジニア主導の現場かつ、大きめのデザインシステムを導入しているプロジェクトではワークフローをシンプルにできそう。

一方で、JSX(React Sketch.appのAPIも加わるのでさらに難易度上がる)をデザイナーに書いてもらうのは現実的なのか、という課題は残る。
また、Sketchのバージョンアップに伴ってAPIが変更された場合に動作しなくなるケースがあるため、Sketchのバージョンを少し古いものに揃えなければならない。