在 ts 中使用 graphql
本文最后更新于:2022年3月28日 上午
场景
graphql 提供前后端一致的 api 架构元数据,同时通过请求合并、按需获取加快 web 与后端交互的性能。
结合 ts 使用
基本思路
- 扫描
gql
文件中的查询文件 - 生成类型定义与 document 对象
- 使用这些类型定义
使用步骤
以下使用 github [email protected] 进行演示
获取后端的元数据
pnpm i -g get-graphql-schema
get-graphql-schema https://docs.github.com/public/schema.docs.graphql > schema.graphql
参考:https://stackoverflow.com/questions/37397886/get-graphql-whole-schema-query
安装基础 sdk
pnpm i @apollo/client graphql
安装代码生成器相关依赖
@graphql-codegen/cli
基础 cli@graphql-codegen/typescript
ts 插件@graphql-codegen/typescript-operations
ts 操作生成插件@graphql-codegen/typed-document-node
生成 document 对象@graphql-codegen/near-operation-file-preset
ts 预设配置
创建配置 codegen.yml
overwrite: true
schema: schema.graphql
generates:
./src/graphql.generated.ts:
plugins:
- typescript
./:
documents:
- src/**/*.gql
preset: near-operation-file
presetConfig:
baseTypesPath: ./src/graphql.generated.ts
extension: .generated.ts
plugins:
- typescript-operations
- typed-document-node
在 src/api/RepoQuery.gql 编写 graphql 查询语句
注: 在非 react 项目中,请从
@apollo/client/core
导入所有非 react 的内容。
fragment Repo on Repository {
id
name
}
query findRepoStar($name: String!, $owner: String!) {
repository(name: $name, owner: $owner) {
...Repo
stargazerCount
}
}
使用 cli 生成类型定义
pnpm graphql-codegen --config codegen.yml
生成结果大致如下,基本上就是参数和结果类型
// src/api/RepoQuery.generated.ts
import * as Types from "../graphql.generated";
import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/core";
export type RepoFragment = {
__typename?: "Repository";
id: string;
name: string;
};
export type FindRepoStarQueryVariables = Types.Exact<{
name: Types.Scalars["String"];
owner: Types.Scalars["String"];
}>;
export type FindRepoStarQuery = {
__typename?: "Query";
repository?:
| {
__typename?: "Repository";
stargazerCount: number;
id: string;
name: string;
}
| null
| undefined;
};
export const RepoFragmentDoc = {
kind: "Document",
definitions: [
{
kind: "FragmentDefinition",
name: { kind: "Name", value: "Repo" },
typeCondition: {
kind: "NamedType",
name: { kind: "Name", value: "Repository" },
},
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{ kind: "Field", name: { kind: "Name", value: "name" } },
],
},
},
],
} as unknown as DocumentNode<RepoFragment, unknown>;
export const FindRepoStarDocument = {
kind: "Document",
definitions: [
{
kind: "OperationDefinition",
operation: "query",
name: { kind: "Name", value: "findRepoStar" },
variableDefinitions: [
{
kind: "VariableDefinition",
variable: { kind: "Variable", name: { kind: "Name", value: "name" } },
type: {
kind: "NonNullType",
type: {
kind: "NamedType",
name: { kind: "Name", value: "String" },
},
},
},
{
kind: "VariableDefinition",
variable: {
kind: "Variable",
name: { kind: "Name", value: "owner" },
},
type: {
kind: "NonNullType",
type: {
kind: "NamedType",
name: { kind: "Name", value: "String" },
},
},
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{
kind: "Field",
name: { kind: "Name", value: "repository" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "name" },
value: {
kind: "Variable",
name: { kind: "Name", value: "name" },
},
},
{
kind: "Argument",
name: { kind: "Name", value: "owner" },
value: {
kind: "Variable",
name: { kind: "Name", value: "owner" },
},
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{
kind: "FragmentSpread",
name: { kind: "Name", value: "Repo" },
},
{
kind: "Field",
name: { kind: "Name", value: "stargazerCount" },
},
],
},
},
],
},
},
...RepoFragmentDoc.definitions,
],
} as unknown as DocumentNode<FindRepoStarQuery, FindRepoStarQueryVariables>;
我们可以这样使用它
const res = await client.query({
query: FindRepoStarDocument,
variables: {
name: "liuli-tools",
owner: "rxliuli",
},
});
console.log("res: ", res.data.repository?.stargazerCount);
Jetbrains IDE 支持
安装插件 JS GraphQL
创建 graphql 基础配置文件 .graphqlconfig
{ "name": "github GraphQL Schema", "schemaPath": "schema.graphql", "extensions": { "endpoints": { "Default GraphQL Endpoint": { "url": "https://api.github.com/graphql", "headers": { "user-agent": "JS GraphQL", "Authorization": "bearer ${env:GH_TOKEN}" }, "introspect": false } } } }
最终效果
VSCode 支持
安装插件 GraphQL
在根目录添加配置 .graphqlconfig
module.exports = { client: { service: { name: "github GraphQL Schema", localSchemaFile: "./schema.graphql", }, }, };
最终效果
注:在 monorepo 中,vscode 插件仅支持在根目录添加配置文件,所以其它子目录中的配置仅用于与生态中的其他工具结合。
graphql 插件不支持查询参数的体验,只有一个非常简单的输入框
集成到 vite
关于为什么要将自动生成作为 vite 插件集成,之前在 是否需要将 cli 工具集成到构建工具中 中已经说明了。
vite-plugin-graphql-codegen 监视模式实际上有 bug,所以自行维护一个 rollup 插件
import { Plugin } from "vite";
import { CodegenContext, generate, loadContext } from "@graphql-codegen/cli";
async function codegen(watch: boolean) {
const codegenContext = await loadContext();
codegenContext.updateConfig({ watch });
try {
await generate(codegenContext);
} catch (error) {
console.log("Something went wrong.");
}
}
export function graphQLCodegen(): Plugin {
let codegenContext: CodegenContext;
return {
name: "rollup-plugin-graphql-codegen",
async buildStart() {
// noinspection ES6MissingAwait
codegen(this.meta.watchMode);
},
};
}
这里其实还可以使用 worker_threads
尝试多线程加速
import { Plugin } from "vite";
import { generate, loadContext } from "@graphql-codegen/cli";
import { isMainThread, parentPort, Worker } from "worker_threads";
import { expose, wrap } from "comlink";
import nodeEndpoint from "comlink/dist/umd/node-adapter";
async function codegen(watch: boolean) {
const codegenContext = await loadContext();
codegenContext.updateConfig({ watch });
try {
await generate(codegenContext);
} catch (error) {
console.log("Something went wrong.");
}
}
if (!isMainThread) {
expose(codegen, nodeEndpoint(parentPort!));
}
export function graphQLCodegen(): Plugin {
return {
name: "rollup-plugin-graphql-codegen",
async buildStart() {
const worker = new Worker(__filename);
try {
const codegenWorker = wrap<(watch: boolean) => void>(
nodeEndpoint(worker)
);
// noinspection ES6MissingAwait
codegenWorker(this.meta.watchMode);
} finally {
worker.unref();
}
},
};
}
更新,默认包含 @graphql-codegen/cli
,不再需要单独维护 codegen.yml 配置文件(当然仍然可以自行维护),使用方法
安装依赖
pnpm i -D @liuli-util/rollup-plugin-graphql-codegen @graphql-typed-document-node/core
配置插件
// vite.config.ts
import { defineConfig } from "vite";
import {
gql2TsConfig,
graphQLCodegen,
} from "@liuli-util/rollup-plugin-graphql-codegen";
export default defineConfig({
plugins: [graphQLCodegen(gql2TsConfig)],
});
安装 chrome 插件
参考
其他方案?
- 使用 rollup 插件
@rollup/plugin-graphql
直接将.graphql
文件视为可导入的 es 模块 – 主要问题是要处理相关工具的兼容性,主要包括 jest/eslint。 - 将 graphql 转成复杂的类型,然后运行时将 js 对象转换为 graphql 查询对象
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!