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

Add relay-config package for centralised configuration #2746

Closed
wants to merge 15 commits into from

Conversation

alloy
Copy link
Contributor

@alloy alloy commented May 28, 2019

Closes #2518

Note: Includes #2745, so that should probably be merged first and then this can be rebased.

I tried multiple times to come up with a doc describing the integration a few times, but then yesterday I had a whole day and figured I’d just write a implementation to show what it could look like. Please feel free to poke holes as much as you need.


What this change does:

  • Add a new relay-config package

  • Usage of the package is optional. If installed it will be picked-up, otherwise relay will function as it does today.

  • RelayConfig relies on cosmiconfig to do its bidding. It’s configured to load from:

    • a relay key in package.json

      {
        "relay": {
          "src": "./src"
        }
      }
    • a relay.config.json file

      {
        "src": "./src"
      }
    • or a relay.config.js file

      module.exports = {
        src: "./src",
      }
  • It accepts all the same configuration as the CLI does. (As a bonus, I typed the configuration object and the Yargs options object.)

  • Additionally, when using the relay.config.js file, a configuration entry like the language plugin also accepts an actual function:

    const typescript = require("relay-compiler-language-typescript")
    
    module.exports = {
      language: typescript,
    }

    In the future, other entries such as persistedQueries and customScalars could also be configured as such and allow for project specific setup.

  • It is used by all pieces of Relay that take configuration–i.e. relay-compiler and babel-plugin-relay. For instance, previously one would have needed to specify artifactDirectory on the CLI to relay-compiler and the same setting in .babelrc for babel-plugin-relay. This can now be reduced to a single centralised setting.

    Additional external tooling can also start relying on this. For instance, the vscode-apollo extension, with this change, will allow for custom GraphQL validation rules to surface in the UI. Leveraging the centralised config allows one to surface relay-compiler specific validations without needing to take in the same configuration in a different place yet again.

  • Finally, this also adds some previously missing test coverage around language plugin loading.

@alloy
Copy link
Contributor Author

alloy commented May 28, 2019

For our apps, this:

$ relay-compiler \
  --src ./src \
  --schema data/schema.graphql \
  --language typescript \
  --artifactDirectory ./src/__generated__ \
  --persist-output ./data/complete.queryMap.json \
  --exclude '**/node_modules/**,**/__mocks__/**,**/__generated__/**'
{
  "plugins": [
    ["relay", { "artifactDirectory": "./src/__generated__" }]
  ]
}

…is replaced with:

module.exports = {
  src: "./src",
  schema: "./data/schema.graphql",
  language: "typescript",
  artifactDirectory: "./src/__generated__",
  persistOutput: "./data/complete.queryMap.json",
  exclude: ["**/node_modules/**", "**/__mocks__/**", "**/__generated__/**"],
}
{
  "plugins": ["relay"]
}

languagePlugin = languagePlugin.default;
let languagePlugin: LanguagePlugin;
if (typeof language === 'string') {
const pluginPath = path.resolve(process.cwd(), language);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module loading from a path could probably be deprecated with the ability to just pass a function using a relay.config.js file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, let's do that. we can deprecate this in a follow-up and delete in a later release

@zth
Copy link
Contributor

zth commented May 28, 2019

This is so great @alloy! Centralized configuration, yummies!

noMinify: true, // Note: uglify can't yet handle modern JS
},
],
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Cargo-culted this from the test utils package.)

Copy link
Contributor

@josephsavona josephsavona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for implementing this, it's really exciting and should make the compiler and babel plugin easier to use. See questions/comments but overall looks great.

languagePlugin = languagePlugin.default;
let languagePlugin: LanguagePlugin;
if (typeof language === 'string') {
const pluginPath = path.resolve(process.cwd(), language);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, let's do that. we can deprecate this in a follow-up and delete in a later release

const reporter = new ConsoleReporter({
verbose: options.verbose,
quiet: options.quiet,
const watchman = config.watchman && (await WatchmanClient.isAvailable());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm the previous name made it clear that this wasn't identical to config.watchman - the logic has actually changed, see comment below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree on the name being better–perhaps we can rename the option–but after having added test coverage for all behaviour and looking at the old behaviour (current master) I can’t spot any difference in logic.

  1. useWatchman was determined based on options.watchman and if the client is available
    const useWatchman = options.watchman && (await WatchmanClient.isAvailable());
  2. useWatchman was from then on only used, options.watchman wasn’t ever used for anything other substantive.

Additionally, while adding full test coverage, I also noticed that previously there would be no error thrown if one were to enable watch and watchman but the client isn’t available. I changed that now, so that should improve the UX of the CLI slightly.

getFileFilter: sourceModuleParser.getFileFilter,
getParser: sourceModuleParser.getParser,
getSchema: () => schema,
watchmanExpression: useWatchman
watchmanExpression: config.watchman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert? we should only pass a watchman expression if watchman is available, not just if config.watchman is set.

? buildWatchExpression(sourceSearchOptions)
: null,
filepaths: useWatchman
filepaths: config.watchman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

getParser: DotGraphQLParser.getParser,
getSchema: () => schema,
watchmanExpression: useWatchman
watchmanExpression: config.watchman
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here and below

@josephsavona
Copy link
Contributor

One more thought: should we only support relay.config.js, or at least heavily encourage it in the docs? Several useful features like persisted queries only work with a function, and it seems good to encourage people to use a config format that they won't have to change later when they realize they want some feature.

@alloy
Copy link
Contributor Author

alloy commented May 30, 2019

I’m up for that 👍 I’ll follow up with a doc PR first.

config = getPathBasedConfig(config);
config = await getWatchConfig(config);

const codegenRunner = module.exports.getCodegenRunner(config);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than adding optional function parameters to these functions to allow for DI, this appears to be a suggestion to being able to mock functions that reside in the same module as the caller jestjs/jest#936 (comment).

I have no real preference for one or the other, let me know if you do.

@alloy
Copy link
Contributor Author

alloy commented Jun 3, 2019

Btw here’s a PoC of how having this unified config interface will allow other tooling to hook in better with one’s relay setup–in this case we can have (and share with the community) a generic vscode-apollo config: https://github.com/artsy/emission/blob/b7f138d2b4311d0eed5923b39219e124a6a96a65/apollo.config.js

(I would have loved to include validation rules about eg operation/fragment names having to start with the name of the module they reside in, but I just noticed those aren’t proper validation rules, but operate directly on the parsed GraphQL tags 😞.)

@alloy alloy force-pushed the add-relay-config branch from 10e60a9 to 292cb1c Compare June 3, 2019 13:10
@alloy
Copy link
Contributor Author

alloy commented Jun 3, 2019

One more thought on my end, if we were to only allow configuration through relay.config.js at some point, does that meant that perhaps we can move some of the configuration validation and expansion of path globs into relay-config as well?

That would allow other tooling relying on the configuration to get the same quality as relay-compiler and babel-plugin-relay would and alleviate e.g. this.

@alloy
Copy link
Contributor Author

alloy commented Jun 3, 2019

As noted here, the CI failures exist on master as well.

@alloy alloy force-pushed the add-relay-config branch from 292cb1c to 8dd5f9c Compare June 5, 2019 11:05
Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jstejada has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jstejada has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@facebook-github-bot
Copy link
Contributor

@jstejada merged this pull request in d3ec68e.

@enisdenjo
Copy link
Contributor

Maybe I am doing it wrong, but the config file is not picked up for me...

What I did:

  • Installed relay-config alongside all other Relay deps (all at v5.0.0)
  • Created relay.config.js at the root of my project (same level as package.json)
    Having the config in package.json changes nothing...
  • relay.config.js has the same contents as @alloy's comment
  • Added a relay script in package.json which just does: relay-compiler
  • Running the script results in: Error: --schema path does not exist: [...]

Any ideas? Thanks! :)

@enisdenjo
Copy link
Contributor

Never mind guys, I was searching for schema.graphql while I had schema.json... Silly me 😬

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RFC] Allow for more complex configuration of relay-compiler / babel plugin
6 participants