Let's face it: even the most versatile formats can become limiting.
For example, Markdown seems to support every kind of textual element we could ever want. Yet there will always be some users (like me) who want to, say, apply special styling to pull-quotes.
In this article I present the idea of a "subformat": a backwards-compatible variation of an existing format, which adds additional information to that format. That additional information can be used by a parser which understands the subformat.
I recently encountered a problem while migrating content to my new homepage.
The articles are in plain Markdown format (.md
) but I had to migrate them to MDX format in
order to allow certain sections to be rendered as React components.
Example of React-rendered components enabled by using MDX format:
MdxCode
component, using PrismJSMdxMermaid
component, using MermaidJSHowever I also wanted the ability to copy and paste the source Markdown code directly into other Markdown-based content platforms such as Github and DEV.to.
To do this, I would have to convert the content from MDX format back into plain Markdown format, then copy and paste it into the platform.
This wouldn't be so easy, with so many Mdx tags all over the place.
There are a couple of options to speed up the conversion:
However both of these options would require some busywork / tedium and might bring up errors and anomalies.
I eventually settled on what seems like a better solution: subformats.
I coined this term because there doesn't seem to be any pre-existing term that describes what I'm trying to do.
By "subformat", I basically mean a variation of an existing format, which adds additional information to that format. That additional information can be used by a parser which understands the subformat.
For example: I use the Markdown format, but then add my own small backwards-compatible variations to it, creating my own Markdown subformat.
In this case, I add small pieces of text to Markdown, which do not interfere with the normal rendering of the Markdown, but which can still be picked up and used by my own parser.
This keeps the content backwards-compatible (i.e., the same content can be used by normal Markdown parsers) while allowing a newer / different parser to access the additional information.
Using this additional information, my own parser can add enhancements such as React controls or special styling.
I'm using a Markdown subformat to provide additional functionality to my Markdown-based articles in a way that maintains compatibility with regular Markdown parsers like Github and DEV.to.
Here are the variations that make up the subformat:
Markdown element | Variation | Used for |
---|---|---|
> (block-quote) | Content prefixed with:Blurb: | Brief summary in purple text, at the top of an article. |
> (block-quote) | Content prefixed with:Aside: | Aside text in a box, in an article body. |
> (block-quote) | Content prefixed with:Pull-quote: | Pull-quote in a box, with a quotes icon, in an article body. |
``` (code block) | Content prefixed with:// Lines (x) | Code with lines (x) highlighted. |
You can see an example implementation in my pull-quote variation of the Markdown blockquote
element.
For context: I'm using MDX, which allows Markdown to be rendered using React components. The mdx-components.tsx file defines a mapping between DOM nodes and React components, including blockquote
-> MdxBlockBuote.tsx
.
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
a: MdxA,
aside: MdxAside,
blockquote: MdxBlockQuote, // *
// ... etc
};
}
In the MdxBlockQuote
component which renders the Markdown blockquote, I pass the props through getBlockQuoteSubformatProps
:
export function MdxBlockQuote(props: MdxBlockQuoteProps) {
props = getBlockQuoteSubformatProps(props);
return (
<blockquote {...props}>
{children}
</blockquote>
);
}
The getBlockQuoteSubformatProps
function in turn passes the props through a set of chained functions, each applying a variation and returning the resulting props.
function getBlockQuoteSubformatProps(props) {
props = getAsideVariationProps(props);
props = getPullQuoteVariationProps(props); // *
}
Note: This chaining pattern allows multiple subformats to be applied independent of one another.
The getPullQuoteVariationProps
function first checks if the children
prop has the "Pull-quote:" prefix (this is handled by getIsSubformatChildrenPrefixed
). If it does, then this is a pull-quote. So then it adds the pullQuote
class (imported from CSS modules), which applies pull-quote styling to the element.
const PULL_QUOTE_PREFIX = "Pull-quote:";
export function getBlockQuotePullQuoteSubformatProps(
props: MdxBlockQuoteProps,
) {
if (!getIsSubformatChildrenPrefixed(PULL_QUOTE_PREFIX, props.children)) {
return props;
}
// Add pull-quote class
const className = cn(props.className ?? "", moduleStyles.pullQuote);
// Remove pull-quote prefix
const children = removeSubformatChildrenPrefix(
PULL_QUOTE_PREFIX,
props.children,
);
return {
...props,
className,
children,
};
}
Now I can write the following in my Markdown file:
This is a paragraph.
> Aside:
>
> This is an aside
This is another paragraph.
If I copy/paste it into, say, Github, it will render it like this:
But on my own website, it will render like this:
I think the subformats concept could be useful in the following senario:
Based on these situations I can think of the following use cases for subformats:
<!--
/ -->
comments.If the underlying format will support your use case in the near future, or there is a simpler alternative technique, then a subformat might be better avoided. This is a very specific tool for a very small subset of use cases.