From 9e02abeacff5668c1b9aafde385113acc3ffe032 Mon Sep 17 00:00:00 2001 From: LB Date: Thu, 30 Jul 2020 16:17:31 -0400 Subject: [PATCH] perf(gatsby-plugin-mdx): performance changes (#26004) Re-submit of #24595 Drops a babel step This Babel step was used to get exports and remark header but we can use the mdast for this purpose, just like for imports. > This cuts off 20 to 30% of the total runtime of a baseline mdx benchmark! Co-authored-by: Laurie Barth Co-authored-by: Peter van der Zee --- .../gatsby/on-create-node.js | 21 ++++--- .../gatsby-plugin-mdx/loaders/mdx-loader.js | 12 +++- .../loaders/mdx-loader.test.js | 4 +- .../utils/create-mdx-node.js | 57 +++++-------------- packages/gatsby-plugin-mdx/utils/gen-mdx.js | 33 ++++++----- .../utils/mdx-node-with-scope.js | 45 +++++++++++++++ 6 files changed, 99 insertions(+), 73 deletions(-) create mode 100644 packages/gatsby-plugin-mdx/utils/mdx-node-with-scope.js diff --git a/packages/gatsby-plugin-mdx/gatsby/on-create-node.js b/packages/gatsby-plugin-mdx/gatsby/on-create-node.js index b33d246c6f6d9..380b47f858e3d 100644 --- a/packages/gatsby-plugin-mdx/gatsby/on-create-node.js +++ b/packages/gatsby-plugin-mdx/gatsby/on-create-node.js @@ -4,9 +4,8 @@ const babel = require(`@babel/core`) const { createContentDigest } = require(`gatsby-core-utils`) const defaultOptions = require(`../utils/default-options`) -const createMDXNode = require(`../utils/create-mdx-node`) +const createMDXNodeWithScope = require(`../utils/mdx-node-with-scope`) const { MDX_SCOPES_LOCATION } = require(`../constants`) -const { findImports } = require(`../utils/gen-mdx`) const contentDigest = val => createContentDigest(val) @@ -46,18 +45,14 @@ module.exports = async ( const content = await loadNodeContent(node) - const mdxNode = await createMDXNode({ + const { + mdxNode, + scopeIdentifiers, + scopeImports, + } = await createMDXNodeWithScope({ id: createNodeId(`${node.id} >>> Mdx`), node, content, - }) - - createNode(mdxNode) - createParentChildLink({ parent: node, child: mdxNode }) - - // write scope files into .cache for later consumption - const { scopeImports, scopeIdentifiers } = await findImports({ - node: mdxNode, getNode, getNodes, reporter, @@ -69,6 +64,10 @@ module.exports = async ( createNodeId, ...helpers, }) + + createNode(mdxNode) + createParentChildLink({ parent: node, child: mdxNode }) + await cacheScope({ cache, scopeIdentifiers, diff --git a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js index 962090df3cec9..30f61d6943197 100644 --- a/packages/gatsby-plugin-mdx/loaders/mdx-loader.js +++ b/packages/gatsby-plugin-mdx/loaders/mdx-loader.js @@ -25,7 +25,7 @@ const debugMore = require(`debug`)(`gatsby-plugin-mdx-info:mdx-loader`) const genMdx = require(`../utils/gen-mdx`) const withDefaultOptions = require(`../utils/default-options`) -const createMDXNode = require(`../utils/create-mdx-node`) +const createMDXNodeWithScope = require(`../utils/mdx-node-with-scope`) const { createFileNode } = require(`../utils/create-fake-file-node`) const DEFAULT_OPTIONS = { @@ -94,6 +94,7 @@ module.exports = async function (content) { const { getNode: rawGetNode, getNodes, + getNodesByType, reporter, cache, pathPrefix, @@ -121,11 +122,15 @@ module.exports = async function (content) { let mdxNode try { - mdxNode = await createMDXNode({ + // This node attempts to break the chicken-egg problem, where parsing mdx + // allows for custom plugins, which can receive a mdx node + ;({ mdxNode } = await createMDXNodeWithScope({ id: `fakeNodeIdMDXFileABugIfYouSeeThis`, node: fileNode, content, - }) + options, + getNodesByType, + })) } catch (e) { return callback(e) } @@ -192,6 +197,7 @@ ${contentWithoutFrontmatter}` node: { ...mdxNode, rawBody: code }, getNode, getNodes, + getNodesByType, reporter, cache, pathPrefix, diff --git a/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js b/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js index 397e8c28ba308..6610550442e31 100644 --- a/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js +++ b/packages/gatsby-plugin-mdx/loaders/mdx-loader.test.js @@ -16,9 +16,9 @@ array: [1,2,3] )`, namedExports: `export const meta = {author: "chris"}`, body: `# Some title - + a bit of a paragraph - + some content`, } diff --git a/packages/gatsby-plugin-mdx/utils/create-mdx-node.js b/packages/gatsby-plugin-mdx/utils/create-mdx-node.js index 1071f9dbad46a..6c2684dce148d 100644 --- a/packages/gatsby-plugin-mdx/utils/create-mdx-node.js +++ b/packages/gatsby-plugin-mdx/utils/create-mdx-node.js @@ -1,46 +1,17 @@ -const { createContentDigest } = require(`gatsby-core-utils`) +const withDefaultOptions = require(`../utils/default-options`) +const { getNodesByType } = require(`gatsby/dist/redux/nodes.js`) +const createMdxNodeWithScope = require(`../utils/mdx-node-with-scope`) -const mdx = require(`../utils/mdx`) -const extractExports = require(`../utils/extract-exports`) - -module.exports = async ({ id, node, content }) => { - let code - try { - code = await mdx(content) - } catch (e) { - // add the path of the file to simplify debugging error messages - e.message += `${node.absolutePath}: ${e.message}` - throw e - } - - // extract all the exports - const { frontmatter, ...nodeExports } = extractExports(code) - - const mdxNode = { +async function createMdxNodeLegacy({ id, node, content } = {}) { + const nodeWithScope = await createMdxNodeWithScope({ id, - children: [], - parent: node.id, - internal: { - content: content, - type: `Mdx`, - }, - } - - mdxNode.frontmatter = { - title: ``, // always include a title - ...frontmatter, - } - - mdxNode.excerpt = frontmatter.excerpt - mdxNode.exports = nodeExports - mdxNode.rawBody = content - - // Add path to the markdown file path - if (node.internal.type === `File`) { - mdxNode.fileAbsolutePath = node.absolutePath - } - - mdxNode.internal.contentDigest = createContentDigest(mdxNode) - - return mdxNode + node, + content, + getNodesByType, + options: withDefaultOptions({ plugins: [] }), + }) + return nodeWithScope.mdxNode } + +// This function is deprecated in favor of createMDXNodeWithScope and slated to be dropped in v3 +module.exports = createMdxNodeLegacy diff --git a/packages/gatsby-plugin-mdx/utils/gen-mdx.js b/packages/gatsby-plugin-mdx/utils/gen-mdx.js index 05c7df33cec80..3c2d9105122a9 100644 --- a/packages/gatsby-plugin-mdx/utils/gen-mdx.js +++ b/packages/gatsby-plugin-mdx/utils/gen-mdx.js @@ -194,8 +194,10 @@ ${code}` module.exports = genMDX // Legacy API, drop in v3 in favor of named export module.exports.genMDX = genMDX -async function findImports({ +async function findImportsExports({ node, + rawInput, + absolutePath = null, options, getNode, getNodes, @@ -205,7 +207,7 @@ async function findImports({ pathPrefix, ...helpers }) { - const { content } = grayMatter(node.rawBody) + const { data: frontmatter, content } = grayMatter(rawInput) const gatsbyRemarkPluginsAsremarkPlugins = await getSourcePluginsAsRemarkPlugins( { @@ -226,7 +228,7 @@ async function findImports({ ) const compilerOptions = { - filepath: node.fileAbsolutePath, + filepath: absolutePath, ...options, remarkPlugins: [ ...options.remarkPlugins, @@ -236,8 +238,8 @@ async function findImports({ const compiler = mdx.createCompiler(compilerOptions) const fileOpts = { contents: content } - if (node.fileAbsolutePath) { - fileOpts.path = node.fileAbsolutePath + if (absolutePath) { + fileOpts.path = absolutePath } const mdast = await compiler.parse(fileOpts) @@ -246,16 +248,17 @@ async function findImports({ // we don't need to dedupe the symbols here. const identifiers = [] const imports = [] + const exports = [] mdast.children.forEach(node => { - if (node.type !== `import`) return - - const importCode = node.value - - imports.push(importCode) - - const bindings = parseImportBindings(importCode) - identifiers.push(...bindings) + if (node.type === `import`) { + const importCode = node.value + imports.push(importCode) + const bindings = parseImportBindings(importCode) + identifiers.push(...bindings) + } else if (node.type === `export`) { + exports.push(node.value) + } }) if (!identifiers.includes(`React`)) { @@ -264,9 +267,11 @@ async function findImports({ } return { + frontmatter, scopeImports: imports, + scopeExports: exports, scopeIdentifiers: identifiers, } } -module.exports.findImports = findImports +module.exports.findImportsExports = findImportsExports diff --git a/packages/gatsby-plugin-mdx/utils/mdx-node-with-scope.js b/packages/gatsby-plugin-mdx/utils/mdx-node-with-scope.js new file mode 100644 index 0000000000000..ecbd38f2ca292 --- /dev/null +++ b/packages/gatsby-plugin-mdx/utils/mdx-node-with-scope.js @@ -0,0 +1,45 @@ +const { createContentDigest } = require(`gatsby-core-utils`) + +const { findImportsExports } = require(`../utils/gen-mdx`) + +async function createMdxNodeWithScope({ id, node, content, ...helpers }) { + const { + frontmatter, + scopeImports, + scopeExports, + scopeIdentifiers, + } = await findImportsExports({ + node, + rawInput: content, + absolutePath: node.absolutePath, + ...helpers, + }) + + const mdxNode = { + id, + children: [], + parent: node.id, + internal: { + content, + type: `Mdx`, + }, + excerpt: frontmatter.excerpt, + exports: scopeExports, + rawBody: content, + frontmatter: { + title: ``, // always include a title + ...frontmatter, + }, + } + + // Add path to the markdown file path + if (node.internal.type === `File`) { + mdxNode.fileAbsolutePath = node.absolutePath + } + + mdxNode.internal.contentDigest = createContentDigest(mdxNode) + + return { mdxNode, scopeIdentifiers, scopeImports } +} + +module.exports = createMdxNodeWithScope