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 browserAPIs and ssrAPIs to loaded plugin info #3989

Merged
merged 3 commits into from
Feb 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -74,24 +85,29 @@ 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 [],
"pluginOptions": Object {
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "d41d8cd98f00b204e9800998ecf8427e",
},
]
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -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 [
Expand All @@ -171,16 +198,19 @@ Array [
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "1.0.0",
},
Object {
"browserAPIs": Array [],
"id": "Plugin default-site-plugin",
"name": "default-site-plugin",
"nodeAPIs": Array [],
"pluginOptions": Object {
"plugins": Array [],
},
"resolve": "",
"ssrAPIs": Array [],
"version": "d41d8cd98f00b204e9800998ecf8427e",
},
]
Expand Down
178 changes: 116 additions & 62 deletions packages/gatsby/src/bootstrap/load-plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down Expand Up @@ -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`,
Expand Down