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

Warn when implementing apis twice #3889

Merged
merged 17 commits into from
Feb 27, 2018

Conversation

m-allanson
Copy link
Contributor

@m-allanson m-allanson commented Feb 7, 2018

This has come up a whole bunch of times, see #2005, #2963, #3231, #2578, #1079 (there might be more).

Now when implementing replaceRenderer multiple times, you'll see a warning in the console.

Still to do:

The warning is output multiple times, which is a bit messy, but an improvement on no output.

Add a dedicated docs page demonstrating how to fix this. For now it points to @akadop's comment here

Ensure local gatsby-ssr.js takes precedence when replaceRenderer is implemented in multiple places

Use gatsby-cli's reporter for nicer output formatting?

@gatsbybot
Copy link
Collaborator

gatsbybot commented Feb 7, 2018

Deploy preview for gatsbygram ready!

Built with commit db3e56d

https://deploy-preview-3889--gatsbygram.netlify.com

Copy link
Contributor

@KyleAMathews KyleAMathews left a comment

Choose a reason for hiding this comment

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

Sweeeeeet!!! This is so needed. Thanks for taking this on!

}

// Run the specified api in any plugins that have implemented it
const apiRunner = ({ api, args, defaultReturn, checkDupes = false }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like named arguments (should have used them here originally) but we'd need to update the node/browser api runners as well to keep things consistent internally. So could you revert this change for now and perhaps do the named args change in another PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

const dupes = duplicatedApis(plugins, api)
if (dupes.length > 0) {
let m = `\nThe "${api}" api has been implemented multiple times. Only the last implementation will be used.`
let m2 = `This is probably an error, see https://github.com/gatsbyjs/gatsby/issues/2005#issuecomment-326787567 for workarounds.`
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a docs page — could you add a new docs page explaining the problem and solutions and link to that instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, you already said you were going to do this :-)


if (checkDupes) {
const dupes = duplicatedApis(plugins, api)
if (dupes.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like you need to filter this list to APIs where duplicates is a problem e.g. replaceRenderer. Most SSR APIs are fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I think we should suppress warnings for duplicated APIs where there's an implementation in the site's gatsby-* file as those always override other plugins. This is the normal fix — merge plugin's implementations of replaceRenderer. As things are now, the warning would keep showing which isn't what we want as then the user might uninstall the plugin to fix things but which would actually probably break things as many plugins with gatsby-ssr.js files also have other API implementations e.g. for browser APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like you need to filter this list to APIs where duplicates is a problem

This is handled at the point that apiRunner is called, using the new checkDupes flag:

// check for duplicate api implementations
apiRunner({api: `replaceRenderer`, args, checkDupes: true})

// don't check for duplicate api implementations (default behaviour)
apiRunner({api: `replaceRenderer`, args})

But actually it's just the replaceRenderer api that shouldn't be duplicated? For now I can hardcode the name replaceRenderer within apiRunner. Then we can extract it out later if necessary.

we should suppress warnings for duplicated APIs where there's an implementation in the site's gatsby-* file

👍

@m-allanson
Copy link
Contributor Author

@KyleAMathews thinking a bit more about your comment on when to show an error...

we should suppress warnings for duplicated APIs where there's an implementation in the site's gatsby-* file

Is it true that - when someone has a couple of replaceRenderer plugins installed and also has replaceRenderer in their gatsby-ssr.js, we have no way of knowing if that's intentional or accidental?

The different states that we can detect are like this:

Status replaceRenderer implementations
Error in plugin A, in plugin B
Possible error in plugin A, in plugin B, in local gatsby-ssr.js
No error in plugin C
No error in local gatsby-ssr.js

When there's definitely an error, we can show an error message. When there's possibly an error, what should Gatsby do? I guess the options are:

  • Show the standard error message (with link to docs page)
  • Show a shorter "heads up" message pointing to the docs page
  • Stay silent
  • Something else?

@m-allanson
Copy link
Contributor Author

m-allanson commented Feb 8, 2018

@KyleAMathews I made a first draft at some docs, added to this PR. I also created an example repo to check what I was writing as I went along.

In that example repo, the final commit should result in a functioning gatsby build, but that's not happening. The installed plugin is taking precedence over the local gatsby-ssr.js. Is that a bug?


## What is the `replaceRenderer` API?

The `replaceRenderer` API is one of [Gatsby's Server Side Rendering (SSR) hooks](https://www.gatsbyjs.org/docs/ssr-apis/#replaceRenderer). It's used to customise how Gatsby renders your static content. It can be implemented by any Gatsby plugin or your `gatsby-ssr.js` file - adding support for Redux, CSS-in-JS libraries or any code that needs to change Gatsby's default HTML output.
Copy link
Contributor

Choose a reason for hiding this comment

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

do a relative link so gatsby-plugin-catch-links can do its thing

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd also immediately make it clear this stuff is all involved when running gatsby build. E.g. the second sentence is "This API is called when you run gatsby build"


## Why does it cause build errors?

When using `replaceRenderer` many times in your project, only the newest instance will be used - which can cause unexpected problems with your site.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/will/can/

Copy link
Contributor

Choose a reason for hiding this comment

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

"only the last plugin implementing the API can be called"

If your project uses replaceRenderer more than once, gatsby build will warn you:

```
The "replaceRenderer" api has been implemented multiple times. Only the last implementation will be used.
Copy link
Contributor

Choose a reason for hiding this comment

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

Make sure the actor is mentioned i.e. plugin. So the sentence could read 'The "replaceRenderer" API is implemented by several enabled plugins."

The "replaceRenderer" api has been implemented multiple times. Only the last implementation will be used.
This is probably an error, see https://example.com for workarounds.
Check the following files for "replaceRenderer" implementations:
/path/to/my/site/node_modules/gatsby-plugin-styled-components/gatsby-ssr.js
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd list the plugin name first (as that's what will be recognizable" and then list the exact path to the file implementing the API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's the best way to get the plugin name? Just extract it from the path?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could move the duplicate plugin detection to when we load the plugins. There we have access to all the info we need. Then when we write out the SSR runner, we just specify which plugin's implementation of replaceRenderer it should run which is all that file needs to know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍


In this example, your `gatsby-ssr.js` file and `gatsby-plugin-styled-components` are both using `replaceRenderer`.

### 2. Move their `replaceRenderer` functionality to your `gatsby-ssr.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.

"Copy the plugins' replaceRenderer functionality to your site's gatsby-ssr.js file"


### Initial setup

In this example project you're using [`redux`](https://github.com/gatsbyjs/gatsby/tree/master/examples/using-redux) and [Gatsby's Styled Components plugin](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-styled-components).
Copy link
Contributor

Choose a reason for hiding this comment

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

Say "we" instead of "you"

@@ -6,10 +6,45 @@

const apis = require(`./api-ssr-docs`)

module.exports = (api, args, defaultReturn) => {
/**
* Some apis should only be implemented once. Given a list of plugins, and an
Copy link
Contributor

Choose a reason for hiding this comment

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

Capitalize API

@KyleAMathews
Copy link
Contributor

Show a shorter "heads up" message pointing to the docs page

Yeah, that makes sense. The user probably has things handled at that point but it's still useful to warn people e.g. they add a custom replaceRenderer but then add later another plugin with a replaceRenderer. When at that point they're wondering why things aren't working, they'll have the console message to hopefully point them in the right direction.

@KyleAMathews
Copy link
Contributor

In that example repo, the final commit should result in a functioning gatsby build, but that's not happening. The installed plugin is taking precedence over the local gatsby-ssr.js. Is that a bug?

Yes... that would be a bug...

Hmmm not even sure when/where this is implemented. I think it's last one wins but that's kind of confusing as that means we're running code for API implementations we're not using.

We should add the APIs here which can only be run once and then explicitly choose just the last one to run https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby/cache-dir/api-runner-ssr.js

@m-allanson
Copy link
Contributor Author

m-allanson commented Feb 8, 2018

That's great docs feedback, thanks! PR updated to cover that. Tomorrow I'll take another look at the functionality side of things.

Note to self - update error message to mention plugin name.

Copy link
Contributor

@KyleAMathews KyleAMathews left a comment

Choose a reason for hiding this comment

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

More copy tweaks


## What is the `replaceRenderer` API?

The `replaceRenderer` API is one of [Gatsby's Server Side Rendering (SSR) hooks](/docs/ssr-apis/#replaceRenderer). This API is called when you run `gatsby build` and is used to customise how Gatsby renders your static content. It can be implemented by any Gatsby plugin or your `gatsby-ssr.js` file - adding support for Redux, CSS-in-JS libraries or any code that needs to change Gatsby's default HTML output.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be called "extension APIs" instead of hooks https://www.gatsbyjs.org/docs/api-specification/#extension-apis


## Why does it cause build errors?

When using `replaceRenderer` many times in your project, only the last plugin implementing the API can be called - which will break your site builds.
Copy link
Contributor

Choose a reason for hiding this comment

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

"If multiple plugins implement replaceRenderer in your project"


Note that `replaceRenderer` is only used during `gatsby build`. It won't cause problems as you work on your site with `gatsby develop`.

If your project uses `replaceRenderer` more than once, `gatsby build` will warn you:
Copy link
Contributor

Choose a reason for hiding this comment

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

Make similar change here. The project isn't the actor, plugins are.

@KyleAMathews
Copy link
Contributor

Deploy preview for using-glamor failed.

Built with commit 4268d14c182e197b549283cc96be5e87d512680c

https://app.netlify.com/sites/using-glamor/deploys/5a7dbe6948769b76452b9f41

@m-allanson
Copy link
Contributor Author

m-allanson commented Feb 9, 2018

@KyleAMathews in bootstrap/load-plugins.js, Gatsby checks that plugins only implement known APIs, but it only does this for the Node APIs.

A plugin that implements ssrAPIs won't have those APIs checked, its plugin object will end up looking like this:

{ 
  resolve: '/path/to/site/node_modules/gatsby-plugin-styled-components',
  id: 'Plugin gatsby-plugin-styled-components',
  name: 'gatsby-plugin-styled-components',
  version: '2.0.5',
  pluginOptions: { plugins: [] },
  nodeAPIs: [] 
}

should this object be expanded to include ssrAPIs and browserAPIs keys?

@KyleAMathews
Copy link
Contributor

@m-allanson yeah I think so. I'm not entirely sure now why I didn't make it check browser/ssr APIs as well.

@m-allanson
Copy link
Contributor Author

Add link to https://github.com/caki0915/gatsby-starter-redux on the docs page?

@ghost ghost assigned m-allanson Feb 17, 2018
@ghost ghost added the review label Feb 17, 2018
@m-allanson
Copy link
Contributor Author

m-allanson commented Feb 17, 2018

@KyleAMathews I was going to add some tests, but it's late so I'm just going to push this as-is. Here's what the various error outputs look like now:

Develop

Possible error (build continues)

There's three replaceRenderers here, the first two are skipped, leaving only the one in the site's local gatsby-ssr.js.

screen shot 2018-02-17 at 23 20 33

Definite error (build continues)

There's multiple replaceRenderers and none of them are in the site's local gatsby-ssr.js. This is definitely an error.

screen shot 2018-02-17 at 23 20 59

Build

Possible error (build continues)

As with develop, show a warning but continue with the build.

screen shot 2018-02-17 at 23 21 24

Definite error (build exits)

As with develop, show an error but because this is definitely an error - stop the build.

screen shot 2018-02-17 at 23 21 42

@@ -10,6 +10,7 @@ const nodeAPIs = require(`../utils/api-node-docs`)
const browserAPIs = require(`../utils/api-browser-docs`)
const ssrAPIs = require(`../../cache-dir/api-ssr-docs`)
const resolveModuleExports = require(`./resolve-module-exports`)
const reporter = require(`gatsby-cli/lib/reporter`)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm maybe using reporter() here isn't right?

@m-allanson
Copy link
Contributor Author

@KyleAMathews The latest commit splits the plugin loader out into smaller pieces and adds some tests, so I think this is ready for another look. See my previous comment ⬆️for examples of the error messages.

const hasAPIFile = (env, plugin) => {
// The plugin loader has disabled SSR APIs for this plugin. Usually due to
// multiple implementations of an API that can only be implemented once
if (env === `ssr` && plugin.skipSSR === true) return undefined
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we disable plugins for individual APIs not all SSR APIs?

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 implemented disabling individual APIs on my checkout, but then had a thought - is there any reason for a plugin to implement replaceRenderer and onRenderBody? I can't find any official or community plugins that do this.

Disabling all SSR APIs keeps things simpler as it means api-runner-ssr.js doesn't have to check whether it should run an API or not. I'd be tempted to roll with this implementation and then review if it causes problems.

Copy link
Contributor

Choose a reason for hiding this comment

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

It does seem unlikely. If we add more APIs in the future, this could potentially become not true — that a plugin would implement multiple ssr APIs. I did a query (this one was fun ag -l replaceRenderer | xargs -L 1 ag onRenderBody -l) and there isn't any plugin currently that implements both. So let's go with it and see what happens. It'd be easy to change later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That sounds good. Nice querying :)

@KyleAMathews KyleAMathews merged commit aa14992 into gatsbyjs:master Feb 27, 2018
@ghost ghost removed the review label Feb 27, 2018
@KyleAMathews
Copy link
Contributor

Great stuff! Super glad you took this on! It'll save a lot of people from long head-scratching debugging sessions!

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

Successfully merging this pull request may close these issues.

3 participants