Next.js で現在開いているページが Dynamic Route かどうか調べる

現在開いているページが Dynamic Route であるページが調べるにはどうすればよいでしょうか?それは単純にパスを調べるだけでわかります。

// pages/[name]/index.tsx

import { isDynamicRoute } from "next/dist/next-server/lib/router/utils";
  ...
    
const Index: NextPage = () => {
  const router = useRouter();
    if (isDynamicRoute(router.pathname)) {
      // true なら Dynamic Route
    }
    ...
}

isDynamicRoute(...) は Next.js で用意されている関数で、単純な正規表現マッチです。

github.com

Next.js の内部処理を見てみると、同じように Dynamic Route かどうかがチェックされているのがわかります。

github.com

MAZDA3 FASTBACK 15S Touring(AT) 試乗した

近頃 MAZDA3 の1.5リッターエンジンが気になりまして、試乗に行ってきました。


過去に MAZDA3 はディーゼルエンジンに試乗した経験がありましたが、MAZDA3 が発売してしばらく経って「実は1.5リッターがベストなのでは??」という記事が溢れ始め、これがかなり気になって試乗してみたという経緯です。

ひと目で「良い!」と思えるデザイン

久々に実物の MAZDA3 を間近で見ました。もうなんの文句もないデザインです。欧州車の高級グレード車と戦わせてもなんら遜色ない素晴らしいデザインです。よくこんなクルマ作ったなと思います。日本車の普通の価格帯のクルマでこんなものが生まれる日が来るとは思いも寄りませんでした。日本車には好きになる努力が必要なデザインも多々あるのですが、そういったものとはもう全く別次元のクオリティです。


ただし、デザインによって後方視界が犠牲になっています。後ろ見えません正直。バックの際はバックカメラ使う前提になると思います。おそらくはそれでも足りなくて、360度モニタのオプションを付けて購入することになるのではないかと見ています。

パワーがなくても苛つかない不思議なエンジン

まず気になるパワーですが、パワーはそんなに無いです。ただし「車体が重い割に進まないなあ...」と感じるほどではない、絶妙な位置に落ち着いています。


絶対的な馬力はないのでベタ踏みするとよくわかりますが、バン!と踏んだあとにズイーッと頑張って速度が上がっていく感じ。遅くは無いものの、まあこんなものか、と言った程度のパワーです。


ところが、不思議なことにパワーがなくても特に不満にならないのです。この原因ですが、エンジンのフィールと車体の良さにあります。


まずこの1.5リッターエンジン、パワーはないですが音が妙に良いのです。これはエンジン始動時にいきなりわかります。文字で伝えるのが非常に難しいのですが...スポーティー感、高揚感のある音で、加速時にもよく聞こえます。この良い音によって、パワーが無くても爽快な印象があります。


またエンジンの吹け上がりがかなり良いです。軽快に吹け上がります。本物のスポーツカーほどではないもののスポーティーな印象です。これによって「エンジンをいのままに操っている」印象がかなり強くなります。


そして車体の軽快感です。前はディーゼルエンジンということもあり、全重量が1.5リッターより 70kg 重い車体でした。ところが今回は -70kg となり、比較すると車体の軽快感がかなりあります。これは車体の前部分の軽さが 70kg 変わることになるために軽快感を感じやすくなっているのではと思います(ママチャリのかごに重い荷物を入れて走っているときと、そうでないときを考えるとわかりやすいのかなと思います)。


最後に着座位置です。かなり下目な着座位置になっています。これによって地面との距離が近くなるため、視覚的に加速感が強くなるのです。


これらの要素が組み合わさり、いのままに操れる車としてうまく作られているため、パワーがなくても「お?良いじゃないか」という印象を抱けるようになっています。ものすごくクルマづくりがうまいです。このクルマに試乗したとき、ND ロードスターを思い出しました。なんとなく全体的な印象が似ているんです。もちろん性能や楽しさはロードスターにはかないませんが、結構似てます。実際「ロードスターっぽい」というレビューもいくつかあるようです。また実際にMazdaとしては(全グレードに共通してだと思いますが)「4人乗りのロードスターを作ったつもりです」ということだそうです。


セールスの方に伺うと、エンジン自体はロードスターとベースは同じとのことです。

ブレーキ

かなり良いです。前試乗したときより良く感じました。前は踏んでもあまり効かずに、奥まで踏み込むとようやく効く、Mazdaの主張通りのブレーキになっていました。ただしちょっとやりすぎな感じもあったんですよね。


ところが今回は踏み込むと割とすぐ効き始め、奥まで踏めば踏むほどしっかり効くという作りになっていました。マイナーチェンジの恩恵か、ディーゼルエンジンとの重さの差か、あるいは両方かわかりませんが、とにかく違和感がないブレーキでした。

車体

先ほど軽快感がある車体と書きました。そのおかげか、コーナリングもすいっと入っていくイメージです。かなり自然に曲がれます。また MAZDA3 には G-ベクタリング コントロール プラス が搭載されており、この恩恵があるものと思います。正直ちょっと試乗したぐらいではそれが如何なるものか詳しくはわかりませんでしたが、つまり乗り手に意識せずに自然に曲がれるシステムがうまく働いているということなのかなと思いました。

総評

MAZDA3 FASTBACK 15S Touring は、エンジンのパワーはさほど無いものの様々な「走りを楽しくする要素」が高バランスで組み合わさっているので、 乗り手を不満にさせないクルマになっていることがよくわかりました。そしてこれが重要な点なのですが、1. 家族が問題なく乗れて、2. ロードスターにある程度近い楽しみを得ることができ、3. かつ欧州の高級車以上のデザインを得ることができる。このクルマの車体価格は230万程度なので、この価格で普段遣いできるクルマであるにも関わらず、運転が楽しく、特別感のある存在を手に入れることができるというのは、凄まじい価値だと感じました。

Apollo CLI の codegen から GraphQL Code Generator に移行する

Apollo CLIcodegen から GraphQL Code Generator に移行したくなり、移行しました。移行するにあたって、一度に全部変更すると破滅するので、以下の作戦で行いました。

  • GraphQL Code Generator を導入
  • apollo client:codegen と GraphQL Code Generator を共存させる
  • 徐々に GraphQL Code Generator で生成されたコードに置き換えていく
  • 最後に apollo client:codegen 記述を package.json から削除

といった具合で進めます。

GraphQL Code Generator を導入

こちらに導入方法をまとめてあるので良かったらどうぞ。導入だけであれば、特に既存の何かと衝突したりはせずすんなり入ると思います。 funnelbit.hatenablog.com

apollo client:codegen と GraphQL Code Generator を共存させる

多くの場合、package.json などにコード生成タスクを書いていて、そこで apollo client:codegen を動かしているプロダクトが多いと思います。これはそのままにしておいてください。また GraphQL Code Generator を導入したことで GraphQL Code Generator もコード生成処理が package.json に書かれていると思いますが、これもそのままにしてください。そして少し無茶な感じですが apollo client:codegen と GraphQL Code Generator のコード生成を同時に走らせても破滅しないようにします。

とはいえ、デフォルトの状態では駄目な可能性があります。GraphQL Code Generator でコード生成をした後に apollo-codegen のコード生成プロセスを実行してみてください。場合によってはエラーが出ます(もしでなければここは読み飛ばしてください)。

intro-graphql-codegen ● ⍟4  npm run dev:apollo # apollo client:codegen です
 
 > app@0.1.0 dev:apollo /app
 > apollo client:codegen --target=typescript --outputFlat --customScalarsPrefix=GraphQL --watch ./src/types/graphql.ts
 
 CLIError: Error in "Loading queries for Unnamed Project": Error: ️️There are multiple definitions for the `GetUser` operation. Please rename or remove all operations with the duplicated name before continuing.
     at Object.error (/app/node_modules/@oclif/errors/lib/index.js:26:15)
     at Generate.error (/app/node_modules/@oclif/command/lib/command.js:60:23)
     at OclifLoadingHandler.showError (/app/node_modules/apollo/lib/OclifLoadingHandler.js:28:22)
     at OclifLoadingHandler.handle (/app/node_modules/apollo/lib/OclifLoadingHandler.js:13:18) {
   oclif: { exit: 2 },
   code: undefined
 }
   ✖ Loading Apollo Project
     → Error initializing Apollo GraphQL project "Unnamed Project": Error: Error in "Loading queries for Unnamed Project": Error: ️️There are multiple definitions for the `GetUser`

GetUser が複数定義していると言われて怒られています。これは、GetUser の定義が graphql-codegen によって生成されており、そこと競合しているとみなされエラーとなっているためです。

// とあるコンポーネント内。gql が定義されている
 ...
 gql`
   query GetUser($id: ID!) {
     node(id: $id) {
       id
       ... on Entry {
         title
       }
     }
   }
 `;
 ...
//  GraphQL Code Generator によって作られた graphql.ts ファイル内
 ...
 export const GetUserDocument = gql` // ここで定義されているので、二重にあると認識されている
     query GetUser($id: ID!) {
   node(id: $id) {
     id
       ... on Entry {
         title
       }
   }
 }
     `;
     ...

graphql-codegen で作られた GetUserDocument は、各コンポーネントで import して利用すると便利なので、このままにしておきたいです。とはいえこのままでは apollo-codegen でコード生成できないので、何かしらの対策が必要になります。

そのための策としては、単純に apollo-codegen が graphql-codegen によって作られたファイルを見ないようにすると良いです。

// package.json
 ...
 "dev:apollo": "apollo client:codegen --target=typescript --includes='src/**/!(graphql).{ts,tsx}' --outputFlat --customScalarsPrefix=GraphQL --watch ./src/types/graphql.ts",
  ...

ポイントは --includes='src/**/!(graphql).{ts,tsx}' で、ここで graphql-codegen によって作られたコード src/generated/graphql.ts を含まないようにしています。この状態で apollo-codegen のコード生成を走らせるとうまくいきます。

徐々に GraphQL Code Generator で生成されたコードに置き換えていく

import や gql の宣言箇所、型などいくつか書き換える必要があります。以下に例を diff で示します。

  import { gql } from "@apollo/client";
 - import { CreatedWorksContent_createdWorks } from "types/graphql"; // 1
   import {
 -   createdWorkFragment,
 -   ListCreatedWork,
 - } from "./elements/ListItem"; // 2
 +   CreatedWorkContentFragment,
 +   CreatedWorkContentFragmentDoc,
 + } from "generated/graphql"; // 3
 + import { ListCreatedWork } from "./elements/ListItem";
  
 - export const createdWorksFragment = gql` // 4
 + gql` 
     fragment CreatedWorksContent on RegisteredViewer {
       createdWorks {
         id
         ...CreatedWorkContent
       }
     }
 -   ${createdWorkFragment}
 +   ${CreatedWorkContentFragmentDoc} // 5
   `;
   
   interface ListCreatedProps {
 -   createdWorks: CreatedWorksContent_createdWorks[] | null;
 +   createdWorks?: CreatedWorkContentFragment[] | null; // 6
   }
   
   export const ListCreated: React.FC<ListCreatedProps> = ({
...
  1. types/graphqlapollo codegen によって生成された定義が入っている。これは利用しないので import を削除する
  2. ここではわかりにくいですが、 ListItem コンポーネントの中でも apollo codegen で生成されたコードを GraphQLCodegen によって生成されたコードにおきかえています。ListItem は Fragment Collocation として props を要求しているので、結果として import が変わっている、ということになります。
  3. generated/graphql は graphql-codegen によって生成された定義が入っています。これからはこちらを利用するので import します。ここには 2 で削除した項目が(変数名は若干違うが中身は同じ)入っていることになります。
  4. graphql-codegen はここに相当する変数を export してくれているので、必要がなくなります。
  5. 4 によって export された変数を代わりにここで使います。
  6. apollo codegen は Fragment 内の各キーごとに型としていました。graphql-codegen は Fragment ごとの型が生成されます。これに則ると、このコンポーネントは自身で定義した Fragment の中の型を要求するのではなく、自信で定義した Fragment そのものの型を要求する形になります。

意外と様々な箇所を変えることになるので、最も下層のコンポーネントから手を入れて、そこから影響のあるコンポーネントも変えていく、というスタイルが良さそうです。

これからも、どうぞよろしく ~ 陸奥八仙 純米大吟醸 蔵限定 ver. ~

陸奥八仙の存在を知ったのはどうやら2015年の頃らしい。この頃は日本酒の面白さにどっぷり使っていて、いろんな店に行っては様々な銘酒を楽しんだものだった。当時はどこでもあるポピュラーな酒や缶入の日本酒から、少し日本酒が好きなら誰もが知っている高級酒まで様々な酒を一通り味わっていて、特に高級酒をよく頼んでおり、十四代や田酒などを惜しみなく飲んでいたのを覚えている。今考えるととんでもないお金の使い方だが、美酒にありつけるのが何よりも楽しく、お金に糸目をつけていなかった。


陸奥八仙自体はそれよりも前にあったようだが、関西ではあまり見なかった記憶がある。おそらく関東や東北ではもっと早い段階からたくさん流通していたのだと思うが、関西まではまだそこまで届いていなかったのではないか。


とはいえこのお酒が関西圏でも多くの日本酒ファンを魅了するにはそう時間はかからなかったろう。今では様々な酒屋や飲み屋で陸奥八仙の名前を見ることができる。陸奥八仙はラベルもわかりやすい。大きな字で「八仙」と書かれているので、遠目でも「お、あそこに八仙が置かれているな」と確認できる。飲み屋を探すとき、店前に置かれている空の酒瓶などで店のクオリティを測ることは往々にしてあって、そんなときに陸奥八仙のラベルを確認できると、一定のラインは確実にクリアしていると思え、安心して入店できたものだった。


これはかなり前の印象になるので怪しいのだが、陸奥八仙を初めて飲んだ感想としては「フルーティーでフレッシュ感が強いけど、かっちりしていてキレるな」という印象だった...はずである(書いていて自信がなくなってきた)。大体の「フルーティーでフレッシュ」というのは、飲んで口に含んだときに真っ先に抱く印象で、その後の予想としてはふんわりとした味わいを期待している。ところが陸奥八仙はその後に結構「カッチリ」としていて、そこが少し意外な風味に感じて印象深かった。


これは憶測だが、陸奥八仙の蔵は地産地消にこだわっている蔵である。酵母酒米も、当然水も現地青森のものを使っている。これによって、高級酒を飲みまくっていた自分が抱いていた予想とは違う風味を感じたのだと思う。高級酒は大体の場合山田錦を使っていたり、ポピュラーな酵母を使っていたりするので、ある程度味のテンプレートみたいなものが自身の中にあったのではないか、と推察している。



陸奥八仙の存在を知ってから6年程の月日が流れた2021年。世の中が暗いニュースばかりのまま年明けした三が日に、一本の日本酒の封を開けた。それが「陸奥八仙 純米大吟醸 蔵限定 ver.」である。


f:id:funnelbit:20210102112846j:plain


蔵限定 ver. という名前が指し示すとおり、この酒は蔵でのみ販売される。購入するには現地に行くしかなく、インターネットでの注文も全てお断りしているという。これは意地悪でこうしているのではなく、かつて蔵に足を運んだ多くの方々から「この場所でしか買えないお酒はないか」と要望が出たために、このようなお酒となっている。値段は4合瓶1本で税込み3000円程度だったはずなので、ハイグレードな日本酒に相当する。


その販売形態からかなり貴重な1本となるために、前から家にあったもののもったいなくて中々封を開けることができなかった。今回「せっかくの新年なんだから」ということで開封するに至った。


味わいとしては、期待通り爽やかなフルーティーさがあり、更にはシュワッとした発泡性を感じとれる。この発泡性も相まってか、やはりカッチリとした感じと苦味があった。この時点で「なんとなく、陸奥八仙っぽいな」という感想を抱く。最後にはキレていく。言うまでもなく旨い。そして蔵でしか買えない酒を飲んでいるのだ、という付加価値もそこに加わる。贅沢な一本である。


この陸奥八仙を醸す八戸酒造は、蔵を取り戻す苦労をした蔵としてよく知られている。もともと自分の蔵であるにもかかわらず、長い間そこで酒造りを行えない期間があった。結局裁判にまで発展した末にようやく取り戻し、今に至っている。ラインナップが幅広い蔵でもあり、フルーティーな酒はもちろん、辛口を重視した酒、4合で1万円を超える高級酒、米で作ったビール(これについては後日書こうと思う)など、様々な味わいと製法にチャレンジしている(よりしっかりとした辛口を求めたければ「男山」がある)。杜氏を含め非常に若いメンバーで構成されているので、このような面白い取り組みができるのだろう。



陸奥八仙は昔からよく飲んでいたようで、写真を振り返ると思い出深い。久々に友人とあった時。夏の暑い時期に宅飲みをした時。初めてのお店にチャレンジした時。旅行した時。様々な場面で陸奥八仙を飲んでいた。特に印象に残っているのは一人暮らしに疲れ、実家に帰っていたとき。友人の実家で陸奥八仙を飲んでいたことを思い出す。その時飲んでいたのは、陸奥八仙の黒ラベル。京都の市内で買ってきて、好きだったテレビアニメを見ながら夜遅くまで話をしたものだった。かなり前なので味はもうあまり覚えていないが、派手さを抑えた飽きにくい味わいで、長く話をしながら飲むのにはマッチしていたのを覚えている。冒頭にも書いたが、様々な高級酒を飲みまくっていたこの時代。旨い酒はたくさんのんだし、1杯の値段が笑えるものも飲んだが、結局は心を許せる中でのんびり飲む酒がどんな美酒よりもまさるのだと、若いうちから感じたのだった。

しっぽり飲みたいときにも、派手さを味わいたいときにも、酒に驚きを求めたいときにも、様々なラインナップと高い品質で安心して楽しめる陸奥八仙は、これからもずっと飲んでいくのだと思う。

f:id:funnelbit:20210117000852p:plain

Next.js の API Routes に書かれた実装のテスト

Next.js の API Routes に書かれた実装をテストしたいときを考えたい(jest)。

まずは簡単な api を作ってみる。クエリパラメータ animal=dog をつけて/api/check?animal=dog としてアクセスすると /dog にリダイレクトし、逆にクエリパラメータ animal=dog 無しでアクセスすると 400 が返ってくる API を作ってみる。

import { NextApiRequest, NextApiResponse } from "next";

// pages/api/check.api.ts
export default async (
  req: NextApiRequest,
  res: NextApiResponse
): Promise<void> => {
  if (req.query.animal !== "dog") {
    res.statusCode = 400;
    res.end();
    return;
  }

  res.redirect(
    302,
    "/dog",
  );
};

これに対するテストは以下のようにかけそう。

import httpMocks from "node-mocks-http";
import handler from "pages/api/check.api.ts";

describe("/api/check", () => {
  test("400", async () => {
    const mockReq = httpMocks.createRequest<NextApiRequest>({
      query: {
        animal: "cat",
      },
    });
    const mockRes = httpMocks.createResponse<NextApiResponse>();
  
    await handler(mockReq, mockRes);
    expect(mockRes.statusCode).toEqual(400);
  });

  test("302", async () => {
    const mockReq = httpMocks.createRequest<NextApiRequest>({
      query: {
        animal: "dog",
      },
    });
    const mockRes = httpMocks.createResponse<NextApiResponse>();
  
    await handler(mockReq, mockRes);
    expect(mockRes.statusCode).toEqual(302);
    expect(mockRes._getRedirectUrl()).toEqual(
      "/dog"
    );
  });
}

今回の API Routes は関数無名関数で export default しているので適当な名前を付けて import する。それをそのまま呼んで必要としている変数 NextApiRequestNextApiResponse を渡してあげると良い。この2つを自力で作るよりも node-mocks-http を使ったほうが一瞬でできて楽そうなので使っている。

www.npmjs.com

mockRes に処理後の状態が入っているので、mockRes のプロパティをチェックしてちゃんと出来ているか確認する感じです。

React 環境へ GraphQL Code Generator を導入

GraphQL Code Generator は GraphQL のスキーマ情報やオペレーションコードをもとにし、アプリケーションが触るコード生成を行うことができるツールです。プラグイン形式で機能追加できる特徴があり、プロダクトの環境によって様々なコード生成を行うことができます。

今回は React 環境に GraphQL Code Generator を導入する手順を紹介します。

モチベーション

GraphQL のスキーマやオペレーションコードから、フロントエンド用にコード生成するツールとして最もポピュラーなのは apollo-tooling でしょうか。というのも Apollo 自体がポピュラーであり、そのドキュメントに記載があるためにこれを利用するケースが多いと思われます。apollo-tooling でも開発に問題は有りませんが、 GraphQL Code Generator を使うとより有益になる可能性があります。

先にも述べたとおり GraphQL Code Generatorプラグイン形式で機能を追加できます。React かつ Next.js を利用している場合、React 用のプラグイン、Next.js 用のプラグインが利用できます。Vue.js 用もありますし、Angular 用もあります。他にも、プロダクトで利用されうる技術ごとに様々なプラグインも用意されています。これが意味するのは、GraphQL Code Generator はアプリケーションの構成ごとに最適な機能を提供しているために、よりプロダクトの実態に合ったコード生成を実現できるということが言えます。

プロダクト構成

シンプルに考え、以下のような構成であるとします(Next.js の構成となっていますが、Next.js 特有の GraphQL Code Generator plugin は今回利用しません)。

- src/
  - pages/
    - _app.tsx
    - index.tsx // 今回関係ある
- graphql/schema.graphql // 今回関係ある
- package.json
- tsconfig.json
- node_modules
- .next

// 今回関係ある と書かれたファイルだけが今回取り扱うファイルたちです。

index.tsx はこのアプリケーション唯一の画面でありロジックです。ユーザが投稿した記事を表示します。関連する GraphQL 部分のコードだけ以下に示します。

// index.tsx
 export const ENTRY_QUERY = gql`
   query GetEntryQuery($id: String!) {
     entry
   }
 `;
 
 ...
 const { data } = useQuery<EntryQuery, EntryQueryVariables>(
     ENTRY_QUERY,
     {
       variables: {
         id: id,
       },
       skip: !id,
     }
   );

schema.graphql は GraphQL のスキーマです。これは API 側の実装によって作成されたものです。スキーマAPI から配信しても良いのですが、今回はファイルをそのまま利用することにし、ここに置いています。

今回のような構成では、例えば以下のようなコード生成が可能です。

query や mutation などのオペレーション定義から React の Hooks を作成できる(コード量を削減できる)

GraphQL Code Generator 導入前、React 内では以下のように GraphQL のコードを書いていました。

// index.tsx
 export const ENTRY_QUERY = gql` // ENTRY_QUERY に注目
   query GetEntryQuery($id: String!) {
   ....
   }
 `;
 
 ...
 const { data } = useQuery<EntryQuery, EntryQueryVariables>( // useQuery に注目
     ENTRY_QUERY, // ENTRY_QUERY に注目
     {
       variables: {
         id: id,
       },
       skip: !id,
     }
   );

export const ENTRY_QUERYuseQuerytsx の外、例えばテストコードで使うために宣言していたものです。quey を叩くには useQuery を使い、ジェネリクスに型を指定します。

これでも問題はありませんが、GraphQL Code Generator を使うとよりコード量が削減できます。useQuery を直接使わずに通信ができるようオペレーションごとに専用の Hooks が用意されるようになり、また export const ENTRY_QUERY 相当のコードが graphql-codegen によって生成されるようになります。

 ...
 // useQuery 相当
 export function useGetEntryQuery(baseOptions?: Apollo.QueryHookOptions<GetEntryQuery, GetEntryQueryVariables>) {}
 
 // useLazyQuery 相当
 export function useGetEntryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetEntryQuery, GetEntryQueryVariables>) {}
 
 // ENTRY_QUERY 相当
 export const GetEntryDocument = gql`
     query GetEntry($id: String!) {
       entry(id: $id) {
         id
 ...

よって ENTRY_QUERY というような変数に入れなくとも、以下のようにできます。

 import { GetEntryDocument, useGetEntryQuery } from "generated/graphql";
 
  gql` // ENTRY_QUERY に代入しなくなった
    query EntryQuery($id: String!) {
    ....
    }
  `;
  ...
    const { data } = useGetEntryQuery({ // かつての useQuery に変わって生成された Hooks を使う
      variables: {
        id: id,
      },
      skip: !id,
    });
    ...

Enum を封印できる

TypeScript | GraphQL Code Generator にある enumsAsTypestrue にすれば、生成されるコードから enum を排除し、代わりに Union Types とすることができます。

// enumsAsTypes 設定無し
export enum Animal {
  Dog = 'DOG',
  Cat = 'CAT
}
// enumsAsTypes true
export type Animal = 
  | 'DOG'
  | 'CAT';

TypeScript の Enum は何かと話題が多いもので、気をつけて利用するよりもいっその事利用しないプロダクトも多いと思うので、こういうオプションがあるのはありがたいです。

Install

graphql-code-generator.com

npm install --save-dev @graphql-codegen/cli

npx graphql-codegen init するとセットアップウィザードが始まるので、答えていきます。

What type of application are you building?

$ npx graphql-codegen init
 
     Welcome to GraphQL Code Generator!
     Answer few questions and we will setup everything for you.
 
 ? What type of application are you building? (Press <space> to select, <a> to toggle all, <i> to invert selection)
 ❯◯ Backend - API or server
  ◯ Application built with Angular
  ◉ Application built with React
  ◯ Application built with Stencil
  ◯ Application built with other framework or vanilla JS

導入するプロダクトの利用フレームワークを聞かれています。今回は React で行っているので、Application build with React を選択します。

Where is your schema?

? Where is your schema?: (path or url) (http://localhost:4000)

GraphQL スキーマの場所を聞かれています。スキーマが URL を叩くことで得れれるなら URL、直接ファイルを触るなら .graphql の場所を指定します。今回は graphql/schema.graphql にあるので graphql/schema.graphql と指定しました。graphql/ ディレクトリ以下に複数の graphql ファイルがある場合は graphql/*.graphql とします。

Where are your operations and fragments?

? Where are your operations and fragments?: (src/**/*.graphql)

ここで要求されているのは、クライアントサイドで書かれている query や mutation といったオペレーションの定義です。今回の場合、定義は src ディレクトリ以下にある tsx にかかれています。また今後 tsx だけではなく ts ファイルにもオペレーションを書く可能性を考えて、ここに書かれているような *.graphql ではなく "src/**/!(*.d).{ts,tsx}" と指定します。

Pick plugins

 ? Pick plugins: (Press <space> to select, <a> to toggle all, <i> to invert selection)
 ❯◉ TypeScript (required by other typescript plugins)
  ◉ TypeScript Operations (operations and fragments)
  ◉ TypeScript React Apollo (typed components and HOCs)
  ◯ TypeScript GraphQL files modules (declarations for .graphql files)
  ◯ TypeScript GraphQL document nodes (embedded GraphQL document)
  ◯ Introspection Fragment Matcher (for Apollo Client)

プラグインを選択します。ドキュメントにそれぞれ書いてあるので見てみましょう。

? Where to write the output

? Where to write the output: (src/generated/graphql.tsx)

GraphQL Code Generator が生成したコードを置く場所を指定できます。tsx である必要はないので src/generated/graphql.ts とします。

Do you want to generate an introspection file?

? Do you want to generate an introspection file? (Y/n)

introspection file が必要なら Y を指定しましょう。ここでは必要ないので n とします。

How to name the config file?

How to name the config file? (codegen.yml)

GraphQL Code Generator は yml で設定ファイルを記述します(今までウィザードで聞かれていた設定はここに書かれます)。こだわりが無ければこのままで良いと思うのでこのまま codegen.yml で進めます。

What script in package.json should run the codegen?

What script in package.json should run the codegen?

GraphQL Code Generator がコード生成するスクリプトを package.json に用意してくれますが、そのスクリプト名を設定できます。今回は codegen としました。

     Config file generated at codegen.yml
 
       $ npm install
 
     To install the plugins.
 
       $ npm run codegen
 
     To run GraphQL Code Generator.

完了するとこのようなメッセージが出てきます。npm i して npm run codegen すると src/generated/graphql.ts が吐き出されるはずです。

ちなみに package.json には以下のようなものが追加されており

...
     "codegen": "graphql-codegen --config codegen.yml"
"devDependencies": {
     ...
     "@graphql-codegen/typescript": "1.17.9",
     "@graphql-codegen/typescript-operations": "1.17.8",
     "@graphql-codegen/typescript-react-apollo": "2.0.6",
...

codegen.yml は新規に作成されています。

 overwrite: true
 schema: "graphql/schema.graphql"
 documents: "src/**/!(*.d).{ts,tsx}"
 generates:
   src/generated/graphql.tsx:
     plugins:
       - "typescript"
       - "typescript-operations"
       - "typescript-react-apollo"

GraphQL Code Generator を理解していれば、ウィザードを使わずにこれらを直接編集しても良さそうです。

ESLint の設定

ESLint を設定されているプロダクトがほとんどだと思います。このままでは src/generated/graphql.ts のコードに対し ESLint から大量の怒られが発生するので、除外する設定を書いておくと良さそうです。

gnorePatterns: ["src/generated/graphql.ts"],

Enum 禁止

codegen.yml に enumsAsTypes: true を追加することで、Enum ではなく Union Types にできます。必要であれば指定しておくと良いです。

 overwrite: true
 schema: "graphql/schema.graphql"
 documents: "src/**/!(*.d).{ts,tsx}"
 generates:
   src/generated/graphql.tsx:
     plugins:
       - "typescript"
       - "typescript-operations"
       - "typescript-react-apollo"
    config:
      enumsAsTypes: true // これ

iOS「ミュージック」で再生中の楽曲情報をシェアするアプリ「The Playing」をリリースしました

iOS の「ミュージック」アプリで再生中の楽曲情報を「タイトル、アーティスト名、アルバム名、アートワーク」という構成でシェアできるアプリをリリースしました。いわゆる「ナウプレ」アプリです。

The Playing

The Playing

  • Ryo Kitamura
  • ミュージック
  • 無料
apps.apple.com
f:id:funnelbit:20200527110325p:plain

iOS 13.5 以上で動作します。

開発のモチベーションとしては、すでにたくさんの便利な「ナウプレ」アプリが世の中にあるのですが、Twitter にシェアするには認証情報を渡さないといけないのがネックに感じていました。また再生ボタンなどいろんな機能がついていて、シェアだけをしたいという用途にはいささかオーバスペックかなあと思っていて、とにかくシンプルなアプリがほしいと感じていました。

そこで、iOS にもともと備わっている「Share extension」を利用し、好きなアプリに楽曲情報を渡す設計にすることで(例えば Twitter にシェアしたければ Twitter アプリを利用する)認証云々を抜きにしたシェアを可能にしました。これによって、メモ帳に聴いている曲を「タイトル、アーティスト名、アルバム名、アートワーク」という構成で記録していくとか、メールや Slack に共有するとかも可能になっています。

アイコンはイラレでさっと作りました。このアプリはそんな大それたものではなく文房具みたいなものだと思っていて、シャープペンシルとか、鉛筆とか、そういった地味な存在ということにしたかったので、あまり主張しない地味な色味にしています。

コードは SwiftUI で作られています。