虽然我们认为数据和显示的严格分离非常重要,但我们也知道,混合使用两者的格式,例如 MDX(嵌入 JSX 组件的 Markdown),已经成为开发者常用且强大的创作格式。
¥While we believe that a strong separation of data and display is important, we understand that formats that mix the two, such as MDX (Markdown with embedded JSX components) have become a popular and powerful authoring format for developers.
Remix 内置了在构建时使用 MDX 的两种支持方式:
¥Remix has built-in support for using MDX at build-time in two ways:
你可以将 .mdx
文件用作路由模块之一。
¥You can use a .mdx
file as one of your route modules
你可以将 .mdx
文件 import
到你的某个路由模块中(在 app/routes
中)
¥You can import
a .mdx
file into one of your route modules (in app/routes
)
¥Routes
在 Remix 中使用 MDX 最简单的方法是创建一个路由模块。与 .tsx
返回来自 .js
的值一样,.jsx
将返回来自操作的数据。
¥The simplest way to get started with MDX in Remix is to create a route module. Just like .tsx
, .js
and .jsx
files in your app/routes
directory, .mdx
(and .md
) files will participate in automatic file-system-based routing.
MDX 路由允许你定义元信息和标头,就像它们是基于代码的路由一样:
¥MDX routes allow you to define both meta and headers as if they were a code-based route:
---
meta:
- title: My First Post
- name: description
content: Isn't this awesome?
headers:
Cache-Control: no-cache
---
# Hello Content!
上面文档中 ---
之间的代码行称为 frontmatter
。你可以将它们视为文档的元数据,格式为 YAML。
¥The lines in the document above between the ---
are called frontmatter
. You can think of them like metadata for your document, formatted as YAML.
你可以通过 MDX 中的全局 attributes
变量引用你的前置字段:
¥You can reference your frontmatter fields through the global attributes
variable in your MDX:
---
componentData:
label: Hello, World!
---
import SomeComponent from "~/components/some-component";
# Hello MDX!
<SomeComponent {...attributes.componentData} />
¥Example
通过创建 app/routes/posts.first-post.mdx
,我们可以开始撰写博客文章:
¥By creating a app/routes/posts.first-post.mdx
we can start writing a blog post:
---
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}!
¥Advanced Example
你甚至可以导出此模块中所有其他可以在 mdx 文件(如 loader
、action
和 handle
)中的常规路由模块中导出的内容:
¥You can even export all the other things in this module that you can in regular route modules in your mdx files like loader
, action
, and handle
:
---
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 />
¥Modules
除了路由级别的 MDX 之外,你还可以像导入常规 JavaScript 模块一样,将这些文件导入到任何地方。
¥Besides just route level MDX, you can also import these files anywhere yourself as if it were a regular JavaScript module.
当你 import
一个 .mdx
文件时,模块的导出如下:
¥When you import
a .mdx
file, the exports of the module are:
默认:可供使用的 React 组件
¥default: The React component for consumption
属性:作为对象的 frontmatter 数据
¥attributes: The frontmatter data as an object
文件名:源文件的基本名称(例如 "first-post.mdx")
¥filename: The basename of the source file (e.g. "first-post.mdx")
import Component, {
attributes,
filename,
} from "./first-post.mdx";
¥Example Blog Usage
以下示例演示了如何使用 MDX 构建一个简单的博客,包括用于显示帖子本身的独立页面以及显示所有帖子的索引页。
¥The following example demonstrates how you might build a simple blog with MDX, including individual pages for the posts themselves and an index page that shows all posts.
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 与 MDX Bundler 一起使用。
¥This is not a scalable solution for a blog with thousands of posts. Realistically speaking, writing is hard, so if your blog starts to suffer from too much content, that's an awesome problem to have. If you get to 100 posts (congratulations!), we suggest you rethink your strategy and turn your posts into data stored in a database so that you don't have to rebuild and redeploy your blog every time you fix a typo. You can even keep using MDX with MDX Bundler.
¥Advanced Configuration
如果你希望配置自己的 remark
插件,可以通过 remix.config.js
的 mdx
导出进行配置:
¥If you wish to configure your own remark
plugins, you can do so through the remix.config.js
's mdx
export:
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],
};
};
上述配置解析 Markdown 以插入 highlight.js 友好的 DOM 元素。要显示语法高亮,你还需要包含 highlight.js
CSS 文件。另请参阅 显示样式。
¥The above configuration parses the Markdown to insert highlight.js friendly DOM elements. To have the syntax highlighting appear, you will also need to include the highlight.js
CSS file. See also surfacing styles.