俺でもわかるGraphQLでsvelte-apolloクライアント(4)
はじめに
いやー、気候がいいので週末はBBQやピザ作りにかまけてほったらかしになってしまっていましたが、そろそろクライアント側もやってみようと思います。前回までで、サーバー側が一通り動いたので、これと繋がるクライアントを作ってみます。
Apollo公式サイトでは、クライアントはReact以外は知らん、というオーラ満開で悲しいですが、ここはあえてSvelteでTODOアプリのクライアントつくってみます。
TL;DR
- SvelteとApolloでGraphQLのクライアントを動かしてみた
- 普通に
@apollo/client
でもできるが、Apollo公式でも紹介されている、svelte-apollo
がなかなかよかった。svelte-apollo
はドキュメント少なめ[1]なので、こんなのあったらよかったのになっていうのを書く - Svelteはタスクランナーは
rollup
推し。rollup
でちょいハマったところも書く
SvelteとApollo Clientの準備
こんなプロジェクト構成にしていく
svelte-apollo-todo/
├── apollo
└── svelte
Svelte入れる
cd svelte-apollo-todo/
npx degit sveltejs/template svelte
cd svelte
npm install
GraphQL関係を入れる
npm i --save @apollo/client graphql
ApolloとSvelteを起動
-
localhost:4000
で前回作ったApolloバックエンドを起動 >node apollo-server.js
-
localhost:5000
でSvelteを起動 >npm run dev
ここで、App.svelte
を以下のようにすればとりあえず動きます。ここではgql
がGraphQLの構文を解釈し、JSモジュールにしてくれている。
import { InMemoryCache, ApolloClient, gql } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache: new InMemoryCache()
});
client
.query({
query: gql`
query list {
tasks {
id
name
isActive
createdAt
updatedAt
owner
}
}
`
})
.then(result => console.log(result));
レスポンスはこんな感じ
{data: {…}, loading: false, networkStatus: 7}
data:
tasks: Array(4)
0: {__typename: "Task", id: "1", name: "Soak in an Onsen", isActive: true, createdAt: null, …}
1: {__typename: "Task", id: "2", name: "Sing Karaoke", isActive: false, createdAt: null, …}
2: {__typename: "Task", id: "3", name: "See cherry blossom", isActive: true, createdAt: null, …}
3: {__typename: "Task", id: "4", name: "Buy some milk", isActive: true, createdAt: 1621812683, …}
length: 4
__proto__: Array(0)
__proto__: Object
loading: false
networkStatus: 7
__proto__: Object
トップレベルに、loading: false
とかあるので、これで読み込み中 or 読み込み完了などの制御もできそうだ。まあ、このやり方でもいいのだが、軽く探してみると、Svelteとのインテグレーションを楽にしてくれそうなsvelte-apollo
というのがあるみたいなので、そっちでやってみることにします。
svelte-apollo
の場合
インストールする
cd svelte
npm i --save svelte-apollo @rollup/plugin-graphql @rollup/plugin-replace
この時点で、package.json
の各種バージョンはこんな感じ
"dependencies": {
"@apollo/client": "^3.3.19",
"@rollup/plugin-graphql": "^1.0.0", // こっちのローダーを使う
"graphql": "^15.5.0", // 今後はこのローダー使わない
"sirv-cli": "^1.0.0",
"svelte-apollo": "^0.4.0"
}
TODOのGraphQLバックエンドに対する一通りの操作をするApp.svelte
はこんな感じ
import { InMemoryCache, ApolloClient } from '@apollo/client'; // ❶
import { setClient, query, mutation } from "svelte-apollo"; // ❸
import { list_tasks, get_task, add_task, complete_task, delete_task } from './schema.graphql'; // ❷
const client = new ApolloClient({
uri: 'http://localhost:4000',
cache: new InMemoryCache()
});
setClient(client); // ❸
// list
const listTasks = async () => {
const reply = await query(list_tasks);
reply.subscribe(data => console.log('list', data)); // ❹
};
listTasks();
// getTask
const getTask = async (tid) => {
const reply = await query(get_task, { variables: { id: tid } });
reply.subscribe(data => console.log('get', data));
};
getTask(3);
// addTask
const add = mutation(add_task);
const addTask = async (tname) => {
const reply = await add({ variables: { name: tname } }); // ❺
console.log('add', reply)
};
addTask('New task');
// deleteTask
const del = mutation(delete_task);
const deleteTask = async (tid) => {
const reply = await del({ variables: { id: tid } });
console.log('del', reply)
};
deleteTask(5);
// completeTask
const done = mutation(complete_task);
const completeTask = async (tid) => {
const reply = await done({ variables: { id: tid } });
console.log('done', reply) // ❻
};
completeTask(10);
Apolloクライアントをそのまま使う場合からの変更点:
- Apolloクライアントの
gql
モジュールではなく、@rollup/plugin-graphql
でGraphQLを解釈する(いろんな実装があるのな) - QueryとMutaionを
schema.graphql
から読み込む。中身は以下参照。 -
svelte-apollo
をインポートして、デフォルトのクライアント・インスタンスをセット -
query()
の返り値はSvelteのStores
をPromiseで包んだものなので、中身を除く場合はsubscribe()
を使う。Stores
が何をしているかはここを見ればざっくりわかる。一言で言うと複数コンポーネントの状態管理を楽にしてくれるオブジェクト。 - Mutaionの返り値は
Stores
ではない - 本題ではないが、
console.log('label', reply)
としてラベルづけするとわかりやすい
query list_tasks {
tasks {
id
name
isActive
createdAt
updatedAt
owner
}
}
query get_task($id: ID!) {
task(id: $id) {
id
name
}
}
mutation add_task($name: String!) {
addTask(name: $name) {
id
name
}
}
mutation complete_task($id: ID!) {
completeTask(id: $id) {
id
name
isActive
}
}
mutation delete_task($id: ID!) {
deleteTask(id: $id) {
id
name
}
}
reply
はこんな感じ
list {loading: false, data: {…}, error: undefined}
data:
tasks: Array(4)
0: {__typename: "Task", id: "1", name: "Soak in an Onsen", isActive: true, createdAt: null, …}
1: {__typename: "Task", id: "2", name: "Sing Karaoke", isActive: false, createdAt: null, …}
2: {__typename: "Task", id: "3", name: "See cherry blossom", isActive: true, createdAt: null, …}
3: {__typename: "Task", id: "4", name: "Buy some milk", isActive: true, createdAt: 1621812683, …}
length: 4
__proto__: Array(0)
__proto__: Object
error: undefined
loading: false
__proto__: Object
上記は、オブジェクトの中身を覗くためにconsole.log()
してますが、実際には以下のように扱うのがSvelte流のようです。すっきりかけていい感じ。
const reply = query(list_tasks);
{#if $reply.loading}
Loading...
{:else if $reply.error}
Error: {$reply.error.message}
{:else}
{#each $reply.data.tasks as task}
<p>{task.id} {task.name}</p>
{/each}
{/if}
GraphQLでのCRUDは出来るようになりました。ここまでくれば、あとはApp.svelte
のHTML側をいじるだけです。
Rollupに怒られた件
初心者なので、Rollup先生からはいくつかお叱りをいただきました。
Error#1
bundle.js:5019 Uncaught ReferenceError: process is not defined
at new ApolloClient (bundle.js:5019)
これは環境変数がうまく渡せてない。以下の@rollup/plugin-replace
で解決
replace({
'process.env.NODE_ENV': JSON.stringify( 'development' )
}),
Error#2
Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
GraphQLが解釈できていない。@rollup/plugin-graphql
で解決
Error#3
これはRollupじゃなくてApolloのエラー。cache
は必須。
Uncaught Invariant Violation: To initialize Apollo Client, you must specify a 'cache' property in the options object.
Error#4
これはなんのエラーだったか?実行上関係なかった気がする。
Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for ca
次回
もうちょっと勉強のために、以前RESTで書いたTODOアプリをGraphQLに置き換えてみよう
シリーズ
- 俺でもわかるGraphQLでリスト表示(1)
- 俺でもわかるGraphQLでサーチ(2)
- 俺でもわかるGraphQLでデータ更新(3)
- 俺でもわかるGraphQLでsvelte-apolloクライアント(4)
- 俺でもわかるGraphQLで基本のTodoアプリをSvelteとApolloでやってみる(5)
-
Readmeも微妙に間違ってる部分がある気がする。Apolloのバージョンが上がったからかもしれないが、そのままでは動かないところがあった。 ↩︎
Discussion