Skip to content

Commit f2226a7

Browse files
committed
2.5.0
2.5.0 - 2017-08-21 ----------------------------------------------- ## Redux * **Breaking change** - reducers should no longer be registered as `{ state, reducer}`. Instead, the format is: ```js // root key name, reducer function, and initial state (as a plain object) config.addReducer('keyName', reducerFunction, { count: 0 }); ``` * Initial state should now be a plain object; it will be wrapped in a call to `seamless-immutable` automatically, providing immutability by default * Refactors `kit/lib/redux.js -> unwind()` to wrap custom reducers in a `defaultReducer` function, that detects a Redux undefined sentinel `state` and returns a plain object -- otherwise, calls the 'real' reducer. (The side-effect to this is that reducer no longer need to handle undefined values!) ## GraphQL * Adds optional middleware / afterware to Apollo client instantiation via: - `config.addApolloMiddleware()` - `config.addApolloAfterware()` Both functions can be called isomorphically; the middleware will be attached only to the environment in which it's called, so wrap in an `if (SERVER)...` block if you need to isolate this behaviour. Note: If you're using a built-in GraphQL server, ReactQL will use [apollo-local-query](https://github.com/af/apollo-local-query) instead of regular HTTP requests so middleware won't be executed -- be aware of this if you're doing HTTP header authentication! ## Config * Refactors the `Config` class to progressively add server-side config functions by moving the class into sub-classes for the browser/server
1 parent 1221711 commit f2226a7

8 files changed

Lines changed: 213 additions & 144 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
2.5.0 - 2017-08-21
2+
-----------------------------------------------
3+
4+
## Redux
5+
* **Breaking change** - reducers should no longer be registered as `{ state, reducer}`. Instead, the format is:
6+
7+
```js
8+
// root key name, reducer function, and initial state (as a plain object)
9+
config.addReducer('keyName', reducerFunction, { count: 0 });
10+
```
11+
12+
* Initial state should now be a plain object; it will be wrapped in a call to `seamless-immutable` automatically, providing immutability by default
13+
14+
* Refactors `kit/lib/redux.js -> unwind()` to wrap custom reducers in a `defaultReducer` function, that detects a Redux undefined sentinel `state` and returns a plain object -- otherwise, calls the 'real' reducer. (The side-effect to this is that reducer no longer need to handle undefined values!)
15+
16+
## GraphQL
17+
* Adds optional middleware / afterware to Apollo client instantiation via:
18+
- `config.addApolloMiddleware()`
19+
- `config.addApolloAfterware()`
20+
Both functions can be called isomorphically; the middleware will be attached only to the environment in which it's called, so wrap in an `if (SERVER)...` block if you need to isolate this behaviour. Note: If you're using a built-in GraphQL server, ReactQL will use [apollo-local-query](https://github.com/af/apollo-local-query) instead of regular HTTP requests so middleware won't be executed -- be aware of this if you're doing HTTP header authentication!
21+
22+
## Config
23+
* Refactors the `Config` class to progressively add server-side config functions by moving the class into sub-classes for the browser/server
24+
125
2.4.0 - 2017-08-18
226
-----------------------------------------------
327

kit/config.js

Lines changed: 122 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,164 @@
11
// Simple class to act as a singleton for app-wide configuration.
22

3-
class Config {
3+
// We'll start with a common config that can be extended separately by the
4+
// server/client, to provide environment-specific functionality
5+
class Common {
46
constructor() {
57
// Store reducers in a `Map`, for easy key retrieval
68
this.reducers = new Map();
79

8-
// Create a set for routes -- to retrieve based on insertion order
9-
this.routes = new Set();
10-
11-
// Custom middleware -- again, based on insertion order
12-
this.middleware = new Set();
10+
// Apollo (middle|after)ware
11+
this.apolloMiddleware = [];
12+
this.apolloAfterware = [];
1313

1414
// GraphQL endpoint. This needs setting via either `config.enableGraphQLServer()`
1515
// or `config.setGraphQLEndpoint()`
1616
this.graphQLEndpoint = null;
1717

1818
// Set to true if we're using an internal GraphQL server
1919
this.graphQLServer = false;
20-
21-
// GraphQL schema (if we're using an internal server)
22-
this.graphQLSchema = null;
23-
24-
// Attach a GraphiQL IDE endpoint to our server? By default - no. If
25-
// this === true, this will default to `/graphql`. If it's a string, it'll
26-
// default to the string value
27-
this.graphiQL = false;
28-
29-
// Enable body parsing by default. Leave `koa-bodyparser` opts as default
30-
this.enableBodyParser = true;
31-
this.bodyParserOptions = {};
3220
}
3321

3422
/* REDUX */
3523

36-
// Adds a new reducer. Checks that `reducer` fits the right shape, otherwise
37-
// throws an error
38-
addReducer(key, reducer) {
39-
if (typeof reducer !== 'object' || !reducer.state || typeof reducer.reducer !== 'function') {
40-
throw new Error(`Can't add reducer for '${key}' - reducer must be an object of {state, reducer}`);
24+
// Adds a new reducer. Accepts a `key` string, a `reducer` function, and a
25+
// (by default empty) `initialState` object, which will ultimately become immutable
26+
addReducer(key, reducer, initialState = {}) {
27+
if (typeof reducer !== 'function') {
28+
throw new Error(`Can't add reducer for '${key}' - reducer must be a function`);
4129
}
42-
this.reducers.set(key, reducer);
30+
this.reducers.set(key, {
31+
reducer,
32+
initialState,
33+
});
4334
}
4435

45-
/* WEB SERVER / SSR */
36+
/* GRAPHQL */
4637

47-
// Disable the optional `koa-bodyparser`, to prevent POST data being sent to
48-
// each request. By default, body parsing is enabled.
49-
disableBodyParser() {
50-
this.enableBodyParser = false;
38+
// Enables internal GraphQL server. Default GraphQL and GraphiQL endpoints
39+
// can be overridden
40+
enableGraphQLServer(endpoint = '/graphql', graphiQL = true) {
41+
this.graphQLServer = true;
42+
this.graphQLEndpoint = endpoint;
43+
this.graphiQL = graphiQL;
5144
}
5245

53-
setBodyParserOptions(opt = {}) {
54-
this.bodyParserOptions = opt;
46+
// Set an external GraphQL URI for use with Apollo
47+
setGraphQLEndpoint(uri, graphiQL = true) {
48+
this.graphQLEndpoint = uri;
49+
this.graphiQL = graphiQL;
5550
}
5651

57-
// Add custom middleware. This should be an async func, for use with Koa
58-
addMiddleware(middlewareFunc) {
59-
this.middleware.add(middlewareFunc);
52+
// Register Apollo middleware function
53+
addApolloMiddleware(middlewareFunc) {
54+
this.apolloMiddleware.push(middlewareFunc);
6055
}
6156

62-
// Adds a custom server route to attach to our Koa router
63-
addRoute(method, route, handler) {
64-
this.routes.add({
65-
method,
66-
route,
67-
handler,
68-
});
57+
// Register Apollo afterware function
58+
addApolloAfterware(afterwareFunc) {
59+
this.apolloAfterware.push(afterwareFunc);
6960
}
61+
}
7062

71-
// Adds custom GET route
72-
addGetRoute(route, handler) {
73-
this.addRoute('get', route, handler);
74-
}
63+
// Placeholder for the class we'll attach
64+
let Config;
7565

76-
// Adds custom POST route
77-
addPostRoute(route, handler) {
78-
this.addRoute('post', route, handler);
79-
}
66+
// Server Config extensions. This is wrapped in a `SERVER` block to avoid
67+
// adding unnecessary functionality to the server bundle. Every byte counts!
68+
if (SERVER) {
69+
Config = class ServerConfig extends Common {
70+
constructor() {
71+
super();
72+
// Create a set for routes -- to retrieve based on insertion order
73+
this.routes = new Set();
8074

81-
// Adds custom PUT route
82-
addPutRoute(route, handler) {
83-
this.addRoute('put', route, handler);
84-
}
75+
// Custom middleware -- again, based on insertion order
76+
this.middleware = new Set();
8577

86-
// Adds custom PATCH route
87-
addPatchRoute(route, handler) {
88-
this.addRoute('patch', route, handler);
89-
}
78+
// GraphQL schema (if we're using an internal server)
79+
this.graphQLSchema = null;
9080

91-
// Adds custom DELETE route
92-
addDeleteRoute(route, handler) {
93-
this.addRoute('delete', route, handler);
94-
}
81+
// Attach a GraphiQL IDE endpoint to our server? By default - no. If
82+
// this === true, this will default to `/graphql`. If it's a string, it'll
83+
// default to the string value
84+
this.graphiQL = false;
9585

96-
// 404 handler for the server. By default, `kit/entry/server.js` will
97-
// simply return a 404 status code without modifying the HTML render. By
98-
// setting a handler here, this will be returned instead
99-
set404Handler(func) {
100-
if (typeof func !== 'function') {
101-
throw new Error('404 handler must be a function');
86+
// Enable body parsing by default. Leave `koa-bodyparser` opts as default
87+
this.enableBodyParser = true;
88+
this.bodyParserOptions = {};
10289
}
103-
this.handler404 = func;
104-
}
10590

106-
/* GRAPHQL */
91+
/* WEB SERVER / SSR */
10792

108-
// Enables internal GraphQL server. Default GraphQL and GraphiQL endpoints
109-
// can be overridden
110-
enableGraphQLServer(endpoint = '/graphql', graphiQL = true) {
111-
this.graphQLServer = true;
112-
this.graphQLEndpoint = endpoint;
113-
this.graphiQL = graphiQL;
114-
}
93+
// Disable the optional `koa-bodyparser`, to prevent POST data being sent to
94+
// each request. By default, body parsing is enabled.
95+
disableBodyParser() {
96+
this.enableBodyParser = false;
97+
}
11598

116-
// Set the GraphQL schema. This should only be called on the server, otherwise
117-
// the bundle size passed by the `schema` object will be unnecessarily inflated
118-
setGraphQLSchema(schema) {
119-
this.graphQLSchema = schema;
120-
}
99+
setBodyParserOptions(opt = {}) {
100+
this.bodyParserOptions = opt;
101+
}
121102

122-
// Set an external GraphQL URI for use with Apollo
123-
setGraphQLEndpoint(uri, graphiQL = true) {
124-
this.graphQLEndpoint = uri;
125-
this.graphiQL = graphiQL;
126-
}
103+
// 404 handler for the server. By default, `kit/entry/server.js` will
104+
// simply return a 404 status code without modifying the HTML render. By
105+
// setting a handler here, this will be returned instead
106+
set404Handler(func) {
107+
if (typeof func !== 'function') {
108+
throw new Error('404 handler must be a function');
109+
}
110+
this.handler404 = func;
111+
}
112+
113+
// Add custom middleware. This should be an async func, for use with Koa
114+
addMiddleware(middlewareFunc) {
115+
this.middleware.add(middlewareFunc);
116+
}
117+
118+
// Adds a custom server route to attach to our Koa router
119+
addRoute(method, route, handler) {
120+
this.routes.add({
121+
method,
122+
route,
123+
handler,
124+
});
125+
}
126+
127+
// Adds custom GET route
128+
addGetRoute(route, handler) {
129+
this.addRoute('get', route, handler);
130+
}
131+
132+
// Adds custom POST route
133+
addPostRoute(route, handler) {
134+
this.addRoute('post', route, handler);
135+
}
136+
137+
// Adds custom PUT route
138+
addPutRoute(route, handler) {
139+
this.addRoute('put', route, handler);
140+
}
141+
142+
// Adds custom PATCH route
143+
addPatchRoute(route, handler) {
144+
this.addRoute('patch', route, handler);
145+
}
146+
147+
// Adds custom DELETE route
148+
addDeleteRoute(route, handler) {
149+
this.addRoute('delete', route, handler);
150+
}
151+
152+
// Set the GraphQL schema. This should only be called on the server, otherwise
153+
// the bundle size passed by the `schema` object will be unnecessarily inflated
154+
setGraphQLSchema(schema) {
155+
this.graphQLSchema = schema;
156+
}
157+
};
158+
} else {
159+
// For the client config, we'll extend `Common` by default -- but if we need
160+
// anything unique to the browser in the future, we'd add it here...
161+
Config = class ClientConfig extends Common {};
127162
}
128163

129164
// Since there's only one `Config` instance globally, we'll create the new

kit/entry/server.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import Koa from 'koa';
3636
// `ApolloProvider` HOC component, which will inject any 'listening' React
3737
// components with GraphQL data props. We'll also use `getDataFromTree`
3838
// to await data being ready before rendering back HTML to the client
39-
import { createNetworkInterface, ApolloProvider, getDataFromTree } from 'react-apollo';
39+
import { ApolloProvider, getDataFromTree } from 'react-apollo';
4040

4141
// Enable cross-origin requests
4242
import koaCors from 'kcors';
@@ -84,8 +84,8 @@ import createNewStore from 'kit/lib/redux';
8484
// Initial view to send back HTML render
8585
import Html from 'kit/views/ssr';
8686

87-
// Grab the shared Apollo Client
88-
import { createClient } from 'kit/lib/apollo';
87+
// Grab the shared Apollo Client / network interface instantiation
88+
import { getNetworkInterface, createClient } from 'kit/lib/apollo';
8989

9090
// App settings, which we'll use to customise the server -- must be loaded
9191
// *after* app.js has been called, so the correct settings have been set
@@ -109,9 +109,7 @@ const createNeworkInterface = (() => {
109109
}
110110

111111
function externalInterface() {
112-
return createNetworkInterface({
113-
uri: config.graphQLEndpoint,
114-
});
112+
return getNetworkInterface(config.graphQLEndpoint);
115113
}
116114

117115
return config.graphQLServer ? localInterface : externalInterface;

kit/lib/apollo.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ export function createClient(opt = {}) {
2424
}, opt));
2525
}
2626

27+
// Wrap `createNetworkInterface` to attach middleware
28+
export function getNetworkInterface(uri) {
29+
const networkInterface = createNetworkInterface({
30+
uri,
31+
});
32+
33+
// Attach middleware
34+
networkInterface.use(config.apolloMiddleware.map(f => ({ applyMiddleware: f })));
35+
networkInterface.useAfter(config.apolloAfterware.map(f => ({ applyAfterware: f })));
36+
37+
return networkInterface;
38+
}
39+
2740
// Creates a new browser client
2841
export function browserClient() {
2942
// If we have an internal GraphQL server, we need to append it with a
@@ -32,10 +45,6 @@ export function browserClient() {
3245
? `${getServerURL()}${config.graphQLEndpoint}` : config.graphQLEndpoint;
3346

3447
return createClient({
35-
networkInterface: createNetworkInterface({
36-
// If we have an internal GraphQL server, then we should append the
37-
// URL with `getServerURL()` to get the correct hostname
38-
uri,
39-
}),
48+
networkInterface: getNetworkInterface(uri),
4049
});
4150
}

0 commit comments

Comments
 (0)