Skip to content

Commit

Permalink
Add warning when node-creating plugins create no nodes
Browse files Browse the repository at this point in the history
The warning serves two purposes: a) to notify those that are
using the plugin that it hasn't created any nodes and can likely
be removed, and b) to reduce the confusion caused by a GraphQL
query for a specific node erroring as no nodes have been created
yet. See gatsbyjs#2212 for more.

As part of this work, two key changes have been made to the
redux store:

a) Each plugin now has a `nodeAPIs` key whose value retains an array
   of strings denoting the names of which Gatsby APIs the plugin
   implements.

b) At the root of the store, a new key exists named `apiToPlugins`
   has been created which stores a simple key -> val map from the
   name of a Gatsby API, to an array containing the string names
   of all plugins which implement that API.

a) is helpful when needing to know what APIs a given plugin implements
and b) is helpful when code requires a quick look up of which plugins
implement a specific API - we pay the cost once at creation time rather
than iterating through the plugin objects whenever we need to know.
  • Loading branch information
djm committed Oct 22, 2017
1 parent ad99189 commit fba53c4
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Array [
Object {
"id": "Plugin component-page-creator",
"name": "component-page-creator",
"nodeAPIs": Array [
"createPagesStatefully",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -14,6 +17,9 @@ Array [
Object {
"id": "Plugin component-layout-creator",
"name": "component-layout-creator",
"nodeAPIs": Array [
"createLayouts",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -23,6 +29,10 @@ Array [
Object {
"id": "Plugin internal-data-bridge",
"name": "internal-data-bridge",
"nodeAPIs": Array [
"sourceNodes",
"onCreatePage",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -32,6 +42,9 @@ Array [
Object {
"id": "Plugin dev-404-page",
"name": "dev-404-page",
"nodeAPIs": Array [
"createPages",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -41,6 +54,9 @@ Array [
Object {
"id": "Plugin prod-404",
"name": "prod-404",
"nodeAPIs": Array [
"onCreatePage",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -50,6 +66,10 @@ Array [
Object {
"id": "Plugin query-runner",
"name": "query-runner",
"nodeAPIs": Array [
"onCreatePage",
"onCreateLayout",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -58,6 +78,7 @@ Array [
},
Object {
"name": "TEST",
"nodeAPIs": Array [],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -66,6 +87,7 @@ Array [
Object {
"id": "Plugin default-site-plugin",
"name": "default-site-plugin",
"nodeAPIs": Array [],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -80,6 +102,9 @@ Array [
Object {
"id": "Plugin component-page-creator",
"name": "component-page-creator",
"nodeAPIs": Array [
"createPagesStatefully",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -89,6 +114,9 @@ Array [
Object {
"id": "Plugin component-layout-creator",
"name": "component-layout-creator",
"nodeAPIs": Array [
"createLayouts",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -98,6 +126,10 @@ Array [
Object {
"id": "Plugin internal-data-bridge",
"name": "internal-data-bridge",
"nodeAPIs": Array [
"sourceNodes",
"onCreatePage",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -107,6 +139,9 @@ Array [
Object {
"id": "Plugin dev-404-page",
"name": "dev-404-page",
"nodeAPIs": Array [
"createPages",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -116,6 +151,9 @@ Array [
Object {
"id": "Plugin prod-404",
"name": "prod-404",
"nodeAPIs": Array [
"onCreatePage",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -125,6 +163,10 @@ Array [
Object {
"id": "Plugin query-runner",
"name": "query-runner",
"nodeAPIs": Array [
"onCreatePage",
"onCreateLayout",
],
"pluginOptions": Object {
"plugins": Array [],
},
Expand All @@ -134,6 +176,7 @@ Array [
Object {
"id": "Plugin default-site-plugin",
"name": "default-site-plugin",
"nodeAPIs": Array [],
"pluginOptions": Object {
"plugins": Array [],
},
Expand Down
25 changes: 22 additions & 3 deletions packages/gatsby/src/bootstrap/load-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,19 @@ module.exports = async (config = {}) => {
extractPlugins(plugin)
})

// Validate plugins before saving. Plugins can only export known APIs. Collect
// any bad exports (either typos or outdated) and output an error and quit.
// Validate plugins before saving. Plugins can only export known APIs. The known
// APIs that a plugin supports are saved along with the plugin in the store for
// easier filtering later. If there are bad exports (either typos, outdated, or
// plain incorrect), then we output a readable error & quit.
const apis = _.keys(nodeAPIs)
const apiToPlugins = apis.reduce((acc, value) => {
acc[value] = []
return acc
}, {})
let badExports = []
flattenedPlugins.forEach(plugin => {
let gatsbyNode
plugin.nodeAPIs = []
try {
gatsbyNode = require(`${plugin.resolve}/gatsby-node`)
} catch (err) {
Expand All @@ -209,8 +216,15 @@ module.exports = async (config = {}) => {
}

if (gatsbyNode) {
const gatsbyNodeKeys = _.keys(gatsbyNode)
// Discover which nodeAPIs this plugin implements and store
// an array against the plugin node itself *and* in a node
// API to plugins map for faster lookups later.
plugin.nodeAPIs = _.intersection(gatsbyNodeKeys, apis)
plugin.nodeAPIs.map(nodeAPI => apiToPlugins[nodeAPI].push(plugin.name))
// Discover any exports from plugins which are not "known"
badExports = badExports.concat(
_.without(_.keys(gatsbyNode), ...apis).map(e => {
_.difference(gatsbyNodeKeys, apis).map(e => {
return {
exportName: e,
pluginName: plugin.name,
Expand Down Expand Up @@ -264,5 +278,10 @@ module.exports = async (config = {}) => {
payload: flattenedPlugins,
})

store.dispatch({
type: `SET_SITE_API_TO_PLUGINS`,
payload: apiToPlugins,
})

return flattenedPlugins
}
8 changes: 8 additions & 0 deletions packages/gatsby/src/redux/reducers/api-to-plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = (state = [], action) => {
switch (action.type) {
case `SET_SITE_API_TO_PLUGINS`:
return { ...action.payload }
default:
return state
}
}
1 change: 1 addition & 0 deletions packages/gatsby/src/redux/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
lastAction: require(`./last-action`),
plugins: require(`./plugins`),
flattenedPlugins: require(`./flattened-plugins`),
apiToPlugins: require(`./api-to-plugins`),
config: require(`./config`),
pages: require(`./pages`),
layouts: require(`./layouts`),
Expand Down
32 changes: 32 additions & 0 deletions packages/gatsby/src/utils/source-nodes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
const _ = require(`lodash`)
const report = require(`gatsby-cli/lib/reporter`)

const apiRunner = require(`./api-runner-node`)
const { store, getNode } = require(`../redux`)
const { boundActionCreators } = require(`../redux/actions`)
const { deleteNodes } = boundActionCreators

/**
* Finds the name of all plugins which implement Gatsby APIs that
* may create nodes, but which have not actually created any nodes.
*/
function discoverPluginsWithoutNodes(storeState) {
// Discover which plugins implement APIs which may create nodes
const nodeCreationPlugins = _.without(
_.union(
storeState.apiToPlugins.sourceNodes,
storeState.apiToPlugins.onCreateNode
),
`default-site-plugin`
)
// Find out which plugins own already created nodes
const nodeOwners = _.uniq(
_.values(storeState.nodes).reduce((acc, node) => {
acc.push(node.internal.owner)
return acc
}, [])
)
return _.difference(nodeCreationPlugins, nodeOwners)
}

module.exports = async () => {
await apiRunner(`sourceNodes`, {
traceId: `initial-sourceNodes`,
Expand All @@ -13,6 +37,14 @@ module.exports = async () => {

const state = store.getState()

// Warn about plugins that should have created nodes but didn't.
const pluginsWithNoNodes = discoverPluginsWithoutNodes(state)
pluginsWithNoNodes.map(name =>
report.warn(
`The ${name} plugin has generated no Gatsby nodes. Do you need it?`
)
)

// Garbage collect stale data nodes
const touchedNodes = Object.keys(state.nodesTouched)
const staleNodes = _.values(state.nodes).filter(node => {
Expand Down

0 comments on commit fba53c4

Please sign in to comment.