Advertisement
Guest User

Untitled

a guest
Jun 26th, 2019
202
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.77 KB | None | 0 0
  1. /**
  2. * In order to resolve frontmatter as markdown using the same infrastructure
  3. * as with the markdown body, we call the `setFieldsOnGraphQLNodeType` function
  4. * exported by gatsby-transformer-remark
  5. *
  6. * We get the field to operate on using the generated MarkdownRemarkFieldsEnum
  7. * In order to resolve field enum to a corresponding value, we must use the
  8. * getValueAt function, which is a utility function internal to gatsby.
  9. * We should find a more stable way of doing this.
  10. *
  11. * gatsby-transformer-remark's `setFieldsOnGraphQLNodeType` function
  12. * checks the `type` value first thing, returning `{}` if it is not set
  13. * to 'MarkdownRemark', so we must spoof that.
  14. *
  15. * We are not calling this function through gatsby from the plugin context,
  16. * so we must manually get the plugin options for gatsby-transformer-remark
  17. * by using the store and getting the flattenedPlugins value. Note that the
  18. * store is considered internal, and may change at any time. It would be
  19. * a good idea to find a better way of doing this.
  20. *
  21. * The `setFieldsOnGraphQLNodeType` function returns a map of resolvers
  22. * we can provide these resolvers with newly created nodes containing
  23. * the markdown content that we want to convert. Many other plugins expect
  24. * markdown nodes to be the children of File nodes, or nodes containing some
  25. * File properties, so first we get the parent node that owns this Frontmatter,
  26. * and merge most of the properties into the new node before creation
  27. *
  28. * We must guarantee that the markdown Node that we provide to the resolvers
  29. * has a unique `internal.contentDigest`
  30. *
  31. * This should remain stable unless the `setFieldsOnGraphQLNodeType` signature
  32. * changes, or gatsby-transformer-remark changes what resolvers they return,
  33. * or the store changes to not provide the flattened plugins
  34. *
  35. * As a fallback, if any errors are thrown during execution of this function,
  36. * our normal markdown stack using unified could be used, with a consequence
  37. * of a loss of functionality and performance
  38. *
  39. * @param {import('gatsby').API.NodeJS.SharedHelpers & { type?: null | { name: string } }} helpers
  40. * @returns {Promise<{ [key: string]: { resolver: Function } }>} resolver map
  41. */
  42. const getMarkdownResolvers = helpers => {
  43. /** @type {import('gatsby').API.NodeJS.Exports} */
  44. // @ts-ignore
  45. const gatsbyTransformerRemark = require('gatsby-transformer-remark/gatsby-node')
  46.  
  47. // get the gatsby-transformer-remark plugin options
  48. // there should be a better way to do this, store is considered
  49. // internal
  50. const plugins = helpers.store.getState().flattenedPlugins
  51. const transformerRemarkPlugin = plugins.find(
  52. p => p.name === 'gatsby-transformer-remark',
  53. )
  54.  
  55. if (!transformerRemarkPlugin)
  56. throw new Error('gatsby-transformer-remark plugin not found')
  57.  
  58. const { setFieldsOnGraphQLNodeType } = gatsbyTransformerRemark
  59. if (!setFieldsOnGraphQLNodeType)
  60. throw new Error('gatsby-transformer-remark implementation changed')
  61.  
  62. // bypass type check by gatsby-transformer-remark
  63. if (!helpers.type || helpers.type.name !== 'MarkdownRemark')
  64. helpers.type = { name: 'MarkdownRemark' }
  65.  
  66. return setFieldsOnGraphQLNodeType(
  67. // @ts-ignore
  68. helpers,
  69. transformerRemarkPlugin.pluginOptions,
  70. )
  71. }
  72.  
  73. // @ts-ignore
  74. const { getValueAt } = require('gatsby/dist/utils/get-value-at')
  75.  
  76. /**
  77. * Gets a field to operate on from the frontmatter,
  78. * @param {import('gatsby').API.NodeJS.SharedHelpers} options
  79. * @param {'html' | 'htmlAst' | 'excerpt' | 'excerptAst' | 'headings' | 'timeToRead' | 'tableOfContents' | 'wordCount'} resolver
  80. * @returns {import('gatsby').API.NodeJS.Resolver<any, any, any>}
  81. */
  82. const wrappedRemarkResolver = (options, resolver) => async (
  83. source,
  84. allArgs,
  85. context,
  86. info,
  87. ) => {
  88. const {
  89. createNodeId,
  90. createContentDigest,
  91. actions: { createNode, createParentChildLink },
  92. } = options
  93.  
  94. /** @type {{ field: string }} */
  95. let { field, ...args } = allArgs
  96. if (!field.startsWith('frontmatter.')) {
  97. throw new Error('field must be in frontmatter')
  98. }
  99.  
  100. field = field.replace('frontmatter.', '')
  101.  
  102. const value = getValueAt(source, field)
  103. if (typeof value !== 'string') return null
  104.  
  105. // we get the parent markdown node
  106. // and pretend that the content is the
  107. // content of the frontmatter field instead
  108. const markdownNode = context.nodeModel.findRootNodeAncestor(source)
  109.  
  110. /** @typedef {import('gatsby').Node} Node */
  111. /** @type {Node} */
  112. const parentNode = {
  113. ...markdownNode,
  114. id: createNodeId('fake-markdown'),
  115. parent: markdownNode && markdownNode.id,
  116. children: [],
  117. // @ts-ignore
  118. internal: {
  119. ...(markdownNode && markdownNode.internal),
  120. // we must set content, otherwise when loadNodeContent
  121. // is called in gatsby-transform-remark
  122. // if it isn't set, a search will go for the owner plugin
  123. // and request it to load the content and since the owner
  124. // does not exist, that will fail and an error will be thrown
  125. content: value,
  126. contentDigest:
  127. ((markdownNode && markdownNode.internal.contentDigest) || '') +
  128. createContentDigest(value),
  129. mediaType: 'text/markdown',
  130. type: FAKE_MARKDOWN_FILE_TYPE,
  131. },
  132. }
  133.  
  134. // owner is created by gatsby based on plugin. throws on creation if set
  135. delete parentNode.internal.owner
  136.  
  137. const node = await createNode(parentNode)
  138. // add a parent link if we have a parent markdown node
  139. if (markdownNode)
  140. createParentChildLink({ parent: markdownNode, child: parentNode })
  141.  
  142. // sometimes an array is returned sometimes not. weird
  143. const realNode = Array.isArray(node) ? node[0] : node
  144. if (!realNode) return null
  145.  
  146. // make sure correct type is sent to gatsby-transformer-remark resolvers
  147. const MarkdownRemarkType = info.schema.getType('MarkdownRemark')
  148. const resolvers = await getMarkdownResolvers({
  149. ...options,
  150. type: MarkdownRemarkType,
  151. })
  152.  
  153. // we create nodes and don't clean up after
  154. // if we do delete them we get undefined errors
  155. // no amount of querying through graphql shows the
  156. // created nodes, so I expect they get cleaned up somehow
  157. // besides, since gatsby isn't a long-running process (just static generation)
  158. // a memory leak shouldn't be too big of an issue
  159.  
  160. return resolvers[resolver].resolve(realNode, args, context, info)
  161. }
  162.  
  163. module.exports = {
  164. createResolvers(options) {
  165. const { createResolvers } = options
  166.  
  167. createResolvers({
  168. Frontmatter: {
  169. getHtml: {
  170. type: 'String',
  171. args: {
  172. field: {
  173. type: 'MarkdownRemarkFieldsEnum!',
  174. },
  175. },
  176. resolve: wrappedRemarkResolver(options, 'html'),
  177. },
  178. getHtmlAst: {
  179. type: 'JSON',
  180. args: {
  181. field: {
  182. type: 'MarkdownRemarkFieldsEnum!',
  183. },
  184. },
  185. resolve: wrappedRemarkResolver(options, 'htmlAst'),
  186. },
  187. }
  188. })
  189. }
  190. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement