8

I am trying to get load a markdown file to be loaded on build. I have setup a create-react-app that uses Typescript and using a material_ui blog theme.

import ReactMarkdown from 'markdown-to-jsx';
import post1 from './blog-post.1.md';

export default function Main(props: MainProps) {
  const classes = useStyles();
  const { posts, title } = props;

  return (
    <Grid item xs={12} md={8}>
      <Typography variant="h6" gutterBottom>
        {title}
      </Typography>
      <Divider />
      {posts.map((post) => (
        <Markdown className={classes.markdown} key={post.substring(0, 40)}>
          {post}
        </Markdown>
      ))}
    </Grid>
  );
}

If a post is assigned to be markdown, it will render correctly: const post = '# Sample blog post #### April 1, 2020 by [Olivier](/)'

So the issue is that .md file is not loading into the variable.

When npm start is run, the html displays: /static/media/blog-post.1.09e7833f.md Why would this be and what is that number: 09e7833f in the filepath?

I would ideally like to load the file in during build time so that it can be served more quickly on somewhere like Netlify. I do not want to use an asynchronous fetch like this answer suggests.

I assume I would have to create a webpack loader? The problem with this is that create-react-app has webpack.config.js in the node_modules which means customising it would be overwritten during a new build. I don't think using raw-loader helps if I can't configure it.

I've also tried import post1 from 'raw-loader!./blog-post.1.md'; as suggested here but Typescript doesn't know what to do with that.

Any suggestions would be helpful.

Hughes
  • 366
  • 4
  • 13

2 Answers2

8

This seems to be something that's been asked previously (https://github.com/facebook/create-react-app/issues/3025), but unfortunately it doesn't seem possible right now to directly import markdown as a string in JavaScript/TypeScript.

When asking create-react-app to import an md file, it seems to get a URL (with a hex string) to the file instead of the raw string contained within. A workaround is as follows:

  1. Create a .d.ts file to tell TypeScript how to interpret an md file:
declare module '*.md' {
  const value: string; // markdown is just a string
  export default value;
}
  1. Import your md file (the_text.md) as follows
import theTextFile from './the_text.md';
  1. fetch the contents of the file to get the actual markdown (https://github.com/facebook/create-react-app/issues/2961#issuecomment-322916352)
const [postMarkdown, setPostMarkdown] = useState('');

// useEffect with an empty dependency array (`[]`) runs only once
useEffect(() => {
  fetch(theTextFile)
    .then((response) => response.text())
    .then((text) => {
      // Logs a string of Markdown content.
      // Now you could use e.g. <rexxars/react-markdown> to render it.
      // console.log(text);
      setPostMarkdown(text);
    });
}, []);
  1. Place the markdown text into the Markdown tag and it will be rendered:
<Markdown className={classes.markdown}>
  {post}
</Markdown>

Keep in mind you can also override the JSX elements used to display the final thing. For Material-UI, this could look something like:

import { Typography } from '@material-ui/core';
import { MarkdownToJSX } from 'markdown-to-jsx';

const Foo = (): JSX.Element => {

  const MdOverrideTypography = ({
    children,
  }: // ...props
  React.DetailedHTMLProps<
    React.HTMLAttributes<HTMLParagraphElement>,
    HTMLParagraphElement
  >): JSX.Element => (
    <Typography paragraph>
      {children}
    </Typography>
  );

  const mdOverrides: MarkdownToJSX.Overrides = {
    // "p" paragraph elements are substituted with MUI "Typography"
    p: MdOverrideTypography,
  };

  // ...

  return (
    <Markdown options={{ overrides: mdOverrides }}>
      {post}
    </Markdown>
  )
}

(https://www.npmjs.com/package/markdown-to-jsx#optionsoverrides---rendering-arbitrary-react-components)

jmaio
  • 98
  • 1
  • 5
  • 4
    Does anyone know how material-ui is doing it on their sample site online? (https://material-ui.com/getting-started/templates/blog/) Are they loading it during their SSR build? Their code is structured similarly to the question above. Thanks – jboxxx Aug 21 '21 at 19:57
  • Minor update you may want to consider: typography is now include via: import Typography from '@mui/material/Typography'; – Jamie Nicholl-Shelley Aug 03 '23 at 00:15
1

An easy way seems to be to use the raw.macro package (I quote from the start of its README):

Installation

In order to use raw.macro in your own project, you can use one of the following commands:

# or $ npm install --save-dev raw.macro

Make sure babel-plugin-macros already installed. If you're using Create React App, it's installed by default.

Usage

raw.macro is similar to Node’s require call:

import raw from "raw.macro";

const markdown = raw("./README.md");

TypeScript sees the markdown variable as type string.

The one problem is that editing the markdown file doesn't cause the project to rebuild, to include the change. Instead you have to touch (e.g. re-save) the source file which imports it.

ChrisW
  • 54,973
  • 13
  • 116
  • 224
  • Yes - I can confirm that. It's a known issue https://github.com/pveyes/raw.macro/issues/20 For me that was the decisive factor to go with the `raw-loader` approach instead. Besides the caching issue, raw.macro works just fine. – DanielH Jul 28 '22 at 08:50