graphql-request v7をCommonJS環境で使い続ける
graphql-request@7.0.0にてCommonJSがサポート対象外に
Release Noteに記載されている通り、v7以降ではCommonJSサポートが削除されESMのみサポートとなりました。
7.0.0
BREAKING CHANGES
- 2a121c6 remove inlined graphql websocket code
- 99a192e add spec compliant default Accept header (#618)
- 0e53aed replace GraphQLClientRequestHeaders with built-in HeadersInit type (#616)
- c3a309f remove support for CommonJS (#607)
- 6efcc0d remove cross-fetch polyfill
- Valid URL must be passed (no more path-only strings like /foo) (#745)
このため、Node.jsをCommonJSで実行している場合、requireやTypeScriptのimportでパッケージを利用することができません。例えば、以下のようなコードがエラーとなります。
import { ClientError, GraphQLClient } from 'graphql-request';
// error TS2307: Cannot find module 'graphql-request' or its corresponding type declarations.
対処法
CommonJS環境であっても、dynamic importを使用することでESMをimportすることができます。ただし、非同期のimportにしか対応していないためコードを書き換える必要があります。
例:
async function createClient() {
const { GraphQLClient } = await import('graphql-request');
return new GraphQLClient(...);
}
module: "NodeNext"
を使用
TypeScriptの場合はTypeScriptの場合、module: "CommonJS"
を使用しているとawait importがrequireに変換されてしまいます。これを防ぐため、以下のようにtsconfig.jsonを書き換えます。
{
"compilerOptions": {
- "module": "CommonJS",
+ "module": "NodeNext",
// moduleResolutionもNodeNextに変更するか、削除する
- "moduleResolution": "Node",
...
import typeによる型のみのimportを行う
TS5.3で追加されたimport type ... with
構文を使用することで型のみをimportできます。
例えば、GraphQLResponse
型をコード中で使用したい場合は以下のようになります。
import type { GraphQLResponse } from 'graphql-request' with {
"resolution-mode": "import" // ESMとしてimport文を処理する
};
参考: https://github.com/microsoft/TypeScript/issues/49721
@graphql-codegen/typescript-graphql-requestでの対応
生成結果をESMにする
@graphql-codegen/typescript-graphql-requestでは生成結果に普通にgraphql-requestのimport文が埋め込まれてしまうため、上述のようなdynamic importやimport typeを用いる方法が使用できません。このため、代わりに生成されるコード自体をESMファイルとし、それを使用側でdynamic importすることにします。
codegen.ymlにemitLegacyCommonJSImport: false
を追加することでESMとして動作するコードを出力できます。また、出力先の拡張子は.mts
とします。
overwrite: true
schema:
- ...
documents: ['./src/**/*.graphql']
+ emitLegacyCommonJSImports: false
generates:
- src/hoge/generated.ts:
+ src/hoge/generated.mts:
plugins:
...
使用する際には以下のようにimportします。ESMをimportする際には拡張子.mjs
を付ける必要がある点に注意してください。
async function createClient() {
const { GraphQLClient } = await import('graphql-request');
const { getSdk } = await import('./generated.mjs');
return getSdk(
new GraphQLClient(...)
);
}
gqlの代わりにDocumentNodeを用いる
ここまでの手順を行っても、生成されたファイルで以下のエラーが発生してしまいます。
// error TS2349: This expression is not callable.
// Type 'typeof import("/.../node_modules/graphql-tag/lib/index")' has no call signatures.
export const ExampleQueryDocument = gql`
...
これは、以下のようにgqlをdefault exportとしてimportしようとしているためです。graphql-tagのESMにおいてはgqlはdefault exportではないため、これはエラーとなります。
import gql from 'graphql-tag';
現状では以下のようにDocumentNodeを代わりに使用するようにします。(documentModeとdocumentNodeが紛らわしいので注意してください)
generates:
src/hoge/generated.mts:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-graphql-request'
+ config:
+ documentMode: documentNode
参考: https://github.com/dotansimha/graphql-code-generator-community/issues/228
Discussion