Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * In order to resolve frontmatter as markdown using the same infrastructure
- * as with the markdown body, we call the `setFieldsOnGraphQLNodeType` function
- * exported by gatsby-transformer-remark
- *
- * We get the field to operate on using the generated MarkdownRemarkFieldsEnum
- * In order to resolve field enum to a corresponding value, we must use the
- * getValueAt function, which is a utility function internal to gatsby.
- * We should find a more stable way of doing this.
- *
- * gatsby-transformer-remark's `setFieldsOnGraphQLNodeType` function
- * checks the `type` value first thing, returning `{}` if it is not set
- * to 'MarkdownRemark', so we must spoof that.
- *
- * We are not calling this function through gatsby from the plugin context,
- * so we must manually get the plugin options for gatsby-transformer-remark
- * by using the store and getting the flattenedPlugins value. Note that the
- * store is considered internal, and may change at any time. It would be
- * a good idea to find a better way of doing this.
- *
- * The `setFieldsOnGraphQLNodeType` function returns a map of resolvers
- * we can provide these resolvers with newly created nodes containing
- * the markdown content that we want to convert. Many other plugins expect
- * markdown nodes to be the children of File nodes, or nodes containing some
- * File properties, so first we get the parent node that owns this Frontmatter,
- * and merge most of the properties into the new node before creation
- *
- * We must guarantee that the markdown Node that we provide to the resolvers
- * has a unique `internal.contentDigest`
- *
- * This should remain stable unless the `setFieldsOnGraphQLNodeType` signature
- * changes, or gatsby-transformer-remark changes what resolvers they return,
- * or the store changes to not provide the flattened plugins
- *
- * As a fallback, if any errors are thrown during execution of this function,
- * our normal markdown stack using unified could be used, with a consequence
- * of a loss of functionality and performance
- *
- * @param {import('gatsby').API.NodeJS.SharedHelpers & { type?: null | { name: string } }} helpers
- * @returns {Promise<{ [key: string]: { resolver: Function } }>} resolver map
- */
- const getMarkdownResolvers = helpers => {
- /** @type {import('gatsby').API.NodeJS.Exports} */
- // @ts-ignore
- const gatsbyTransformerRemark = require('gatsby-transformer-remark/gatsby-node')
- // get the gatsby-transformer-remark plugin options
- // there should be a better way to do this, store is considered
- // internal
- const plugins = helpers.store.getState().flattenedPlugins
- const transformerRemarkPlugin = plugins.find(
- p => p.name === 'gatsby-transformer-remark',
- )
- if (!transformerRemarkPlugin)
- throw new Error('gatsby-transformer-remark plugin not found')
- const { setFieldsOnGraphQLNodeType } = gatsbyTransformerRemark
- if (!setFieldsOnGraphQLNodeType)
- throw new Error('gatsby-transformer-remark implementation changed')
- // bypass type check by gatsby-transformer-remark
- if (!helpers.type || helpers.type.name !== 'MarkdownRemark')
- helpers.type = { name: 'MarkdownRemark' }
- return setFieldsOnGraphQLNodeType(
- // @ts-ignore
- helpers,
- transformerRemarkPlugin.pluginOptions,
- )
- }
- // @ts-ignore
- const { getValueAt } = require('gatsby/dist/utils/get-value-at')
- /**
- * Gets a field to operate on from the frontmatter,
- * @param {import('gatsby').API.NodeJS.SharedHelpers} options
- * @param {'html' | 'htmlAst' | 'excerpt' | 'excerptAst' | 'headings' | 'timeToRead' | 'tableOfContents' | 'wordCount'} resolver
- * @returns {import('gatsby').API.NodeJS.Resolver<any, any, any>}
- */
- const wrappedRemarkResolver = (options, resolver) => async (
- source,
- allArgs,
- context,
- info,
- ) => {
- const {
- createNodeId,
- createContentDigest,
- actions: { createNode, createParentChildLink },
- } = options
- /** @type {{ field: string }} */
- let { field, ...args } = allArgs
- if (!field.startsWith('frontmatter.')) {
- throw new Error('field must be in frontmatter')
- }
- field = field.replace('frontmatter.', '')
- const value = getValueAt(source, field)
- if (typeof value !== 'string') return null
- // we get the parent markdown node
- // and pretend that the content is the
- // content of the frontmatter field instead
- const markdownNode = context.nodeModel.findRootNodeAncestor(source)
- /** @typedef {import('gatsby').Node} Node */
- /** @type {Node} */
- const parentNode = {
- ...markdownNode,
- id: createNodeId('fake-markdown'),
- parent: markdownNode && markdownNode.id,
- children: [],
- // @ts-ignore
- internal: {
- ...(markdownNode && markdownNode.internal),
- // we must set content, otherwise when loadNodeContent
- // is called in gatsby-transform-remark
- // if it isn't set, a search will go for the owner plugin
- // and request it to load the content and since the owner
- // does not exist, that will fail and an error will be thrown
- content: value,
- contentDigest:
- ((markdownNode && markdownNode.internal.contentDigest) || '') +
- createContentDigest(value),
- mediaType: 'text/markdown',
- type: FAKE_MARKDOWN_FILE_TYPE,
- },
- }
- // owner is created by gatsby based on plugin. throws on creation if set
- delete parentNode.internal.owner
- const node = await createNode(parentNode)
- // add a parent link if we have a parent markdown node
- if (markdownNode)
- createParentChildLink({ parent: markdownNode, child: parentNode })
- // sometimes an array is returned sometimes not. weird
- const realNode = Array.isArray(node) ? node[0] : node
- if (!realNode) return null
- // make sure correct type is sent to gatsby-transformer-remark resolvers
- const MarkdownRemarkType = info.schema.getType('MarkdownRemark')
- const resolvers = await getMarkdownResolvers({
- ...options,
- type: MarkdownRemarkType,
- })
- // we create nodes and don't clean up after
- // if we do delete them we get undefined errors
- // no amount of querying through graphql shows the
- // created nodes, so I expect they get cleaned up somehow
- // besides, since gatsby isn't a long-running process (just static generation)
- // a memory leak shouldn't be too big of an issue
- return resolvers[resolver].resolve(realNode, args, context, info)
- }
- module.exports = {
- createResolvers(options) {
- const { createResolvers } = options
- createResolvers({
- Frontmatter: {
- getHtml: {
- type: 'String',
- args: {
- field: {
- type: 'MarkdownRemarkFieldsEnum!',
- },
- },
- resolve: wrappedRemarkResolver(options, 'html'),
- },
- getHtmlAst: {
- type: 'JSON',
- args: {
- field: {
- type: 'MarkdownRemarkFieldsEnum!',
- },
- },
- resolve: wrappedRemarkResolver(options, 'htmlAst'),
- },
- }
- })
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement