Skip to content

Commit 95155e0

Browse files
alexkirszwardpeet
andcommittedMar 13, 2019
feat(gatsby-transformer-remark): Allow for multiple different remark 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>
1 parent c6583d4 commit 95155e0

File tree

6 files changed

+198
-85
lines changed

6 files changed

+198
-85
lines changed
 

‎packages/gatsby-transformer-remark/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ plugins: [
1414
{
1515
resolve: `gatsby-transformer-remark`,
1616
options: {
17+
18+
// Defaults to `() => true`
19+
filter: node => node.sourceInstanceName === `blog`,
20+
// Defaults to `MarkdownRemark`
21+
type: `BlogPost`,
1722
// CommonMark mode (default: true)
1823
commonmark: true,
1924
// Footnotes mode (default: true)

‎packages/gatsby-transformer-remark/package.json

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
},
99
"dependencies": {
1010
"@babel/runtime": "^7.0.0",
11-
"bluebird": "^3.5.0",
1211
"gray-matter": "^4.0.0",
1312
"hast-util-raw": "^4.0.0",
1413
"hast-util-to-html": "^4.0.0",

‎packages/gatsby-transformer-remark/src/__tests__/extend-node.js

+47-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const {
44
GraphQLList,
55
GraphQLSchema,
66
} = require(`gatsby/graphql`)
7-
const { onCreateNode } = require(`../gatsby-node`)
7+
const { onCreateNode, setFieldsOnGraphQLNodeType } = require(`../gatsby-node`)
88
const {
99
inferObjectStructureFromNodes,
1010
} = require(`../../../gatsby/src/schema/infer-graphql-type`)
@@ -14,7 +14,7 @@ const extendNodeType = require(`../extend-node-type`)
1414
async function queryResult(
1515
nodes,
1616
fragment,
17-
{ types = [] } = {},
17+
{ typeName = `MarkdownRemark`, types = [] } = {},
1818
{ additionalParameters = {}, pluginOptions = {} }
1919
) {
2020
const inferredFields = inferObjectStructureFromNodes({
@@ -51,7 +51,7 @@ async function queryResult(
5151
name: `LISTNODE`,
5252
type: new GraphQLList(
5353
new GraphQLObjectType({
54-
name: `MarkdownRemark`,
54+
name: typeName,
5555
fields: markdownRemarkFields,
5656
})
5757
),
@@ -835,3 +835,47 @@ describe(`Headings are generated correctly from schema`, () => {
835835
}
836836
)
837837
})
838+
839+
describe(`Adding fields to the GraphQL schema`, () => {
840+
it(`only adds fields when the GraphQL type matches the provided type`, async () => {
841+
const getNode = jest.fn()
842+
const getNodesByType = jest.fn()
843+
844+
expect(
845+
setFieldsOnGraphQLNodeType({
846+
type: { name: `MarkdownRemark` },
847+
getNode,
848+
getNodesByType,
849+
})
850+
).toBeInstanceOf(Promise)
851+
852+
expect(
853+
setFieldsOnGraphQLNodeType(
854+
{ type: { name: `MarkdownRemark` }, getNode, getNodesByType },
855+
{ type: `MarkdownRemark` }
856+
)
857+
).toBeInstanceOf(Promise)
858+
859+
expect(
860+
setFieldsOnGraphQLNodeType(
861+
{ type: { name: `MarkdownRemark` }, getNode, getNodesByType },
862+
{ type: `GatsbyTestType` }
863+
)
864+
).toEqual({})
865+
866+
expect(
867+
setFieldsOnGraphQLNodeType(
868+
{ type: { name: `GatsbyTestType` }, getNode, getNodesByType },
869+
{ type: `GatsbyTestType` }
870+
)
871+
).toBeInstanceOf(Promise)
872+
873+
expect(
874+
setFieldsOnGraphQLNodeType({
875+
type: { name: `GatsbyTestType` },
876+
getNode,
877+
getNodesByType,
878+
})
879+
).toEqual({})
880+
})
881+
})

‎packages/gatsby-transformer-remark/src/__tests__/on-node-create.js

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const Promise = require(`bluebird`)
21
const _ = require(`lodash`)
32

43
const onCreateNode = require(`../on-node-create`)
@@ -120,6 +119,50 @@ yadda yadda
120119

121120
expect(parsed.frontmatter.date).toEqual(new Date(date).toJSON())
122121
})
122+
123+
it(`Filters nodes with the given filter function, if provided`, async () => {
124+
const content = ``
125+
126+
node.content = content
127+
node.sourceInstanceName = `gatsby-test-source`
128+
129+
const createNode = jest.fn()
130+
const createParentChildLink = jest.fn()
131+
const actions = { createNode, createParentChildLink }
132+
const createNodeId = jest.fn()
133+
createNodeId.mockReturnValue(`uuid-from-gatsby`)
134+
135+
await onCreateNode(
136+
{
137+
node,
138+
loadNodeContent,
139+
actions,
140+
createNodeId,
141+
},
142+
{
143+
filter: node =>
144+
node.sourceInstanceName === `gatsby-other-test-source`,
145+
}
146+
).then(() => {
147+
expect(createNode).toHaveBeenCalledTimes(0)
148+
expect(createParentChildLink).toHaveBeenCalledTimes(0)
149+
})
150+
151+
await onCreateNode(
152+
{
153+
node,
154+
loadNodeContent,
155+
actions,
156+
createNodeId,
157+
},
158+
{
159+
filter: node => node.sourceInstanceName === `gatsby-test-source`,
160+
}
161+
).then(() => {
162+
expect(createNode).toHaveBeenCalledTimes(1)
163+
expect(createParentChildLink).toHaveBeenCalledTimes(1)
164+
})
165+
})
123166
})
124167

125168
describe(`process graphql correctly`, () => {

‎packages/gatsby-transformer-remark/src/extend-node-type.js

+90-74
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const toHAST = require(`mdast-util-to-hast`)
1616
const hastToHTML = require(`hast-util-to-html`)
1717
const mdastToToc = require(`mdast-util-toc`)
1818
const mdastToString = require(`mdast-util-to-string`)
19-
const Promise = require(`bluebird`)
2019
const unified = require(`unified`)
2120
const parse = require(`remark-parse`)
2221
const stringify = require(`remark-stringify`)
@@ -69,6 +68,72 @@ const safeGetCache = ({ getCache, cache }) => id => {
6968
return getCache(id)
7069
}
7170

71+
/**
72+
* @template T
73+
* @param {Array<T>} input
74+
* @param {(input: T) => Promise<void>} iterator
75+
* @return Promise<void>
76+
*/
77+
const eachPromise = (input, iterator) =>
78+
input.reduce(
79+
(accumulatorPromise, nextValue) =>
80+
accumulatorPromise.then(() => void iterator(nextValue)),
81+
Promise.resolve()
82+
)
83+
84+
const HeadingType = new GraphQLObjectType({
85+
name: `MarkdownHeading`,
86+
fields: {
87+
value: {
88+
type: GraphQLString,
89+
resolve(heading) {
90+
return heading.value
91+
},
92+
},
93+
depth: {
94+
type: GraphQLInt,
95+
resolve(heading) {
96+
return heading.depth
97+
},
98+
},
99+
},
100+
})
101+
102+
const HeadingLevels = new GraphQLEnumType({
103+
name: `HeadingLevels`,
104+
values: {
105+
h1: { value: 1 },
106+
h2: { value: 2 },
107+
h3: { value: 3 },
108+
h4: { value: 4 },
109+
h5: { value: 5 },
110+
h6: { value: 6 },
111+
},
112+
})
113+
114+
const ExcerptFormats = new GraphQLEnumType({
115+
name: `ExcerptFormats`,
116+
values: {
117+
PLAIN: { value: `plain` },
118+
HTML: { value: `html` },
119+
},
120+
})
121+
122+
const WordCountType = new GraphQLObjectType({
123+
name: `wordCount`,
124+
fields: {
125+
paragraphs: {
126+
type: GraphQLInt,
127+
},
128+
sentences: {
129+
type: GraphQLInt,
130+
},
131+
words: {
132+
type: GraphQLInt,
133+
},
134+
},
135+
})
136+
72137
/**
73138
* Map that keeps track of generation of AST to not generate it multiple
74139
* times in parallel.
@@ -88,29 +153,31 @@ module.exports = (
88153
reporter,
89154
...rest
90155
},
91-
pluginOptions
156+
{
157+
type: typeName = `MarkdownRemark`,
158+
plugins = [],
159+
blocks,
160+
commonmark = true,
161+
footnotes = true,
162+
gfm = true,
163+
pedantic = true,
164+
tableOfContents = {
165+
heading: null,
166+
maxDepth: 6,
167+
},
168+
...grayMatterOptions
169+
} = {}
92170
) => {
93-
if (type.name !== `MarkdownRemark`) {
171+
if (type.name !== typeName) {
94172
return {}
95173
}
96-
pluginsCacheStr = pluginOptions.plugins.map(p => p.name).join(``)
174+
pluginsCacheStr = plugins.map(p => p.name).join(``)
97175
pathPrefixCacheStr = pathPrefix || ``
98176

99177
const getCache = safeGetCache({ cache, getCache: possibleGetCache })
100178

101179
return new Promise((resolve, reject) => {
102180
// Setup Remark.
103-
const {
104-
blocks,
105-
commonmark = true,
106-
footnotes = true,
107-
gfm = true,
108-
pedantic = true,
109-
tableOfContents = {
110-
heading: null,
111-
maxDepth: 6,
112-
},
113-
} = pluginOptions
114181
const tocOptions = tableOfContents
115182
const remarkOptions = {
116183
commonmark,
@@ -123,7 +190,7 @@ module.exports = (
123190
}
124191
let remark = new Remark().data(`settings`, remarkOptions)
125192

126-
for (let plugin of pluginOptions.plugins) {
193+
for (let plugin of plugins) {
127194
const requiredPlugin = require(plugin.resolve)
128195
if (_.isFunction(requiredPlugin.setParserPlugins)) {
129196
for (let parserPlugin of requiredPlugin.setParserPlugins(
@@ -167,8 +234,8 @@ module.exports = (
167234
if (process.env.NODE_ENV !== `production` || !fileNodes) {
168235
fileNodes = getNodesByType(`File`)
169236
}
170-
// Use Bluebird's Promise function "each" to run remark plugins serially.
171-
await Promise.each(pluginOptions.plugins, plugin => {
237+
238+
await eachPromise(plugins, plugin => {
172239
const requiredPlugin = require(plugin.resolve)
173240
if (_.isFunction(requiredPlugin.mutateSource)) {
174241
return requiredPlugin.mutateSource(
@@ -235,8 +302,8 @@ module.exports = (
235302
if (process.env.NODE_ENV !== `production` || !fileNodes) {
236303
fileNodes = getNodesByType(`File`)
237304
}
238-
// Use Bluebird's Promise function "each" to run remark plugins serially.
239-
await Promise.each(pluginOptions.plugins, plugin => {
305+
306+
await eachPromise(plugins, plugin => {
240307
const requiredPlugin = require(plugin.resolve)
241308
if (_.isFunction(requiredPlugin)) {
242309
return requiredPlugin(
@@ -448,44 +515,6 @@ module.exports = (
448515
return text
449516
}
450517

451-
const HeadingType = new GraphQLObjectType({
452-
name: `MarkdownHeading`,
453-
fields: {
454-
value: {
455-
type: GraphQLString,
456-
resolve(heading) {
457-
return heading.value
458-
},
459-
},
460-
depth: {
461-
type: GraphQLInt,
462-
resolve(heading) {
463-
return heading.depth
464-
},
465-
},
466-
},
467-
})
468-
469-
const HeadingLevels = new GraphQLEnumType({
470-
name: `HeadingLevels`,
471-
values: {
472-
h1: { value: 1 },
473-
h2: { value: 2 },
474-
h3: { value: 3 },
475-
h4: { value: 4 },
476-
h5: { value: 5 },
477-
h6: { value: 6 },
478-
},
479-
})
480-
481-
const ExcerptFormats = new GraphQLEnumType({
482-
name: `ExcerptFormats`,
483-
values: {
484-
PLAIN: { value: `plain` },
485-
HTML: { value: `html` },
486-
},
487-
})
488-
489518
return resolve({
490519
html: {
491520
type: GraphQLString,
@@ -523,7 +552,7 @@ module.exports = (
523552
format,
524553
pruneLength,
525554
truncate,
526-
excerptSeparator: pluginOptions.excerpt_separator,
555+
excerptSeparator: grayMatterOptions.excerpt_separator,
527556
})
528557
},
529558
},
@@ -543,7 +572,7 @@ module.exports = (
543572
return getExcerptAst(markdownNode, {
544573
pruneLength,
545574
truncate,
546-
excerptSeparator: pluginOptions.excerpt_separator,
575+
excerptSeparator: grayMatterOptions.excerpt_separator,
547576
}).then(ast => {
548577
const strippedAst = stripPosition(_.clone(ast), true)
549578
return hastReparseRaw(strippedAst)
@@ -602,20 +631,7 @@ module.exports = (
602631
},
603632
// TODO add support for non-latin languages https://github.com/wooorm/remark/issues/251#issuecomment-296731071
604633
wordCount: {
605-
type: new GraphQLObjectType({
606-
name: `wordCount`,
607-
fields: {
608-
paragraphs: {
609-
type: GraphQLInt,
610-
},
611-
sentences: {
612-
type: GraphQLInt,
613-
},
614-
words: {
615-
type: GraphQLInt,
616-
},
617-
},
618-
}),
634+
type: WordCountType,
619635
resolve(markdownNode) {
620636
let counts = {}
621637

‎packages/gatsby-transformer-remark/src/on-node-create.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,28 @@ const _ = require(`lodash`)
44

55
module.exports = async function onCreateNode(
66
{ node, loadNodeContent, actions, createNodeId, reporter },
7-
pluginOptions
7+
{
8+
plugins = null,
9+
filter = () => true,
10+
type = `MarkdownRemark`,
11+
...grayMatterOptions
12+
} = {}
813
) {
914
const { createNode, createParentChildLink } = actions
1015

1116
// We only care about markdown content.
1217
if (
13-
node.internal.mediaType !== `text/markdown` &&
14-
node.internal.mediaType !== `text/x-markdown`
18+
(node.internal.mediaType !== `text/markdown` &&
19+
node.internal.mediaType !== `text/x-markdown`) ||
20+
!filter(node)
1521
) {
1622
return {}
1723
}
1824

1925
const content = await loadNodeContent(node)
2026

2127
try {
22-
let data = grayMatter(content, pluginOptions)
28+
let data = grayMatter(content, grayMatterOptions)
2329

2430
if (data.data) {
2531
data.data = _.mapValues(data.data, value => {
@@ -31,12 +37,12 @@ module.exports = async function onCreateNode(
3137
}
3238

3339
let markdownNode = {
34-
id: createNodeId(`${node.id} >>> MarkdownRemark`),
40+
id: createNodeId(`${node.id} >>> ${type}`),
3541
children: [],
3642
parent: node.id,
3743
internal: {
3844
content: data.content,
39-
type: `MarkdownRemark`,
45+
type,
4046
},
4147
}
4248

0 commit comments

Comments
 (0)
Please sign in to comment.