雖然我們認為資料和顯示的強烈分離非常重要,但我們理解混合兩者的格式 (例如 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!
上述文件中 ---
之間的行稱為「前言」。您可以將它們視為文件的中繼資料,格式為 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 檔案中匯出此模組中的所有其他內容,就像您在常規路由模組中可以做的那樣,例如 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],
};
};