Для реализации UI рассмотрим пример написания веб-клиента для нашего GraphQL-сервера. И коли уж мы решили не следовать старым добрым традициям, то и в UI этого делать, конечно же, не будем и напишем его на кастомных веб-компонентах с использованием замечательной библиотеки Polymer.
Почему для подключения к GraphQL-серверу была выбрана библиотека Apollo сказать сложно, так как аналогов для работы с GraphQL я не встречал. Изначально библиотека, как и вся ее документация заточена под React, а вся информация об интеграции с Polymer ограничивается лишь ссылкой на компонент в разделе Integrations. Таким образом, если вы работаете с React, вам и карты в руки. Все примеры, твики и хуки именно для него.
Собственно, код
Готовый проект клиента можно посмотреть и скачать тут.
Шаг 1. Установка Polymer
Данный шаг не несет целевой нагрузки, но, опять же, необходим для начала работы с GraphQL. Таким образом, если вы с веб-компонентами на "ты", то можете этот шаг смело пропускать.
Как и в случае с сервером, для начала поднимем environment. Как это сделать вы сможете найти в официальной документаци. Уточню лишь, что из всех предлагаемых опций, нам потребуется создать пустой проект Polymer (опция "2) polymer-2-application"), остальные опции можно оставить по-умолчанию:
$> polymer init
? Which starter template would you like to use?
? Which starter template would you like to use? polymer-init-polymer-2-applicat
info: Running template polymer-2-application...
? Application name (graphql-client-demo)
? Application name graphql-client-demo
? Main element name (graphql-client-demo-app)
? Main element name graphql-client-demo-app
? Brief description of the application GraphQL Demo Client
? Brief description of the application GraphQL Demo Client
create bower.json
create index.html
create manifest.json
create polymer.json
create README.md
create srcgraphql-client-demo-appgraphql-client-demo-app.html
create testgraphql-client-demo-appgraphql-client-demo-app_test.html
Project generated!
Installing dependencies...
И сразу же запустим:
$> polymer serve
info: Files in this directory are available under the following URLs
applications: http://127.0.0.1:8081
reusable components: http://127.0.0.1:8081/components/graphql-client-demo/
… и оставим запущенным, т.к. все изменения подхватываются на лету (для меня, как backend-щика, изначально это было не до конца очевидно).
Шаг 2. Установка модуля для работы с GraphQL
Модуль для работы с библиотекой Apollo с неплохой документацией.
$> npm install --save polymer-apollo apollo-client
Шаг 3. Установка и настройка webpack
К сожалению реальность такова, что работа с этим модулем происходит через webpack. С одной стороны это не так и плохо, ввиду того, что webpack упаковывает все наши скрипты так, чтоб всё работало быстрее и эффективнее. Но с другой стороны для полноценной работы с webpack необходимо использовать npm вместо Polymer CLI. Из-за этого в работе с Polymer могут возникать непредвиденные ситуации. Это происходит из-за того, что невозможно настроить некоторые функции мимо конфигурационного файла polymer.json. Но тут, скорее всего, проблема в кривых руках backend-щика, так что не минусуйте сильно, а лучше поделитесь способами конфигурации в комментариях.
Примечание. Хотя существует лоадер вебпака для полимера от разработчика самого расширения polymer-apollo, но я, по правде сказать, особой пользы от этого лоадера не ощутил.
$ npm install -g webpack
Затем добавим в корень проекта 2 файла:
webpack.config.js — настойки упаковщика;
entry.js — список всех js-файлов для упаковки (будет наполняться по ходу разработки приложения).
webpack.config.js:
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
}
};
После чего в index.html нужно будет подключить только упакованный файл:
<script src="bundle.js"></script>
Чтобы подобрать правильную последовательность подключения всех скриптов и компонентов мне пришлось потратить определенное количество времени, так что советую не тратить своё и посмотреть секцию <head>
индекса в репозитории к статье.
Шаг 4. Создание структуры
Определенной структуры на стороне frontend, как мне кажется, быть не может по той причине, что она, как правило, заточена под конкретный проект. Но два элемента нам всё же понадобятся:
-
Это директория src/models, которая будет хранить все (прямо таки все-все) GraphQL запросы и ничего кроме них, и в дальнейшем будут подключаться по мере необходимости к местам использования. И...
- src/client.js — место подключения к GraphQL-серверу, которым мы сразу же и займемся.
Шаг 4.1 src/client.js
import ApolloClient, { createNetworkInterface, addTypename } from 'apollo-client';
// Создаем GraphQL клиент
export const apolloClient = new ApolloClient({
networkInterface: createNetworkInterface({
// здесь заменяем URL на необходимый
uri: 'http://graphql.server.demo/api/graphql',
transportBatching: true,
})
});
… и не забываем добавить его в entry.js:
import "src/client.js"
Шаг 4.2 Модель user.js
К понятию модель как таковому эти файлы никакого отношения не имеют. Это лишь библиотеки строковых констант с запросами, сгруппированные по конкретному функционалу. Не принципиально привязываться именно к сущности. Возможна разбивка по конкретным страницам или функциональным элементам. Все на усмотрение разработчика.
src/models/user.js:
// чтобы мы могли писать наши запросы подключаем
import gql from 'graphql-tag';
// несмотря на то, что это и не обязательно,
// важно писать именованные запросы
// (т.е. запросы с алиасом: query getUserInfo { ... })
// в дальнейшем это нам поможет при отладке
export const getUserInfoQuery = gql`
query getUserInfo {
user(id: 1) {
firstname
lastname
createDate(format: "d M, H:i")
addresses {
city {
name
}
}
}
addresses {
user {
firstname
}
}
}
`;
Шаг 4.3 Компонент
Для удобства вынесем js из нашего созданного полимером компонента в отдельный файл, и внесем в него изменения чтобы он сотрудничал с библиотекой Apollo.
src/graphql-client-demo-app/graphql-client-demo-app.js:
import { PolymerApolloMixin } from 'polymer-apollo';
import { apolloClient } from '../client';
// заимпортим все необходимые запросы
// (пока что он у нас один)
import { getUserInfoQuery } from '../models/user';
class GraphqlClientDemoApp extends PolymerApolloMixin({ apolloClient }, Polymer.Element) {
static get is() { return 'graphql-client-demo-app'; }
static get properties() {
return {
appName: {
type: String,
value: 'GraphQL Client Demo'
},
// ВАЖНО!!!
// постарайтесь хорошо запомнить,
// что имя property должно в
// точности соответствовать названию
// корнегово поля запроса
// т.е. в данном случае наш запрос
// будет выглядеть следующим образом:
// query { user { ... } }
user: {
type: Object,
value: {}
}
};
}
// ну а здесь и будут наши запросы из моделей
get apollo() {
return {
getUserInfo: {
// наш запрос, который мы
// заимпортили из моделей
// нужно понимать, что этот запрос
// дернется сразу же при инициализации
// компонента
// можно ли этого избежать,
// я пока не разобрался
// разве что вызовом отдельной функции
query: getUserInfoQuery
}
};
}
}
window.customElements.define(GraphqlClientDemoApp.is, GraphqlClientDemoApp);
… и снова не забываем добавить его в entry.js:
import "src/client.js"
Ну и собственно осталось посмотреть на всё то, что придет с сервера.
src/graphql-client-demo-app/graphql-client-demo-app.html:
<dom-module id="graphql-client-demo-app">
<template>
<style>
</style>
<!-- просто пример вывода property -->
<h1>[[appName]]</h1>
<!-- пытаемся подтянуть данные из того
что должно было вернуться с сервера -->
<p><b>Name:</b> [[user.firstname]] [[user.lastname]]!</p>
<p><b>Created date:</b> [[user.createDate]]</p>
<h2>Addresses:</h2>
<table>
<!-- перебор массива -->
<template is="dom-repeat" items="{{user.addresses}}">
<!-- к несчастью в этом примере
только одно поле, но ведь мы понимаем,
что их может быть больше -->
<tr>
<td>{{index}}</td>
<td>{{item.city.name}}</td>
</tr>
</template>
</table>
</template>
</dom-module>
К слову, во время разработки шаблона мое окно среды выглядит примерно следующим образом:
В левой части экрана я полностью вижу структуру запроса (которая идентична структуре ответа), и описываю ее в шаблоне необходимым мне образом. Удобно.
Шаг 5. Сборка, тестирование и отладка
Для начала не забываем собрать webpack:
$> webpack
Во время разработки вам, к сожалению, придется это делать после каждого обновления js-скриптов. Этого можно избежать, если следовать туториалу, написанным самим разработчиком polymer-apollo, и использовать npm start, но повторюсь, что если работать мимо polymer CLI, у вас могут возникнуть проблемы с некоторыми компонентами Polymer и с деплоем (конкретно я столкнулся с проблемой некорректной работы iron-pages и lazy-import, которую здесь описывать не буду т.к., к сожалению, пока еще не до конца решил).
Открываем Chrome, переходим на адрес нашего уже поднятого клиента (адрес можно увидеть сразу после выполнения $ polymer serve
, как правило это http://127.0.0.1:8081/) и радуемся результатам, вернувшимся с нашего GraphQL сервера:
И снова я не могу нарадоваться тулзам для отладки. Ставим расширение для Chrome Apollo Client Developer Tools, открываем консоль, переходим на вкладку Apollo, откидываемся на спинку кресла и начинаем получать удовольствие:
Помните, мы задавали алиас нашему запросу? Так вот в этом списке будут отображаться запросы именно по этим алиасам (иначе просто номер). Запросы и мутации отображаются отдельными списками. Стоит отметить, что эта тулза не является полной заменой вкладке Network, т.к. она показывает только запросы, а вот ответы — нет. Правда любой запрос одним нажатием на кнопочку возле алиаса запроса (или мутации) можно тут же загрузить во встроенный в это же расширение GraphiQL.
В целом такой себе лаконичный инспектор, так что коли вы захотите все же посмотреть по хедерам и статусам, милости просим на старую добрую вкладку Network.
В следующей(их) части(ях) мы наконец рассмотрим мутации, рассмотрим один из способов серверной валидации, и конечно же сделаем выводы.
Автор: timur560