Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relay support #662

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ The [User Guide](https://github.com/facebookincubator/create-react-app/blob/mast
- [Adding `<link>` and `<meta>` 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.

Expand Down
7 changes: 7 additions & 0 deletions packages/react-scripts/config/babel.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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'));
}
7 changes: 7 additions & 0 deletions packages/react-scripts/config/babel.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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'));
}
6 changes: 6 additions & 0 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand All @@ -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
Expand All @@ -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
4 changes: 4 additions & 0 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"files": [
"bin",
"config",
"plugins",
"scripts",
"template"
],
Expand All @@ -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",
Expand All @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions packages/react-scripts/plugins/relay/babelRelayPlugin.js
Original file line number Diff line number Diff line change
@@ -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);
153 changes: 153 additions & 0 deletions packages/react-scripts/plugins/relay/index.js
Original file line number Diff line number Diff line change
@@ -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
};
39 changes: 24 additions & 15 deletions packages/react-scripts/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion packages/react-scripts/scripts/eject.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
15 changes: 12 additions & 3 deletions packages/react-scripts/scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Loading