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 06aff2ffc9522..4609e91b310ea 100644 --- a/packages/gatsby/src/bootstrap/__tests__/__snapshots__/load-plugins.js.snap +++ b/packages/gatsby/src/bootstrap/__tests__/__snapshots__/load-plugins.js.snap @@ -3,6 +3,7 @@ exports[`Load plugins Loads plugins defined with an object but without an option key 1`] = ` Array [ Object { + "browserAPIs": Array [], "id": "Plugin dev-404-page", "name": "dev-404-page", "nodeAPIs": Array [ @@ -12,9 +13,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin component-page-creator", "name": "component-page-creator", "nodeAPIs": Array [ @@ -24,9 +27,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin component-layout-creator", "name": "component-layout-creator", "nodeAPIs": Array [ @@ -36,9 +41,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin internal-data-bridge", "name": "internal-data-bridge", "nodeAPIs": Array [ @@ -49,9 +56,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin prod-404", "name": "prod-404", "nodeAPIs": Array [ @@ -61,9 +70,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin query-runner", "name": "query-runner", "nodeAPIs": Array [ @@ -74,17 +85,21 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "name": "TEST", "nodeAPIs": Array [], "pluginOptions": Object { "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], }, Object { + "browserAPIs": Array [], "id": "Plugin default-site-plugin", "name": "default-site-plugin", "nodeAPIs": Array [], @@ -92,6 +107,7 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "d41d8cd98f00b204e9800998ecf8427e", }, ] @@ -100,6 +116,7 @@ Array [ exports[`Load plugins load plugins for a site 1`] = ` Array [ Object { + "browserAPIs": Array [], "id": "Plugin dev-404-page", "name": "dev-404-page", "nodeAPIs": Array [ @@ -109,9 +126,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin component-page-creator", "name": "component-page-creator", "nodeAPIs": Array [ @@ -121,9 +140,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin component-layout-creator", "name": "component-layout-creator", "nodeAPIs": Array [ @@ -133,9 +154,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin internal-data-bridge", "name": "internal-data-bridge", "nodeAPIs": Array [ @@ -146,9 +169,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin prod-404", "name": "prod-404", "nodeAPIs": Array [ @@ -158,9 +183,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin query-runner", "name": "query-runner", "nodeAPIs": Array [ @@ -171,9 +198,11 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "1.0.0", }, Object { + "browserAPIs": Array [], "id": "Plugin default-site-plugin", "name": "default-site-plugin", "nodeAPIs": Array [], @@ -181,6 +210,7 @@ Array [ "plugins": Array [], }, "resolve": "", + "ssrAPIs": Array [], "version": "d41d8cd98f00b204e9800998ecf8427e", }, ] diff --git a/packages/gatsby/src/bootstrap/load-plugins.js b/packages/gatsby/src/bootstrap/load-plugins.js index 47331eef13114..0f44b55b9ac15 100644 --- a/packages/gatsby/src/bootstrap/load-plugins.js +++ b/packages/gatsby/src/bootstrap/load-plugins.js @@ -10,6 +10,80 @@ const nodeAPIs = require(`../utils/api-node-docs`) const testRequireError = require(`../utils/test-require-error`) const report = require(`gatsby-cli/lib/reporter`) +// Given a plugin object and a moduleName like `gatsby-node`, check that the +// path to moduleName can be resolved. +const resolvePluginModule = (plugin, moduleName) => { + let resolved = false + try { + resolved = require(`${plugin.resolve}/${moduleName}`) + } catch (err) { + if (!testRequireError(moduleName, err)) { + // ignore + } else { + report.panic(`Error requiring ${plugin.resolve}/${moduleName}.js`, err) + } + } + return resolved +} + +// Given a plugin object, an array of the API names it exports and an +// array of valid API names, return an array of invalid API exports. +const getBadExports = (plugin, pluginAPIKeys, apis) => { + let badExports = [] + // Discover any exports from plugins which are not "known" + badExports = badExports.concat( + _.difference(pluginAPIKeys, apis).map(e => { + return { + exportName: e, + pluginName: plugin.name, + pluginVersion: plugin.version, + } + }) + ) + return badExports +} + +const getBadExportsMessage = (badExports, exportType, apis) => { + const { stripIndent } = require(`common-tags`) + const stringSimiliarity = require(`string-similarity`) + let capitalized = `${exportType[0].toUpperCase()}${exportType.slice(1)}` + if (capitalized === `Ssr`) capitalized = `SSR` + + let message = `\n` + message += stripIndent` + Your plugins must export known APIs from their gatsby-${exportType}.js. + The following exports aren't APIs. Perhaps you made a typo or + your plugin is outdated? + + See https://www.gatsbyjs.org/docs/${exportType}-apis/ for the list of Gatsby ${capitalized} APIs` + + badExports.forEach(bady => { + const similarities = stringSimiliarity.findBestMatch( + bady.exportName, + apis + ) + message += `\n — ` + if (bady.pluginName == `default-site-plugin`) { + message += `Your site's gatsby-${exportType}.js is exporting a variable named "${ + bady.exportName + }" which isn't an API.` + } else { + message += `The plugin "${bady.pluginName}@${ + bady.pluginVersion + }" is exporting a variable named "${ + bady.exportName + }" which isn't an API.` + } + if (similarities.bestMatch.rating > 0.5) { + message += ` Perhaps you meant to export "${ + similarities.bestMatch.target + }"?` + } + }) + + return message +} + function createFileContentHash(root, globPattern) { const hash = crypto.createHash(`md5`) const files = glob.sync(`${root}/${globPattern}`, { nodir: true }) @@ -201,79 +275,59 @@ module.exports = async (config = {}) => { acc[value] = [] return acc }, {}) - let badExports = [] + + + const badExports = { + node: [], + browser: [], + ssr: [], + } + flattenedPlugins.forEach(plugin => { - let gatsbyNode plugin.nodeAPIs = [] - try { - gatsbyNode = require(`${plugin.resolve}/gatsby-node`) - } catch (err) { - if (!testRequireError(`gatsby-node`, err)) { - // ignore - } else { - report.panic(`Error requiring ${plugin.resolve}/gatsby-node.js`, err) - } - } + plugin.browserAPIs = [] + plugin.ssrAPIs = [] + const gatsbyNode = resolvePluginModule(plugin, `gatsby-node`) + const gatsbyBrowser = resolvePluginModule(plugin, `gatsby-browser`) + const gatsbySSR = resolvePluginModule(plugin, `gatsby-ssr`) + + // Discover which APIs this plugin implements and store an array against + // the plugin node itself *and* in an API to plugins map for faster lookups + // later. 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( - _.difference(gatsbyNodeKeys, apis).map(e => { - return { - exportName: e, - pluginName: plugin.name, - pluginVersion: plugin.version, - } - }) - ) + badExports.node = getBadExports(plugin, gatsbyNodeKeys, apis) // Collate any bad exports + } + + if (gatsbyBrowser) { + const gatsbyBrowserKeys = _.keys(gatsbyBrowser) + plugin.browserAPIs = _.intersection(gatsbyBrowserKeys, apis) + plugin.browserAPIs.map(browserAPI => apiToPlugins[browserAPI].push(plugin.name)) + badExports.browser = getBadExports(plugin, gatsbyBrowserKeys, apis) // Collate any bad exports + } + + if (gatsbySSR) { + const gatsbySSRKeys = _.keys(gatsbySSR) + plugin.ssrAPIs = _.intersection(gatsbySSRKeys, apis) + plugin.ssrAPIs.map(ssrAPI => apiToPlugins[ssrAPI].push(plugin.name)) + badExports.ssr = getBadExports(plugin, gatsbySSRKeys, apis) // Collate any bad exports } }) - if (badExports.length > 0) { - const stringSimiliarity = require(`string-similarity`) - const { stripIndent } = require(`common-tags`) - console.log(`\n`) - console.log( - stripIndent` - Your plugins must export known APIs from their gatsby-node.js. - The following exports aren't APIs. Perhaps you made a typo or - your plugin is outdated? - - See https://www.gatsbyjs.org/docs/node-apis/ for the list of Gatsby Node APIs` - ) - badExports.forEach(bady => { - const similarities = stringSimiliarity.findBestMatch( - bady.exportName, - apis - ) - let message = `\n — ` - if (bady.pluginName == `default-site-plugin`) { - message += `Your site's gatsby-node.js is exporting a variable named "${ - bady.exportName - }" which isn't an API.` - } else { - message += `The plugin "${bady.pluginName}@${ - bady.pluginVersion - }" is exporting a variable named "${ - bady.exportName - }" which isn't an API.` - } - if (similarities.bestMatch.rating > 0.5) { - message += ` Perhaps you meant to export "${ - similarities.bestMatch.target - }"?` - } + // Output error messages for all bad exports + let bad = false + _.toPairs(badExports).forEach(bad => { + const [exportType, entries] = bad + if (entries.length > 0) { + bad = true + console.log(getBadExportsMessage(entries, exportType, apis)) + } + }) - console.log(message) - }) - process.exit() - } + if (bad) process.exit() store.dispatch({ type: `SET_SITE_PLUGINS`,