contentlayerdev / contentlayer

Contentlayer turns your content into data - making it super easy to import MD(X) and CMS content in your app
https://www.contentlayer.dev
MIT License
3.32k stars 201 forks source link

Support static HTML from MDX content #94

Open seancdavis opened 2 years ago

seancdavis commented 2 years ago

An mdx field delivers an object with raw input and code output, which can be passed to a Contentlayer method to render it as a component.

It would be great to also have the HTML code (without interactivity or the ability to hydrate).

I would personally like this as a way to use Contentlayer outside a dynamic/server-side context (i.e. in a non-Next project). But we've also heard a scenario in which someone may want to render content statically to an XML feed where the code/interactivity is irrelevant, but the content (along with the semantic markup) is important.

pmarsceill commented 2 years ago

This would also be helpful in writing scripts to generate static files (like writing an RSS feed xml file) where you may want some form of the compiled body available for consumption.

alexcarpenter commented 1 year ago

Does anyone have a solution for this at the moment? Working on adding an RSS feed and unable to render the content from MDX source at the moment.

ThePaulMcBride commented 1 year ago

I've just ran into the same issue. I am trying to take posts written in MDX and add them to an RSS feed, but I just can't can't plain HTML back. Anyone had any luck with this?

schickling commented 1 year ago

I'm not yet convinced whether we should actually make this a built-in feature (in order to keep Contentlayer simple) but this should already be possible using the following approach:

import { defineDocumentType } from 'contentlayer/source-files'
import { bundleMDX } from 'mdx-bundler'
import * as ReactDOMServer from 'react-dom/server'
import { getMDXComponent } from 'mdx-bundler/client/index.js'

const mdxToHtml = async (mdxSource: string) => {
  const { code } = await bundleMDX({ source: mdxSource })
  const MDXLayout = getMDXComponent(code)
  // TODO add your own components here
  const element = MDXLayout({ components: {} })!
  const html = ReactDOMServer.renderToString(element)
  return html
}

const Post = defineDocumentType(() => ({
  name: 'Post',
  filePathPattern: `posts/**/*.mdx`,
  contentType: 'mdx',
  fields: {
    title: {
      type: 'string',
      required: true,
    },
  },
  computedFields: {
    mdxHtml: {
      type: 'string',
      resolve: (doc) => mdxToHtml(doc.body.raw),
    },
  },
}))

I've also added an example here.

ThePaulMcBride commented 1 year ago

Thanks for this example.

I've tried this and I'm running into issues. Seems if my components object contains anything that requires jsx, it throws an error. I've set up a branch in my repo to show this.

https://github.com/ThePaulMcBride/paulmcbride.com/tree/feature/full-rss-feed

Am I doing something dumb?

manfromanotherland commented 1 year ago

I’m asking myself the same thing @ThePaulMcBride 😅 Have you found a fix? I can’t get it to build, getting the same error when using JSX:

The JSX syntax extension is not currently enabled

The esbuild loader for this file is currently set to js but it must be set to jsx to be able to parse JSX syntax. You can use "loader: { '.js': 'jsx' }" to do that.

Not too sure where I should change the loader for next.js, but will keep looking.

ThePaulMcBride commented 1 year ago

I have had no luck either. I'm currently just living with a crappy rss feed for now.

pmarsceill commented 1 year ago

I used some native ReactDOMServer functions to handle this. Since I use mdx components in my content, I had to create some RSS feed specific components to use in order to render them as something that would be reasonable in an RSS feed.

Take a look at this script I wrote that builds the feed:

https://github.com/pmarsceill/this-modern-web/blob/main/scripts/rss-generator.mjs