React Router v7 已发布。 查看文档
MDX
本页内容

MDX

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

虽然我们认为数据和显示之间的强分离非常重要,但我们理解混合两者的格式(如 MDX(带有嵌入式 JSX 组件的 Markdown))已成为开发人员流行的强大创作格式。

与其像本文档演示的那样在构建时编译您的内容,通常更好的用户体验和开发者体验是您通过类似 mdx-bundler 的工具在运行时执行此操作。它也更具可定制性和强大功能。但是,如果您希望在构建时进行此编译,请继续阅读。

Remix 内置了两种在构建时使用 MDX 的方式

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

路由

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

MDX 路由允许您像定义基于代码的路由一样定义 meta 和 headers

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

# Hello Content!

文档中 --- 之间的行称为“frontmatter”。您可以将其视为文档的元数据,格式为 YAML

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

---
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 模块一样在任何地方导入这些文件。

当你 import 一个 .mdx 文件时,该模块的导出包括:

  • default:用于消费的 React 组件
  • attributes:作为对象的前言数据
  • filename:源文件的基本名称(例如,“first-post.mdx”)
import Component, {
  attributes,
  filename,
} from "./first-post.mdx";

博客使用示例

以下示例演示了如何使用 MDX 构建一个简单的博客,包括文章的独立页面和一个显示所有文章的索引页面。

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。

高级配置

如果你希望配置自己的 remark 插件,可以通过 remix.config.jsmdx 导出进行配置

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],
  };
};
文档和示例在以下许可下发布 MIT