diff --git a/README.md b/README.md
index f4954a1ee2a..2bf31ae410f 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,7 @@ The [User Guide](https://github.com/facebookincubator/create-react-app/blob/mast
- [Adding `` and `` Tags](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-link-and-meta-tags)
- [Running Tests](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#running-tests)
- [Deployment](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment)
+- [Relay and GraphQl Support](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#relay-support)
A copy of the user guide will be created as `README.md` in your project folder.
diff --git a/packages/react-scripts/config/babel.dev.js b/packages/react-scripts/config/babel.dev.js
index 0113b847824..236bf524d12 100644
--- a/packages/react-scripts/config/babel.dev.js
+++ b/packages/react-scripts/config/babel.dev.js
@@ -11,6 +11,7 @@
var path = require('path');
var findCacheDir = require('find-cache-dir');
+var relayPlugin = require('../plugins/relay');
module.exports = {
// Don't try to find .babelrc because we want to force this configuration.
@@ -49,3 +50,9 @@ module.exports = {
}]
]
};
+
+// optional relay support https://facebook.github.io/relay
+if (relayPlugin.isEnabled()) {
+ // relay QL babel transform needs to run before react https://facebook.github.io/relay/docs/guides-babel-plugin.html#react-native-configuration
+ module.exports.plugins.unshift(require.resolve('../plugins/relay/babelRelayPlugin'));
+}
diff --git a/packages/react-scripts/config/babel.prod.js b/packages/react-scripts/config/babel.prod.js
index f53094ababd..6cf95ca7c3d 100644
--- a/packages/react-scripts/config/babel.prod.js
+++ b/packages/react-scripts/config/babel.prod.js
@@ -10,6 +10,7 @@
// @remove-on-eject-end
var path = require('path');
+var relayPlugin = require('../plugins/relay');
module.exports = {
// Don't try to find .babelrc because we want to force this configuration.
@@ -47,3 +48,9 @@ module.exports = {
// require.resolve('babel-plugin-transform-react-constant-elements')
]
};
+
+// optional relay support https://facebook.github.io/relay
+if (relayPlugin.isEnabled()) {
+ // relay QL babel transform needs to run before react https://facebook.github.io/relay/docs/guides-babel-plugin.html#react-native-configuration
+ module.exports.plugins.unshift(require.resolve('../plugins/relay/babelRelayPlugin'));
+}
diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js
index 314bcc2051f..03a02ba57c7 100644
--- a/packages/react-scripts/config/paths.js
+++ b/packages/react-scripts/config/paths.js
@@ -43,8 +43,10 @@ module.exports = {
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
testsSetup: resolveApp('src/setupTests.js'),
+ relaySchema: resolveApp('schema.json'),
appNodeModules: resolveApp('node_modules'),
ownNodeModules: resolveApp('node_modules'),
+ plugins: resolveApp('plugins'),
nodePaths: nodePaths
};
@@ -61,9 +63,11 @@ module.exports = {
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
testsSetup: resolveApp('src/setupTests.js'),
+ relaySchema: resolveApp('schema.json'),
appNodeModules: resolveApp('node_modules'),
// this is empty with npm3 but node resolution searches higher anyway:
ownNodeModules: resolveOwn('../node_modules'),
+ plugins: resolveApp('plugins'),
nodePaths: nodePaths
};
// @remove-on-eject-end
@@ -76,8 +80,10 @@ module.exports = {
appPackageJson: resolveOwn('../package.json'),
appSrc: resolveOwn('../template/src'),
testsSetup: resolveOwn('../template/src/setupTests.js'),
+ relaySchema: resolveOwn('../schema.json'),
appNodeModules: resolveOwn('../node_modules'),
ownNodeModules: resolveOwn('../node_modules'),
+ plugins: resolveOwn('../plugins'),
nodePaths: nodePaths
};
// @remove-on-publish-end
diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json
index ced35306854..a80b288c6e1 100644
--- a/packages/react-scripts/package.json
+++ b/packages/react-scripts/package.json
@@ -13,6 +13,7 @@
"files": [
"bin",
"config",
+ "plugins",
"scripts",
"template"
],
@@ -32,6 +33,7 @@
"babel-plugin-transform-runtime": "6.15.0",
"babel-preset-latest": "6.14.0",
"babel-preset-react": "6.11.1",
+ "babel-relay-plugin": "0.9.3",
"babel-runtime": "6.11.6",
"case-sensitive-paths-webpack-plugin": "1.1.4",
"chalk": "1.1.3",
@@ -50,12 +52,14 @@
"filesize": "3.3.0",
"find-cache-dir": "0.1.1",
"fs-extra": "0.30.0",
+ "graphql": "0.7.0",
"gzip-size": "3.0.0",
"html-loader": "0.4.3",
"html-webpack-plugin": "2.22.0",
"http-proxy-middleware": "0.17.1",
"jest": "15.1.1",
"json-loader": "0.5.4",
+ "node-fetch": "1.6.1",
"object-assign": "4.1.0",
"opn": "4.0.2",
"path-exists": "2.1.0",
diff --git a/packages/react-scripts/plugins/relay/babelRelayPlugin.js b/packages/react-scripts/plugins/relay/babelRelayPlugin.js
new file mode 100644
index 00000000000..491e858bde0
--- /dev/null
+++ b/packages/react-scripts/plugins/relay/babelRelayPlugin.js
@@ -0,0 +1,18 @@
+// @remove-on-eject-begin
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @relay
+ */
+// @remove-on-eject-end
+
+var paths = require('../../config/paths');
+var getbabelRelayPlugin = require('babel-relay-plugin');
+
+var schema = require(paths.relaySchema);
+
+module.exports = getbabelRelayPlugin(schema.data);
diff --git a/packages/react-scripts/plugins/relay/index.js b/packages/react-scripts/plugins/relay/index.js
new file mode 100644
index 00000000000..10182920e0a
--- /dev/null
+++ b/packages/react-scripts/plugins/relay/index.js
@@ -0,0 +1,153 @@
+// @remove-on-eject-begin
+/**
+ * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @relay
+ */
+// @remove-on-eject-end
+
+var paths = require('../../config/paths');
+var chalk = require('chalk');
+var fetch = require('node-fetch');
+var graphQlutilities = require('graphql/utilities');
+var fs = require('fs');
+
+/**
+ * relay requires 'react-relay' install and in package.json, GRAPHQL_URL env variable set to retrieve local copy of schema.json resulting from `npm run fetchRelaySchema`
+ */
+var isEnabled = function() {
+ var appPackageJson = require(paths.appPackageJson);
+ if (appPackageJson && appPackageJson.dependencies && appPackageJson.dependencies['react-relay']) {
+
+ return true;
+ }
+
+ return false
+}
+
+var requireGraphQlConfig = function() {
+ return new Promise((resolve, reject) => {
+ //TODO we could use graphql-config package here instead
+
+ // check that required env var REACT_APP_GRAPHQL_URL is setup
+ if (!process.env.REACT_APP_GRAPHQL_URL) {
+ // console.log(chalk.red('Relay requires a url to your graphql server'));
+ // console.log('Specifiy this in a ' + chalk.cyan('REACT_APP_GRAPHQL_URL') + ' environment variable.');
+ // console.log();
+ // process.exit(1);
+
+ var errorMessage = chalk.red('Relay requires a url to your graphql server\n') +
+ 'Specifiy this in a ' + chalk.cyan('REACT_APP_GRAPHQL_URL') + ' environment variable.';
+ reject(new Error(errorMessage));
+ }
+
+ console.log("Relay support - graphql configured successfully");
+ resolve();
+ })
+}
+
+var validateSchemaJson = function() {
+ return new Promise((resolve, reject) => {
+ // check that schema.json is there and is readable
+ try {
+ var schemaFileContents = fs.readFileSync(paths.relaySchema);
+ } catch (err) {
+ // console.log(chalk.red('babel-relay-plugin requires a local copy of your graphql schema'));
+ // console.log('Run ' + chalk.cyan('npm run fetchRelaySchema') + ' to fetch it from the ' + chalk.cyan('REACT_APP_GRAPHQL_URL') + ' environment variable.');
+ // console.log();
+ // console.error(err);
+ // console.log();
+ // process.exit(1)
+ var errorMessage = chalk.red('babel-relay-plugin requires a local copy of your graphql schema\n') +
+ 'Run ' + chalk.cyan('npm run fetchRelaySchema') + ' to fetch it from the ' + chalk.cyan('REACT_APP_GRAPHQL_URL') + ' environment variable.';
+ reject(new Error(errorMessage));
+ }
+
+ // check that schema.json is valid json
+ try {
+ var schemaJSON = JSON.parse(schemaFileContents);
+ } catch (err) {
+ // console.log(chalk.red('JSON parsing of the contents of ' + chalk.cyan(paths.relaySchema) + ' failed.'));
+ // console.log('Check the contents of ' + chalk.cyan(paths.relaySchema) + '. It does not appear to be valid json');
+ // console.log('Also try running ' + chalk.cyan('npm run fetchRelaySchema') + ' to re-fetch the schema.json from the ' + chalk.cyan('REACT_APP_GRAPHQL_URL') + ' environment variable.');
+ // console.log();
+ // console.error(err);
+ // console.log();
+ // process.exit(1)
+ var errorMessage = chalk.red('JSON parsing of the contents of ' + chalk.cyan(paths.relaySchema) + ' failed.\n') +
+ 'Check the contents of ' + chalk.cyan(paths.relaySchema) + '. It does not appear to be valid json\n' +
+ 'Also try running ' + chalk.cyan('npm run fetchRelaySchema') + ' to re-fetch the schema.json from the ' + chalk.cyan('REACT_APP_GRAPHQL_URL') + ' environment variable.';
+ reject(new Error(errorMessage));
+ }
+
+ // check contents of schema.json has valid graphql schema
+ try {
+ var graphQLSchema = graphQlutilities.buildClientSchema(schemaJSON.data);
+ } catch (err) {
+ // console.log(chalk.red('Could not parse the contents of schema.json into a valid graphql schema that is compatiable with this version of Relay and babel-relay-plugin'));
+ // console.log('Upgrading graphql library on your server may be a solution.');
+ // console.log();
+ // console.error(err);
+ // console.log();
+ // process.exit(1)
+ var errorMessage = chalk.red('Could not parse the contents of schema.json into a valid graphql schema that is compatiable with this version of Relay and babel-relay-plugin\n') +
+ 'Upgrading graphql library on your server may be a solution.';
+ reject(new Error(errorMessage));
+ }
+
+ console.log('Relay support - schema.json is found and valid');
+ resolve();
+ });
+}
+
+// retreive JSON of graaphql schema via introspection for Babel Relay Plugin to use
+var fetchRelaySchema = function() {
+ return fetch(process.env.REACT_APP_GRAPHQL_URL, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({'query': graphQlutilities.introspectionQuery}),
+ })
+ .then(res => res.json()).then(schemaJSON => {
+ // verify that the schemaJSON is valid a graphQL Schema
+ var graphQLSchema = graphQlutilities.buildClientSchema(schemaJSON.data);
+ // Save relay compatible schema.json
+ fs.writeFileSync(paths.relaySchema, JSON.stringify(schemaJSON, null, 2));
+
+ // Save user readable schema.graphql
+ fs.writeFileSync(paths.relaySchema.replace('.json','.graphql'), graphQlutilities.printSchema(graphQLSchema));
+
+ console.log('Relay support - fetch schema.json from ' + chalk.cyan(process.env.REACT_APP_GRAPHQL_URL));
+ });
+}
+
+// build does not fetch the relay schema, only uses local copy of shema.js.
+// this helps, but does not guaruntee that builds, dev, and tests use the same version of the schema.
+var build = function() {
+ return requireGraphQlConfig()
+ .then(validateSchemaJson)
+ .then(() => {
+ console.log(chalk.green('Relay support built successfully!'));
+ })
+}
+
+var start = function() {
+ return requireGraphQlConfig()
+ .then(fetchRelaySchema)
+ .then(validateSchemaJson)
+ .then(() => {
+ console.log(chalk.green('Relay support enabled successfully!'));
+ })
+}
+
+module.exports = {
+ isEnabled: isEnabled,
+ build: build,
+ start: start
+};
diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js
index 71dcc798bba..12c12b2f72e 100644
--- a/packages/react-scripts/scripts/build.js
+++ b/packages/react-scripts/scripts/build.js
@@ -24,6 +24,7 @@ var paths = require('../config/paths');
var checkRequiredFiles = require('./utils/checkRequiredFiles');
var recursive = require('recursive-readdir');
var stripAnsi = require('strip-ansi');
+var plugins = require('./utils/plugins');
checkRequiredFiles();
@@ -54,23 +55,31 @@ function getDifferenceLabel(currentSize, previousSize) {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
-recursive(paths.appBuild, (err, fileNames) => {
- var previousSizeMap = (fileNames || [])
- .filter(fileName => /\.(js|css)$/.test(fileName))
- .reduce((memo, fileName) => {
- var contents = fs.readFileSync(fileName);
- var key = removeFileNameHash(fileName);
- memo[key] = gzipSize(contents);
- return memo;
- }, {});
+plugins.build()
+ .then(() => {
+ recursive(paths.appBuild, (err, fileNames) => {
+ var previousSizeMap = (fileNames || [])
+ .filter(fileName => /\.(js|css)$/.test(fileName))
+ .reduce((memo, fileName) => {
+ var contents = fs.readFileSync(fileName);
+ var key = removeFileNameHash(fileName);
+ memo[key] = gzipSize(contents);
+ return memo;
+ }, {});
- // Remove all content but keep the directory so that
- // if you're in it, you don't end up in Trash
- rimrafSync(paths.appBuild + '/*');
+ // Remove all content but keep the directory so that
+ // if you're in it, you don't end up in Trash
+ rimrafSync(paths.appBuild + '/*');
- // Start the webpack build
- build(previousSizeMap);
-});
+ // Start the webpack build
+ build(previousSizeMap);
+ });
+ })
+ .catch((err) => {
+ console.error(err);
+ console.log();
+ process.exit(1)
+ });
// Print a detailed summary of build files.
function printFileSizes(stats, previousSizeMap) {
diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js
index 74c5c9ef99b..b0d04cd888e 100644
--- a/packages/react-scripts/scripts/eject.js
+++ b/packages/react-scripts/scripts/eject.js
@@ -47,7 +47,10 @@ prompt(
path.join('scripts', 'utils', 'checkRequiredFiles.js'),
path.join('scripts', 'utils', 'chrome.applescript'),
path.join('scripts', 'utils', 'prompt.js'),
- path.join('scripts', 'utils', 'WatchMissingNodeModulesPlugin.js')
+ path.join('scripts', 'utils', 'plugins.js'),
+ path.join('scripts', 'utils', 'WatchMissingNodeModulesPlugin.js'),
+ path.join('plugins', 'relay', 'index.js'),
+ path.join('plugins', 'relay', 'babelRelayPlugin.js')
];
// Ensure that the app folder is clean and we won't override any files
@@ -69,6 +72,8 @@ prompt(
fs.mkdirSync(path.join(appPath, 'config', 'jest'));
fs.mkdirSync(path.join(appPath, 'scripts'));
fs.mkdirSync(path.join(appPath, 'scripts', 'utils'));
+ fs.mkdirSync(path.join(appPath, 'plugins'));
+ fs.mkdirSync(path.join(appPath, 'plugins', 'relay'));
files.forEach(function(file) {
console.log('Copying ' + file + ' to ' + appPath);
diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js
index b0290bfc492..0762e653922 100644
--- a/packages/react-scripts/scripts/start.js
+++ b/packages/react-scripts/scripts/start.js
@@ -24,6 +24,7 @@ var checkRequiredFiles = require('./utils/checkRequiredFiles');
var prompt = require('./utils/prompt');
var config = require('../config/webpack.config.dev');
var paths = require('../config/paths');
+var plugins = require('./utils/plugins');
// Tools like Cloud9 rely on this.
var DEFAULT_PORT = process.env.PORT || 3000;
@@ -311,9 +312,17 @@ function runDevServer(port, protocol) {
function run(port) {
var protocol = process.env.HTTPS === 'true' ? "https" : "http";
- checkRequiredFiles();
- setupCompiler(port, protocol);
- runDevServer(port, protocol);
+ plugins.start()
+ .then(() => {
+ checkRequiredFiles();
+ setupCompiler(port, protocol);
+ runDevServer(port, protocol);
+ })
+ .catch((err) => {
+ console.error(err);
+ console.log();
+ process.exit(1)
+ });
}
// We attempt to use the default port but if it is busy, we offer the user to
diff --git a/packages/react-scripts/scripts/utils/plugins.js b/packages/react-scripts/scripts/utils/plugins.js
new file mode 100644
index 00000000000..62ac66b3494
--- /dev/null
+++ b/packages/react-scripts/scripts/utils/plugins.js
@@ -0,0 +1,29 @@
+// @remove-on-eject-begin
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+// @remove-on-eject-end
+
+var relayPlugin = require('../../plugins/relay');
+
+var start = function() {
+ return Promise.all([
+ (relayPlugin.isEnabled()) ? relayPlugin.start() : false,
+ ])
+}
+
+var build = function() {
+ return Promise.all([
+ (relayPlugin.isEnabled()) ? relayPlugin.build() : false,
+ ])
+}
+
+module.exports = {
+ start: start,
+ build: build,
+}
diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md
index 085406077b5..e2588781553 100644
--- a/packages/react-scripts/template/README.md
+++ b/packages/react-scripts/template/README.md
@@ -29,6 +29,7 @@ You can find the most recent version of this guide [here](https://github.com/fac
- [Adding `` and `` Tags](#adding-link-and-meta-tags)
- [Referring to Static Assets from ``](#referring-to-static-assets-from-link-href)
- [Generating Dynamic `` Tags on the Server](#generating-dynamic-meta-tags-on-the-server)
+- [Relay & GraphQL Support](#relay-support)
- [Running Tests](#running-tests)
- [Filename Conventions](#filename-conventions)
- [Command Line Interface](#command-line-interface)
@@ -603,6 +604,101 @@ Then, on the server, regardless of the backend you use, you can read `index.html
If you use a Node server, you can even share the route matching logic between the client and the server. However duplicating it also works fine in simple cases.
+## Relay Support
+
+You can create a [Relay](https://facebook.github.io/relay/) application with create-react-app. You will need a standalone GraphQL server ([CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) enabled, most likely) for your create-react-app to connect to. The steps for setting up the standalone GraphQL server are outside the scope of these docs.
+
+To enable Relay in your create-react-app, install `react-relay`
+
+```cmd
+npm install react-relay --save
+```
+
+Then define an environment variable `REACT_APP_GRAPHQL_URL` with a value that is the URL to your graphql server (for example `REACT_APP_GRAPHQL_URL=http://localhost:3001/graphql`). See [environment variables](#adding-custom-environment-variables) for more information.
+
+With your REACT_APP_GRAPHQL_URL environment variable defined, start your app. This could be as simple as:
+
+```cmd
+REACT_APP_GRAPHQL_URL=http://localhost:3001/graphql npm start
+```
+
+This will configure the `babel-relay-plugin` for you and fetch your graphql schema. Woo!
+
+Next, add Relay to `my-app/src/index.js`. Setting the `DefaultNetworkLayer` to point to REACT_APP_GRAPHQL_URL is important. Here is an example. Note for this example you also need to `npm install react-router react-router-relay`:
+
+```js
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './App';
+import './index.css';
+import Relay from 'react-relay';
+import { applyRouterMiddleware, Router, Route, /*Link,*/ browserHistory } from 'react-router';
+import useRelay from 'react-router-relay';
+
+
+const ViewerQueries = {
+ viewer: () => Relay.QL`query { viewer }`
+};
+
+Relay.injectNetworkLayer(
+ new Relay.DefaultNetworkLayer(process.env.REACT_APP_GRAPHQL_URL)
+);
+
+ReactDOM.render(
+
+ To get started, edit src/App.js
and save to reload.
+
This came from Relay {this.props.viewer.id}
+