Skip to content

Commit

Permalink
feat(gatsby-transformer-remark): Allow remark subplugins to modify gr…
Browse files Browse the repository at this point in the history
…aphql types owned by parent plugin #15688
  • Loading branch information
stefanprobst authored and freiksenet committed Jul 15, 2019
1 parent 8c46237 commit 6959c6d
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 127 deletions.
2 changes: 1 addition & 1 deletion packages/gatsby-transformer-remark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
],
"license": "MIT",
"peerDependencies": {
"gatsby": "^2.0.88"
"gatsby": "^2.12.0"
},
"repository": {
"type": "git",
Expand Down
12 changes: 7 additions & 5 deletions packages/gatsby-transformer-remark/src/__tests__/extend-node.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const { graphql } = require(`gatsby/graphql`)
const { onCreateNode } = require(`../gatsby-node`)
const extendNodeType = require(`../extend-node-type`)
const { typeDefs } = require(`../create-schema-customization`)
const { createContentDigest } = require(`gatsby/utils`)

// given a set of nodes and a query, return the result of the query
Expand Down Expand Up @@ -39,6 +40,7 @@ async function queryResult(
const typeName = `MarkdownRemark`
const sc = createSchemaComposer()
const tc = sc.createObjectTC(typeName)
sc.addTypeDefs(typeDefs)
addInferredFields({
schemaComposer: sc,
typeComposer: tc,
Expand All @@ -53,11 +55,11 @@ async function queryResult(
const result = await graphql(
schema,
`query {
listNode {
${fragment}
}
}
`
listNode {
${fragment}
}
}
`
)
return result
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const typeDefs = `
type MarkdownHeading {
value: String
depth: Int
}
enum MarkdownHeadingLevels {
h1
h2
h3
h4
h5
h6
}
enum MarkdownExcerptFormats {
PLAIN
HTML
MARKDOWN
}
type MarkdownWordCount {
paragraphs: Int
sentences: Int
words: Int
}
`

module.exports = (nodeApiArgs, pluginOptions = {}) => {
const { plugins = [] } = pluginOptions

nodeApiArgs.actions.createTypes(typeDefs)

// This allows subplugins to use Node APIs bound to `gatsby-transformer-remark`
// to customize the GraphQL schema. This makes it possible for subplugins to
// modify types owned by `gatsby-transformer-remark`.
plugins.forEach(plugin => {
const resolvedPlugin = require(plugin.resolve)
if (typeof resolvedPlugin.createSchemaCustomization === `function`) {
resolvedPlugin.createSchemaCustomization(
nodeApiArgs,
plugin.pluginOptions
)
}
})
}

module.exports.typeDefs = typeDefs
149 changes: 28 additions & 121 deletions packages/gatsby-transformer-remark/src/extend-node-type.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
const {
GraphQLObjectType,
GraphQLList,
GraphQLString,
GraphQLInt,
GraphQLEnumType,
GraphQLJSON,
GraphQLBoolean,
} = require(`gatsby/graphql`)
const Remark = require(`remark`)
const select = require(`unist-util-select`)
const sanitizeHTML = require(`sanitize-html`)
Expand Down Expand Up @@ -91,6 +82,11 @@ const SpaceMarkdownNodeTypesSet = new Set([
`break`,
])

const headingLevels = [...Array(6).keys()].reduce((acc, i) => {
acc[`h${i}`] = i
return acc
}, {})

module.exports = (
{
type,
Expand Down Expand Up @@ -217,36 +213,6 @@ module.exports = (
})
}

// source => parse (can order parsing for dependencies) => typegen
//
// source plugins identify nodes, provide id, initial parse, know
// when nodes are created/removed/deleted
// get passed cached DataTree and return list of clean and dirty nodes.
// Also get passed `dirtyNodes` function which they can call with an array
// of node ids which will then get re-parsed and the inferred schema
// recreated (if inferring schema gets too expensive, can also
// cache the schema until a query fails at which point recreate the
// schema).
//
// parse plugins take data from source nodes and extend it, never mutate
// it. Freeze all nodes once done so typegen plugins can't change it
// this lets us save off the DataTree at that point as well as create
// indexes.
//
// typegen plugins identify further types of data that should be lazily
// computed due to their expense, or are hard to infer graphql type
// (markdown ast), or are need user input in order to derive e.g.
// markdown headers or date fields.
//
// wrap all resolve functions to (a) auto-memoize and (b) cache to disk any
// resolve function that takes longer than ~10ms (do research on this
// e.g. how long reading/writing to cache takes), and (c) track which
// queries are based on which source nodes. Also if connection of what
// which are always rerun if their underlying nodes change..
//
// every node type in DataTree gets a schema type automatically.
// typegen plugins just modify the auto-generated types to add derived fields
// as well as computationally expensive fields.
if (process.env.NODE_ENV !== `production` || !fileNodes) {
fileNodes = getNodesByType(`File`)
}
Expand Down Expand Up @@ -510,14 +476,14 @@ module.exports = (
markdownNode,
{ format, pruneLength, truncate, excerptSeparator }
) {
if (format === `html`) {
if (format === `HTML`) {
return getExcerptHtml(
markdownNode,
pruneLength,
truncate,
excerptSeparator
)
} else if (format === `markdown`) {
} else if (format === `MARKDOWN`) {
return getExcerptMarkdown(
markdownNode,
pruneLength,
Expand All @@ -533,69 +499,15 @@ module.exports = (
)
}

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` },
MARKDOWN: { value: `markdown` },
},
})

const WordCountType = new GraphQLObjectType({
name: `wordCount`,
fields: {
paragraphs: {
type: GraphQLInt,
},
sentences: {
type: GraphQLInt,
},
words: {
type: GraphQLInt,
},
},
})

return resolve({
html: {
type: GraphQLString,
type: `String`,
resolve(markdownNode) {
return getHTML(markdownNode)
},
},
htmlAst: {
type: GraphQLJSON,
type: `JSON`,
resolve(markdownNode) {
return getHTMLAst(markdownNode).then(ast => {
const strippedAst = stripPosition(_.clone(ast), true)
Expand All @@ -604,19 +516,19 @@ module.exports = (
},
},
excerpt: {
type: GraphQLString,
type: `String`,
args: {
pruneLength: {
type: GraphQLInt,
type: `Int`,
defaultValue: 140,
},
truncate: {
type: GraphQLBoolean,
type: `Boolean`,
defaultValue: false,
},
format: {
type: ExcerptFormats,
defaultValue: `plain`,
type: `MarkdownExcerptFormats`,
defaultValue: `PLAIN`,
},
},
resolve(markdownNode, { format, pruneLength, truncate }) {
Expand All @@ -629,14 +541,14 @@ module.exports = (
},
},
excerptAst: {
type: GraphQLJSON,
type: `JSON`,
args: {
pruneLength: {
type: GraphQLInt,
type: `Int`,
defaultValue: 140,
},
truncate: {
type: GraphQLBoolean,
type: `Boolean`,
defaultValue: false,
},
},
Expand All @@ -652,23 +564,22 @@ module.exports = (
},
},
headings: {
type: new GraphQLList(HeadingType),
type: [`MarkdownHeading`],
args: {
depth: {
type: HeadingLevels,
},
depth: `MarkdownHeadingLevels`,
},
resolve(markdownNode, { depth }) {
return getHeadings(markdownNode).then(headings => {
if (typeof depth === `number`) {
headings = headings.filter(heading => heading.depth === depth)
const level = depth && headingLevels[depth]
if (typeof level === `number`) {
headings = headings.filter(heading => heading.depth === level)
}
return headings
})
},
},
timeToRead: {
type: GraphQLInt,
type: `Int`,
resolve(markdownNode) {
return getHTML(markdownNode).then(html => {
let timeToRead = 0
Expand All @@ -684,26 +595,22 @@ module.exports = (
},
},
tableOfContents: {
type: GraphQLString,
type: `String`,
args: {
pathToSlugField: {
type: GraphQLString,
type: `String`,
defaultValue: `fields.slug`,
},
maxDepth: {
type: GraphQLInt,
},
heading: {
type: GraphQLString,
},
maxDepth: `Int`,
heading: `String`,
},
resolve(markdownNode, args) {
return getTableOfContents(markdownNode, args)
},
},
// TODO add support for non-latin languages https://github.com/wooorm/remark/issues/251#issuecomment-296731071
wordCount: {
type: WordCountType,
type: `MarkdownWordCount`,
resolve(markdownNode) {
let counts = {}

Expand Down
1 change: 1 addition & 0 deletions packages/gatsby-transformer-remark/src/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
exports.createSchemaCustomization = require(`./create-schema-customization`)
exports.onCreateNode = require(`./on-node-create`)
exports.setFieldsOnGraphQLNodeType = require(`./extend-node-type`)

0 comments on commit 6959c6d

Please sign in to comment.