虽然我们认为数据和显示之间的强分离非常重要,但我们理解混合两者的格式(如 MDX(带有嵌入式 JSX 组件的 Markdown))已成为开发人员流行的强大创作格式。
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 文件中导出此模块中的所有其他内容,就像在常规路由模块中一样,例如 loader
、action
和 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 />
除了路由级别的 MDX 之外,您还可以像导入常规 JavaScript 模块一样在任何地方导入这些文件。
当你 import
一个 .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.js
的 mdx
导出进行配置
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],
};
};