This is about WordPress and the static site generator Gatsby, not the Gutenberg and Gatsby you might know. I’m not sure where that post would go.
Anyway. Parsing Gutenberg blocks in Gatsby turns out to be much more straightforward than I thought it would be. Not that it’s completely straightforward, but I didn’t find it horrible. I’ve done it the cheap and cheerful way, porting over the circle text and code highlighting blocks from mjj-why as components and styles and doing the minimum needed to get the attributes out of the raw content.
I’m currently getting the raw (as opposed to rendered) content for the posts from WP GraphQL – this is the content with all the block editor html comments which include attributes and everything necessary to parse the block.
Those attributes in the comment, that stringified JSON object, is what I need to render my server side rendered blocks. I also need the block name, it helps to know which block to render. Happily, there’s a package for this! The block serialization default parser.
Running the raw content through that gives me a list of blocks (supposedly an array but coding-wise it dies if I don’t treat it as an object) which I can iterate over. Each block contains:
- the block name
- the saved attributes
- any inner blocks
- the inner html
For core blocks, all I need is the inner html and I’m good. For things which aren’t a block, the html I need is also in the inner html but with a null
block name.
My blocks still need to be rendered. But first a quick aside about why the mjj-why blocks are server-side rendered rather than saved in the content. The short *and* the long answer is that I mess around with them too much to have something I can’t tweak on the fly. I know there are ways of deprecating old save functions but it would be embarrassing how big those functions would get. So I server-side render. There usually is a jsx component floating around there somewhere for use by the block too, I would much rather do it all in js than have to piss about replicating what I want in php. Having to do it in php is a downside to this decision for me.
To parse my blocks and get the html I need from the others, I’m using a utility function through which I run the content. (In this function createLocalLinks
swaps the links to the WP install to links to its current home on Netlify.)
import { parse } from '@wordpress/block-serialization-default-parser'
import { getLocalBlock } from '../wp-blocks'
export const parseContent = (html, wordPressUrl, prefix = '') => {
//Is there a better way to do this? Rather than sequentially?
// First clean up the links.
let finalHtml = ''
const blocks = html ? parse(html) : {}
for (const block in blocks) {
if (blocks.hasOwnProperty(block)) {
// This probably doesn't handle inner blocks.
const { blockName, attrs, innerBlocks, innerHTML } = blocks[block]
if (blockName && blockName.startsWith('core')) {
finalHtml += createLocalLinks(innerHTML, wordPressUrl, prefix)
} else if (blockName) {
finalHtml += createLocalLinks(getLocalBlock(blocks[block]))
} else if (!blockName && innerHTML) {
finalHtml += createLocalLinks(innerHTML, wordPressUrl, prefix)
}
}
}
return finalHtml
}
It looks like I should combine the core blocks and null but my core embeds aren’t working so I keep messing around with it (story of my life) and it’s easier to do separated. Also, the actual code may have changed between when this is published and when you’re reading it.
The key in the above code is getLocalBlock()
which handles figuring out which block it is and sending it to the right place.
import { circleText } from './circle-text'
import { codeHighlighting } from './code-highlighting'
import React from 'react'
export function getLocalBlock(block) {
const { blockName, attrs } = block
let html = ''
switch (blockName) {
case 'mjj-why/circle-text':
html += circleText(attrs)
break
case 'mjj-why/code-highlighting':
html += codeHighlighting(attrs)
break
}
return html
}
All the circleText()
and codeHighlighting()
functions do it render the jsx component and send back the html, eg:
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import MJJCircleText from './MJJCircleText'
export const circleText = attributes => {
return ReactDOMServer.renderToStaticMarkup(
<MJJCircleText attributes={attributes} />
)
}
That jsx component is ported over from the plugin I made which adds the blocks to Gutenberg. Doing this elegantly, ie not having to copy and paste one file to another, is a question of deployment — eg it’d be useful to have a package with those functions to import which I could also use in the Gutenberg plugin on the WP install — but I’m going to leave that for later.
Next up: where tf did the core embeds go?
Leave a Reply