使用graphql-codegen工具以GraphQL模式为导向进行前端开发

请提供一个句子作为中国文的例子。

上次,我使用GraphQL在前端实现了一个库(选择了Apollo作为库)。但是令人惋惜的是,虽然后端(使用Go实现)已经使用了gqlgen库来实现了Schema First的方式,但是前端却采用自定义的方式给要使用该Schema的元素加上了类型。
既然这样,我希望前端也能够采用Schema First,所以我使用了graphql-codegen来自动生成符合GraphQL Schema定义的元素类型。

相关文章索引 suǒ

    • 第12回「GraphQLにおけるRelayスタイルによるページング実装再考(Window関数使用版)」

 

    • 第11回「Dataloadersを使ったN+1問題への対応」

 

    • 第10回「GraphQL(gqlgen)エラーハンドリング」

 

    • 第9回「GraphQLにおける認証認可事例(Auth0 RBAC仕立て)」

 

    • 第8回「GraphQL/Nuxt.js(TypeScript/Vuetify/Apollo)/Golang(gqlgen)/Google Cloud Storageの組み合わせで動画ファイルアップロード実装例」

 

    • 第7回「GraphQLにおけるRelayスタイルによるページング実装(後編:フロントエンド)」

 

    • 第6回「GraphQLにおけるRelayスタイルによるページング実装(前編:バックエンド)」

 

    • 第5回「DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動」

 

    • 第4回「graphql-codegenでフロントエンドをGraphQLスキーマファースト」

 

    • 第3回「go+gqlgenでGraphQLサーバを作る(GORM使ってDB接続)」

 

    • 第2回「NuxtJS(with Apollo)のTypeScript対応」

 

    第1回「frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る」

开发环境

操作系统 – Linux(Ubuntu)

$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"

# 前端开发

Nuxt.js -> Nuxt.js是一个选择。

$ cat yarn.lock | grep "@nuxt/vue-app"
    "@nuxt/vue-app" "2.11.0"
"@nuxt/vue-app@2.11.0":
  resolved "https://registry.yarnpkg.com/@nuxt/vue-app/-/vue-app-2.11.0.tgz#05aa5fd7cc69bcf6a763b89c51df3bd27b58869e"

包管理器 – Yarn

$ yarn -v
1.19.2

WebStorm 是一种集成开发环境 (IDE)。

WebStorm 2019.3
Build #WS-193.5233.80, built on November 25, 2019

实践。

这次的资源全部在下面链接中。
https://github.com/sky0621/study-graphql/tree/v0.4.0

引入

请参考以下链接:
https://graphql-code-generator.com/docs/getting-started/

$ pwd
/home/sky0621/src/github.com/sky0621/study-graphql/frontend
$
$ ll package.json 
-rw-rw-r-- 1 sky0621 sky0621 906 Dec 22 00:49 package.json
$
$ yarn add graphql
yarn add v1.19.2
  ・
  ・
  ・
Done in 7.70s.
$
$ yarn add -D @graphql-codegen/cli
yarn add v1.19.2
  ・
  ・
  ・
Done in 16.59s.
$
$ yarn add -D @graphql-codegen/typescript
yarn add v1.19.2
  ・
  ・
  ・
Done in 12.19s.
$
$ yarn add -D @graphql-codegen/typescript-operations
yarn add v1.19.2
  ・
  ・
  ・
Done in 8.53s.

我们可以通过graphql-codegen命令自动生成代码。
为此,请准备一个名为codegen.yml的配置文件,该命令将使用该文件作为设置文件。

$ cat codegen.yml 
overwrite: true
schema: http://localhost:5050/query
generates:
  ./gql-types.d.ts:
    plugins:
      - typescript
      - typescript-operations

在’schema’字段中指定了一个引用GraphQL模式的服务器。换句话说,它是一个GraphQL服务器。在这次的实现中,我们在本地用Go语言搭建了一个GraphQL服务器,它的端口号是5050,所以在这里进行了指定。

最後还需在package.json中注册自动生成代码命令,下面是包含yarn add的最终package.json的差异。

$ git diff package.json
diff --git a/frontend/package.json b/frontend/package.json
index b0b174f..6afbdea 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,16 +9,21 @@
     "build": "nuxt build",
     "start": "nuxt start",
     "generate": "nuxt generate",
-    "lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
+    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+    "codegen": "graphql-codegen"
   },
   "dependencies": {
     "@nuxtjs/apollo": "^4.0.0-rc17",
     "@typescript-eslint/parser": "^2.12.0",
+    "graphql": "^14.5.8",
     "nuxt": "^2.0.0",
     "nuxt-property-decorator": "^2.5.0",
     "vue-apollo": "^3.0.2"
   },
   "devDependencies": {
+    "@graphql-codegen/cli": "^1.9.1",
+    "@graphql-codegen/typescript": "^1.9.1",
+    "@graphql-codegen/typescript-operations": "^1.9.1",
     "@nuxt/typescript-build": "^0.5.2",

代码自动生成

啊,使用了npm。

$ npm run codegen

> frontend@1.0.0 codegen /home/sky0621/src/github.com/sky0621/study-graphql/frontend
> graphql-codegen

  ✔ Parse configuration
  ✔ Generate outputs
$
$ ll gql-types.d.ts 
-rw-r--r-- 1 sky0621 sky0621 1.1K Dec 28 23:51 gql-types.d.ts

自动生成代码的内容

export type Maybe<T> = T | null;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string,
  String: string,
  Boolean: boolean,
  Int: number,
  Float: number,
};

export type Mutation = {
   __typename?: 'Mutation',
  createTodo: Scalars['ID'],
  createUser: Scalars['ID'],
};


export type MutationCreateTodoArgs = {
  input: NewTodo
};


export type MutationCreateUserArgs = {
  input: NewUser
};

export type NewTodo = {
  text: Scalars['String'],
  userId: Scalars['String'],
};

export type NewUser = {
  name: Scalars['String'],
};

export type Query = {
   __typename?: 'Query',
  todos: Array<Todo>,
  todo: Todo,
  users: Array<User>,
  user: User,
};


export type QueryTodoArgs = {
  id: Scalars['ID']
};


export type QueryUserArgs = {
  id: Scalars['ID']
};

export type Todo = {
   __typename?: 'Todo',
  id: Scalars['ID'],
  text: Scalars['String'],
  done: Scalars['Boolean'],
  user: User,
};

export type User = {
   __typename?: 'User',
  id: Scalars['ID'],
  name: Scalars['String'],
  todos: Array<Todo>,
};

下方若为GraphQL模式本身的生成元。

# GraphQL schema example
#
# https://gqlgen.com/getting-started/

type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

type User {
  id: ID!
  name: String!
  todos: [Todo!]!
}

type Query {
  todos: [Todo!]!
  todo(id: ID!): Todo!

  users: [User!]!
  user(id: ID!): User!
}

input NewTodo {
  text: String!
  userId: String!
}

input NewUser {
  name: String!
}

type Mutation {
  createTodo(input: NewTodo!): ID!
  createUser(input: NewUser!): ID!
}

这样,当进行功能添加和更改时,首先要修改GraphQL模式。
然后,不论是后端还是前端,依赖于各自的自动生成命令来进行模式定义的部分就不再需要手动维护了。

应用自动生成的代码

将之前手动定义在相同组件内的GraphQL模式更改为自动生成的适合该模式的类型。

截至上次的部分

<template>
  <div>
    <v-row>
      <v-col cols="12" sm="6" offset-sm="3">
        <v-card>
          <v-list two-line subheader>
            <v-list-item v-for="todo in todos" :key="todo.id" link>
              <v-list-item-avatar>
                <v-icon>mdi-gift-outline</v-icon>
              </v-list-item-avatar>
              <v-list-item-content>
                <v-list-item-title>{{ todo.text }}</v-list-item-title>
                <v-list-item-subtitle>{{ todo.done }}</v-list-item-subtitle>
              </v-list-item-content>
              <v-list-item-content>
                <v-list-item-title>
                  {{ todo.user.name }}
                </v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list>
        </v-card>
      </v-col>
    </v-row>
  </div>
</template>

<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import 'vue-apollo'
import todos from '~/apollo/queries/todos.gql'

// 自前で定義
interface User {
  id: String
  name: String
}

// 自前で定義
interface Todo {
  id: String
  text: String
  done: Boolean
  user: User
}

@Component({
  apollo: {
    todos: {
      prefetch: true,
      query: todos
    }
  }
})
export default class TodoCard extends Vue {
  todos: Todo[] = []
}
</script>

这次的修正之后 (Zhè cì de

<template>
  〜〜 差分無しなので省略 〜〜
</template>

<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import 'vue-apollo'
import todos from '~/apollo/queries/todos.gql'
// eslint-disable-next-line no-unused-vars
import { Todo } from '~/gql-types'
// ↑自前定義していた interface の代わりに自動生成された型を使う。

@Component({
  apollo: {
    todos: {
      prefetch: true,
      query: todos
    }
  }
})
export default class TodoCard extends Vue {
  todos: Todo[] = []
}
</script>

测试适用性之后的操作验证

$ yarn dev
yarn run v1.19.2
$ nuxt --port 3000

   ╭─────────────────────────────────────────────╮
   │                                             │
   │   Nuxt.js v2.11.0                           │
   │   Running in development mode (universal)   │
   │                                             │
   │   Listening on: http://localhost:3000/      │
   │                                             │
   ╰─────────────────────────────────────────────╯

ℹ Preparing project for development                                                                                                                                 00:17:06
ℹ Initial build may take a while                                                                                                                                    00:17:06
✔ Builder initialized                                                                                                                                               00:17:06
✔ Nuxt files generated                                                                                                                                              00:17:06
ℹ Starting type checking service...                                                                                                                 nuxt:typescript 00:17:10
ℹ Using 1 worker with 2048MB memory limit                                                                                                           nuxt:typescript 00:17:10

✔ Client
  Compiled successfully in 17.19s

✔ Server
  Compiled successfully in 14.76s

ℹ No type errors found                                                                                                                              nuxt:typescript 00:17:27
ℹ Version: typescript 3.7.4                                                                                                                         nuxt:typescript 00:17:27
ℹ Time: 13927ms                                                                                                                                     nuxt:typescript 00:17:27

 WARN  Compiled with 1 warnings                                                                                                                     friendly-errors 00:17:27

Module Warning (from ./node_modules/eslint-loader/dist/cjs.js):                                                                                     friendly-errors 00:17:27

/home/sky0621/src/github.com/sky0621/study-graphql/frontend/plugins/apollo-error-handler.js
  2:3  warning  Unexpected console statement  no-console

✖ 1 problem (0 errors, 1 warning)

                                                                                                                                                    friendly-errors 00:17:27
You may use special comments to disable some warnings.                                                                                              friendly-errors 00:17:27
Use // eslint-disable-next-line to ignore the next line.                                                                                            friendly-errors 00:17:27
Use /* eslint-disable */ to ignore all warnings in a file.                                                                                          friendly-errors 00:17:27
ℹ Waiting for file changes                                                                                                                                          00:17:27
ℹ Memory usage: 649 MB (RSS: 768 MB)                                                       
Screenshot from 2019-12-29 00-24-51.png
Screenshot from 2019-12-29 00-25-15.png

总结

如果我们基于能够定义模式的技术来进行开发的话,那么最好还是尽量避免使用样板代码。
这样一来,前端和后端都将采用以“类型”为基础的开发风格。
下次我们可以尝试解决上次提到的N+1问题,或者尝试适应分页(在GraphQL中,遵循Relay所称的基于游标的连接方式可能更合适)。

广告
将在 10 秒后关闭
bannerAds