diff --git a/packages/gatsby-plugin-mdx/gatsby/on-create-node.js b/packages/gatsby-plugin-mdx/gatsby/on-create-node.js
index beaf50f2496bc..b33d246c6f6d9 100644
--- a/packages/gatsby-plugin-mdx/gatsby/on-create-node.js
+++ b/packages/gatsby-plugin-mdx/gatsby/on-create-node.js
@@ -6,7 +6,7 @@ const { createContentDigest } = require(`gatsby-core-utils`)
const defaultOptions = require(`../utils/default-options`)
const createMDXNode = require(`../utils/create-mdx-node`)
const { MDX_SCOPES_LOCATION } = require(`../constants`)
-const genMDX = require(`../utils/gen-mdx`)
+const { findImports } = require(`../utils/gen-mdx`)
const contentDigest = val => createContentDigest(val)
@@ -56,22 +56,19 @@ module.exports = async (
createParentChildLink({ parent: node, child: mdxNode })
// write scope files into .cache for later consumption
- const { scopeImports, scopeIdentifiers } = await genMDX(
- {
- node: mdxNode,
- getNode,
- getNodes,
- reporter,
- cache,
- pathPrefix,
- options,
- loadNodeContent,
- actions,
- createNodeId,
- ...helpers,
- },
- { forceDisableCache: true }
- )
+ const { scopeImports, scopeIdentifiers } = await findImports({
+ node: mdxNode,
+ getNode,
+ getNodes,
+ reporter,
+ cache,
+ pathPrefix,
+ options,
+ loadNodeContent,
+ actions,
+ createNodeId,
+ ...helpers,
+ })
await cacheScope({
cache,
scopeIdentifiers,
diff --git a/packages/gatsby-plugin-mdx/utils/__tests__/__snapshots__/import-parser.js.snap b/packages/gatsby-plugin-mdx/utils/__tests__/__snapshots__/import-parser.js.snap
new file mode 100644
index 0000000000000..95c6d73dfb034
--- /dev/null
+++ b/packages/gatsby-plugin-mdx/utils/__tests__/__snapshots__/import-parser.js.snap
@@ -0,0 +1,934 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 0 1`] = `
+Object {
+ "input": "import foo from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "foo",
+ ],
+ "segments": Array [
+ "foo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 1 1`] = `
+Object {
+ "input": "import foo as bar from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "bar",
+ ],
+ "segments": Array [
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 2 1`] = `
+Object {
+ "input": "import * as foo from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "foo",
+ ],
+ "segments": Array [
+ "* as foo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 3 1`] = `
+Object {
+ "input": "import {foo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "foo",
+ ],
+ "segments": Array [
+ "foo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 4 1`] = `
+Object {
+ "input": "import {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "bar",
+ ],
+ "segments": Array [
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 5 1`] = `
+Object {
+ "input": "import {foo, bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "foo",
+ "bar",
+ ],
+ "segments": Array [
+ "foo",
+ "bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 6 1`] = `
+Object {
+ "input": "import {foo, bar as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "foo",
+ "boo",
+ ],
+ "segments": Array [
+ "foo",
+ "bar as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 7 1`] = `
+Object {
+ "input": "import {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "bar",
+ ],
+ "segments": Array [
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 8 1`] = `
+Object {
+ "input": "import {foo as bar, baz} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "bar",
+ "baz",
+ ],
+ "segments": Array [
+ "foo as bar",
+ "baz",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 9 1`] = `
+Object {
+ "input": "import {foo as bar, baz as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "bar",
+ "boo",
+ ],
+ "segments": Array [
+ "foo as bar",
+ "baz as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 10 1`] = `
+Object {
+ "input": "import ding, {foo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "ding",
+ "foo",
+ ],
+ "segments": Array [
+ "ding",
+ "foo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 11 1`] = `
+Object {
+ "input": "import ding, {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "ding",
+ "bar",
+ ],
+ "segments": Array [
+ "ding",
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 12 1`] = `
+Object {
+ "input": "import ding, {foo, bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "ding",
+ "foo",
+ "bar",
+ ],
+ "segments": Array [
+ "ding",
+ "foo",
+ "bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 13 1`] = `
+Object {
+ "input": "import ding, {foo, bar as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "ding",
+ "foo",
+ "boo",
+ ],
+ "segments": Array [
+ "ding",
+ "foo",
+ "bar as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 14 1`] = `
+Object {
+ "input": "import ding, {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "ding",
+ "bar",
+ ],
+ "segments": Array [
+ "ding",
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 15 1`] = `
+Object {
+ "input": "import ding, {foo as bar, baz} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "ding",
+ "bar",
+ "baz",
+ ],
+ "segments": Array [
+ "ding",
+ "foo as bar",
+ "baz",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 16 1`] = `
+Object {
+ "input": "import ding, {foo as bar, baz as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "ding",
+ "bar",
+ "boo",
+ ],
+ "segments": Array [
+ "ding",
+ "foo as bar",
+ "baz as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 17 1`] = `
+Object {
+ "input": "import ding as dong, {foo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "foo",
+ ],
+ "segments": Array [
+ "ding as dong",
+ "foo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 18 1`] = `
+Object {
+ "input": "import ding as dong, {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ ],
+ "segments": Array [
+ "ding as dong",
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 19 1`] = `
+Object {
+ "input": "import ding as dong, {foo, bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "foo",
+ "bar",
+ ],
+ "segments": Array [
+ "ding as dong",
+ "foo",
+ "bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 20 1`] = `
+Object {
+ "input": "import ding as dong, {foo, bar as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "foo",
+ "boo",
+ ],
+ "segments": Array [
+ "ding as dong",
+ "foo",
+ "bar as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 21 1`] = `
+Object {
+ "input": "import ding as dong, {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ ],
+ "segments": Array [
+ "ding as dong",
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 22 1`] = `
+Object {
+ "input": "import ding as dong, {foo as bar, baz} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ "baz",
+ ],
+ "segments": Array [
+ "ding as dong",
+ "foo as bar",
+ "baz",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 23 1`] = `
+Object {
+ "input": "import ding as dong, {foo as bar, baz as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ "boo",
+ ],
+ "segments": Array [
+ "ding as dong",
+ "foo as bar",
+ "baz as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 24 1`] = `
+Object {
+ "input": "import * as dong, {foo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "foo",
+ ],
+ "segments": Array [
+ "* as dong",
+ "foo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 25 1`] = `
+Object {
+ "input": "import * as dong, {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ ],
+ "segments": Array [
+ "* as dong",
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 26 1`] = `
+Object {
+ "input": "import * as dong, {foo, bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "foo",
+ "bar",
+ ],
+ "segments": Array [
+ "* as dong",
+ "foo",
+ "bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 27 1`] = `
+Object {
+ "input": "import * as dong, {foo, bar as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "foo",
+ "boo",
+ ],
+ "segments": Array [
+ "* as dong",
+ "foo",
+ "bar as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 28 1`] = `
+Object {
+ "input": "import * as dong, {foo as bar} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ ],
+ "segments": Array [
+ "* as dong",
+ "foo as bar",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 29 1`] = `
+Object {
+ "input": "import * as dong, {foo as bar, baz} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ "baz",
+ ],
+ "segments": Array [
+ "* as dong",
+ "foo as bar",
+ "baz",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 30 1`] = `
+Object {
+ "input": "import * as dong, {foo as bar, baz as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "bar",
+ "boo",
+ ],
+ "segments": Array [
+ "* as dong",
+ "foo as bar",
+ "baz as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 31 1`] = `
+Object {
+ "input": "import * as $, {_ as bar, baz as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "$",
+ "bar",
+ "boo",
+ ],
+ "segments": Array [
+ "* as $",
+ "_ as bar",
+ "baz as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 32 1`] = `
+Object {
+ "input": "import _, {foo as $} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "_",
+ "$",
+ ],
+ "segments": Array [
+ "_",
+ "foo as $",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 33 1`] = `
+Object {
+ "input": "import _ as $ from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "$",
+ ],
+ "segments": Array [
+ "_ as $",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 34 1`] = `
+Object {
+ "input": "import {_, $ as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "_",
+ "boo",
+ ],
+ "segments": Array [
+ "_",
+ "$ as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 35 1`] = `
+Object {
+ "input": "import {_ as $, baz as boo} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "$",
+ "boo",
+ ],
+ "segments": Array [
+ "_ as $",
+ "baz as boo",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 36 1`] = `
+Object {
+ "input": "import {_, $} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "_",
+ "$",
+ ],
+ "segments": Array [
+ "_",
+ "$",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 37 1`] = `
+Object {
+ "input": "import as from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "as",
+ ],
+ "segments": Array [
+ "as",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 38 1`] = `
+Object {
+ "input": "import * as as from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "as",
+ ],
+ "segments": Array [
+ "* as as",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 39 1`] = `
+Object {
+ "input": "import from from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "from",
+ ],
+ "segments": Array [
+ "from",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 40 1`] = `
+Object {
+ "input": "import * as from from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "from",
+ ],
+ "segments": Array [
+ "* as from",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 41 1`] = `
+Object {
+ "input": "import as, {from} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "as",
+ "from",
+ ],
+ "segments": Array [
+ "as",
+ "from",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 42 1`] = `
+Object {
+ "input": "import as as x, {from as y} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "x",
+ "y",
+ ],
+ "segments": Array [
+ "as as x",
+ "from as y",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 43 1`] = `
+Object {
+ "input": "import x as as, {x as from} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "as",
+ "from",
+ ],
+ "segments": Array [
+ "x as as",
+ "x as from",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 44 1`] = `
+Object {
+ "input": "import from, {as} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "from",
+ "as",
+ ],
+ "segments": Array [
+ "from",
+ "as",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 45 1`] = `
+Object {
+ "input": "import from as x, {as as y} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "x",
+ "y",
+ ],
+ "segments": Array [
+ "from as x",
+ "as as y",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 46 1`] = `
+Object {
+ "input": "import x as from, {x as as} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "from",
+ "as",
+ ],
+ "segments": Array [
+ "x as from",
+ "x as as",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 47 1`] = `
+Object {
+ "input": "import {as, from} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "as",
+ "from",
+ ],
+ "segments": Array [
+ "as",
+ "from",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 48 1`] = `
+Object {
+ "input": "import {from, as} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "from",
+ "as",
+ ],
+ "segments": Array [
+ "from",
+ "as",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 49 1`] = `
+Object {
+ "input": "import {as as x, from as y} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "x",
+ "y",
+ ],
+ "segments": Array [
+ "as as x",
+ "from as y",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 50 1`] = `
+Object {
+ "input": "import {from as x, as as y} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "x",
+ "y",
+ ],
+ "segments": Array [
+ "from as x",
+ "as as y",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 51 1`] = `
+Object {
+ "input": "import {x as as, y as from} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "as",
+ "from",
+ ],
+ "segments": Array [
+ "x as as",
+ "y as from",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 52 1`] = `
+Object {
+ "input": "import {x as from, y as as} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "from",
+ "as",
+ ],
+ "segments": Array [
+ "x as from",
+ "y as as",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 53 1`] = `
+Object {
+ "input": "import {import as x} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "x",
+ ],
+ "segments": Array [
+ "import as x",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 54 1`] = `
+Object {
+ "input": "import {import as x, y} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "x",
+ "y",
+ ],
+ "segments": Array [
+ "import as x",
+ "y",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 55 1`] = `
+Object {
+ "input": "import {x, import as y} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "x",
+ "y",
+ ],
+ "segments": Array [
+ "x",
+ "import as y",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 56 1`] = `
+Object {
+ "input": "import Events from \\"@components/events/events\\"",
+ "result": Object {
+ "bindings": Array [
+ "Events",
+ ],
+ "segments": Array [
+ "Events",
+ ],
+ },
+}
+`;
+
+exports[`regex import scanner syntactic coverage should parse brute force regular case 57 1`] = `
+Object {
+ "input": "import multi as dong, {foo} from 'bar'
+import as as x, {from as y} from 'bar'",
+ "result": Object {
+ "bindings": Array [
+ "dong",
+ "foo",
+ "x",
+ "y",
+ ],
+ "segments": Array [
+ "multi as dong",
+ "foo",
+ "as as x",
+ "from as y",
+ ],
+ },
+}
+`;
diff --git a/packages/gatsby-plugin-mdx/utils/__tests__/import-parser.js b/packages/gatsby-plugin-mdx/utils/__tests__/import-parser.js
new file mode 100644
index 0000000000000..5c1861a64f0d9
--- /dev/null
+++ b/packages/gatsby-plugin-mdx/utils/__tests__/import-parser.js
@@ -0,0 +1,279 @@
+const { parseImportBindings } = require(`../import-parser`)
+const grayMatter = require(`gray-matter`)
+const mdx = require(`@mdx-js/mdx`)
+
+function getBruteForceCases() {
+ // These cases will be individually tested in four different ways;
+ // - as is
+ // - replace all spaces by newlines
+ // - minified (drop all spaces that are not mandatory)
+ // - replace all spaces by three spaces
+
+ const bruteForceCases = `
+ import foo from 'bar'
+ import foo as bar from 'bar'
+ import * as foo from 'bar'
+ import {foo} from 'bar'
+ import {foo as bar} from 'bar'
+ import {foo, bar} from 'bar'
+ import {foo, bar as boo} from 'bar'
+ import {foo as bar} from 'bar'
+ import {foo as bar, baz} from 'bar'
+ import {foo as bar, baz as boo} from 'bar'
+ import ding, {foo} from 'bar'
+ import ding, {foo as bar} from 'bar'
+ import ding, {foo, bar} from 'bar'
+ import ding, {foo, bar as boo} from 'bar'
+ import ding, {foo as bar} from 'bar'
+ import ding, {foo as bar, baz} from 'bar'
+ import ding, {foo as bar, baz as boo} from 'bar'
+ import ding as dong, {foo} from 'bar'
+ import ding as dong, {foo as bar} from 'bar'
+ import ding as dong, {foo, bar} from 'bar'
+ import ding as dong, {foo, bar as boo} from 'bar'
+ import ding as dong, {foo as bar} from 'bar'
+ import ding as dong, {foo as bar, baz} from 'bar'
+ import ding as dong, {foo as bar, baz as boo} from 'bar'
+ import * as dong, {foo} from 'bar'
+ import * as dong, {foo as bar} from 'bar'
+ import * as dong, {foo, bar} from 'bar'
+ import * as dong, {foo, bar as boo} from 'bar'
+ import * as dong, {foo as bar} from 'bar'
+ import * as dong, {foo as bar, baz} from 'bar'
+ import * as dong, {foo as bar, baz as boo} from 'bar'
+ import * as $, {_ as bar, baz as boo} from 'bar'
+ import _, {foo as $} from 'bar'
+ import _ as $ from 'bar'
+ import {_, $ as boo} from 'bar'
+ import {_ as $, baz as boo} from 'bar'
+ import {_, $} from 'bar'
+ import as from 'bar'
+ import * as as from 'bar'
+ import from from 'bar'
+ import * as from from 'bar'
+ import as, {from} from 'bar'
+ import as as x, {from as y} from 'bar'
+ import x as as, {x as from} from 'bar'
+ import from, {as} from 'bar'
+ import from as x, {as as y} from 'bar'
+ import x as from, {x as as} from 'bar'
+ import {as, from} from 'bar'
+ import {from, as} from 'bar'
+ import {as as x, from as y} from 'bar'
+ import {from as x, as as y} from 'bar'
+ import {x as as, y as from} from 'bar'
+ import {x as from, y as as} from 'bar'
+ import {import as x} from 'bar'
+ import {import as x, y} from 'bar'
+ import {x, import as y} from 'bar'
+ import Events from "@components/events/events"
+ `
+ .trim()
+ .split(/\n/g)
+ .map(s => s.trim())
+
+ // Add double cases
+ bruteForceCases.push(
+ `import multi as dong, {foo} from 'bar'\nimport as as x, {from as y} from 'bar'`
+ )
+
+ return bruteForceCases
+}
+
+describe(`regex import scanner`, () => {
+ describe(`syntactic coverage`, () => {
+ const cases = getBruteForceCases()
+
+ cases.forEach((input, i) => {
+ it(`should parse brute force regular case ${i}`, () => {
+ const output = parseImportBindings(input, true)
+ const bindings = output.bindings
+
+ expect(output.bindings.length).not.toBe(0)
+ // Note: putting everything in the snapshot makes reviews easier
+ expect({ input, result: output }).toMatchSnapshot()
+ expect(
+ // All bindings should be non-empty and trimmed
+ output.bindings.every(
+ binding => binding !== `` && binding === binding.trim()
+ )
+ ).toBe(true)
+
+ // Confirm that the parser works when all spaces become newlines
+ const newlined = input.replace(/ /g, `\n`)
+ expect(parseImportBindings(newlined)).toEqual(bindings)
+
+ // Confirm that the parser works with a minimal amount of spacing
+ const minified = input.replace(
+ /(?<=[_\w$]) (?![_\w$])|(? {
+ it(`double import ends up as one pseudo-node in md parser`, async () => {
+ // Note: the point of this test is to have two back2back imports clustered
+ // as one pseudo-node in the ast.
+
+ const { content } = grayMatter(`
+---
+title: double test
+---
+
+import Events from "@components/events/events"
+import EmailCaptureForm from "@components/email-capture-form"
+
+
+ `)
+
+ const compiler = mdx.createCompiler()
+ const fileOpts = { contents: content }
+ const mdast = await compiler.parse(fileOpts)
+
+ const imports = mdast.children.filter(obj => obj.type === `import`)
+
+ // Assert the md parser outputs same mdast (update test if this changes)
+ expect(
+ imports.map(({ type, value }) => {
+ return { type, value }
+ })
+ ).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "type": "import",
+ "value": "import Events from \\"@components/events/events\\"
+ import EmailCaptureForm from \\"@components/email-capture-form\\"",
+ },
+ ]
+ `)
+
+ // Take the imports being parsed and feed them to the import parser
+ expect(parseImportBindings(imports[0].value, true))
+ .toMatchInlineSnapshot(`
+ Object {
+ "bindings": Array [
+ "Events",
+ "EmailCaptureForm",
+ ],
+ "segments": Array [
+ "Events",
+ "EmailCaptureForm",
+ ],
+ }
+ `)
+ })
+
+ it(`triple imports without newlines`, async () => {
+ // Note: the point of this test is to have multiple back2back imports
+ // clustered as one pseudo-node in the ast.
+
+ const { content } = grayMatter(`
+---
+title: double test
+---
+
+import x, {frp, doo as dag} from "@components/events/events"
+import * as EmailCaptureForm from "@components/email-capture-form"
+import {A} from "@your/name"
+
+
+ `)
+
+ const compiler = mdx.createCompiler()
+ const fileOpts = { contents: content }
+ const mdast = await compiler.parse(fileOpts)
+
+ const imports = mdast.children.filter(obj => obj.type === `import`)
+
+ // Assert the md parser outputs same mdast (update test if this changes)
+ expect(
+ imports.map(({ type, value }) => {
+ return { type, value }
+ })
+ ).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "type": "import",
+ "value": "import x, {frp, doo as dag} from \\"@components/events/events\\"
+ import * as EmailCaptureForm from \\"@components/email-capture-form\\"
+ import {A} from \\"@your/name\\"",
+ },
+ ]
+ `)
+
+ // Take the imports being parsed and feed them to the import parser
+ expect(parseImportBindings(imports[0].value, true))
+ .toMatchInlineSnapshot(`
+ Object {
+ "bindings": Array [
+ "x",
+ "frp",
+ "dag",
+ "EmailCaptureForm",
+ "A",
+ ],
+ "segments": Array [
+ "x",
+ "frp",
+ "doo as dag",
+ "* as EmailCaptureForm",
+ "A",
+ ],
+ }
+ `)
+ })
+
+ it(`triple imports with newlines`, async () => {
+ // Note: the point of this test is to show that imports won't get
+ // clustered by the parser if there are empty lines between them
+
+ const { content } = grayMatter(`
+---
+title: double test
+---
+
+import Events from "@components/events/events"
+
+import EmailCaptureForm from "@components/email-capture-form"
+
+import {A} from "@your/name"
+
+
+ `)
+
+ const compiler = mdx.createCompiler()
+ const fileOpts = { contents: content }
+ const mdast = await compiler.parse(fileOpts)
+
+ const imports = mdast.children.filter(obj => obj.type === `import`)
+
+ expect(
+ imports.map(({ type, value }) => {
+ return { type, value }
+ })
+ ).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "type": "import",
+ "value": "import Events from \\"@components/events/events\\"",
+ },
+ Object {
+ "type": "import",
+ "value": "import EmailCaptureForm from \\"@components/email-capture-form\\"",
+ },
+ Object {
+ "type": "import",
+ "value": "import {A} from \\"@your/name\\"",
+ },
+ ]
+ `)
+ })
+ })
+})
diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js
index 15acbc6911bc3..d35bd60dc02b4 100644
--- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js
+++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js
@@ -9,6 +9,7 @@ const getSourcePluginsAsRemarkPlugins = require(`./get-source-plugins-as-remark-
const htmlAttrToJSXAttr = require(`./babel-plugin-html-attr-to-jsx-attr`)
const removeExportKeywords = require(`./babel-plugin-remove-export-keywords`)
const BabelPluginPluckImports = require(`./babel-plugin-pluck-imports`)
+const { parseImportBindings } = require(`./import-parser`)
/*
* function mutateNode({
@@ -39,7 +40,7 @@ const BabelPluginPluckImports = require(`./babel-plugin-pluck-imports`)
* }
* */
-module.exports = async function genMDX(
+async function genMDX(
{
isLoader,
node,
@@ -189,3 +190,83 @@ ${code}`
}
return results
}
+
+module.exports = genMDX // Legacy API, drop in v3 in favor of named export
+module.exports.genMDX = genMDX
+
+async function findImports({
+ node,
+ options,
+ getNode,
+ getNodes,
+ getNodesByType,
+ reporter,
+ cache,
+ pathPrefix,
+ ...helpers
+}) {
+ const { content } = grayMatter(node.rawBody)
+
+ const gatsbyRemarkPluginsAsremarkPlugins = await getSourcePluginsAsRemarkPlugins(
+ {
+ gatsbyRemarkPlugins: options.gatsbyRemarkPlugins,
+ mdxNode: node,
+ getNode,
+ getNodes,
+ getNodesByType,
+ reporter,
+ cache,
+ pathPrefix,
+ compiler: {
+ parseString: () => compiler.parse.bind(compiler),
+ generateHTML: ast => mdx(ast, options),
+ },
+ ...helpers,
+ }
+ )
+
+ const compilerOptions = {
+ filepath: node.fileAbsolutePath,
+ ...options,
+ remarkPlugins: [
+ ...options.remarkPlugins,
+ ...gatsbyRemarkPluginsAsremarkPlugins,
+ ],
+ }
+ const compiler = mdx.createCompiler(compilerOptions)
+
+ const fileOpts = { contents: content }
+ if (node.fileAbsolutePath) {
+ fileOpts.path = node.fileAbsolutePath
+ }
+
+ const mdast = await compiler.parse(fileOpts)
+
+ // Assuming valid code, identifiers must be unique (they are consts) so
+ // we don't need to dedupe the symbols here.
+ const identifiers = []
+ const imports = []
+
+ mdast.children.forEach(node => {
+ if (node.type !== `import`) return
+
+ const importCode = node.value
+
+ imports.push(importCode)
+
+ const bindings = parseImportBindings(importCode)
+ identifiers.push(...bindings)
+ })
+
+ if (!identifiers.includes(`React`)) {
+ identifiers.push(`React`)
+ imports.push(`import * as React from 'react'`)
+ }
+
+ return {
+ scopeImports: imports,
+ scopeIdentifiers: identifiers,
+ }
+}
+
+module.exports.findImports = findImports
diff --git a/packages/gatsby-plugin-mdx/utils/import-parser.js b/packages/gatsby-plugin-mdx/utils/import-parser.js
new file mode 100644
index 0000000000000..10702efb09d68
--- /dev/null
+++ b/packages/gatsby-plugin-mdx/utils/import-parser.js
@@ -0,0 +1,49 @@
+/**
+ * Parse source code containing (just) ES6 import declarations and return the
+ * names of all bindings created by such a declaration.
+ * The function will assume strict ES6 import code.
+ * The input may contain multiple import statements, each starting on a new line
+ * First it strips the irrelevant bits (like `import`, curly brackets, and
+ * `from` tail. What's left ought to be a string in the form of
+ * `id[ as id] [, id[ as id]]`
+ * (where the brackets represent optional repeating parts). The left-most id
+ * might also contain star (namespaced import).
+ * The second part will trim and split the string on comma, then each segment
+ * is split on `as` (in a proper way) and the right-most identifier is returned.
+ *
+ * For testing purposes you can also request the segments, after split on comma.
+ *
+ * @param {string} importCode
+ * @param {boolean=false} returnSegments
+ * @returns {Array | {bindings: Array, segments: Array}}
+ */
+function parseImportBindings(importCode, returnSegments = false) {
+ const str = importCode.replace(
+ /^\s*import|[{},]|\s*from\s*['"][^'"]*?['"]\s*$/gm,
+ ` , `
+ )
+ const segments = str
+ .trim()
+ .split(/\s*,\s*/g)
+ .filter(s => s !== ``)
+ const bindings = segments.map(
+ segment =>
+ // `s` is either an ident (the binding), or `a as b` where `b` is the
+ // binding. We split on `as` (taking spacing edge cases into account)
+ // and return the right-most ident that is left, trimmed.
+ // If `as` is used, it must be followed by some kind of spacing.
+ // Notable edge case: `*as as` is legit, namespace importing to var `as`
+ segment.split(/as\s+(?=[\w\d$_]+$)/).pop()
+ // Note: since `s` was trimmed, and the split consumed any spacing after
+ // the `as`, the result ident must be trimmed.
+ )
+ if (returnSegments) {
+ // For snapshot testing
+ return { bindings, segments }
+ }
+ return bindings
+}
+
+module.exports = {
+ parseImportBindings,
+}