From 64ae90faab69e2c9b680cb166b7dc7e4a9d2cbee Mon Sep 17 00:00:00 2001 From: Darian Moody Date: Tue, 24 Oct 2017 02:10:51 +0100 Subject: [PATCH] Add warning when node-creating plugins create no nodes (#2588) * Add warning when node-creating plugins create no nodes 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 https://github.com/gatsbyjs/gatsby/issues/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. * Trigger builds --- .../__snapshots__/load-plugins.js.snap | 43 +++++++++++++++++++ packages/gatsby/src/bootstrap/load-plugins.js | 25 +++++++++-- .../src/redux/reducers/api-to-plugins.js | 8 ++++ packages/gatsby/src/redux/reducers/index.js | 1 + packages/gatsby/src/utils/source-nodes.js | 32 ++++++++++++++ 5 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 packages/gatsby/src/redux/reducers/api-to-plugins.js diff --git a/packages/gatsby/src/bootstrap/__tests__/__snapshots__/load-plugins.js.snap b/packages/gatsby/src/bootstrap/__tests__/__snapshots__/load-plugins.js.snap index 63203b94210ad..2602486084c00 100644 --- a/packages/gatsby/src/bootstrap/__tests__/__snapshots__/load-plugins.js.snap +++ b/packages/gatsby/src/bootstrap/__tests__/__snapshots__/load-plugins.js.snap @@ -5,6 +5,9 @@ Array [ Object { "id": "Plugin component-page-creator", "name": "component-page-creator", + "nodeAPIs": Array [ + "createPagesStatefully", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -14,6 +17,9 @@ Array [ Object { "id": "Plugin component-layout-creator", "name": "component-layout-creator", + "nodeAPIs": Array [ + "createLayouts", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -23,6 +29,10 @@ Array [ Object { "id": "Plugin internal-data-bridge", "name": "internal-data-bridge", + "nodeAPIs": Array [ + "sourceNodes", + "onCreatePage", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -32,6 +42,9 @@ Array [ Object { "id": "Plugin dev-404-page", "name": "dev-404-page", + "nodeAPIs": Array [ + "createPages", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -41,6 +54,9 @@ Array [ Object { "id": "Plugin prod-404", "name": "prod-404", + "nodeAPIs": Array [ + "onCreatePage", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -50,6 +66,10 @@ Array [ Object { "id": "Plugin query-runner", "name": "query-runner", + "nodeAPIs": Array [ + "onCreatePage", + "onCreateLayout", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -58,6 +78,7 @@ Array [ }, Object { "name": "TEST", + "nodeAPIs": Array [], "pluginOptions": Object { "plugins": Array [], }, @@ -66,6 +87,7 @@ Array [ Object { "id": "Plugin default-site-plugin", "name": "default-site-plugin", + "nodeAPIs": Array [], "pluginOptions": Object { "plugins": Array [], }, @@ -80,6 +102,9 @@ Array [ Object { "id": "Plugin component-page-creator", "name": "component-page-creator", + "nodeAPIs": Array [ + "createPagesStatefully", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -89,6 +114,9 @@ Array [ Object { "id": "Plugin component-layout-creator", "name": "component-layout-creator", + "nodeAPIs": Array [ + "createLayouts", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -98,6 +126,10 @@ Array [ Object { "id": "Plugin internal-data-bridge", "name": "internal-data-bridge", + "nodeAPIs": Array [ + "sourceNodes", + "onCreatePage", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -107,6 +139,9 @@ Array [ Object { "id": "Plugin dev-404-page", "name": "dev-404-page", + "nodeAPIs": Array [ + "createPages", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -116,6 +151,9 @@ Array [ Object { "id": "Plugin prod-404", "name": "prod-404", + "nodeAPIs": Array [ + "onCreatePage", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -125,6 +163,10 @@ Array [ Object { "id": "Plugin query-runner", "name": "query-runner", + "nodeAPIs": Array [ + "onCreatePage", + "onCreateLayout", + ], "pluginOptions": Object { "plugins": Array [], }, @@ -134,6 +176,7 @@ Array [ Object { "id": "Plugin default-site-plugin", "name": "default-site-plugin", + "nodeAPIs": Array [], "pluginOptions": Object { "plugins": Array [], }, diff --git a/packages/gatsby/src/bootstrap/load-plugins.js b/packages/gatsby/src/bootstrap/load-plugins.js index 82da423dd477d..bf7c95ea86937 100644 --- a/packages/gatsby/src/bootstrap/load-plugins.js +++ b/packages/gatsby/src/bootstrap/load-plugins.js @@ -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) { @@ -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, @@ -264,5 +278,10 @@ module.exports = async (config = {}) => { payload: flattenedPlugins, }) + store.dispatch({ + type: `SET_SITE_API_TO_PLUGINS`, + payload: apiToPlugins, + }) + return flattenedPlugins } diff --git a/packages/gatsby/src/redux/reducers/api-to-plugins.js b/packages/gatsby/src/redux/reducers/api-to-plugins.js new file mode 100644 index 0000000000000..2464def3e2ccb --- /dev/null +++ b/packages/gatsby/src/redux/reducers/api-to-plugins.js @@ -0,0 +1,8 @@ +module.exports = (state = [], action) => { + switch (action.type) { + case `SET_SITE_API_TO_PLUGINS`: + return { ...action.payload } + default: + return state + } +} diff --git a/packages/gatsby/src/redux/reducers/index.js b/packages/gatsby/src/redux/reducers/index.js index 3de1d0d924a61..2f224caac32ad 100644 --- a/packages/gatsby/src/redux/reducers/index.js +++ b/packages/gatsby/src/redux/reducers/index.js @@ -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`), diff --git a/packages/gatsby/src/utils/source-nodes.js b/packages/gatsby/src/utils/source-nodes.js index 04feee60f5311..4b5f31b5291d8 100644 --- a/packages/gatsby/src/utils/source-nodes.js +++ b/packages/gatsby/src/utils/source-nodes.js @@ -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`, @@ -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 => {