🍑

今すぐVue3でApolloを使いたい

2021/03/07に公開

abstract

現在、Vue3にApolloが対応していない。
しかし、後述のpatchを当てることで利用することができる(できた)

現在このpatchはPR中である。
いずれマージされればこのpatchを当てることなく利用できるとのこと。

https://github.com/vuejs/vue-apollo/issues/1011

参考

youtubeで詳しく解説されています。
(この記事はほぼその文字起こし)

https://www.learnwithjason.dev/build-apps-with-vue-3-apollo-client-3

環境

$vue --version
@vue/cli 4.5.11
$node -v
v14.0.0

*nodeのバージョンはかなりデリケート(新しすぎると互換性がなくて動かない)

create project

$vue create test-gg

(いろいろ聞かれるので下記のように回答)

? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

Build Apps With Vue 3 + Apollo Client3

Apollo Installation

Apollo Client:
https://www.apollographql.com/docs/react/get-started/

$yarn add @apollo/client graphql

queryの作成・接続

jsにクライアント情報を追加する。

./src/main.js
import { ApolloClient, InMemoryCache } from '@apollo/client'

const defaultClient = new ApolloClient({
  uri: 'https://rickandmortyapi.com/graphql/',
  cache: new InMemoryCache()
})

テストクエリを追加

適当にクエリを作成してみる。
https://rickandmortyapi.com/graphql

適当に作ったクエリを記述する。

./src/main.js
import { createApp } from 'vue'
import { ApolloClient, gql, InMemoryCache } from '@apollo/client'
import App from './App.vue'

const defaultClient = new ApolloClient({
  uri: 'https://rickandmortyapi.com/graphql/',
  cache: new InMemoryCache()
})

const query = gql`
  query {
    characters {
      results {
        name
      }
    }
  }
`

defaultClient
  .query({
    query
  })
  .then(res => console.log(res))

createApp(App).mount('#app')

*gql@apollo/clientで用意されている。

reactがないとerrorがはかれる。

react installation

$yarn add react
yarn server

ローカルにアクセスし、consoleを開くとデータが取れている。

Composition APIのvue Apollo

vue-Apolloでは3つのパッケージが提供されている。
Vue3なのでComposition APIを選択
*vue2向けのパッケージだけど必要なので無理やり使う。

$yarn add @vue/apollo-composable

Vue2はnew Vueによって生成する。
Vue3はcreateした物をmountして生成する。

ここに無理が生じる。

次のように書き換えてみる。

.src/main.js
import { createApp, provide, h } from 'vue'
import { ApolloClient, gql, InMemoryCache } from '@apollo/client'
import { DefaultApolloClient } from '@vue/apollo-composable'
import App from './App.vue'

const defaultClient = new ApolloClient({
  uri: 'https://rickandmortyapi.com/graphql/',
  cache: new InMemoryCache()
})

const query = gql`
  query {
    characters {
      results {
        name
      }
    }
  }
`
defaultClient
  .query({
    query
  })
  .then(res => console.log(res))

createApp(
  {
    setup() {
      provide(DefaultApolloClient, defaultClient)
    },
    render() {
      return h(App)
    },
  }
).mount('#app')

consoleでエラーがなければ準備OK。

テストコードをコメントアウト。

./src/main.js
import { createApp, provide, h } from 'vue'
import { ApolloClient, gql, InMemoryCache } from '@apollo/client'
import { DefaultApolloClient } from '@vue/apollo-composable'
import App from './App.vue'

const defaultClient = new ApolloClient({
  uri: 'https://rickandmortyapi.com/graphql/',
  cache: new InMemoryCache()
})

// const query = gql`
//   query {
//     characters {
//       results {
//         name
//       }
//     }
//   }
// `
// defaultClient
//   .query({
//     query
//   })
//   .then(res => console.log(res))

createApp(
  {
    setup() {
      provide(DefaultApolloClient, defaultClient)
    },
    render() {
      return h(App)
    },
  }
).mount('#app')

vue.config.js

graphqlorgqlの拡張子が表示された場合、タグローダーを使用してインポートさせる。

./vue.config.js
module.exports = {
  chainWebpack: config => {
    config.module
      .rule('graphql')
      .test(/\.(graphql|gql)$/)
      .use('graphql-tag/loader')
      .loader('graphql-tag/loader')
      .end();
  }
};

grapgqlを作成

ディレクトリ を作成。

./src/graphql

gqlファイルを作成。

./src/graphql/allCharecters.query.gql

queryを追加。
*queryの名前もつける。

./src/graphql/allCharacters.query.gql
query allCharecters {
  characters {
    results {
      name
      image
    }
  }
}

vueファイル

テストメッセージを書いてみる。

./src/App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
+  {{ message }}
</template>

<script>
+ import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
+  setup() {
+    const message = ref('Hello, gamine.')
+
+    return { message }
+  },
}
</script>

<...>

サーバーにアクセスして表示されるか確認。
*Vue2ではtemplateの中をdivなどでラップする必要があったがVue3ではその必要がない。

queryの呼び出し

userQueryでqueryを呼び出す。

./src/App.vue
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
+ import { useQuery } from '@vue/apollo-composable'
+ import allCharactersQuery from './graphql/allCharacters.query.gql'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  setup() {
    const message = ref('Hello, gamine.')
+    const { result } = useQuery(allCharactersQuery)
     console.log(result)

    return { message }
  },
}
</script>

ローカルを開くとエラー。vue-apolloがVue3に対応していないので。

$yarn serve
"export 'onServerPrefetch' was not found in 'vue-demi'
Uncaught Invariant Violation: Expecting a parsed GraphQL document. Perhaps you need to wrap the query string in a "gql" tag?

*2021-02-28現在、GitHubでPRが出されているもののまだマージされていないとのこと。

ハッキーな解決策

実行するだけのscriptを作成

$mkdir ./script
$touch ./script/patch.js

コードを記述。

./script/patch.js
const fs = require('fs')
const path = require('path')

const loadTrackingPath = path.resolve(
  __dirname,
  '../node_modules/@vue/apollo-composable/dist/util/loadingTracking.js'
)

fs.writeFileSync(
  loadTrackingPath,
  fs.readFileSync(loadTrackingPath, 'utf8').replace(/\.\$root/m, '.root')
)

const useQueryPath = path.resolve(
  __dirname,
  '../node_modules/@vue/apollo-composable/dist/useQuery.js'
)

fs.writeFileSync(
  useQueryPath,
  fs
    .readFileSync(useQueryPath, 'utf8')
    .replace(/(^.*onServerPrefetch)/m, '$1=()=>{}; $1')
    .replace(/(.* require\("vue"\);)/m, '')
    .replace(/^.*(nextTick)/m, 'vue_demi_1.$1')
)
// もしくはこっちの方法もある(違いはわからん)
// const fs = require('fs')
// const path = require('path')

// const loadTrackingPath = path.resolve(
//   __dirname,
//   '../node_modules/@vue/apollo-composable/dist/util/loadingTracking.js'
// )

// fs.writeFileSync(
//   loadTrackingPath,
//   fs.readFileSync(loadTrackingPath, 'utf8').replace(/\.\$root/m, '.root')
// )

// const useQueryPath = path.resolve(
//   __dirname,
//   '../node_modules/@vue/apollo-composable/dist/useQuery.js'
// )

// fs.writeFileSync(
//   useQueryPath,
//   fs
//     .readFileSync(useQueryPath, 'utf8')
//     .replace(/(^.*onServerPrefetch)/m, '$1=()=>{}; $1')
//     .replace(/(.* require\("vue"\);)/m, '')
//     .replace(/^.*(nextTick)/m, 'vue_demi_1.$1')
// )

実行

$node ./script/patch.js

ローカルで実行。

consoleでNetWorkを確認し、GraphQLのレスポンスがあればOK。
(console.logで表示してもOK)

補足

実践したときは、ここで次のようなエラーが発生した

 ERROR  Failed to compile with 1 errors

 error  in ./node_modules/@vue/apollo-composable/dist/useQuery.js

Module parse failed: Unexpected token (14:10)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import { ref, isRef, computed, watch, 
| // @ts-expect-error
> vue_demi_1.nextTick, } from 'vue-demi';
| import { throttle, debounce } from 'throttle-debounce';
| import { useApolloClient } from './useApolloClient';

 @ ./node_modules/@vue/apollo-composable/dist/index.js 1:0-39 1:0-39
 @ ./src/main.js
 @ multi (webpack)-dev-server/client?http://192.168.0.198:8080&sockPath=/sockjs-node (webpack)/hot/dev-server.js ./src/main.js

package.jsonを下に変更したら直った。。。

package.json
{
  "name": "vue-3-and-apollo-client-3",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "@apollo/client": "^3.2.3",
    "@vue/apollo-composable": "^4.0.0-alpha.10",
    "core-js": "^3.6.5",
    "graphql": "^15.3.0",
    "react": "^16.13.1",
    "vue": "^3.0.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0"
  }
}

resultを解凍して利用する

useResultでデータを使える形にし、表示する。
(深く理解していないけどresultはオブジェクトで、それをよしなに変換してくれる。

./src/App.vue
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
  {{ message }}
+  {{ character }}
</template>

<script>
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'
- import { useQuery } from '@vue/apollo-composable'
+ import { useQuery, useResult } from '@vue/apollo-composable'
import allCharactersQuery from './graphql/allCharacters.query.gql'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
  setup() {
    const message = ref('Hello, gamine.')
    const { result } = useQuery(allCharactersQuery)
+    const character = useResult(result, null, data => data.characters.results)
    console.log(result)
-    return { message }
+    return { message, character }
  },
}
</script>

queryの結果が表示されました🎉

リスト表示してみる

  <ul>
    <li v-for="character in characters" :key="character.id">
      <h2>{{ character.name }}</h2>
    </li>
  </ul>

mutation

サーバを用意していないので割愛。
(参考にした動画で説明しています)

まとめ

いますぐVue3でApolloを使いたいって人のための記事でした。

近いうちにptchは不要になる

今回追加しているpatchは、pr中。
近い将来mergeされ、patchは不要になる予定。

Discussion