diff --git a/.env b/.env index 259d6ea..7a75aea 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -GRAPHQL=https://graphqlhub.com/graphql +GRAPHQL=https://fakerql.com/graphql WS_SUBSCRIPTIONS=0 \ No newline at end of file diff --git a/.gitignore b/.gitignore index ad4d909..8effb8d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ jspm_packages # Distribution dist + +# Generated files +src/graphql/graphql-types.tsx +src/graphql/graphql-modules.d.ts diff --git a/package.json b/package.json index c3fb4bc..ae20192 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@babel/core": "^7.1.2", "@babel/plugin-syntax-dynamic-import": "^7.0.0", "@types/compression-webpack-plugin": "^0.4.2", + "@types/graphql": "^14.0.3", "@types/history": "^4.7.2", "@types/html-webpack-plugin": "^3.2.0", "@types/kcors": "^2.2.3", @@ -50,6 +51,11 @@ "css-loader": "^1.0.0", "cssnano": "^4.1.5", "file-loader": "^2.0.0", + "graphql-code-generator": "0.14.1", + "graphql-codegen-typescript-graphql-files-modules": "0.14.1", + "graphql-codegen-typescript-react-apollo": "0.14.1", + "graphql-codegen-typescript-common": "0.14.1", + "graphql-codegen-typescript-client": "0.14.1", "html-webpack-plugin": "^3.2.0", "koa-webpack": "^5.1.0", "less": "^3.8.1", diff --git a/src/components/example/count.tsx b/src/components/example/count.tsx index f3a2942..8bdfa62 100644 --- a/src/components/example/count.tsx +++ b/src/components/example/count.tsx @@ -6,32 +6,19 @@ /* NPM */ import * as React from "react"; -// Use the `` component from the React Apollo lib to set-up the -// mutation block that will allow us to fire up GraphQL mutations. We'll also -// grab the `` component, because we'll want to 'listen' to the current -// count as it changes -import { Mutation, Query } from "react-apollo"; - /* Local */ -// Get the Typescript types of our local state, so we can use them -// with our GraphQL queries to hint at the data that should be returned -import { IRoot } from "@/graphql/state"; - // Get the current count, stored in local Apollo state -import getCount from "@/queries/getCount"; - -// Mutation to increment the local counter -import incrementCount from "@/mutations/incrementCount"; +import { Count, IncrementCount } from "@/graphql/graphql-types"; // ---------------------------------------------------------------------------- export default () => ( - + { doIncrementCount => { return ( - query={getCount}> + { ({ data }) => { @@ -48,9 +35,9 @@ export default () => ( ); } } - + ); } } - + ); diff --git a/src/components/example/index.tsx b/src/components/example/index.tsx index 6304202..2235ba6 100644 --- a/src/components/example/index.tsx +++ b/src/components/example/index.tsx @@ -13,7 +13,7 @@ import * as React from "react"; import Count from "./count"; // Hacker News GraphQL example -import HackerNews from "./hackernews"; +import HackerNews from "./todos"; // ---------------------------------------------------------------------------- diff --git a/src/components/example/hackernews.tsx b/src/components/example/todos.tsx similarity index 53% rename from src/components/example/hackernews.tsx rename to src/components/example/todos.tsx index 070537a..f91d7e7 100644 --- a/src/components/example/hackernews.tsx +++ b/src/components/example/todos.tsx @@ -1,4 +1,4 @@ -// ReactQL Hacker News GraphQL example +// Todos list data example // ---------------------------------------------------------------------------- // IMPORTS @@ -6,41 +6,19 @@ /* NPM */ import * as React from "react"; -// Use the `` component from the React Apollo lib to declaratively -// fetch the GraphQL data, to display as part of our component -import { Query } from "react-apollo"; - /* Local */ // Styled component lib for generating CSS in lieu of using SASS import styled from "@/lib/styledComponents"; -// Query to get top stories from HackerNews -import hackerNewsQuery from "@/queries/getHackerNewsTopStories"; - -// ---------------------------------------------------------------------------- - -// Typescript types - -// Represents a HackerNews story - id, title and url -interface IHackerNewsStory { - id: string; - title: string; - url: string; -} - -// Represents the data returned by the Hacker News GraphQL query -interface IHackerNewsTopStories { - hn: { - topStories: IHackerNewsStory[]; - }; -} +// Query to get random todo items +import { AllTodos } from "@/graphql/graphql-types"; // Style the list item so it overrides the default font -const Story = styled.li` +const Todo = styled.li` font-size: 16px; - a:hover { + span:hover { /* shows an example of how we can use themes */ color: ${props => props.theme.colors.orange}; } @@ -52,14 +30,14 @@ const Story = styled.li` // doesn't already have data from the server -- it'll display a loading message // while the data is being retrieved export default () => ( - query={hackerNewsQuery}> + { result => { // Any errors? Say so! if (result.error) { return ( -

Error retrieving news stories! — {result.error}

+

Error retrieving todo tasks! — {result.error}

); } @@ -67,7 +45,7 @@ export default () => ( // message to alert the user if (result.loading) { return ( -

Loading Hacker News stories...

+

Loading Todo...

); } @@ -75,13 +53,13 @@ export default () => ( // bullet-point list return ( <> -

Top stories from Hacker News

+

Todo

    { - result.data!.hn.topStories.map(story => ( - - {story.title} - + result.data!.allTodos!.map(todo => ( + + {todo!.title} + )) }
@@ -89,5 +67,5 @@ export default () => ( ); } } -
+ ); diff --git a/src/graphql/client-schema.graphql b/src/graphql/client-schema.graphql new file mode 100644 index 0000000..d67b02c --- /dev/null +++ b/src/graphql/client-schema.graphql @@ -0,0 +1,13 @@ +directive @client on FIELD + +type Query { + state: State! +} + +type Mutation { + incrementCount: State! +} + +type State { + count: Int! +} diff --git a/src/graphql/state.ts b/src/graphql/state.ts index f6ab50a..239a5b1 100644 --- a/src/graphql/state.ts +++ b/src/graphql/state.ts @@ -11,31 +11,20 @@ import { ClientStateConfig, withClientState } from "apollo-link-state"; /* Local */ // Queries -import getCountQuery from "@/queries/getCount"; +import { count } from "@/queries/getCount.graphql"; +import { Query, State } from "./graphql-types"; // ---------------------------------------------------------------------------- -// Types - -/* STATE */ -export interface IState { - count: number; -} - -// 'Root', which contains the 'State' key -export interface IRoot { - state: IState; -} - export default function createState(cache: InMemoryCache): ApolloLink { // Helper function to retrieve the state from cache - function getState(query: any): IState { - return cache.readQuery({ query }).state; + function getState(query: any): State { + return cache.readQuery({ query }).state; } // Helper function to write data back to the cache - function writeState(state: IState) { + function writeState(state: State) { return cache.writeData({ data: { state } }); } @@ -48,7 +37,7 @@ export default function createState(cache: InMemoryCache): ApolloLink { incrementCount() { // Get the existing state - const state = getState(getCountQuery); + const state = getState(count); // Create new state. Note that we're assigning this to a new // constant, and not simply incrementing the existing `count` @@ -76,7 +65,7 @@ export default function createState(cache: InMemoryCache): ApolloLink { __typename: "State", count: 0, }, - } as IRoot; + } as Query; return withClientState(opt); } diff --git a/src/mutations/incrementCount.graphql b/src/mutations/incrementCount.graphql new file mode 100644 index 0000000..a0b7e4d --- /dev/null +++ b/src/mutations/incrementCount.graphql @@ -0,0 +1,5 @@ +mutation IncrementCount { + incrementCount @client { + count + } +} diff --git a/src/mutations/incrementCount.ts b/src/mutations/incrementCount.ts deleted file mode 100644 index 773e6eb..0000000 --- a/src/mutations/incrementCount.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Add 1 to the counter in local state - -// ---------------------------------------------------------------------------- -// IMPORTS - -/* NPM */ - -// GraphQL tag library, for creating GraphQL queries from plain -// template text -import gql from "graphql-tag"; - -// ---------------------------------------------------------------------------- - -// GraphQL query for retrieving the current count from local state -export default gql` - mutation IncrementCount { - incrementCount @client { - count - } - } -`; diff --git a/src/queries/getCount.graphql b/src/queries/getCount.graphql new file mode 100644 index 0000000..4786875 --- /dev/null +++ b/src/queries/getCount.graphql @@ -0,0 +1,5 @@ +query count { + state @client { + count + } + } \ No newline at end of file diff --git a/src/queries/getCount.ts b/src/queries/getCount.ts deleted file mode 100644 index 09d7b9a..0000000 --- a/src/queries/getCount.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Get local count state - -// ---------------------------------------------------------------------------- -// IMPORTS - -/* NPM */ - -// GraphQL tag library, for creating GraphQL queries from plain -// template text -import gql from "graphql-tag"; - -// ---------------------------------------------------------------------------- - -// GraphQL query for retrieving the current count from local state -export default gql` - { - state @client { - count - } - } -`; diff --git a/src/queries/getHackerNewsTopStories.ts b/src/queries/getHackerNewsTopStories.ts deleted file mode 100644 index e2f67ad..0000000 --- a/src/queries/getHackerNewsTopStories.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Get the top stories from HackerNews - -// ---------------------------------------------------------------------------- -// IMPORTS - -/* NPM */ - -// GraphQL tag library, for creating GraphQL queries from plain -// template text -import gql from "graphql-tag"; - -// ---------------------------------------------------------------------------- - -// GraphQL query for retrieving Hacker News top-stories from the -// https://graphqlhub.com/playground sample server endpoint -export default gql` - { - hn { - topStories { - id - title - url - } - } - } -`; diff --git a/src/queries/getRandomData.graphql b/src/queries/getRandomData.graphql new file mode 100644 index 0000000..565309d --- /dev/null +++ b/src/queries/getRandomData.graphql @@ -0,0 +1,6 @@ +query allTodos { + allTodos { + id + title + } +} diff --git a/src/runner/build.ts b/src/runner/build.ts index 5b5112b..d7f815e 100644 --- a/src/runner/build.ts +++ b/src/runner/build.ts @@ -8,12 +8,14 @@ import * as chalk from "chalk"; /* Local */ import { build, common } from "./app"; +import { generateSchemaTypings } from "./generate"; // ---------------------------------------------------------------------------- common.spinner.info(chalk.default.bgBlue("Build mode")); void (async () => { + await generateSchemaTypings(false); await build(); common.spinner.succeed("Finished building"); })(); diff --git a/src/runner/development.ts b/src/runner/development.ts index 853943d..f398922 100644 --- a/src/runner/development.ts +++ b/src/runner/development.ts @@ -10,6 +10,7 @@ import * as KoaWebpack from "koa-webpack"; /* Local */ import hotServerMiddleware from "../lib/hotServerMiddleware"; import { app, common, compiler } from "./app"; +import { generateSchemaTypings } from "./generate"; // ---------------------------------------------------------------------------- @@ -17,6 +18,8 @@ common.spinner .info(chalk.default.magenta("Development mode")) .info("Building development server..."); +generateSchemaTypings(true); + app.listen({ port: common.port, host: "localhost" }, async () => { // Init Koa-Webpack dev middleware diff --git a/src/runner/generate.ts b/src/runner/generate.ts new file mode 100644 index 0000000..ac3b22c --- /dev/null +++ b/src/runner/generate.ts @@ -0,0 +1,32 @@ +import { generate } from "graphql-code-generator"; +import { Types } from "graphql-codegen-core"; + +export const generateSchemaTypings = async (watch: boolean = false) => { + const documents = ["src/{queries,mutations}/*.{ts,tsx,graphql,gql}"]; + const clientSchema = "src/graphql/client-schema.graphql"; + const remoteSchema = process.env.GRAPHQL; + const schema = [remoteSchema, clientSchema].filter(e => e); + + const config: Types.Config = { + generates: { + "src/graphql/graphql-modules.d.ts": { + documents, + plugins: ["typescript-graphql-files-modules"], + schema, + }, + "src/graphql/graphql-types.tsx": { + documents, + plugins: ["typescript-common", "typescript-client", "typescript-react-apollo"], + schema, + }, + }, + overwrite: true, + watch, + }; + + try { + return await generate(config, true); + } catch (e) { + console.log(e); + } +}; diff --git a/src/webpack/common.ts b/src/webpack/common.ts index c869395..fe63acd 100644 --- a/src/webpack/common.ts +++ b/src/webpack/common.ts @@ -54,6 +54,12 @@ export default (_ssr: boolean /* <-- not currently used */) => { mode: isProduction ? "production" : "development", module: { rules: [ + // GraphQL files + { + exclude: /node_modules/, + loader: "graphql-tag/loader", + test: /\.(graphql|gql)$/, + }, // Typescript { exclude: /node_modules/,