diff --git a/packages/gatsby/src/bootstrap/__mocks__/require/exports.js b/packages/gatsby/src/bootstrap/__mocks__/require/exports.js new file mode 100644 index 0000000000000..18940aa15ea3b --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/require/exports.js @@ -0,0 +1,2 @@ +exports.foo = () => {} +exports.bar = () => {} diff --git a/packages/gatsby/src/bootstrap/__mocks__/require/unusual-exports.js b/packages/gatsby/src/bootstrap/__mocks__/require/unusual-exports.js new file mode 100644 index 0000000000000..aa7a6390900d1 --- /dev/null +++ b/packages/gatsby/src/bootstrap/__mocks__/require/unusual-exports.js @@ -0,0 +1,6 @@ +Object.defineProperty(exports, `foo`, { + enumerable: true, + get: function get() { + return () => {} + }, +}) diff --git a/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.js b/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.js index 9eefec17552ac..4f0cf11755f12 100644 --- a/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.js +++ b/packages/gatsby/src/bootstrap/__tests__/resolve-module-exports.js @@ -134,7 +134,7 @@ describe(`Resolve module exports`, () => { }) it(`Show meaningful error message for invalid JavaScript`, () => { - resolveModuleExports(`/bad/file`, resolver) + resolveModuleExports(`/bad/file`, { resolver }) expect( reporter.panic.mock.calls.map(c => // Remove console colors + trim whitespace @@ -145,57 +145,75 @@ describe(`Resolve module exports`, () => { }) it(`Resolves an export`, () => { - const result = resolveModuleExports(`/simple/export`, resolver) + const result = resolveModuleExports(`/simple/export`, { resolver }) expect(result).toEqual([`foo`]) }) it(`Resolves multiple exports`, () => { - const result = resolveModuleExports(`/multiple/export`, resolver) + const result = resolveModuleExports(`/multiple/export`, { resolver }) expect(result).toEqual([`bar`, `baz`, `foo`]) }) it(`Resolves an export from an ES6 file`, () => { - const result = resolveModuleExports(`/import/with/export`, resolver) + const result = resolveModuleExports(`/import/with/export`, { resolver }) expect(result).toEqual([`baz`]) }) it(`Resolves an exported const`, () => { - const result = resolveModuleExports(`/export/const`, resolver) + const result = resolveModuleExports(`/export/const`, { resolver }) expect(result).toEqual([`fooConst`]) }) it(`Resolves module.exports`, () => { - const result = resolveModuleExports(`/module/exports`, resolver) + const result = resolveModuleExports(`/module/exports`, { resolver }) expect(result).toEqual([`barExports`]) }) it(`Resolves exports from a larger file`, () => { - const result = resolveModuleExports(`/realistic/export`, resolver) + const result = resolveModuleExports(`/realistic/export`, { resolver }) expect(result).toEqual([`replaceHistory`, `replaceComponentRenderer`]) }) it(`Ignores exports.__esModule`, () => { - const result = resolveModuleExports(`/esmodule/export`, resolver) + const result = resolveModuleExports(`/esmodule/export`, { resolver }) expect(result).toEqual([`foo`]) }) it(`Resolves a named export`, () => { - const result = resolveModuleExports(`/export/named`, resolver) + const result = resolveModuleExports(`/export/named`, { resolver }) expect(result).toEqual([`foo`]) }) it(`Resolves a named export from`, () => { - const result = resolveModuleExports(`/export/named/from`, resolver) + const result = resolveModuleExports(`/export/named/from`, { resolver }) expect(result).toEqual([`Component`]) }) it(`Resolves a named export as`, () => { - const result = resolveModuleExports(`/export/named/as`, resolver) + const result = resolveModuleExports(`/export/named/as`, { resolver }) expect(result).toEqual([`bar`]) }) it(`Resolves multiple named exports`, () => { - const result = resolveModuleExports(`/export/named/multiple`, resolver) + const result = resolveModuleExports(`/export/named/multiple`, { resolver }) expect(result).toEqual([`foo`, `bar`, `baz`]) }) + + it(`Resolves exports when using require mode - simple case`, () => { + jest.mock(`require/exports`) + + const result = resolveModuleExports(`require/exports`, { + mode: `require`, + }) + expect(result).toEqual([`foo`, `bar`]) + }) + + it(`Resolves exports when using require mode - unusual case`, () => { + jest.mock(`require/unusual-exports`) + + const result = resolveModuleExports(`require/unusual-exports`, { + mode: `require`, + }) + expect(result).toEqual([`foo`]) + }) }) diff --git a/packages/gatsby/src/bootstrap/load-plugins/validate.js b/packages/gatsby/src/bootstrap/load-plugins/validate.js index cbfeecaafa675..140631673a64e 100644 --- a/packages/gatsby/src/bootstrap/load-plugins/validate.js +++ b/packages/gatsby/src/bootstrap/load-plugins/validate.js @@ -120,7 +120,10 @@ const collatePluginAPIs = ({ apis, flattenedPlugins }) => { // the plugin node itself *and* in an API to plugins map for faster lookups // later. const pluginNodeExports = resolveModuleExports( - `${plugin.resolve}/gatsby-node` + `${plugin.resolve}/gatsby-node`, + { + mode: `require`, + } ) const pluginBrowserExports = resolveModuleExports( `${plugin.resolve}/gatsby-browser` diff --git a/packages/gatsby/src/bootstrap/resolve-module-exports.js b/packages/gatsby/src/bootstrap/resolve-module-exports.js index 6f61e9ade732c..368329ac77817 100644 --- a/packages/gatsby/src/bootstrap/resolve-module-exports.js +++ b/packages/gatsby/src/bootstrap/resolve-module-exports.js @@ -6,16 +6,7 @@ const { codeFrameColumns } = require(`@babel/code-frame`) const { babelParseToAst } = require(`../utils/babel-parse-to-ast`) const report = require(`gatsby-cli/lib/reporter`) -/** - * Given a `require.resolve()` compatible path pointing to a JS module, - * return an array listing the names of the module's exports. - * - * Returns [] for invalid paths and modules without exports. - * - * @param {string} modulePath - * @param {function} resolver - */ -module.exports = (modulePath, resolver = require.resolve) => { +const staticallyAnalyzeExports = (modulePath, resolver = require.resolve) => { let absPath const exportNames = [] @@ -118,3 +109,25 @@ https://gatsby.dev/no-mixed-modules } return exportNames } + +/** + * Given a `require.resolve()` compatible path pointing to a JS module, + * return an array listing the names of the module's exports. + * + * Returns [] for invalid paths and modules without exports. + * + * @param {string} modulePath + * @param {string} mode + * @param {function} resolver + */ +module.exports = (modulePath, { mode = `analysis`, resolver } = {}) => { + if (mode === `require`) { + try { + return Object.keys(require(modulePath)) + } catch { + return [] + } + } else { + return staticallyAnalyzeExports(modulePath, resolver) + } +}