-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
Thoughts on querying Markdown AST for custom rendering #444
Comments
@andreypopp - this is similar to what you're doing with reactdown. would love to get your thoughts! |
Huh, this is super interesting. So basically what you're saying is "screw just embedding react components in Markdown, let's build a new compile-to-jsx language (based on Markdown)"? Treating Markdown as not just a nicer way to render HTML but really a nicer way to write JSX is a fine goal. Or... how much do you agree with what I'm thinking? I don't quite get your content vs. data distinction (they seem about the same to me in this context). For me, I want most anything not actually Javascript out of Gatsby and handle that in GraphQL so there's maximum flexibility. Webpack is great but it's too rigid and one directional (file => module). Which is perfect when a file can be treated as an atomic thing but if you want to query parts of that file or programmatically manipulate in some way from the client it falls short. E.g. an interesting and awesome thing we can do by querying the markdown AST is auto-convert images in Markdown to a smarter GraphQL image type something like:
The Image component on the frontend would then only load the actual image if it came into the viewport. Very similar to what Facebook does here: https://code.facebook.com/posts/991252547593574/the-technology-behind-preview-photos/ Or what Medium.com does. |
Yup, that's pretty much exactly what I'm saying, and I completely agree with what you're saying, and would absolutely love to see it happen too, along with the previous footnote example, and much more. To make sure I've understood the image example it, I'm going to try a simple exercise: Markdown source document Something funny about cars, planes, and trains
![Space Shuttle](https://upload.wikimedia.org/wikipedia/commons/d/d6/STS120LaunchHiRes-edit1.jpg) Parsed AST [
{
nodeType: "paragraph",
children: [
{
nodeType: "text",
value: "Something funny about cars, planes, and trains"
}
]
}, {
nodeType: "image",
altText: "Space Shuttle",
src: "https://upload.wikimedia.org/wikipedia/commons/d/d6/STS120LaunchHiRes-edit1.jpg"
}
] With the GraphQL model, I'm guessing that in the parent page-level renderer, you'd do something like so: export default function Page({ data }) {
const image = get(data, "node.markdownAST.image")
return (
// all the markdown before this
<img src={image.preview} dataLazyLoad={image.src}/>
// the rest of the page
)
}
export const routeQuery = `
{
node {
frontmatter
markdownAST {
image {
preview
src
}
}
}
}
` With the Markdown-as-a-nicer-way-to-write-JSX, the original document gets compiled to: <Paragraph>Something funny about cars, planes, and trains</Paragraph>
<Image altText="Space Shuttle" src="https://upload.wikimedia.org/wikipedia/commons/d/d6/STS120LaunchHiRes-edit1.jpg" /> And the Image Renderer could look something like this: export default function Image({ altText, src }) {
const preview = getPreviewOfImageSomehowPossibly(src)
return (
<img src={preview} dataLazyLoad={src} altText={altText} />
)
} Or another way: export default function Image({ altText, src, data }) {
return (
<img src={data.image.preview)} dataLazyLoad={src} altText={altText} />
)
}
export function graphQuery({ src }) { // receives original props
return `
{
image(src: src) {
preview
}
}
`
} This is somewhat where the content vs data dichotomy comes in, because content in it's current incarnation is naturally completely opaque and unstructured; its not data. By bringing GraphQL into the mix to query the AST, we're treating it as data. I'm not so sure if that's something that should be done, imho, because it becomes really difficult to reason about what goes where for the writer and the developer. To expand, if content AST were to be treated as data that can be queried and manipulated, why stop at just images? Why not embed all kinds of data into the AST and query it? Why not use Markdown lists instead of YAML lists and JSON arrays? This is what I was talking about to in the PS section of the first comment. I can't reason about all data being representable in content, which is why I'm not as sure about it, and prefer the React component model. I'm still interested in continuing to explore down the path of data in content, because it's so out there and could be fruitful, but so far, I've found it difficult to mentally model an easy-to-use syntax this way. On the other hand, the opposite way is easier to reason about, where content is embedded in data. YAML already does this, where you can have multiline strings, so you can essentially have Markdown or any other content markup language embedded in different keys of a file, not just the main body of a file (as it is with Markdown).
Completely agree with you there, which is why I'm really excited about the source plugins for 1.0, and why I made fsdb, and catalyst as well. GraphQL is the perfect abstraction for this kind of stuff. In fact, since the crux of all API interfaces are really their data models, I can totally imagine GraphQL taking over the world!
On a total side note, I loved that technique so much that I made a small library that would modify webpages on the server, embed a blurred base64 encoded image in the HTML, and serve the original on page-load with it's own lazy loader of lazyload.js 😄 |
So I spent a bit of my morning thinking about this again, and a great example I want to try is a resume. I'm going to do a pure content (Markdown) based approach, a pure data (yaml) based approach, and then try to do a data-in-content approach (random syntax), to push this a little further. Let's say the job is to render a pill-shaped status bar (like Github's languages-in-the-repo bar) except for years spent on each category (work, education, etc..) Content # Bugs Bunny's Resume
## Education
### Quacks-a-Lot University
**Bachelor of Applied Elmer Sciences**
*From last week to present*
## Work Experience
### UberCarrot
**Head of Consumption**
*From birth to death* Certainly, this is very simple to write for most people; there's no need to reason too much about structure, just formatting. Querying the resulting AST however becomes more difficult and error-prone. There's no standardized way to represent a resume in Markdown, so until someone tells me that a triple heading is the name of the organization, I have no idea what I'm looking for, so even if it's doable, it isn't portable. The markdown and the renderer are forever tied together. Data type: resume
education:
- place: Quacks-a-Lot University
degree: Bachelor of Applied Elmer Sciences
start: 07/09/2016
isCurrent: true
work:
- org: UberCarrot
position: Head of Consumption
start: 27/07/1940
isCurrent: true This becomes super easy to reason about and render. It's just data, that any function can take as input, and produce HTML or text or whatever else we're targeting. Data in Content Now this is the interesting piece, and the reason for me to write this all up. Let's see if we can semantically place structured data in content, for easy extraction at a later point. Bear in mind, I'm articulating this pretty much for the first time for someone else's consumption (ie. coherently) as I type, so you're getting stream of thought. <ResumeItem>
### UberCarrot
**Head of Consumption**
*From birth to death*
</ResumeItem> This could be a reasonable first step - wrap an atomic piece of data in a portable wrapper. We could use any kind of syntax, doesn't have to be XML-ish. Let's make it simpler for the sake of writability/readability, and assume that from now on the ^^^
### UberCarrot
**Head of Consumption**
*From birth to death*
^^^ This approach will probably break down at some point, because we'll either run out of easily-memorable sequence of characters to delineate data structures, or documents will be so littered with esoteric sequences that people won't bother using it. Let's say we take it the other way, and structure the data a little more. <ResumeItem org="UberCarrot" position="Head of Consumption" time="From birth to death" /> The problem still remains, that we'll need a new XML tag for every kind of data structure we want to represent. Unbounded, this could easily become a full blown XML/HTML document, exactly the thing we're trying to replace. And besides, there have been countless more languages invented to do represent data better than XML! In fact, imho, XML is the absolute zenith of a data-represented-as-content syntax language. Which is precisely why I draw the line between data and content with the following question: |
Ok, I'm getting where you're coming from now. For me, it still doesn't help much to draw the distinction. I think what you're seeing as opaque is free form text as text is hard to slice & dice much further (though NLP could help — which would be an interesting GraphQL schema to expose tbh. Jekyll will use latent semantic indexing to show you related posts). SO "data" e.g. lists or numbers or whatever appears more granular but I'd say that is only a perception. Even a number could hold worlds of meaning. At some point while querying you'll always reach the limit of "data" and get stuck at an opaque, complex, unique "content" thing. All syntaxes from roman alphabet to hieroglyphs to programming languages, to Markdown etc. etc. are for capturing mental models and all make different trade offs on the ease and fidelity you can capture different types of mental models. Markdown is optimized for easy writing of long-form content. It has baked in all sorts of assumptions about what you're doing. It doesn't ever let you get outside of writing long-form text. So use it for anything else and it stops making sense. So I don't see Markdown as a better JSX. If you want to write complex nested documents with embedded logic and state — just use JSX! That's what it's designed for! If you want to write an essay, use Markdown. If you want to store data for a resume, use YAML (or unless I needed to reuse it multiple places, I'd just put it straight in a component). My intention with querying the Markdown AST w/ matching React.js stuff is to merely enhance the Markdown => web conversion a bit. E.g. auto-resize images and make them responsive & progressive. Make footnotes nice. Replace the normal link with the from React Router, etc. |
Sorry, it's been a while, and I haven't gotten a chance to put things down. I'd like to share some progress on the aforementioned library with you when I have it, till then I'm going to close this issue. I hope querying Markdown's AST works well for you! |
Awesome! Next thing I'm working on is being able to add custom GraphQL types to your site schema so looking forward to seeing your library in Gatsby! |
I read some of your comments regarding doing cool things with Markdown source from #420
and from syntax-tree/mdast#13
This is something I've also been exploring for the last year or so, and I'd like to share some opinionated thoughts, and get feedback.
Essentially, Markdown, as specified, is not setup best for this to occur. All markup languages that exist today draw a line between "content" markup and "data" markup, which is correct imho. Data, by definition, is structured, may have a schema, can have relationships, etc. Content, on the other hand, should remain opaque. The way to bring them together is to embed data in content, not to extract data from content. The distinction is crucial.
For example, again from syntax-tree/mdast#13
Let's explore footnote specification in data and in content:
As content
As data
If we were to take it one step further, we would ask the question: "What if everything in Markdown was a React component?". Over the course of my work, this question has evolved to, "What if Markdown was simply a more easily readable/writable syntax of an XML-ish language that was NOT tied to HTML but rendered to any different target with a React-ish component model?"
Let's say this language is called Blub
First, we have the most readable/writable version of Blub
This introduces the concept of interpolating data into Markdown, the same way we do in React with the curly braces
{}
Then, the fully qualified, canonical version of that same text in Blub
While this looks like HTML, it's not! I believe that if we want to bring React and Markdown together, we should start from a new place that does so in a coherent, structured way.
Back to the footnotes example. Hmm, now while it may seem that we've come full circle and we'll need to query the AST to get all instances of the element, they distinction that remains for me is that we're not specifying the exact reference data. And because this is still not HTML, it's just an intermediary Tree that can be represented as XML (above) or as JSON within a running program, we can combine it with custom renderers to do whatever we want. It sort of brings together ideas that already exist in
remark-react
andreactdown
in a more structured way.You could also extend the minimal language itself by registering block or inline delimiters where
So now, we can do the following:
Which compiles to
Actually, a big reason I'm writing all of this is because you're going for similar outcomes, AND you have a successful project where people are using similar technologies. A large part of my motives are to get feedback/thoughts from you. So, what do you think?
TLDR There should be a more structured way to express data in content or content in data, with the writability/readiblity of Markdown. Do we even draw a line between the two (content vs data)?
PS I'm also thinking the other way, "What if all content is really just semantic data?". Where lists specified with
-
in Markdown can be named and becomes available asnode.someList
. This is a lot harder to reason about though, and I'm not as sure whether it'll work, but I'm exploring none-the-less. If you think this would be a better direction, I'd love to get your thoughts even more so!PPS A lot of this is building towards the
universalobjectdb
for me - > essentially Freebase + Google's Knowledge Graph + Git + React + GraphQLThe text was updated successfully, but these errors were encountered: