跳转到内容

MDX

MDX

本文档仅在使用 Classic Remix 编译器 时才相关。想要使用 MDX 的 Vite 用户应使用 MDX Rollup(和 Vite)插件

虽然我们认为数据和显示的严格分离很重要,但我们知道混合两者的格式,例如 MDX(嵌入 JSX 组件的 Markdown)已成为开发人员流行且强大的创作格式。

与本文档所演示的在构建时编译内容不同,如果您在运行时通过 mdx-bundler 之类的工具执行此操作,通常可以获得更好的用户体验和 DX。此外,它还具有更高的可定制性和功能性。但是,如果您更喜欢在构建时执行此编译,请继续阅读。

Remix 内置了对在构建时使用 MDX 的两种支持:

  • 您可以使用 .mdx 文件作为您的路由模块之一
  • 您可以将 .mdx 文件 导入 到您的路由模块之一(在 app/routes 中)

路由

在 Remix 中开始使用 MDX 的最简单方法是创建路由模块。就像 app/routes 目录中的 .tsx.js.jsx 文件一样,.mdx(和 .md)文件将参与基于文件系统的自动路由。

MDX 路由允许您定义元路由和标头路由,就好像它们是基于代码的路由一样:

---
meta:
- title: My First Post
- name: description
content: Isn't this awesome?
headers:
Cache-Control: no-cache
---
# Hello Content!

上述文档中 --- 之间的行称为 frontmatter。您可以将它们视为文档的元数据,格式为 YAML

您可以通过 MDX 中的全局 attributes 变量引用前置字段:

---
componentData:
label: Hello, World!
---
import SomeComponent from "~/components/some-component";
# Hello MDX!
<SomeComponent {...attributes.componentData} />

例子

通过创建 app/routes/posts.first-post.mdx,我们可以开始撰写博客文章:

---
meta:
- title: My First Post
- name: description
content: Isn't this just awesome?
---
# Example Markdown Post
You can reference your frontmatter data through "attributes". The title of this post is {attributes.meta.title}!

高级示例

您甚至可以导出此模块中的所有其他内容,这些内容可以在 mdx 文件中的常规路由模块中使用,例如 loaderactionhandle

---
meta:
- title: My First Post
- name: description
content: Isn't this awesome?
headers:
Cache-Control: no-cache
handle:
someData: abc
---
import styles from "./first-post.css";
export const links = () => [
{ rel: "stylesheet", href: styles },
];
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
export const loader = async () => {
return json({ mamboNumber: 5 });
};
export function ComponentUsingData() {
const { mamboNumber } = useLoaderData<typeof loader>();
return <div id="loader">Mambo Number: {mamboNumber}</div>;
}
# This is some markdown!
<ComponentUsingData />

模块

除了路由级别 MDX 之外,您还可以自己将这些文件导入到任何地方,就像常规 JavaScript 模块一样。

当你导入一个.mdx 文件时,模块的导出如下:

  • default:要使用的 React 组件
  • attributes:作为对象的 frontmatter 数据
  • filename:源文件的基本名称(例如 first-post.mdx
import Component, {
attributes,
filename,
} from "./first-post.mdx";

博客使用示例

下面的示例演示了如何使用 MDX 构建一个简单的博客,其中包括帖子本身的单独页面和显示所有帖子的索引页。

app/routes/_index.tsx
import { json } from "@remix-run/node"; // or cloudflare/deno
import { Link, useLoaderData } from "@remix-run/react";
// Import all your posts from the app/routes/posts directory. Since these are
// regular route modules, they will all be available for individual viewing
// at /posts/a, for example.
import * as postA from "./posts/a.mdx";
import * as postB from "./posts/b.md";
import * as postC from "./posts/c.md";
function postFromModule(mod) {
return {
slug: mod.filename.replace(/\.mdx?$/, ""),
...mod.attributes.meta,
};
}
export async function loader() {
// Return metadata about each of the posts for display on the index page.
// Referencing the posts here instead of in the Index component down below
// lets us avoid bundling the actual posts themselves in the bundle for the
// index page.
return json([
postFromModule(postA),
postFromModule(postB),
postFromModule(postC),
]);
}
export default function Index() {
const posts = useLoaderData<typeof loader>();
return (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link to={post.slug}>{post.title}</Link>
{post.description ? (
<p>{post.description}</p>
) : null}
</li>
))}
</ul>
);
}

显然,对于拥有数千篇帖子的博客来说,这不是一个可扩展的解决方案。从现实角度来说,写作很难,所以如果你的博客开始因内容过多而受到影响,那将是一个严重的问题。如果你的帖子达到 100 篇(恭喜!),我们建议你重新考虑你的策略,将帖子转换为存储在数据库中的数据,这样你就不必在每次修复拼写错误时重建和重新部署博客。你甚至可以继续使用 MDX Bundler 的 MDX。

高级配置

如果您希望配置自己的备注插件,您可以通过 remix.config.jsmdx 导出来进行配置:

remix.config.js
const {
remarkMdxFrontmatter,
} = require("remark-mdx-frontmatter");
// can be an sync / async function or an object
exports.mdx = async (filename) => {
const [rehypeHighlight, remarkToc] = await Promise.all([
import("rehype-highlight").then((mod) => mod.default),
import("remark-toc").then((mod) => mod.default),
]);
return {
remarkPlugins: [remarkToc],
rehypePlugins: [rehypeHighlight],
};
};