From 0c2f7fb79effdc6147af1ab498b9060709ecbbcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=95=87?= Date: Thu, 9 Mar 2017 17:16:47 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20relay=E5=88=9D?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E7=9A=84=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Appendix04/demo-tea/.babelrc | 13 + Appendix04/demo-tea/.gitignore | 5 + Appendix04/demo-tea/LICENSE | 31 + Appendix04/demo-tea/PATENTS | 33 + Appendix04/demo-tea/README.md | 34 + Appendix04/demo-tea/build/babelRelayPlugin.js | 4 + Appendix04/demo-tea/data/schema.js | 71 ++ Appendix04/demo-tea/data/schema.json | 1038 +++++++++++++++++ Appendix04/demo-tea/js/app.js | 17 + Appendix04/demo-tea/js/components/Tea.js | 28 + Appendix04/demo-tea/js/routes/TeaHomeRoute.js | 15 + Appendix04/demo-tea/js/store/TeaStore.js | 32 + Appendix04/demo-tea/package.json | 36 + Appendix04/demo-tea/public/index.html | 18 + Appendix04/demo-tea/scripts/updateSchema.js | 29 + Appendix04/demo-tea/server.js | 97 ++ 16 files changed, 1501 insertions(+) create mode 100644 Appendix04/demo-tea/.babelrc create mode 100644 Appendix04/demo-tea/.gitignore create mode 100644 Appendix04/demo-tea/LICENSE create mode 100644 Appendix04/demo-tea/PATENTS create mode 100644 Appendix04/demo-tea/README.md create mode 100644 Appendix04/demo-tea/build/babelRelayPlugin.js create mode 100644 Appendix04/demo-tea/data/schema.js create mode 100644 Appendix04/demo-tea/data/schema.json create mode 100644 Appendix04/demo-tea/js/app.js create mode 100644 Appendix04/demo-tea/js/components/Tea.js create mode 100644 Appendix04/demo-tea/js/routes/TeaHomeRoute.js create mode 100644 Appendix04/demo-tea/js/store/TeaStore.js create mode 100644 Appendix04/demo-tea/package.json create mode 100644 Appendix04/demo-tea/public/index.html create mode 100755 Appendix04/demo-tea/scripts/updateSchema.js create mode 100644 Appendix04/demo-tea/server.js diff --git a/Appendix04/demo-tea/.babelrc b/Appendix04/demo-tea/.babelrc new file mode 100644 index 0000000..6a1d5dc --- /dev/null +++ b/Appendix04/demo-tea/.babelrc @@ -0,0 +1,13 @@ +{ + "passPerPreset": true, + "presets": [ + { + "plugins": [ + "./build/babelRelayPlugin" + ] + }, + "react", + "es2015", + "stage-0" + ] +} diff --git a/Appendix04/demo-tea/.gitignore b/Appendix04/demo-tea/.gitignore new file mode 100644 index 0000000..19f8223 --- /dev/null +++ b/Appendix04/demo-tea/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +npm-debug.log +data/schema.graphql +.idea/ diff --git a/Appendix04/demo-tea/LICENSE b/Appendix04/demo-tea/LICENSE new file mode 100644 index 0000000..779606a --- /dev/null +++ b/Appendix04/demo-tea/LICENSE @@ -0,0 +1,31 @@ +BSD License + +For Relay Starter Kit software + +Copyright (c) 2013-2015, Facebook, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Appendix04/demo-tea/PATENTS b/Appendix04/demo-tea/PATENTS new file mode 100644 index 0000000..be39fb3 --- /dev/null +++ b/Appendix04/demo-tea/PATENTS @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the Relay Starter Kit software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook's rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/Appendix04/demo-tea/README.md b/Appendix04/demo-tea/README.md new file mode 100644 index 0000000..1923981 --- /dev/null +++ b/Appendix04/demo-tea/README.md @@ -0,0 +1,34 @@ +# Relay Starter Kit + +This kit includes an app server, a GraphQL server, and a transpiler that you can use to get started building an app with Relay. For a walkthrough, see the [Relay tutorial](https://facebook.github.io/relay/docs/tutorial.html). + +## Installation + +``` +npm install +``` + +## Running + +Start a local server: + +``` +npm start +``` + +## Developing + +Any changes you make to files in the `js/` directory will cause the server to +automatically rebuild the app and refresh your browser. + +If at any time you make changes to `data/schema.js`, stop the server, +regenerate `data/schema.json`, and restart the server: + +``` +npm run update-schema +npm start +``` + +## License + +Relay Starter Kit is [BSD licensed](./LICENSE). We also provide an additional [patent grant](./PATENTS). diff --git a/Appendix04/demo-tea/build/babelRelayPlugin.js b/Appendix04/demo-tea/build/babelRelayPlugin.js new file mode 100644 index 0000000..4457160 --- /dev/null +++ b/Appendix04/demo-tea/build/babelRelayPlugin.js @@ -0,0 +1,4 @@ +var getbabelRelayPlugin = require('babel-relay-plugin'); +var schema = require('../data/schema.json'); + +module.exports = getbabelRelayPlugin(schema.data); diff --git a/Appendix04/demo-tea/data/schema.js b/Appendix04/demo-tea/data/schema.js new file mode 100644 index 0000000..092e7aa --- /dev/null +++ b/Appendix04/demo-tea/data/schema.js @@ -0,0 +1,71 @@ +// 引入函式库 +import { + GraphQLInt, + GraphQLList, + GraphQLObjectType, + GraphQLSchema, + GraphQLString, +} from 'graphql'; + +import { + connectionArgs, + connectionDefinitions, + connectionFromArray, + fromGlobalId, + globalIdField, + mutationWithClientMutationId, + nodeDefinitions, +} from 'graphql-relay'; + +// client side 暂存 store,GraphQL Server reponse 会更新 store,再透过 props 传递给 Component +const STORE = { + teas: [ + { name: 'Bancha', steepingTime: 2 }, + { name: 'Sencha Makoto', steepingTime: 2 }, + { name: 'Milk Oolong', steepingTime: 3 }, + { name: 'Jasmine Phoenix Pearls', steepingTime: 3 }, + { name: 'Gunpowder Golden Temple', steepingTime: 3 }, + { name: 'Pu Erh First Grade', steepingTime: 4 }, + { name: 'Kenya Milima', steepingTime: 5 }, + { name: 'Assam Hatimara', steepingTime: 5 }, + { name: 'Golden Tip Yunnan', steepingTime: 5 }, + { name: 'Earl Grey Blue Star', steepingTime: 5 }, + { name: 'Ceylon New Vithanakande', steepingTime: 5 }, + ], +}; + +// 设计 GraphQL Type +var teaType = new GraphQLObjectType({ + name: 'Tea', + fields: () => ({ + name: { type: GraphQLString }, + steepingTime: { type: GraphQLInt }, + }), +}); + +// 将 Tea 整合进来 +var storeType = new GraphQLObjectType({ + name: 'Store', + fields: () => ({ + teas: { type: new GraphQLList(teaType) }, + }), +}); + +var queryType = new GraphQLObjectType({ + name: 'Query', + fields: () => ({ + store: { + type: storeType, + resolve: () => STORE, + }, + }) +}) + +var { connectionType : teaConnection } = connectionDefinitions({ + name: 'Tea', nodeType: teaType +}) + +// 输出 GraphQL Schema +export var Schema = new GraphQLSchema({ + query: queryType +}); diff --git a/Appendix04/demo-tea/data/schema.json b/Appendix04/demo-tea/data/schema.json new file mode 100644 index 0000000..722fc1f --- /dev/null +++ b/Appendix04/demo-tea/data/schema.json @@ -0,0 +1,1038 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "store", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "Store", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Store", + "description": null, + "fields": [ + { + "name": "teas", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Tea", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Tea", + "description": null, + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "steepingTime", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": [ + "FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/Appendix04/demo-tea/js/app.js b/Appendix04/demo-tea/js/app.js new file mode 100644 index 0000000..70f31e1 --- /dev/null +++ b/Appendix04/demo-tea/js/app.js @@ -0,0 +1,17 @@ +import 'babel-polyfill'; + +import TeaStore from './store/TeaStore' +import TeaHomeRoute from './routes/TeaHomeRoute' + +import React from 'react'; +import ReactDOM from 'react-dom'; +import Relay from 'react-relay'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/Appendix04/demo-tea/js/components/Tea.js b/Appendix04/demo-tea/js/components/Tea.js new file mode 100644 index 0000000..0ff80bc --- /dev/null +++ b/Appendix04/demo-tea/js/components/Tea.js @@ -0,0 +1,28 @@ +/** + * Created by simon on 2017/3/8. + */ + +import React from 'react'; +import Relay from 'react-relay'; + +class Tea extends React.Component { + render() { + const tea = this.props.tea; + return ( +
  • + {tea.name} ({tea.steepingTime}) +
  • + ); + } +} + +export default Relay.createContainer(Tea, { + fragments: { + tea: () => Relay.QL` + fragment on Tea { + name + steepingTime + } + ` + } +}) diff --git a/Appendix04/demo-tea/js/routes/TeaHomeRoute.js b/Appendix04/demo-tea/js/routes/TeaHomeRoute.js new file mode 100644 index 0000000..7151bc0 --- /dev/null +++ b/Appendix04/demo-tea/js/routes/TeaHomeRoute.js @@ -0,0 +1,15 @@ +/** + * Created by simon on 2017/3/8. + */ +import Relay from 'react-relay'; + +export default class TeaHomeRoute extends Relay.Route { + static queries = { + store: () => Relay.QL` + query{ + store + } + ` + } + static routeName = 'TeaHomeRoute'; +} diff --git a/Appendix04/demo-tea/js/store/TeaStore.js b/Appendix04/demo-tea/js/store/TeaStore.js new file mode 100644 index 0000000..21f3f9e --- /dev/null +++ b/Appendix04/demo-tea/js/store/TeaStore.js @@ -0,0 +1,32 @@ +/** + * Created by simon on 2017/3/8. + */ + +import React from 'react'; +import Relay from 'react-relay'; +import Tea from '../components/Tea' + +class TeaStore extends React.Component { + render() { + const store = this.props.store + console.info(store) + const teas = store.teas + return ( + + ) + } +} + +export default Relay.createContainer(TeaStore, { + fragments: { + store: () => Relay.QL` + fragment on Store{ + teas { + ${ Tea.getFragment('tea') } + } + } + ` + } +}) diff --git a/Appendix04/demo-tea/package.json b/Appendix04/demo-tea/package.json new file mode 100644 index 0000000..52b7d5a --- /dev/null +++ b/Appendix04/demo-tea/package.json @@ -0,0 +1,36 @@ +{ + "name": "relay-starter-kit", + "private": true, + "description": "A quick way to get up and running with Relay", + "repository": "facebook/relay-starter-kit", + "version": "0.1.0", + "scripts": { + "start": "babel-node ./server.js", + "update-schema": "babel-node ./scripts/updateSchema.js" + }, + "devDependencies": { + "babel-cli": "^6.14.0", + "babel-core": "^6.23.1", + "babel-loader": "^6.4.0", + "babel-polyfill": "^6.13.0", + "babel-preset-es2015": "^6.22.0", + "babel-preset-react": "^6.11.1", + "babel-preset-stage-0": "^6.5.0", + "babel-relay-plugin": "^0.10.0", + "chokidar": "1.6.0", + "classnames": "2.2.5", + "cookie": "^0.3.1", + "express": "4.14.0", + "express-graphql": "0.5.4", + "graphql": "0.7.0", + "graphql-relay": "0.4.3", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "react-relay": "^0.10.0", + "react-router": "^3.0.2", + "react-router-relay": "^0.13.5", + "require-clean": "0.1.3", + "webpack": "^2.2.1", + "webpack-dev-server": "^2.4.1" + } +} diff --git a/Appendix04/demo-tea/public/index.html b/Appendix04/demo-tea/public/index.html new file mode 100644 index 0000000..a275fc4 --- /dev/null +++ b/Appendix04/demo-tea/public/index.html @@ -0,0 +1,18 @@ + + + + + + Relay • Starter Kit + + +
    + + + + + diff --git a/Appendix04/demo-tea/scripts/updateSchema.js b/Appendix04/demo-tea/scripts/updateSchema.js new file mode 100755 index 0000000..3c243c7 --- /dev/null +++ b/Appendix04/demo-tea/scripts/updateSchema.js @@ -0,0 +1,29 @@ +#!/usr/bin/env babel-node --optional es7.asyncFunctions + +import fs from 'fs'; +import path from 'path'; +import { Schema } from '../data/schema'; +import { graphql } from 'graphql'; +import { introspectionQuery, printSchema } from 'graphql/utilities'; + +// Save JSON of full schema introspection for Babel Relay Plugin to use +(async() => { + var result = await (graphql(Schema, introspectionQuery)); + if (result.errors) { + console.error( + 'ERROR introspecting schema: ', + JSON.stringify(result.errors, null, 2) + ); + } else { + fs.writeFileSync( + path.join(__dirname, '../data/schema.json'), + JSON.stringify(result, null, 2) + ); + } +})(); + +// Save user readable type system shorthand of schema +fs.writeFileSync( + path.join(__dirname, '../data/schema.graphql'), + printSchema(Schema) +); diff --git a/Appendix04/demo-tea/server.js b/Appendix04/demo-tea/server.js new file mode 100644 index 0000000..eac5e09 --- /dev/null +++ b/Appendix04/demo-tea/server.js @@ -0,0 +1,97 @@ +import chokidar from 'chokidar'; +import express from 'express'; +import graphQLHTTP from 'express-graphql'; +import path from 'path'; +import webpack from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; +import {clean} from 'require-clean'; +import {exec} from 'child_process'; + +const APP_PORT = 3000; +const GRAPHQL_PORT = 8080; + +let graphQLServer; +let appServer; + +function startAppServer(callback) { + // Serve the Relay app + const compiler = webpack({ + entry: path.resolve(__dirname, 'js', 'app.js'), + module: { + loaders: [ + { + exclude: /node_modules/, + loader: 'babel-loader', + test: /\.js$/, + } + ] + }, + output: {filename: '/app.js', path: '/', publicPath: '/js/'} + }); + appServer = new WebpackDevServer(compiler, { + contentBase: '/public/', + proxy: {'/graphql': `http://localhost:${GRAPHQL_PORT}`}, + publicPath: '/js/', + stats: {colors: true} + }); + // Serve static resources + appServer.use('/', express.static(path.resolve(__dirname, 'public'))); + appServer.listen(APP_PORT, () => { + console.log(`App is now running on http://localhost:${APP_PORT}`); + if (callback) { + callback(); + } + }); +} + +function startGraphQLServer(callback) { + // Expose a GraphQL endpoint + clean('./data/schema'); + const {Schema} = require('./data/schema'); + const graphQLApp = express(); + graphQLApp.use('/', graphQLHTTP({ + graphiql: true, + pretty: true, + schema: Schema, + })); + graphQLServer = graphQLApp.listen(GRAPHQL_PORT, () => { + console.log( + `GraphQL server is now running on http://localhost:${GRAPHQL_PORT}` + ); + if (callback) { + callback(); + } + }); +} + +function startServers(callback) { + // Shut down the servers + if (appServer) { + appServer.listeningApp.close(); + } + if (graphQLServer) { + graphQLServer.close(); + } + + // Compile the schema + exec('npm run update-schema', (error, stdout) => { + console.log(stdout); + let doneTasks = 0; + function handleTaskDone() { + doneTasks++; + if (doneTasks === 2 && callback) { + callback(); + } + } + startGraphQLServer(handleTaskDone); + startAppServer(handleTaskDone); + }); +} +const watcher = chokidar.watch('./data/{database,schema}.js'); +watcher.on('change', path => { + console.log(`\`${path}\` changed. Restarting.`); + startServers(() => + console.log('Restart your browser to use the updated schema.') + ); +}); +startServers(); From 59de4a314813a6424eb12ac9897825ca2220243b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=95=87?= Date: Fri, 10 Mar 2017 23:25:31 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20Relay=E9=A1=B9?= =?UTF-8?q?=E7=9B=AEDemo=EF=BC=8CConnection=E7=9A=84=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../demo1-\345\237\272\346\234\254/LICENSE" | 0 .../demo1-\345\237\272\346\234\254/PATENTS" | 0 .../demo1-\345\237\272\346\234\254/README.md" | 0 .../build/babelRelayPlugin.js" | 0 .../data/schema.graphql" | 12 + .../data/schema.js" | 0 .../data/schema.json" | 0 .../demo1-\345\237\272\346\234\254/js/app.js" | 0 .../js/components/Tea.js" | 0 .../js/routes/TeaHomeRoute.js" | 0 .../js/store/TeaStore.js" | 0 .../package.json" | 0 .../public/index.html" | 0 .../scripts/updateSchema.js" | 0 .../demo1-\345\237\272\346\234\254/server.js" | 0 .../LICENSE" | 31 + .../PATENTS" | 33 + .../README.md" | 34 + .../build/babelRelayPlugin.js" | 4 + .../data/database.js" | 141 ++ .../data/schema.graphql" | 65 + .../data/schema.js" | 177 +++ .../data/schema.json" | 1354 +++++++++++++++++ .../js/app.js" | 17 + .../js/components/Tea.js" | 29 + .../js/routes/TeaHomeRoute.js" | 15 + .../js/store/TeaContry.js" | 36 + .../package.json" | 36 + .../public/index.html" | 18 + .../scripts/updateSchema.js" | 29 + .../server.js" | 97 ++ Appendix04/demo-tea/.babelrc | 13 - Appendix04/demo-tea/.gitignore | 5 - 33 files changed, 2128 insertions(+), 18 deletions(-) rename Appendix04/demo-tea/LICENSE => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/LICENSE" (100%) rename Appendix04/demo-tea/PATENTS => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/PATENTS" (100%) rename Appendix04/demo-tea/README.md => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/README.md" (100%) rename Appendix04/demo-tea/build/babelRelayPlugin.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/build/babelRelayPlugin.js" (100%) create mode 100644 "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.graphql" rename Appendix04/demo-tea/data/schema.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.js" (100%) rename Appendix04/demo-tea/data/schema.json => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.json" (100%) rename Appendix04/demo-tea/js/app.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/app.js" (100%) rename Appendix04/demo-tea/js/components/Tea.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/components/Tea.js" (100%) rename Appendix04/demo-tea/js/routes/TeaHomeRoute.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/routes/TeaHomeRoute.js" (100%) rename Appendix04/demo-tea/js/store/TeaStore.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/store/TeaStore.js" (100%) rename Appendix04/demo-tea/package.json => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/package.json" (100%) rename Appendix04/demo-tea/public/index.html => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/public/index.html" (100%) rename Appendix04/demo-tea/scripts/updateSchema.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/scripts/updateSchema.js" (100%) rename Appendix04/demo-tea/server.js => "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/server.js" (100%) create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/LICENSE" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/PATENTS" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/README.md" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/build/babelRelayPlugin.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/database.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.graphql" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.json" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/app.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/components/Tea.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/routes/TeaHomeRoute.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/store/TeaContry.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/package.json" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/public/index.html" create mode 100755 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/scripts/updateSchema.js" create mode 100644 "Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/server.js" delete mode 100644 Appendix04/demo-tea/.babelrc delete mode 100644 Appendix04/demo-tea/.gitignore diff --git a/Appendix04/demo-tea/LICENSE "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/LICENSE" similarity index 100% rename from Appendix04/demo-tea/LICENSE rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/LICENSE" diff --git a/Appendix04/demo-tea/PATENTS "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/PATENTS" similarity index 100% rename from Appendix04/demo-tea/PATENTS rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/PATENTS" diff --git a/Appendix04/demo-tea/README.md "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/README.md" similarity index 100% rename from Appendix04/demo-tea/README.md rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/README.md" diff --git a/Appendix04/demo-tea/build/babelRelayPlugin.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/build/babelRelayPlugin.js" similarity index 100% rename from Appendix04/demo-tea/build/babelRelayPlugin.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/build/babelRelayPlugin.js" diff --git "a/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.graphql" "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.graphql" new file mode 100644 index 0000000..8db046d --- /dev/null +++ "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.graphql" @@ -0,0 +1,12 @@ +type Query { + store: Store +} + +type Store { + teas: [Tea] +} + +type Tea { + name: String + steepingTime: Int +} diff --git a/Appendix04/demo-tea/data/schema.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.js" similarity index 100% rename from Appendix04/demo-tea/data/schema.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.js" diff --git a/Appendix04/demo-tea/data/schema.json "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.json" similarity index 100% rename from Appendix04/demo-tea/data/schema.json rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/data/schema.json" diff --git a/Appendix04/demo-tea/js/app.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/app.js" similarity index 100% rename from Appendix04/demo-tea/js/app.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/app.js" diff --git a/Appendix04/demo-tea/js/components/Tea.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/components/Tea.js" similarity index 100% rename from Appendix04/demo-tea/js/components/Tea.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/components/Tea.js" diff --git a/Appendix04/demo-tea/js/routes/TeaHomeRoute.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/routes/TeaHomeRoute.js" similarity index 100% rename from Appendix04/demo-tea/js/routes/TeaHomeRoute.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/routes/TeaHomeRoute.js" diff --git a/Appendix04/demo-tea/js/store/TeaStore.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/store/TeaStore.js" similarity index 100% rename from Appendix04/demo-tea/js/store/TeaStore.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/js/store/TeaStore.js" diff --git a/Appendix04/demo-tea/package.json "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/package.json" similarity index 100% rename from Appendix04/demo-tea/package.json rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/package.json" diff --git a/Appendix04/demo-tea/public/index.html "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/public/index.html" similarity index 100% rename from Appendix04/demo-tea/public/index.html rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/public/index.html" diff --git a/Appendix04/demo-tea/scripts/updateSchema.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/scripts/updateSchema.js" similarity index 100% rename from Appendix04/demo-tea/scripts/updateSchema.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/scripts/updateSchema.js" diff --git a/Appendix04/demo-tea/server.js "b/Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/server.js" similarity index 100% rename from Appendix04/demo-tea/server.js rename to "Appendix04/Relay-Example/demo1-\345\237\272\346\234\254/server.js" diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/LICENSE" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/LICENSE" new file mode 100644 index 0000000..779606a --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/LICENSE" @@ -0,0 +1,31 @@ +BSD License + +For Relay Starter Kit software + +Copyright (c) 2013-2015, Facebook, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/PATENTS" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/PATENTS" new file mode 100644 index 0000000..be39fb3 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/PATENTS" @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the Relay Starter Kit software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook's rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/README.md" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/README.md" new file mode 100644 index 0000000..1923981 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/README.md" @@ -0,0 +1,34 @@ +# Relay Starter Kit + +This kit includes an app server, a GraphQL server, and a transpiler that you can use to get started building an app with Relay. For a walkthrough, see the [Relay tutorial](https://facebook.github.io/relay/docs/tutorial.html). + +## Installation + +``` +npm install +``` + +## Running + +Start a local server: + +``` +npm start +``` + +## Developing + +Any changes you make to files in the `js/` directory will cause the server to +automatically rebuild the app and refresh your browser. + +If at any time you make changes to `data/schema.js`, stop the server, +regenerate `data/schema.json`, and restart the server: + +``` +npm run update-schema +npm start +``` + +## License + +Relay Starter Kit is [BSD licensed](./LICENSE). We also provide an additional [patent grant](./PATENTS). diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/build/babelRelayPlugin.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/build/babelRelayPlugin.js" new file mode 100644 index 0000000..4457160 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/build/babelRelayPlugin.js" @@ -0,0 +1,4 @@ +var getbabelRelayPlugin = require('babel-relay-plugin'); +var schema = require('../data/schema.json'); + +module.exports = getbabelRelayPlugin(schema.data); diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/database.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/database.js" new file mode 100644 index 0000000..c96788c --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/database.js" @@ -0,0 +1,141 @@ +/** + * Created by simon on 2017/3/10. + */ + +// client side 暂存 store,GraphQL Server reponse 会更新 store,再透过 props 传递给 Component +const STORE = {}; + +// 中国十大名茶 +// 碧螺春、 +// 西湖龙井、 +// 安徽毛峰、 +// 安徽瓜片、 +// 恩施玉露、 +// 福建铁观音、 +// 福建银针、 +// 云南普洱茶、 +// 福建云茶、 +// 江西云雾茶 + +// 日本三大名茶 [PS: 光听名字就不好喝] +// 宇治茶(京都府)、 +// 狭山茶(崎玉县)、 +// 静冈茶(静冈县) +const teas = [ + { name: '碧螺春', id: 1 }, + { name: '西湖龙井', id: 2 }, + { name: '安徽毛峰', id: 3 }, + { name: '安徽瓜片', id: 4 }, + { name: '恩施玉露', id: 5 }, + { name: '福建铁观音', id: 6 }, + { name: '福建银针', id: 7 }, + { name: '云南普洱茶', id: 8 }, + { name: '福建云茶', id: 9 }, + { name: '江西云雾茶', id: 10 }, + { name: '宇治茶', id: 11 }, + { name: '狭山茶', id: 12 }, + { name: '静冈茶', id: 13 }, +] + +const chineseTea = { + id: '1', + name: 'China', + teaIds: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +} + +const japaneseTea = { + id: '10086', + name: 'japan', + teaIds: [11, 12, 13] +} + +const data = { + contries: [ + chineseTea, + japaneseTea + ], + + teas: teas +} + +/** + * 获取所有国家 + * @returns {Array} + */ +export function getContries(names) { + return names.map(name => { + if (name === 'China') { + return data.contries[0]; + } + if (name === 'japan') { + return data.contries[1]; + } + return null; + }); +} + +export function getContry(id) { + return data.contries.filter(contry => contry.id === id)[0] +} + +export function getContryByName(name) { + console.info(name) + return data.contries.filter(contry => contry.name === name)[0] +} + +/** + * 获取某一个国家的所有茶叶品种 + * @param contryName + * @returns {Array} + */ +export function getTeas(contryName) { + const index = data.contries.find(c => c.name === contryName) + return data.contries[index].teaIds.map(id => teas[id]) +} + +export function getTea(teaId) { + return data.teas.filter(tea => tea.id === teaId)[0] +} + +export function createTea(name, contryName) { + const newTea = { name: name, id: teas.length } + data.teaIds.push(newTea) + + const index = data.contries.find(c => c.name === contryName) + data.contries[index].teaIds.push(newTea.id) +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.graphql" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.graphql" new file mode 100644 index 0000000..553e639 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.graphql" @@ -0,0 +1,65 @@ +# 国家 +type Contry implements Node { + # The ID of an object + id: ID! + + # 国家名称 + name: String + + # 属于这个国家的著名茶叶 + teas(after: String, first: Int, before: String, last: Int): TeaConnection +} + +# An object with an ID +interface Node { + # The id of the object. + id: ID! +} + +# Information about pagination in a connection. +type PageInfo { + # When paginating forwards, are there more items? + hasNextPage: Boolean! + + # When paginating backwards, are there more items? + hasPreviousPage: Boolean! + + # When paginating backwards, the cursor to continue. + startCursor: String + + # When paginating forwards, the cursor to continue. + endCursor: String +} + +type Query { + # Fetches an object given its ID + node( + # The ID of an object + id: ID! + ): Node + contry(name: String): Contry +} + +type Tea implements Node { + # The ID of an object + id: ID! + name: String +} + +# A connection to a list of items. +type TeaConnection { + # Information to aid in pagination. + pageInfo: PageInfo! + + # A list of edges. + edges: [TeaEdge] +} + +# An edge in a connection. +type TeaEdge { + # The item at the end of the edge + node: Tea + + # A cursor for use in pagination + cursor: String! +} diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.js" new file mode 100644 index 0000000..97f53b7 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.js" @@ -0,0 +1,177 @@ +// 引入函式库 +import { + GraphQLInt, + GraphQLList, + GraphQLObjectType, + GraphQLSchema, + GraphQLString, +} from 'graphql'; + +import { + connectionArgs, + connectionDefinitions, + connectionFromArray, + fromGlobalId, + globalIdField, + mutationWithClientMutationId, + nodeDefinitions, +} from 'graphql-relay'; + + +import { + createTea, + getContries, + getContry, + getContryByName, + getTeas, + getTea, +} from './database' + + +// ================================ ================================ +// == node definition +// ================================ ================================ + +const { nodeInterface, nodeField } = nodeDefinitions((globalId) => { + const { type, id } = fromGlobalId(globalId); + if (type === 'Contry') { + const contry = getContry(id) + console.info(`schema.js getContry = ${contry}`) + return contry; + } else if (type === 'Tea') { + const tea = getTea(id) + console.info(`schema.js getTea = ${tea}`) + return tea; + } else { + return null; + } + }, (obj) => { + return obj.teas ? ContryType : TeaType; + } +); + + +// ================================ ================================ +// == Type +// ================================ ================================ + + +var ContryType = new GraphQLObjectType({ + name: 'Contry', + description: '国家', + fields: () => ({ + id: globalIdField('Contry'), + name: { + type: GraphQLString, + description: '国家名称' + }, + teas: { + description: '属于这个国家的著名茶叶', + args: connectionArgs, + type: teaConnection, + resolve: (contry, args) => connectionFromArray(contry.teaIds.map(id => getTea(id)), args) + }, + }), + interfaces: [nodeInterface], +}); + +var TeaType = new GraphQLObjectType({ + name: 'Tea', + fields: () => ({ + id: globalIdField('Tea'), + name: { type: GraphQLString }, + }), + interfaces: [nodeInterface], +}); + + +// ================================ ================================ +// == Mutation +// ================================ ================================ + + +// ================================ ================================ +// == Connection +// ================================ ================================ + +// var { +// connectionType : teaConnection +// } = connectionDefinitions({ name: 'Tea', nodeType: TeaType }); + + +/** + * We define a connection between a faction and its ships. + * + * connectionType implements the following type system shorthand: + * type ShipConnection { + * edges: [ShipEdge] + * pageInfo: PageInfo! + * } + * + * connectionType has an edges field - a list of edgeTypes that implement the + * following type system shorthand: + * type ShipEdge { + * cursor: String! + * node: Ship + * } + */ +const { + connectionType: teaConnection, + edgeType: teaEdge, +} = connectionDefinitions({ name: 'Tea', nodeType: TeaType }); + + +// ================================ ================================ +// == Query +// ================================ ================================ + +var QueryType = new GraphQLObjectType({ + name: 'Query', + fields: () => ({ + node: nodeField, + contry: { + type: ContryType, + args: { + name: { + type: GraphQLString + } + }, + resolve: (root, { name }) => getContryByName(name) + }, + }), +}) + + +// 输出 GraphQL Schema +export var Schema = new GraphQLSchema({ + query: QueryType +}); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.json" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.json" new file mode 100644 index 0000000..c01e445 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/data/schema.json" @@ -0,0 +1,1354 @@ +{ + "data": { + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": null, + "fields": [ + { + "name": "node", + "description": "Fetches an object given its ID", + "args": [ + { + "name": "id", + "description": "The ID of an object", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "contry", + "description": null, + "args": [ + { + "name": "name", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "Contry", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INTERFACE", + "name": "Node", + "description": "An object with an ID", + "fields": [ + { + "name": "id", + "description": "The id of the object.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "Contry", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "Tea", + "ofType": null + } + ] + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Contry", + "description": "国家", + "fields": [ + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "国家名称", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "teas", + "description": "属于这个国家的著名茶叶", + "args": [ + { + "name": "after", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "TeaConnection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Int", + "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TeaConnection", + "description": "A connection to a list of items.", + "fields": [ + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "TeaEdge", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PageInfo", + "description": "Information about pagination in a connection.", + "fields": [ + { + "name": "hasNextPage", + "description": "When paginating forwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hasPreviousPage", + "description": "When paginating backwards, are there more items?", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "startCursor", + "description": "When paginating backwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "endCursor", + "description": "When paginating forwards, the cursor to continue.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "TeaEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "node", + "description": "The item at the end of the edge", + "args": [], + "type": { + "kind": "OBJECT", + "name": "Tea", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "cursor", + "description": "A cursor for use in pagination", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Tea", + "description": null, + "fields": [ + { + "name": "id", + "description": "The ID of an object", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Directive", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Field", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__EnumValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "__InputValue", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + } + ], + "directives": [ + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": [ + "FIELD", + "FRAGMENT_SPREAD", + "INLINE_FRAGMENT" + ], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": [ + "FIELD_DEFINITION", + "ENUM_VALUE" + ], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/app.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/app.js" new file mode 100644 index 0000000..da8bf04 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/app.js" @@ -0,0 +1,17 @@ +import 'babel-polyfill'; + +import TeaContry from './store/TeaContry' +import TeaHomeRoute from './routes/TeaHomeRoute' + +import React from 'react'; +import ReactDOM from 'react-dom'; +import Relay from 'react-relay'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/components/Tea.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/components/Tea.js" new file mode 100644 index 0000000..a764a43 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/components/Tea.js" @@ -0,0 +1,29 @@ +/** + * Created by simon on 2017/3/8. + */ + +import React from 'react'; +import Relay from 'react-relay'; + +class Tea extends React.Component { + render() { + const tea = this.props.tea; + console.info(tea) + return ( +
  • + {tea.name} + ({tea.id}) +
  • + ); + } +} + +export default Relay.createContainer(Tea, { + fragments: { + tea: () => Relay.QL` + fragment on Tea { + name + } + ` + } +}) diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/routes/TeaHomeRoute.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/routes/TeaHomeRoute.js" new file mode 100644 index 0000000..7405c0c --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/routes/TeaHomeRoute.js" @@ -0,0 +1,15 @@ +/** + * Created by simon on 2017/3/8. + */ +import Relay from 'react-relay'; + +export default class TeaHomeRoute extends Relay.Route { + static queries = { + contry: () => Relay.QL` + query{ + contry(name: $contryName) + } + ` + } + static routeName = 'TeaHomeRoute'; +} diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/store/TeaContry.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/store/TeaContry.js" new file mode 100644 index 0000000..46bbb18 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/js/store/TeaContry.js" @@ -0,0 +1,36 @@ +/** + * Created by simon on 2017/3/8. + */ + +import React from 'react'; +import Relay from 'react-relay'; +import Tea from '../components/Tea' + +class TeaContry extends React.Component { + render() { + const contry = this.props.contry + return ( +
      + {contry.teas.edges.map(node => )} +
    + ) + } +} + +export default Relay.createContainer(TeaContry, { + fragments: { + contry: () => Relay.QL` + fragment on Contry{ + id + name + teas(first: 10){ + edges{ + node{ + ${Tea.getFragment('tea')} + } + } + } + } + ` + } +}) diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/package.json" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/package.json" new file mode 100644 index 0000000..a199e52 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/package.json" @@ -0,0 +1,36 @@ +{ + "name": "Tea", + "private": true, + "description": "A quick way to get up and running with Relay", + "repository": "facebook/relay-starter-kit", + "version": "0.1.0", + "scripts": { + "start": "babel-node ./server.js", + "update-schema": "babel-node ./scripts/updateSchema.js" + }, + "devDependencies": { + "babel-cli": "^6.14.0", + "babel-core": "^6.23.1", + "babel-loader": "^6.4.0", + "babel-polyfill": "^6.13.0", + "babel-preset-es2015": "^6.22.0", + "babel-preset-react": "^6.11.1", + "babel-preset-stage-0": "^6.5.0", + "babel-relay-plugin": "^0.10.0", + "chokidar": "1.6.0", + "classnames": "2.2.5", + "cookie": "^0.3.1", + "express": "4.14.0", + "express-graphql": "0.5.4", + "graphql": "0.7.0", + "graphql-relay": "0.4.3", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "react-relay": "^0.10.0", + "react-router": "^3.0.2", + "react-router-relay": "^0.13.5", + "require-clean": "0.1.3", + "webpack": "^2.2.1", + "webpack-dev-server": "^2.4.1" + } +} diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/public/index.html" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/public/index.html" new file mode 100644 index 0000000..a275fc4 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/public/index.html" @@ -0,0 +1,18 @@ + + + + + + Relay • Starter Kit + + +
    + + + + + diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/scripts/updateSchema.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/scripts/updateSchema.js" new file mode 100755 index 0000000..3c243c7 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/scripts/updateSchema.js" @@ -0,0 +1,29 @@ +#!/usr/bin/env babel-node --optional es7.asyncFunctions + +import fs from 'fs'; +import path from 'path'; +import { Schema } from '../data/schema'; +import { graphql } from 'graphql'; +import { introspectionQuery, printSchema } from 'graphql/utilities'; + +// Save JSON of full schema introspection for Babel Relay Plugin to use +(async() => { + var result = await (graphql(Schema, introspectionQuery)); + if (result.errors) { + console.error( + 'ERROR introspecting schema: ', + JSON.stringify(result.errors, null, 2) + ); + } else { + fs.writeFileSync( + path.join(__dirname, '../data/schema.json'), + JSON.stringify(result, null, 2) + ); + } +})(); + +// Save user readable type system shorthand of schema +fs.writeFileSync( + path.join(__dirname, '../data/schema.graphql'), + printSchema(Schema) +); diff --git "a/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/server.js" "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/server.js" new file mode 100644 index 0000000..eac5e09 --- /dev/null +++ "b/Appendix04/Relay-Example/demo2-\344\275\277\347\224\250Connection/server.js" @@ -0,0 +1,97 @@ +import chokidar from 'chokidar'; +import express from 'express'; +import graphQLHTTP from 'express-graphql'; +import path from 'path'; +import webpack from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; +import {clean} from 'require-clean'; +import {exec} from 'child_process'; + +const APP_PORT = 3000; +const GRAPHQL_PORT = 8080; + +let graphQLServer; +let appServer; + +function startAppServer(callback) { + // Serve the Relay app + const compiler = webpack({ + entry: path.resolve(__dirname, 'js', 'app.js'), + module: { + loaders: [ + { + exclude: /node_modules/, + loader: 'babel-loader', + test: /\.js$/, + } + ] + }, + output: {filename: '/app.js', path: '/', publicPath: '/js/'} + }); + appServer = new WebpackDevServer(compiler, { + contentBase: '/public/', + proxy: {'/graphql': `http://localhost:${GRAPHQL_PORT}`}, + publicPath: '/js/', + stats: {colors: true} + }); + // Serve static resources + appServer.use('/', express.static(path.resolve(__dirname, 'public'))); + appServer.listen(APP_PORT, () => { + console.log(`App is now running on http://localhost:${APP_PORT}`); + if (callback) { + callback(); + } + }); +} + +function startGraphQLServer(callback) { + // Expose a GraphQL endpoint + clean('./data/schema'); + const {Schema} = require('./data/schema'); + const graphQLApp = express(); + graphQLApp.use('/', graphQLHTTP({ + graphiql: true, + pretty: true, + schema: Schema, + })); + graphQLServer = graphQLApp.listen(GRAPHQL_PORT, () => { + console.log( + `GraphQL server is now running on http://localhost:${GRAPHQL_PORT}` + ); + if (callback) { + callback(); + } + }); +} + +function startServers(callback) { + // Shut down the servers + if (appServer) { + appServer.listeningApp.close(); + } + if (graphQLServer) { + graphQLServer.close(); + } + + // Compile the schema + exec('npm run update-schema', (error, stdout) => { + console.log(stdout); + let doneTasks = 0; + function handleTaskDone() { + doneTasks++; + if (doneTasks === 2 && callback) { + callback(); + } + } + startGraphQLServer(handleTaskDone); + startAppServer(handleTaskDone); + }); +} +const watcher = chokidar.watch('./data/{database,schema}.js'); +watcher.on('change', path => { + console.log(`\`${path}\` changed. Restarting.`); + startServers(() => + console.log('Restart your browser to use the updated schema.') + ); +}); +startServers(); diff --git a/Appendix04/demo-tea/.babelrc b/Appendix04/demo-tea/.babelrc deleted file mode 100644 index 6a1d5dc..0000000 --- a/Appendix04/demo-tea/.babelrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "passPerPreset": true, - "presets": [ - { - "plugins": [ - "./build/babelRelayPlugin" - ] - }, - "react", - "es2015", - "stage-0" - ] -} diff --git a/Appendix04/demo-tea/.gitignore b/Appendix04/demo-tea/.gitignore deleted file mode 100644 index 19f8223..0000000 --- a/Appendix04/demo-tea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.DS_Store -node_modules -npm-debug.log -data/schema.graphql -.idea/ From 7d2e5c140b00af792595a2964da82475dc51d81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=95=87?= Date: Sat, 11 Mar 2017 00:00:24 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4a688ff..0fe0602 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -bower_components/ \ No newline at end of file +bower_components/ +./idea