Skip to content

Commit

Permalink
feat(gatsby-transformer-remark): Allow for multiple different remark …
Browse files Browse the repository at this point in the history
…sources (#7512)

<!--
  Q. Which branch should I use for my pull request?
  A. Use `master` branch (probably).

  Q. Which branch if my change is an update to Gatsby v2?
  A. Definitely use `master` branch :)

  Q. Which branch if my change is an update to documentation or gatsbyjs.org?
  A. Use `master` branch. A Gatsby maintainer will copy your changes over to the `v1` branch for you

  Q. Which branch if my change is a bug fix for Gatsby v1?
  A. In this case, you should use the `v1` branch

  Q. Which branch if I'm still not sure?
  A. Use `master` branch. Ask in the PR if you're not sure and a Gatsby maintainer will be happy to help :)

  Note: We will only accept bug fixes for Gatsby v1. New features should be added to Gatsby v2.

  Learn more about contributing: https://www.gatsbyjs.org/docs/how-to-contribute/
-->

This PR allows for having more than one gatsby-transformer-remark plugin. With the addition of the `filter` option, the user can now decide which nodes should be handled by which plugin. For instance, with gatsby-source-filesystem, one could do the following in order to have multiple different markdown sources:

```js
const plugins = [
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      path: `${__dirname}/src/pages`,
      name: `pages`,
    },
  },
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      path: `${__dirname}/src/blog`,
      name: `blog`,
    },
  },
  {
    resolve: `gatsby-transformer-remark`,
    options: {
      filter: node => node.sourceInstanceName === `pages`,
      type: `MarkdownPage`,
    },
  },
  {
    resolve: `gatsby-transformer-remark`,
    options: {
      filter: node => node.sourceInstanceName === `blog`,
      type: `BlogPost`,
    },
  }
]
```

I've also added a `type` option so that these different plugins create GraphQL types with different names.

In the above example, we could expect that our markdown pages might have different frontmatter fields than our blog entries, but having them all under the `MarkdownRemark` type would force us to add filters on fields in order to only retrieve a list of either of them. Having different frontmatter formats would also pollute our GraphiQL documentation.

Co-authored-by: Ward Peeters <ward@coding-tech.com>
  • Loading branch information
alexkirsz and wardpeet committed Mar 13, 2019
1 parent c6583d4 commit 95155e0
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 85 deletions.
5 changes: 5 additions & 0 deletions packages/gatsby-transformer-remark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion packages/gatsby-transformer-remark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
50 changes: 47 additions & 3 deletions packages/gatsby-transformer-remark/src/__tests__/extend-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -14,7 +14,7 @@ const extendNodeType = require(`../extend-node-type`)
async function queryResult(
nodes,
fragment,
{ types = [] } = {},
{ typeName = `MarkdownRemark`, types = [] } = {},
{ additionalParameters = {}, pluginOptions = {} }
) {
const inferredFields = inferObjectStructureFromNodes({
Expand Down Expand Up @@ -51,7 +51,7 @@ async function queryResult(
name: `LISTNODE`,
type: new GraphQLList(
new GraphQLObjectType({
name: `MarkdownRemark`,
name: typeName,
fields: markdownRemarkFields,
})
),
Expand Down Expand Up @@ -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({})
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const Promise = require(`bluebird`)
const _ = require(`lodash`)

const onCreateNode = require(`../on-node-create`)
Expand Down Expand Up @@ -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`, () => {
Expand Down
164 changes: 90 additions & 74 deletions packages/gatsby-transformer-remark/src/extend-node-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down Expand Up @@ -69,6 +68,72 @@ const safeGetCache = ({ getCache, cache }) => id => {
return getCache(id)
}

/**
* @template T
* @param {Array<T>} input
* @param {(input: T) => Promise<void>} iterator
* @return Promise<void>
*/
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.
Expand All @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -523,7 +552,7 @@ module.exports = (
format,
pruneLength,
truncate,
excerptSeparator: pluginOptions.excerpt_separator,
excerptSeparator: grayMatterOptions.excerpt_separator,
})
},
},
Expand All @@ -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)
Expand Down Expand Up @@ -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 = {}

Expand Down
Loading

0 comments on commit 95155e0

Please sign in to comment.