首页 > web前端 > css教程 > Next.js中负责的赌博

Next.js中负责的赌博

Joseph Gordon-Levitt
发布: 2025-03-21 10:57:11
原创
822 人浏览过

Responsible Markdown in Next.js

Markdown 确实是一种很棒的格式。它足够接近纯文本,任何人都可以快速学习,并且它的结构足够好,可以被解析并最终转换为任何你想要的格式。

然而:解析、处理、增强和转换 Markdown 需要代码。在客户端部署所有这些代码会付出代价。它本身并不巨大,但仍然是几十KB的代码,仅用于处理 Markdown,而没有其他用途。

在本文中,我将解释如何在 Next.js 应用程序中将 Markdown 保持在客户端之外,使用 Unified/Remark 生态系统(真的不知道该用哪个名字,这太令人困惑了)。

主要思路

其思路是在 Next.js 的 getStaticProps 函数中仅使用 Markdown,以便在构建过程中完成此操作(如果使用 Vercel 的增量构建,则在 Next 无服务器函数中完成),但绝不在客户端使用。我想 getServerSideProps 也行,但我认为 getStaticProps 更可能是常见的用例。

这将返回一个由解析和处理 Markdown 内容生成的 AST(抽象语法树,也就是说,一个描述我们内容的大型嵌套对象),客户端只需负责将该 AST 渲染成 React 组件。

我想我们甚至可以直接在 getStaticProps 中将 Markdown 渲染为 HTML 并返回它以使用 dangerouslySetInnerHtml 渲染,但我们不是那种人。安全很重要。还有,用我们自己的组件以我们想要的方式渲染 Markdown 的灵活性,而不是将其渲染为纯 HTML。说真的,朋友们,不要那样做。?

export const getStaticProps = async () => {
  // 从某个地方获取 Markdown 内容,例如 CMS 或其他内容。就本文而言,这并不重要。也可以从文件中读取。
  const markdown = await getMarkdownContentFromSomewhere()
  const ast = parseMarkdown(markdown)

  return { props: { ast } }
}

const Page = props => {
  // 这通常也包含你的布局等等,但这里为了简单起见省略了。
  return <markdownrenderer ast="{props.ast}"></markdownrenderer>
}

export default Page
登录后复制

解析 Markdown

我们将使用 Unified/Remark 生态系统。我们需要安装 unified 和 remark-parse,仅此而已。解析 Markdown 本身相对简单:

import { unified } from 'unified'
import remarkParse from 'remark-parse'

const parseMarkdown = content => unified().use(remarkParse).parse(content)

export default parseMarkdown
登录后复制

现在,让我费了好长时间才明白的是,为什么我的额外插件(如 remark-prism 或 remark-slug)不能像这样工作。这是因为 Unified 的 .parse(..) 方法不会使用插件处理 AST。顾名思义,它只将 Markdown 字符串内容解析成树。

如果我们希望 Unified 应用我们的插件,我们需要 Unified 经历他们所谓的“运行”阶段。通常,这是通过使用 .process(..) 方法而不是 .parse(..) 方法来完成的。不幸的是,.process(..) 不仅解析 Markdown 并应用插件,还会将 AST 字符串化为另一种格式(例如,通过 remark-html 使用 HTML,或通过 remark-react 使用 JSX)。而这并不是我们想要的,因为我们想要保留 AST,但在它被插件处理之后。

<code>| ........................ process ........................... |
| .......... parse ... | ... run ... | ... stringify ..........|

           --------                       ---------- 
输入 ->- | 解析器 | ->- 语法树 ->- | 编译器 | ->- 输出
           --------           |           ---------- 
                              X
                              |
                        -------------- 
                       | 变换器       |
                        -------------- </code>
登录后复制

因此,我们需要做的就是运行解析和运行阶段,但不运行字符串化阶段。Unified 没有提供执行这三个阶段中的两个阶段的方法,但它为每个阶段提供了单独的方法,因此我们可以手动执行:

import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkPrism from 'remark-prism'

const parseMarkdown = content => {
  const engine = unified().use(remarkParse).use(remarkPrism)
  const ast = engine.parse(content)

  // Unified 的 *process* 包含三个不同的阶段:解析、运行和字符串化。我们不想经历字符串化阶段,因为我们想保留 AST,所以我们不能调用 `.process(..)`。但是,仅调用 `.parse(..)` 不够,因为插件(因此是 Prism)在运行阶段执行。所以我们需要手动调用运行阶段(为简单起见,同步进行)。
  // 请参阅:https://github.com/unifiedjs/unified#description
  return engine.runSync(ast)
}
登录后复制

瞧!我们将 Markdown 解析成了语法树。然后,我们在该树上运行了我们的插件(这里为了简单起见同步完成,但你可以使用 .run(..) 异步完成)。但是,我们没有将我们的树转换成其他语法,如 HTML 或 JSX。我们可以在渲染中自己完成。

渲染 Markdown

现在我们已经准备好我们的酷炫树了,我们可以按照我们的意图来渲染它。让我们创建一个 MarkdownRenderer 组件,它接收树作为 ast 属性,并使用 React 组件渲染它。

const getComponent = node => {
  switch (node.type) {
    case 'root':
      return ({ children }) => {children}>

    case 'paragraph':
      return ({ children }) => <p>{children}</p>

    case 'emphasis':
      return ({ children }) => <em>{children}</em>

    case 'heading':
      return ({ children, depth = 2 }) => {
        const Heading = `h${depth}`
        return <heading>{children}</heading>
      }

    case 'text':
      return ({ value }) => {value}>

    /* 在此处处理所有类型…… */

    default:
      console.log('未处理的节点类型', node)
      return ({ children }) => {children}>
  }
}

const Node = ({ node }) => {
  const Component = getComponent(node)
  const { children } = node

  return children ? (
    <component>
      {children.map((child, index) => (
        <node key="{index}" node="{child}"></node>
      ))}
    </component>
  ) : (
    <component></component>
  )
}

const MarkdownRenderer = ({ ast }) => <node node="{ast}"></node>

export default React.memo(MarkdownRenderer)
登录后复制

我们渲染器的逻辑大部分都位于 Node 组件中。它根据 AST 节点的 type 键找出要渲染的内容(这是我们的 getComponent 方法处理每种类型的节点),然后渲染它。如果节点有子节点,它会递归地进入子节点;否则,它只渲染组件作为最终的叶子节点。

清理树

根据我们使用的 Remark 插件,在尝试渲染页面时,我们可能会遇到以下问题:

错误:序列化 .content[0].content.children[3].data.hChildren[0].data.hChildren[0].data.hChildren[0].data.hChildren[0].data.hName(来自“/”中的 getStaticProps)时出错。原因:undefined 无法序列化为 JSON。请使用 null 或省略此值。

发生这种情况是因为我们的 AST 包含值未定义的键,这并不是可以安全地序列化为 JSON 的内容。Next 为我们提供了解决方案:我们可以完全省略该值,或者如果我们多少需要它,则将其替换为 null。

但是,我们不会手动修复每条路径,因此我们需要递归遍历该 AST 并将其清理干净。我发现当使用 remark-prism(一个启用代码块语法高亮的插件)时会发生这种情况。该插件确实会向节点添加一个 [data] 对象。

我们可以做的是在返回 AST 之前遍历它以清理这些节点:

const cleanNode = node => {
  if (node.value === undefined) delete node.value
  if (node.tagName === undefined) delete node.tagName
  if (node.data) {
    delete node.data.hName
    delete node.data.hChildren
    delete node.data.hProperties
  }

  if (node.children) node.children.forEach(cleanNode)

  return node
}

const parseMarkdown = content => {
  const engine = unified().use(remarkParse).use(remarkPrism)
  const ast = engine.parse(content)
  const processedAst = engine.runSync(ast)

  cleanNode(processedAst)

  return processedAst
}
登录后复制

我们可以做的最后一件事是删除存在于每个节点上的 position 对象,该对象保存 Markdown 字符串中的原始位置。它不是一个大的对象(它只有两个键),但是当树变大时,它会迅速累加。

const cleanNode = node => {
  delete node.position
  // ... 其他清理逻辑
}
登录后复制

总结

就是这样!我们设法将 Markdown 处理限制在构建/服务器端代码中,因此我们不会向浏览器发送不必要的 Markdown 运行时,这会不必要地增加成本。我们将数据树传递给客户端,我们可以遍历它并将其转换为我们想要的任何 React 组件。

希望这有帮助。:)

以上是Next.js中负责的赌博的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板