diff --git a/packages/gatsby-transformer-remark/README.md b/packages/gatsby-transformer-remark/README.md index ffea63c965858..6bfff15147284 100644 --- a/packages/gatsby-transformer-remark/README.md +++ b/packages/gatsby-transformer-remark/README.md @@ -14,6 +14,11 @@ plugins: [ { resolve: `gatsby-transformer-remark`, options: { + + // Defaults to `() => true` + filter: node => node.sourceInstanceName === `blog`, + // Defaults to `MarkdownRemark` + type: `BlogPost`, // CommonMark mode (default: true) commonmark: true, // Footnotes mode (default: true) diff --git a/packages/gatsby-transformer-remark/package.json b/packages/gatsby-transformer-remark/package.json index a15821a17e2eb..d0f47171f3e31 100644 --- a/packages/gatsby-transformer-remark/package.json +++ b/packages/gatsby-transformer-remark/package.json @@ -8,7 +8,6 @@ }, "dependencies": { "@babel/runtime": "^7.0.0", - "bluebird": "^3.5.0", "gray-matter": "^4.0.0", "hast-util-raw": "^4.0.0", "hast-util-to-html": "^4.0.0", diff --git a/packages/gatsby-transformer-remark/src/__tests__/extend-node.js b/packages/gatsby-transformer-remark/src/__tests__/extend-node.js index 435fc8b8d1862..e6b41b17a228b 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/extend-node.js +++ b/packages/gatsby-transformer-remark/src/__tests__/extend-node.js @@ -4,7 +4,7 @@ const { GraphQLList, GraphQLSchema, } = require(`gatsby/graphql`) -const { onCreateNode } = require(`../gatsby-node`) +const { onCreateNode, setFieldsOnGraphQLNodeType } = require(`../gatsby-node`) const { inferObjectStructureFromNodes, } = require(`../../../gatsby/src/schema/infer-graphql-type`) @@ -14,7 +14,7 @@ const extendNodeType = require(`../extend-node-type`) async function queryResult( nodes, fragment, - { types = [] } = {}, + { typeName = `MarkdownRemark`, types = [] } = {}, { additionalParameters = {}, pluginOptions = {} } ) { const inferredFields = inferObjectStructureFromNodes({ @@ -51,7 +51,7 @@ async function queryResult( name: `LISTNODE`, type: new GraphQLList( new GraphQLObjectType({ - name: `MarkdownRemark`, + name: typeName, fields: markdownRemarkFields, }) ), @@ -835,3 +835,47 @@ describe(`Headings are generated correctly from schema`, () => { } ) }) + +describe(`Adding fields to the GraphQL schema`, () => { + it(`only adds fields when the GraphQL type matches the provided type`, async () => { + const getNode = jest.fn() + const getNodesByType = jest.fn() + + expect( + setFieldsOnGraphQLNodeType({ + type: { name: `MarkdownRemark` }, + getNode, + getNodesByType, + }) + ).toBeInstanceOf(Promise) + + expect( + setFieldsOnGraphQLNodeType( + { type: { name: `MarkdownRemark` }, getNode, getNodesByType }, + { type: `MarkdownRemark` } + ) + ).toBeInstanceOf(Promise) + + expect( + setFieldsOnGraphQLNodeType( + { type: { name: `MarkdownRemark` }, getNode, getNodesByType }, + { type: `GatsbyTestType` } + ) + ).toEqual({}) + + expect( + setFieldsOnGraphQLNodeType( + { type: { name: `GatsbyTestType` }, getNode, getNodesByType }, + { type: `GatsbyTestType` } + ) + ).toBeInstanceOf(Promise) + + expect( + setFieldsOnGraphQLNodeType({ + type: { name: `GatsbyTestType` }, + getNode, + getNodesByType, + }) + ).toEqual({}) + }) +}) diff --git a/packages/gatsby-transformer-remark/src/__tests__/on-node-create.js b/packages/gatsby-transformer-remark/src/__tests__/on-node-create.js index 62564aedcfa9e..e138c4255119b 100644 --- a/packages/gatsby-transformer-remark/src/__tests__/on-node-create.js +++ b/packages/gatsby-transformer-remark/src/__tests__/on-node-create.js @@ -1,4 +1,3 @@ -const Promise = require(`bluebird`) const _ = require(`lodash`) const onCreateNode = require(`../on-node-create`) @@ -120,6 +119,50 @@ yadda yadda expect(parsed.frontmatter.date).toEqual(new Date(date).toJSON()) }) + + it(`Filters nodes with the given filter function, if provided`, async () => { + const content = `` + + node.content = content + node.sourceInstanceName = `gatsby-test-source` + + const createNode = jest.fn() + const createParentChildLink = jest.fn() + const actions = { createNode, createParentChildLink } + const createNodeId = jest.fn() + createNodeId.mockReturnValue(`uuid-from-gatsby`) + + await onCreateNode( + { + node, + loadNodeContent, + actions, + createNodeId, + }, + { + filter: node => + node.sourceInstanceName === `gatsby-other-test-source`, + } + ).then(() => { + expect(createNode).toHaveBeenCalledTimes(0) + expect(createParentChildLink).toHaveBeenCalledTimes(0) + }) + + await onCreateNode( + { + node, + loadNodeContent, + actions, + createNodeId, + }, + { + filter: node => node.sourceInstanceName === `gatsby-test-source`, + } + ).then(() => { + expect(createNode).toHaveBeenCalledTimes(1) + expect(createParentChildLink).toHaveBeenCalledTimes(1) + }) + }) }) describe(`process graphql correctly`, () => { diff --git a/packages/gatsby-transformer-remark/src/extend-node-type.js b/packages/gatsby-transformer-remark/src/extend-node-type.js index b4732bb0339bf..e314ce2a7c488 100644 --- a/packages/gatsby-transformer-remark/src/extend-node-type.js +++ b/packages/gatsby-transformer-remark/src/extend-node-type.js @@ -16,7 +16,6 @@ const toHAST = require(`mdast-util-to-hast`) const hastToHTML = require(`hast-util-to-html`) const mdastToToc = require(`mdast-util-toc`) const mdastToString = require(`mdast-util-to-string`) -const Promise = require(`bluebird`) const unified = require(`unified`) const parse = require(`remark-parse`) const stringify = require(`remark-stringify`) @@ -69,6 +68,72 @@ const safeGetCache = ({ getCache, cache }) => id => { return getCache(id) } +/** + * @template T + * @param {Array} input + * @param {(input: T) => Promise} iterator + * @return Promise + */ +const eachPromise = (input, iterator) => + input.reduce( + (accumulatorPromise, nextValue) => + accumulatorPromise.then(() => void iterator(nextValue)), + Promise.resolve() + ) + +const HeadingType = new GraphQLObjectType({ + name: `MarkdownHeading`, + fields: { + value: { + type: GraphQLString, + resolve(heading) { + return heading.value + }, + }, + depth: { + type: GraphQLInt, + resolve(heading) { + return heading.depth + }, + }, + }, +}) + +const HeadingLevels = new GraphQLEnumType({ + name: `HeadingLevels`, + values: { + h1: { value: 1 }, + h2: { value: 2 }, + h3: { value: 3 }, + h4: { value: 4 }, + h5: { value: 5 }, + h6: { value: 6 }, + }, +}) + +const ExcerptFormats = new GraphQLEnumType({ + name: `ExcerptFormats`, + values: { + PLAIN: { value: `plain` }, + HTML: { value: `html` }, + }, +}) + +const WordCountType = new GraphQLObjectType({ + name: `wordCount`, + fields: { + paragraphs: { + type: GraphQLInt, + }, + sentences: { + type: GraphQLInt, + }, + words: { + type: GraphQLInt, + }, + }, +}) + /** * Map that keeps track of generation of AST to not generate it multiple * times in parallel. @@ -88,29 +153,31 @@ module.exports = ( reporter, ...rest }, - pluginOptions + { + type: typeName = `MarkdownRemark`, + plugins = [], + blocks, + commonmark = true, + footnotes = true, + gfm = true, + pedantic = true, + tableOfContents = { + heading: null, + maxDepth: 6, + }, + ...grayMatterOptions + } = {} ) => { - if (type.name !== `MarkdownRemark`) { + if (type.name !== typeName) { return {} } - pluginsCacheStr = pluginOptions.plugins.map(p => p.name).join(``) + pluginsCacheStr = plugins.map(p => p.name).join(``) pathPrefixCacheStr = pathPrefix || `` const getCache = safeGetCache({ cache, getCache: possibleGetCache }) return new Promise((resolve, reject) => { // Setup Remark. - const { - blocks, - commonmark = true, - footnotes = true, - gfm = true, - pedantic = true, - tableOfContents = { - heading: null, - maxDepth: 6, - }, - } = pluginOptions const tocOptions = tableOfContents const remarkOptions = { commonmark, @@ -123,7 +190,7 @@ module.exports = ( } let remark = new Remark().data(`settings`, remarkOptions) - for (let plugin of pluginOptions.plugins) { + for (let plugin of plugins) { const requiredPlugin = require(plugin.resolve) if (_.isFunction(requiredPlugin.setParserPlugins)) { for (let parserPlugin of requiredPlugin.setParserPlugins( @@ -167,8 +234,8 @@ module.exports = ( if (process.env.NODE_ENV !== `production` || !fileNodes) { fileNodes = getNodesByType(`File`) } - // Use Bluebird's Promise function "each" to run remark plugins serially. - await Promise.each(pluginOptions.plugins, plugin => { + + await eachPromise(plugins, plugin => { const requiredPlugin = require(plugin.resolve) if (_.isFunction(requiredPlugin.mutateSource)) { return requiredPlugin.mutateSource( @@ -235,8 +302,8 @@ module.exports = ( if (process.env.NODE_ENV !== `production` || !fileNodes) { fileNodes = getNodesByType(`File`) } - // Use Bluebird's Promise function "each" to run remark plugins serially. - await Promise.each(pluginOptions.plugins, plugin => { + + await eachPromise(plugins, plugin => { const requiredPlugin = require(plugin.resolve) if (_.isFunction(requiredPlugin)) { return requiredPlugin( @@ -448,44 +515,6 @@ module.exports = ( return text } - const HeadingType = new GraphQLObjectType({ - name: `MarkdownHeading`, - fields: { - value: { - type: GraphQLString, - resolve(heading) { - return heading.value - }, - }, - depth: { - type: GraphQLInt, - resolve(heading) { - return heading.depth - }, - }, - }, - }) - - const HeadingLevels = new GraphQLEnumType({ - name: `HeadingLevels`, - values: { - h1: { value: 1 }, - h2: { value: 2 }, - h3: { value: 3 }, - h4: { value: 4 }, - h5: { value: 5 }, - h6: { value: 6 }, - }, - }) - - const ExcerptFormats = new GraphQLEnumType({ - name: `ExcerptFormats`, - values: { - PLAIN: { value: `plain` }, - HTML: { value: `html` }, - }, - }) - return resolve({ html: { type: GraphQLString, @@ -523,7 +552,7 @@ module.exports = ( format, pruneLength, truncate, - excerptSeparator: pluginOptions.excerpt_separator, + excerptSeparator: grayMatterOptions.excerpt_separator, }) }, }, @@ -543,7 +572,7 @@ module.exports = ( return getExcerptAst(markdownNode, { pruneLength, truncate, - excerptSeparator: pluginOptions.excerpt_separator, + excerptSeparator: grayMatterOptions.excerpt_separator, }).then(ast => { const strippedAst = stripPosition(_.clone(ast), true) return hastReparseRaw(strippedAst) @@ -602,20 +631,7 @@ module.exports = ( }, // TODO add support for non-latin languages https://github.com/wooorm/remark/issues/251#issuecomment-296731071 wordCount: { - type: new GraphQLObjectType({ - name: `wordCount`, - fields: { - paragraphs: { - type: GraphQLInt, - }, - sentences: { - type: GraphQLInt, - }, - words: { - type: GraphQLInt, - }, - }, - }), + type: WordCountType, resolve(markdownNode) { let counts = {} diff --git a/packages/gatsby-transformer-remark/src/on-node-create.js b/packages/gatsby-transformer-remark/src/on-node-create.js index c2937b08595da..e89def5f1cf15 100644 --- a/packages/gatsby-transformer-remark/src/on-node-create.js +++ b/packages/gatsby-transformer-remark/src/on-node-create.js @@ -4,14 +4,20 @@ const _ = require(`lodash`) module.exports = async function onCreateNode( { node, loadNodeContent, actions, createNodeId, reporter }, - pluginOptions + { + plugins = null, + filter = () => true, + type = `MarkdownRemark`, + ...grayMatterOptions + } = {} ) { const { createNode, createParentChildLink } = actions // We only care about markdown content. if ( - node.internal.mediaType !== `text/markdown` && - node.internal.mediaType !== `text/x-markdown` + (node.internal.mediaType !== `text/markdown` && + node.internal.mediaType !== `text/x-markdown`) || + !filter(node) ) { return {} } @@ -19,7 +25,7 @@ module.exports = async function onCreateNode( const content = await loadNodeContent(node) try { - let data = grayMatter(content, pluginOptions) + let data = grayMatter(content, grayMatterOptions) if (data.data) { data.data = _.mapValues(data.data, value => { @@ -31,12 +37,12 @@ module.exports = async function onCreateNode( } let markdownNode = { - id: createNodeId(`${node.id} >>> MarkdownRemark`), + id: createNodeId(`${node.id} >>> ${type}`), children: [], parent: node.id, internal: { content: data.content, - type: `MarkdownRemark`, + type, }, }