Skip to content

Instantly share code, notes, and snippets.

@danieldunderfelt
Created July 10, 2019 05:49
Show Gist options
  • Save danieldunderfelt/1982786761cf4156b732b3a128a8050f to your computer and use it in GitHub Desktop.
Save danieldunderfelt/1982786761cf4156b732b3a128a8050f to your computer and use it in GitHub Desktop.
MDX in React-native
// The actual components that will be rendered with markdown. You may need to change or add components,
// this list is not fully tested. Some components may also never be used by MDX.
// Copied from https://github.com/mientjan/react-native-markdown-renderer and heavily modified.
import React from 'react'
import { Text, TouchableOpacity, View } from 'react-native'
import FitImage from 'react-native-fit-image'
import openUrl from './openUrl'
import { styles } from './styles'
const components = {
div: ({ children }) => <View style={styles.div}>{children}</View>,
wrapper: ({ children }) => <View style={styles.div}>{children}</View>,
textgroup: ({ children }) => {
return <Text style={styles.text}>{children}</Text>
},
inline: ({ children }) => {
return <Text>{children}</Text>
},
text: ({ children }) => {
return <Text>{children}</Text>
},
span: ({ children }) => {
return <Text>{children}</Text>
},
strong: ({ children }) => {
return <Text style={styles.strong}>{children}</Text>
},
a: ({ href, children }) => {
return (
<TouchableOpacity style={styles.link} onPress={() => openUrl(href)}>
{children}
</TouchableOpacity>
)
},
em: ({ children }) => {
return <Text style={styles.em}>{children}</Text>
},
h1: ({ children }) => {
return (
<View style={styles.headingContainer}>
<Text style={[styles.heading, styles.heading1]}>
{children}
</Text>
</View>
)
},
h2: ({ children }) => {
return (
<View style={styles.headingContainer}>
<Text style={[styles.heading, styles.heading2]}>
{children}
</Text>
</View>
)
},
h3: ({ children }) => (
<View style={styles.headingContainer}>
<Text style={[styles.heading, styles.heading3]}>
{children}
</Text>
</View>
),
h4: ({ children }) => (
<View style={styles.headingContainer}>
<Text style={[styles.heading, styles.heading4]}>
{children}
</Text>
</View>
),
h5: ({ children }) => (
<View style={styles.headingContainer}>
<Text style={[styles.heading, styles.heading5]}>
{children}
</Text>
</View>
),
h6: ({ children }) => (
<View style={styles.headingContainer}>
<Text style={[styles.heading, styles.heading6]}>
{children}
</Text>
</View>
),
p: ({ children }) => <View style={styles.paragraph}>{children}</View>,
blockquote: ({ children }) => <View style={styles.blockquote}>{children}</View>,
inlineCode: ({ children }) => {
return <Text style={styles.codeInline}>{children}</Text>
},
code: ({ children }) => {
return <Text style={styles.codeBlock}>{children}</Text>
},
pre: ({ children }) => <View style={styles.pre}>{children}</View>,
ul: ({ children }) => {
return <View style={[styles.list, styles.listUnordered]}>{children}</View>
},
ol: ({ children }) => {
return <View style={[styles.list, styles.listOrdered]}>{children}</View>
},
li: ({ children }) => {
return (
<View style={styles.listUnorderedItem}>
<Text style={styles.listUnorderedItemIcon}>{'\u00B7'}</Text>
<View style={[styles.listItem]}>{children}</View>
</View>
)
},
table: ({ children }) => <View style={[styles.table]}>{children}</View>,
thead: ({ children }) => <View style={[styles.tableHeader]}>{children}</View>,
tbody: ({ children }) => <View>{children}</View>,
th: ({ children }) => {
return <View style={[styles.tableHeaderCell]}>{children}</View>
},
tr: ({ children }) => {
return <View style={[styles.tableRow]}>{children}</View>
},
td: ({ children }) => {
return <View style={[styles.tableRowCell]}>{children}</View>
},
hr: ({ children }) => {
return <View style={[styles.hr]} />
},
br: ({ children }) => <Text>{'\n'}</Text>,
img: ({ src, children }) => {
return <FitImage indicator={true} style={styles.image} source={{ uri: src }} />
},
}
export default components
// Use your MDX content with this component.
import React from 'react'
import MDX from '@mdx-js/runtime'
import components from '../utils/markdown/markdown'
// Renders a cimple loading spinner as a test
import Loading from './Loading'
const mdxComponents = {
...components,
Loading: Loading, // Add the custom component to the default markdown HTML components
}
// Add variables here if needed.
const scope = {}
// Children is an MDX string
const MdxContent = ({ children, style = {} }) => {
return (
<View style={style}>
<MDX components={mdxComponents} scope={scope}>
{children}
</MDX>
</View>
)
}
export default MdxContent
// To open URLs from Markdown links. Modify as needed. Untested.
// Copied from https://github.com/mientjan/react-native-markdown-renderer
import { Linking } from 'react-native';
export default function openUrl(url) {
if( url ) {
Linking.openURL(url);
}
}
// Add this to the root of your project.
// Adds Node libs that are needed, plus an fs implementation.
// mdx-runtime needs these.
// Yes, this works with Expo.
const libs = require('node-libs-react-native')
libs['fs'] = require.resolve('react-native-level-fs')
module.exports = {
resolver: {
extraNodeModules: libs,
},
}
// Styles used for the components. Change to your liking. Some styles may
// be superfluous or not working, this list is not tested.
// Copied from https://github.com/mientjan/react-native-markdown-renderer and modified slightly.
import { StyleSheet } from 'react-native'
export const styles = StyleSheet.create({
root: {},
view: {},
codeBlock: {
borderWidth: 1,
borderColor: '#CCCCCC',
backgroundColor: '#f5f5f5',
padding: 10,
borderRadius: 4,
},
codeInline: {
borderWidth: 1,
borderColor: '#CCCCCC',
backgroundColor: '#f5f5f5',
padding: 10,
borderRadius: 4,
},
del: {
backgroundColor: '#000000',
},
em: {
fontStyle: 'italic',
},
headingContainer: {
flexDirection: 'row',
},
heading: {},
heading1: {
fontSize: 32,
},
heading2: {
fontSize: 24,
},
heading3: {
fontSize: 18,
},
heading4: {
fontSize: 16,
},
heading5: {
fontSize: 13,
},
heading6: {
fontSize: 11,
},
hr: {
backgroundColor: '#000000',
height: 1,
},
blockquote: {
paddingHorizontal: 20,
paddingVertical: 10,
margin: 20,
backgroundColor: '#CCCCCC',
},
inlineCode: {
borderRadius: 3,
borderWidth: 1,
fontFamily: 'Courier',
fontWeight: 'bold',
},
list: {},
listItem: {
flex: 1,
flexWrap: 'wrap',
// backgroundColor: 'green',
},
listUnordered: {},
listUnorderedItem: {
flexDirection: 'row',
justifyContent: 'flex-start',
},
listUnorderedItemIcon: {
marginLeft: 10,
marginRight: 10,
lineHeight: 30,
},
listUnorderedItemText: {
fontSize: 20,
lineHeight: 20,
},
listOrdered: {},
listOrderedItem: {
flexDirection: 'row',
},
listOrderedItemIcon: {
marginLeft: 10,
marginRight: 10,
lineHeight: 30,
},
listOrderedItemText: {
fontWeight: 'bold',
lineHeight: 20,
},
div: {},
paragraph: {
marginTop: 10,
marginBottom: 10,
flexWrap: 'wrap',
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'flex-start',
},
hardbreak: {
width: '100%',
height: 1,
},
strong: {
fontWeight: 'bold',
},
table: {
borderWidth: 1,
borderColor: '#000000',
borderRadius: 3,
},
tableHeader: {},
tableHeaderCell: {
flex: 1,
// color: '#000000',
padding: 5,
// backgroundColor: 'green',
},
tableRow: {
borderBottomWidth: 1,
borderColor: '#000000',
flexDirection: 'row',
},
tableRowCell: {
flex: 1,
padding: 5,
},
text: {},
strikethrough: {
textDecorationLine: 'line-through',
},
pre: {},
link: {},
image: {
flex: 1,
},
})
@slorber
Copy link

slorber commented Feb 1, 2020

thanks, useful to me :)

note your

does not wrap in which leads to RN errors

@danieldunderfelt
Copy link
Author

@slorber thanks! Yeah, I've made multiple fixes since this gist. It's only a starting point.

@slorber
Copy link

slorber commented Feb 2, 2020

Sure, also got the same problem with li

Also wondering how did you do to make images work. The src is just a string while for RN we should not have "src": "./pathToImage" but instead "src": require("./pathToImage");

Could you share more details about what you figured out since this gist? (I'm available on Twitter DM to chat if you want to)

@danieldunderfelt
Copy link
Author

I actually haven't tried images yet! 😅 Thanks for the heads up. I can update this gist with the newest version of my MDX code, it's coming along rather nicely. I'm also maybe going to release it as a library when I'm done.

It might be better to pre-process the MDX instead of using the runtime though. Will have to investigate.

@slorber
Copy link

slorber commented Feb 3, 2020

I'm actually running MDX AOT in a build step. Will write about it when everything will be more production ready but we can chat if you want

@andrekovac
Copy link

@danieldunderfelt if you still have plans to update this gist or create a lib, I'd be very interested to try it! Great initiative!

@slorber
Copy link

slorber commented Aug 28, 2020

Hey,

I'm running MDX on my website/mobile app, and it will be the subject of my RN EU talk the 4th September.

I think I'll try to work on a Metro transformer to provide the compilation automatically, like require("myBlogPost.mdx"), as it's more convenient than embedding the mdx compilation at runtime or having a build step, but nothing published yet and the Metro transformer API is not very easy to understand :D

@farcaller
Copy link

Have anyone faced node_modules/@babel/core/lib/config/files/import.js: node_modules/@babel/core/lib/config/files/import.js:Invalid call at line 9: import(filepath)? Any idea what to do about it?

@enstulen
Copy link

Have anyone faced node_modules/@babel/core/lib/config/files/import.js: node_modules/@babel/core/lib/config/files/import.js:Invalid call at line 9: import(filepath)? Any idea what to do about it?

I get the same issue, did you figure it out?

@farcaller
Copy link

I get the same issue, did you figure it out?

Yeah, I ditched MDX and wrote my own markdown parser and renderer. I don't not suggest to try that :-)

@danieldunderfelt
Copy link
Author

Apologies for not responding to comments here, I just haven't noticed the activity. Github notifications are an absolute mess.

@danieldunderfelt
Copy link
Author

@andrekovac I have been thinking about it! The app where this is used has been on ice for a while, but I'm continuing with it soon and I want to develop a great way to handle content within it. So yeah, something will probably come of this yet!

@danieldunderfelt
Copy link
Author

@slorber that would be ideal! I will continue developing an MDX solution for my needs soon. I'm also looking at https://github.com/kentcdodds/mdx-bundler, it might be useful.

@slorber
Copy link

slorber commented May 18, 2021

Didn't work on this much this year but my talk on MDX + RN is here: https://www.youtube.com/watch?v=ScgFQojbAAc
The 2nd part is dedicated to running MDX in RN

@danieldunderfelt
Copy link
Author

Didn't work on this much this year but my talk on MDX + RN is here: https://www.youtube.com/watch?v=ScgFQojbAAc
The 2nd part is dedicated to running MDX in RN

Great, thanks! I'll check it out.

@MaganAnkur
Copy link

Screen Shot 2021-10-07 at 10 07 09 PM

Any idea how to resolve this, I already installed mdx-js

@MaganAnkur
Copy link

I fixed above issue by upgrading @mdx-js/runtime to 2.0.0-next.9

@danieldunderfelt
Copy link
Author

@MaganAnkur Yes, that's what you needed to do. I've also made a library, check it out and give feedback: https://www.npmjs.com/package/rn-mdx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment