Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby-transformer-remark): Allow for multiple different remark sources #7512

Merged
merged 4 commits into from
Mar 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this void here is problematic? after removing it, it seems to work - why is it here? some context is needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also generally - where do we stand on this? We use bluebird pretty heavily--so I'm not sure it was worth refactoring this.

But a bit tangential, if we went that way--just need to ensure we have a working alternative for sequential promises!

Copy link
Contributor

@pieh pieh Mar 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally don't agree with removing dependency for the sake of removing it (at least for stuff that is only used during the builds and don't end up in production js bundles produced by webpack). If it fixes bugs or improve performance, then that's fine.

I will open PR fixing that and add some tests for this helper.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When utils like bluebird have maintainers with domain knowledge and it was battle-tested by thousands of developers - I will trust that more than hand rolled and not very tested approaches.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case - here's hot-fix for issue #12578 (first commit on purpose just added failing test, and second commit "fixes" the problem.

@alexkirsz I would appreciate you taking a look on this, because I imagine there was some reason for that void to be there, but I have no clue what that would be (and it did break that utility)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pieh I'm not the author of this particular piece of code, @wardpeet is (I only authored the first commit of this PR).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok - sorry @alexkirsz. I should pay more attention to individual commits in the PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my bady, sorry about this

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 => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't seem to work

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