Merge pull request #96 from timlrx/mdx-bundler

feat: mdx-bundler and xdm
This commit is contained in:
Timothy 2021-07-04 15:23:40 +08:00 committed by GitHub
commit 0fcb9fc90c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1536 additions and 971 deletions

View File

@ -1,5 +1,5 @@
import Image from 'next/image'
import Link from '@/components/Link'
import Image from './Image'
import Link from './Link'
const Card = ({ title, description, imgSrc, href }) => (
<div className="p-4 md:w-1/2 md" style={{ maxWidth: '544px' }}>

View File

@ -1,4 +1,6 @@
import { MDXRemote } from 'next-mdx-remote'
/* eslint-disable react/display-name */
import { useMemo } from 'react'
import { getMDXComponent } from 'mdx-bundler/client'
import Image from './Image'
import CustomLink from './Link'
import Pre from './Pre'
@ -7,14 +9,14 @@ export const MDXComponents = {
Image,
a: CustomLink,
pre: Pre,
wrapper: ({ components, layout, ...rest }) => {
const Layout = require(`../layouts/${layout}`).default
return <Layout {...rest} />
},
}
export const MDXLayoutRenderer = ({ layout, mdxSource, ...rest }) => {
const LayoutComponent = require(`../layouts/${layout}`).default
const MDXLayout = useMemo(() => getMDXComponent(mdxSource), [mdxSource])
return (
<LayoutComponent {...rest}>
<MDXRemote {...mdxSource} components={MDXComponents} />
</LayoutComponent>
)
return <MDXLayout layout={layout} components={MDXComponents} {...rest} />
}

View File

@ -14,6 +14,19 @@
@apply hidden;
}
.code-line {
@apply pl-4 -mx-4 border-l-4 border-gray-800;
}
.highlight-line {
@apply -mx-4 bg-gray-700 bg-opacity-50 border-l-4 border-primary-500;
}
.line-number::before {
@apply pr-4 -ml-2 text-gray-400;
content: attr(line);
}
html {
scroll-behavior: smooth;
}

View File

@ -1,6 +1,6 @@
---
name: Sparrow Hawk
avatar: https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Accnis_edit.jpg/220px-Accnis_edit.jpg
avatar: /static/images/sparrowhawk-avatar.jpg
occupation: Wizard of Earthsea
company: Earthsea
twitter: https://twitter.com/sparrowhawk

View File

@ -140,6 +140,7 @@ function fancyAlert(arg) {
$.facebox({ div: '#foo' })
}
}
```
````
And here's how it looks - nicely colored with styled code titles!

View File

@ -50,14 +50,10 @@ _Note_: If you try to save the image, it is in webp format, if your browser supp
![ocean](/static/images/ocean.jpeg)
<p>
Photo by{' '}
<a href="https://unsplash.com/@yucar?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
YUCAR FotoGrafik
</a>{' '}
on{' '}
<a href="https://unsplash.com/s/photos/sea?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Unsplash
</a>
Photo by [YUCAR
FotoGrafik](https://unsplash.com/@yucar?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
on
[Unsplash](https://unsplash.com/s/photos/sea?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
</p>
# Benefits

View File

@ -1,6 +1,6 @@
---
title: 'New features in v1'
date: '2021-06-02'
date: '2021-06-29'
tags: ['next-js', 'tailwind', 'guide']
draft: false
summary: 'An overview of the new features released in v1 - code block copy, multiple authors, frontmatter layout and more'
@ -12,15 +12,17 @@ layout: PostSimple
A post on the new features introduced in v1.0. New features:
- [Theme colors](#theme-colors)
- [Xdm MDX compiler](#xdm-mdx-compiler)
- [Layouts](#layouts)
- [Multiple authors](#multiple-authors)
- [Copy button for code blocks](#copy-button-for-code-blocks)
- [Line highlighting and line numbers](#line-highlighting-and-line-numbers)
## Theme colors
You can easily modify the theme color by changing the primary attribute in the tailwind config file:
```:tailwind.config.js
```js:tailwind.config.js
theme: {
colors: {
primary: colors.teal,
@ -37,6 +39,37 @@ Tailwind includes great default color palettes that can be used for theming your
Migrating from v1? You can revert to the previous theme by setting `primary` to `colors.sky` (Tailwind 2.2.2 and above, otherwise `colors.lightBlue`) and changing gray to `colors.coolGray`.
## Xdm MDX compiler
We switch the MDX bundler from [next-mdx-remote](https://github.com/hashicorp/next-mdx-remote) to [mdx-bundler](https://github.com/kentcdodds/mdx-bundler).
This uses [xdm](https://github.com/wooorm/xdm) under the hood uses the latest micromark 3 and remark, rehype libraries.
**Warning:** If you were using custom remark or rehype libraries, please upgrade to micromark 3 compatible ones. If you are upgrading, please delete `node_modules` and `package-lock.json` to avoid having past dependencies related issues.
[xdm](https://github.com/wooorm/xdm) contains multiple improvements over [@mdx-js/mdx](https://github.com/mdx-js/mdx), the compiler used internally by next-mdx-remote, but there might be some breaking behaviour changes.
Please check your markdown output to verify.
Some new possibilities include loading components directly in the mdx file using the import syntax and including js code which could be compiled at the build step.
For example, the following jsx snippet can be used directly in an MDX file to render the page title component:
```js
import PageTitle from './PageTitle.js'
;<PageTitle> Using JSX components in MDX </PageTitle>
```
import PageTitle from './PageTitle.js'
<PageTitle> Using JSX components in MDX </PageTitle>
The default configuration resolves all components relative to the `components` directory.
**Note**:
Components which require external image loaders would require additional esbuild configuration.
Components which are dependent on global application state on lifecycle like the Nextjs `Link` component would also not work with this setup as each mdx file is built indepedently.
For such cases, it is better to use component substitution.
## Layouts
You can map mdx blog content to layout components by configuring the frontmatter field. For example, this post is written with the new `PostSimple` layout!
@ -110,7 +143,7 @@ Information on authors is now split from `siteMetadata.json` and stored in its o
Here's how an author markdown file might looks like:
```:default.md
```md:default.md
---
name: Tails Azimuth
avatar: /static/images/avatar.png
@ -153,3 +186,46 @@ A demo of a multiple author post is shown in the [Introducing Tailwind Nextjs St
Hover over a code block and you will notice a Github inspired copy button! You can modify `./components/Pre.js` to further customise it.
The component is passed to `MDXComponents` and modifies all `<pre>` blocks.
## Line highlighting and line numbers
Line highlighting and line numbers is now supported out of the box thanks to the new [rehype-prism-plus plugin](https://github.com/timlrx/rehype-prism-plus)
The following javascript code block:
````
```js {1, 3-4} showLineNumbers
var num1, num2, sum
num1 = prompt('Enter first number')
num2 = prompt('Enter second number')
sum = parseInt(num1) + parseInt(num2) // "+" means "add"
alert('Sum = ' + sum) // "+" means combine into a string
```
````
will appear as:
```js {1,3-4} showLineNumbers
var num1, num2, sum
num1 = prompt('Enter first number')
num2 = prompt('Enter second number')
sum = parseInt(num1) + parseInt(num2) // "+" means "add"
alert('Sum = ' + sum) // "+" means combine into a string
```
To modify the styles, change the following class selectors in the `tailwind.css` file:
```css
.code-line {
@apply pl-4 -mx-4 border-l-4 border-gray-800;
}
.highlight-line {
@apply -mx-4 bg-gray-700 bg-opacity-50 border-l-4 border-primary-500;
}
.line-number::before {
@apply pr-4 -ml-2 text-gray-400;
content: attr(line);
}
```

View File

@ -59,42 +59,26 @@ When MDX v2 is ready, one could potentially interleave markdown in jsx directly!
### Photo Credits
<div>
Maple photo by{' '}
<a href="https://unsplash.com/@i_am_g?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Guillaume Jaillet
</a>{' '}
on{' '}
<a href="https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Unsplash
</a>
Maple photo by [Guillaume
Jaillet](https://unsplash.com/@i_am_g?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
on
[Unsplash](https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
</div>
<div>
Mountains photo by{' '}
<a href="https://unsplash.com/@john_artifexfilms?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
John Lee
</a>{' '}
on{' '}
<a href="https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Unsplash
</a>
Mountains photo by [John
Lee](https://unsplash.com/@john_artifexfilms?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
on
[Unsplash](https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
</div>
<div>
Lake photo by{' '}
<a href="https://unsplash.com/@tjholowaychuk?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Tj Holowaychuk
</a>{' '}
on{' '}
<a href="https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Unsplash
</a>
Lake photo by [Tj
Holowaychuk](https://unsplash.com/@tjholowaychuk?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
on
[Unsplash](https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
</div>
<div>
Toronto photo by{' '}
<a href="https://unsplash.com/@matthewhenry?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Matthew Henry
</a>{' '}
on{' '}
<a href="https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">
Unsplash
</a>
Toronto photo by [Matthew
Henry](https://unsplash.com/@matthewhenry?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
on
[Unsplash](https://unsplash.com/s/photos/canada?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)
</div>

View File

@ -55,7 +55,7 @@ export default function PostLayout({ frontMatter, authorDetails, next, prev, chi
<li className="flex items-center space-x-2" key={author.name}>
{author.avatar && (
<Image
src={siteMetadata.image}
src={author.avatar}
width="38px"
height="38px"
alt="avatar"

View File

@ -15,13 +15,14 @@ module.exports = (options) => (tree) => {
const dimensions = sizeOf(`${process.cwd()}/public${imageNode.url}`)
// Convert original node to next/image
imageNode.type = 'jsx'
imageNode.value = `<Image
alt={\`${imageNode.alt}\`}
src={\`${imageNode.url}\`}
width={${dimensions.width}}
height={${dimensions.height}}
/>`
;(imageNode.type = 'mdxJsxFlowElement'),
(imageNode.name = 'Image'),
(imageNode.attributes = [
{ type: 'mdxJsxAttribute', name: 'alt', value: imageNode.alt },
{ type: 'mdxJsxAttribute', name: 'src', value: imageNode.url },
{ type: 'mdxJsxAttribute', name: 'width', value: dimensions.width },
{ type: 'mdxJsxAttribute', name: 'height', value: dimensions.height },
])
// Change node type from p to div to avoid nesting error
node.type = 'div'

View File

@ -1,10 +1,10 @@
import { MDXComponents } from '@/components/MDXComponents'
import { bundleMDX } from 'mdx-bundler'
import fs from 'fs'
import matter from 'gray-matter'
import { serialize } from 'next-mdx-remote/serialize'
import path from 'path'
import readingTime from 'reading-time'
import visit from 'unist-util-visit'
import codeTitles from './remark-code-title'
import imgToJsx from './img-to-jsx'
import getAllFilesRecursively from './utils/files'
@ -48,21 +48,45 @@ export async function getFileBySlug(type, slug) {
? fs.readFileSync(mdxPath, 'utf8')
: fs.readFileSync(mdPath, 'utf8')
const { data, content } = matter(source)
const mdxSource = await serialize(content, {
components: MDXComponents,
mdxOptions: {
remarkPlugins: [
// https://github.com/kentcdodds/mdx-bundler#nextjs-esbuild-enoent
if (process.platform === 'win32') {
process.env.ESBUILD_BINARY_PATH = path.join(
process.cwd(),
'node_modules',
'esbuild',
'esbuild.exe'
)
} else {
process.env.ESBUILD_BINARY_PATH = path.join(
process.cwd(),
'node_modules',
'esbuild',
'bin',
'esbuild'
)
}
const { frontmatter, code } = await bundleMDX(source, {
// mdx imports can be automatically source from the components directory
cwd: path.join(process.cwd(), 'components'),
xdmOptions(options) {
// this is the recommended way to add custom remark/rehype plugins:
// The syntax might look weird, but it protects you in case we add/remove
// plugins in the future.
options.remarkPlugins = [
...(options.remarkPlugins ?? []),
require('remark-slug'),
require('remark-autolink-headings'),
require('remark-code-titles'),
require('remark-gfm'),
codeTitles,
[require('remark-footnotes'), { inlineNotes: true }],
require('remark-math'),
imgToJsx,
],
inlineNotes: true,
rehypePlugins: [
]
options.rehypePlugins = [
...(options.rehypePlugins ?? []),
require('rehype-katex'),
require('@mapbox/rehype-prism'),
[require('rehype-prism-plus'), { ignoreMissing: true }],
() => {
return (tree) => {
visit(tree, 'element', (node, index, parent) => {
@ -73,17 +97,25 @@ export async function getFileBySlug(type, slug) {
})
}
},
],
]
return options
},
esbuildOptions: (options) => {
options.loader = {
...options.loader,
'.js': 'jsx',
}
return options
},
})
return {
mdxSource,
mdxSource: code,
frontMatter: {
readingTime: readingTime(content),
readingTime: readingTime(code),
slug: slug || null,
fileName: fs.existsSync(mdxPath) ? `${slug}.mdx` : `${slug}.md`,
...data,
...frontmatter,
},
}
}

32
lib/remark-code-title.js Normal file
View File

@ -0,0 +1,32 @@
import visit from 'unist-util-visit'
module.exports = function (options) {
return (tree) =>
visit(tree, 'code', (node, index) => {
const nodeLang = node.lang || ''
let language = ''
let title = ''
if (nodeLang.includes(':')) {
language = nodeLang.slice(0, nodeLang.search(':'))
title = nodeLang.slice(nodeLang.search(':') + 1, nodeLang.length)
}
if (!title) {
return
}
const className = 'remark-code-title'
const titleNode = {
type: 'mdxJsxFlowElement',
name: 'div',
attributes: [{ type: 'mdxJsxAttribute', name: 'className', value: className }],
children: [{ type: 'text', value: title }],
data: { _xdmExplicitJsx: true },
}
tree.children.splice(index, 0, titleNode)
node.lang = language
})
}

2204
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,25 +12,25 @@
"prepare": "husky install"
},
"dependencies": {
"@mapbox/rehype-prism": "^0.6.0",
"@tailwindcss/forms": "^0.3.2",
"@tailwindcss/typography": "^0.4.0",
"autoprefixer": "^10.2.5",
"gray-matter": "^4.0.2",
"image-size": "1.0.0",
"mdx-bundler": "^4.1.0",
"next": "11.0.1",
"next-mdx-remote": "^3.0.1",
"next-themes": "^0.0.14",
"postcss": "^8.3.5",
"preact": "^10.5.13",
"react": "17.0.2",
"react-dom": "17.0.2",
"reading-time": "1.3.0",
"rehype-katex": "^4.0.0",
"rehype-katex": "^5.0.0",
"rehype-prism-plus": "0.0.1",
"remark-autolink-headings": "6.0.1",
"remark-code-titles": "0.1.2",
"remark-footnotes": "^3.0.0",
"remark-math": "^3.0.1",
"remark-gfm": "^1.0.0",
"remark-math": "^4.0.0",
"remark-slug": "6.0.0",
"tailwindcss": "^2.2.2"
},

View File

@ -1,6 +1,6 @@
import Image from 'next/image'
import siteMetadata from '@/data/siteMetadata'
import projectsData from '@/data/projectsData'
import Image from '@/components/Image'
import Link from '@/components/Link'
import Card from '@/components/Card'
import { PageSeo } from '@/components/SEO'

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB