如何为无头WordPress解析古腾堡内容

如何为无头WordPress解析古腾堡内容

古腾堡是 WordPress 的默认编辑器。该编辑器让您可以通过拖放界面,使用文本、图片、视频和其他网站元素的离散区块来制作和风格化内容。这种方法增强了 WordPress 的灵活性和设计能力。

本指南介绍如何在 Next.js 静态网站中使用 WordPress REST API 将 Gutenberg 内容解析为 HTML

前提条件

要跟上进度,您需要:

  • 计算机上安装 Node.js 和 npm(Node 包管理器)或 yarn
  • JavaScript 和 React 的基础知识
  • 有一些已发布文章的 WordPress 网站

使用 REST API 获取 Gutenberg 内容

要以编程方式与 WordPress 网站交互并检索 Gutenberg 区块中的内容结构,您可以使用 WordPress REST API 或 WPGraphQL 插件。通过这些工具,您可以获取 JSON 格式的 WordPress 内容。

要通过 REST API 启用 JSON 数据访问,请将 WordPress 的固定链接设置从 “朴素” 调整为其他形式。这样就可以通过结构化 URL 访问 API,如下所示:

https://yoursite.com/wp-json/wp/v2

通过向该 URL 发送 API 请求,您可以在 WordPress 网站上以编程方式检索各种信息并执行操作。例如,您可以通过向以下网址发送 GET 请求来获取文章列表:

https://yoursite.com/wp-json/wp/v2/posts

这将返回一个 JSON 对象,其中包含 WordPress 网站上的文章信息,包括标题、内容、作者详细信息等。

将古腾堡区块解析为 HTML

从使用 Gutenberg 编辑器的 WordPress 网站检索文章时,数据库中存储的内容会混合使用 HTML 和 JSON 元数据来描述各种区块类型,例如引语和图库。例如:

<!-- wp:quote {"className":"inspirational-quote","style":{"typography":{"fontSize":"large"}}} -->
<blockquote class="wp-block-quote inspirational-quote has-large-font-size"><p>“The journey of a thousand miles begins with one step.”</p><cite>Lao Tzu</cite></blockquote>
<!-- /wp:quote -->
<!-- wp:gallery {"ids":[34,35],"columns":2,"linkTo":"none","sizeSlug":"medium","className":"custom-gallery"} -->
<ul class="wp-block-gallery columns-2 is-cropped custom-gallery"><li class="blocks-gallery-item"><figure><img src="http://example.com/wp-content/uploads/2021/09/image1-300x200.jpg" alt="A breathtaking view of the mountains" class="wp-image-34"/></figure></li><li class="blocks-gallery-item"><figure><img src="http://example.com/wp-content/uploads/2021/09/image2-300x200.jpg" alt="Serene lakeside at dawn" class="wp-image-35"/></figure></li></ul>
<!-- /wp:gallery -->

该代码段展示了两个 Gutenberg 区块:引言和图库。每个区块都有封装在 HTML 注释中的 JSON 元数据。元数据定义了类名、样式等属性,以及与区块展示相关的其他配置。

当您通过 WordPress REST API 或 WPGraphQL 获取这些区块时,WordPress 会对其进行处理,将 HTML 和 JSON 元数据的组合转换为完全呈现的 HTML 元素,您可以直接将其整合到网页中。上述区块转换后的 HTML 显示如下:

<blockquote class="wp-block-quote inspirational-quote has-large-font-size"><p>“The journey of a thousand miles begins with one step.”</p><cite>Lao Tzu</cite></blockquote>
<ul class="wp-block-gallery columns-2 is-cropped custom-gallery">
<li class="blocks-gallery-item"><figure><img loading="lazy" src="http://example.com/wp-content/uploads/2021/09/image1-300x200.jpg" alt="A breathtaking view of the mountains" class="wp-image-34" sizes="(max-width: 300px) 100vw, 300px" /></figure></li>
<li class="blocks-gallery-item"><figure><img loading="lazy" src="http://example.com/wp-content/uploads/2021/09/image2-300x200.jpg" alt="Serene lakeside at dawn" class="wp-image-35" sizes="(max-width: 300px) 100vw, 300px" /></figure></li>
</ul>

对于使用 Next.js 等 JavaScript 框架构建解耦或无头应用程序的开发人员来说,这提供了一种直接显示内容的方法,即使用 dangerouslySetInnerHTML 属性将 HTML 直接注入页面以渲染标记。

<div dangerouslySetInnerHTML={{ __html: <raw_html_string> }} />

此外,您可能还需要对链接等元素执行进一步格式化,并处理多余的换行符 ( \n ),本指南稍后将对此进行说明。

将 Gutenberg 区块内容解析到 Next.js 静态网站中

在本节中,让我们将 WordPress 内容提取到 Next.js 项目中,然后将 Gutenberg 区块解析为 HTML。

  1. 首先,设置一个从 WordPress 网站获取文章的函数。打开项目中的 src/page.js 文件,用以下代码段替换其内容:
    const getWpPosts = async () => {
    const res = await fetch('https://yoursite.com/wp-json/wp/v2/posts');
    const posts = await res.json();
    return posts;
    };

    此异步函数向 WordPress REST API 执行 API 请求。它会获取网站上的所有文章,并以数组形式返回。

  2. 接下来,让我们在一个简单的 Next.js 页面组件中利用获取的文章,将文章记录到控制台并呈现一个基本的问候语:
    const page = async () => {
    const posts = await getWpPosts();
    console.log(posts);
    return (
    <div>
    <h1>Hello World</h1>
    </div>
    );
    };
    export default page;

    使用 npm run dev 运行项目时,它会显示 “Hello World” 信息,并将获取的文章记录到终端。

    [
    {
    "_links" : {
    "about" : [...],
    "author" : [...],
    "collection" : [...],
    "curies" : [...],
    "predecessor-version" : [...],
    "replies" : [...],
    "self" : [...],
    "version-history" : [...],
    "wp:attachment" : [...],
    "wp:term" : [...]
    },
    "author" : 1,
    "categories" : [...],
    "comment_status" : "open",
    "content" : {
    "protected" : false,
    "rendered" : "\n<p>Fire, a primal force, captivates with its <strong>flickering flames</strong>, evoking both awe and caution. Its <quote>dance</quote> symbolizes destruction and renewal, consuming the old to make way for the new. While it warms our homes and hearts, fire demands respect for its power to devastate.</p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"250\" height=\"148\" src=\"https://img.example.com/wp-content/uploads/2024/02/burningbuilding.jpg\" alt=\"\" class=\"wp-image-14\"/></figure>\n\n\n\n<p>In ancient times, fire was a beacon of light and warmth, essential for survival. Today, it remains a symbol of human ingenuity and danger. From the comforting glow of a hearth to the destructive fury of wildfires, fire’s dual nature reminds us of our fragile relationship with the elements.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https://img.example.com/premium-photo/painting-burning-building-illuminated-by-bright-flames-night_168058-249.jpg?w=1380\" alt=\"\"/></figure>\n\n\n\n<p>You can check out other articles on our blog:</p>\n\n\n\n<ul>\n<li><a href=\"https://yoursite.com/?p=6\">Lorem Ipsum: Beginnings</a></li>\n\n\n\n<li><a href=\"https://yoursite.com/?p=9\">Lorem Ipsum: Act 2</a></li>\n\n\n\n<li><a href=\"https://yoursite.com/?p=11\">Lorem Ipsum: Act 3</a></li>\n</ul>\n"
    },
    "date" : "2024-02-27T12:08:30",
    "date_gmt" : "2024-02-27T12:08:30",
    "excerpt" : {
    "protected" : false,
    "rendered" : "<p>Fire, a primal force, captivates with its flickering flames, evoking both awe and caution. Its dance symbolizes destruction and renewal, consuming the old to make way for the new. While it warms our homes and hearts, fire demands respect for its power to devastate. In ancient times, fire was a beacon of light and warmth, […]</p>\n"
    },
    "featured_media" : 0,
    "format" : "standard",
    "guid" : {
    "rendered" : "https://yoursite.com/?p=13"
    },
    "id" : 13,
    "link" : "https://yoursite.com/?p=13",
    "meta" : {
    "footnotes" : ""
    },
    "modified" : "2024-02-29T16:45:36",
    "modified_gmt" : "2024-02-29T16:45:36",
    "ping_status" : "open",
    "slug" : "fire-fire",
    "status" : "publish",
    "sticky" : false,
    "tags" : [],
    "template" : "",
    "title" : {
    "rendered" : "Fire"
    },
    "type" : "post"
    },
    },
    ...
    ]

    代表古腾堡单个文章数据的 JSON 对象包含多个字段,其中内容和摘录字段将作为解析为 HTML 字符串的古腾堡区块返回。

  3. 为了在 Next.js 中正确呈现这些 HTML 内容,我们使用了 dangerouslySetInnerHTML 属性:
    const page = async () => {
    const posts = await getWpPosts();
    return (
    <>
    <h1> Headless Blog </h1>
    <div>
    {posts.map((post) => (
    <Link href={'/blog/' + post.id} key={post.id}>
    <h2>
    {post.title.rendered} <span>-></span>
    </h2>
    <div dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
    </Link>
    ))}
    </div>
    </>
    );
    };
    export default page;

    在这个更新的组件中,我们对获取的文章数组进行映射,生成一个文章摘录列表。每个摘录都封装在一个 Link 组件中,用于导航,显示文章标题和内容片段。

    dangerouslySetInnerHTML 属性用于解析和呈现 excerpt.rendered 字段中包含的 HTML 内容。

  4. 接下来,在 app 目录下创建文件 blog/[id]/page.js。您可以使用文件夹来定义路由。因此,通过创建 blog 文件夹,就定义了 blog 路由。结合动态路由,就能为每篇文章生成路由。
  5. 每个文章都有一个 ID。您可以使用此 ID 在应用程序中生成其唯一路由,即 /blog/{post_id}。添加以下代码:
    import Link from 'next/link';
    export async function generateStaticParams() {
    const res = await fetch('https://yoursite.com/wp-json/wp/v2/posts');
    const posts = await res.json();
    return posts.map((post) => {
    return {
    params: {
    id: post.id.toString(),
    },
    };
    });
    }
    export async function getPost(id) {
    const response = await fetch('https://yoursite.com/wp-json/wp/v2/posts/' + id);
    const post = await response.json();
    return post;
    }

    generateStaticParams()  函数在构建时根据每个文章返回的相应 ID 静态生成路由。 getPost() 函数会从 REST API 获取带有所传 ID 的文章的 Gutenberg 数据。

    前面的章节展示了从 REST API 返回的 Gutenberg 数据解析示例。目前,我们只关注 content.rendered 字段:

    [
    {
    ...
    "content": {
    "rendered" : "\n<p>Fire, a primal force, captivates with its <strong>flickering flames</strong>, evoking both awe and caution. Its <quote>dance</quote> symbolizes destruction and renewal, consuming the old to make way for the new. While it warms our homes and hearts, fire demands respect for its power to devastate.</p>\n\n\n\n<figure> class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"250\" height=\"148\" src=\"https://img.example.com/wp-content/uploads/2024/02/burningbuilding.jpg\" alt=\"\" class=\"wp-image-14\"/></figure>\n\n\n\n<p>In ancient times, fire was a beacon of light and warmth, essential for survival. Today, it remains a symbol of human ingenuity and danger. From the comforting glow of a hearth to the destructive fury of wildfires, fire’s dual nature reminds us of our fragile relationship with the elements.</p>\n\n\n\n<figure> class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https://img.example.com/premium-photo/painting-burning-building-illuminated-by-bright-flames-night_168058-249.jpg?w=1380\" alt=\"\"/></figure>\n\n\n\n<p>You can check out other articles on our blog:</p>\n\n\n\n<ul>\n<li><a> href=\"https://yoursite.com/?p=6\">Lorem Ipsum: Beginnings</a></li>\n\n\n\n<li><a> href=\"https://yoursite.com/?p=9\">Lorem Ipsum: Act 2</a></li>\n\n\n\n<li><a> href=\"https://yoursite.com/?p=11\">Lorem Ipsum: Act 3</a></li>\n</ul>\n"
    },
    ...
    }
    ]

    该字段包含文章的原始 HTML 代码。它可以直接使用 dangerouslySetInnerHTML 属性渲染,如下所示:<div dangerouslySetInnerHTML={{ __html: <raw_html_string> }} />

  6. 接下来,你可以通过解析内部链接和调整图片大小来处理数据。安装 html-react-parser 包可简化标签解析过程:
    npm install html-react-parser --save
  7.  在 blog/[id]/page.js 文件中添加以下代码:
    import parse, { domToReact } from "html-react-parser";
    /*
    * We use a regular expression (pattern) to match the specific URL you want to replace.
    * The (\d+) part captures the numeric ID after ?p=.
    * Then, we use the replacement string 'data-internal-link="true" href="/blog/$1"',
    * where $1 is a placeholder for the captured ID.
    */
    export function fixInternalLinks(html_string) {
    const pattern = /href="https:\/\/yoursite.com\/\?p=(\d+)"/g;
    const replacement = 'data-internal-link="true" href="/blog/$1"';
    return html_string.replace(pattern, replacement);
    }
    export function parseHtml(html) {
    // Replace 2+ sequences of '\n' with a single '<br />' tag
    const _content = html.replace(/\n{2,}/g, '<br />');
    const content = fixInternalLinks(_content);
    const options = {
    replace: ({ name, attribs, children }) => {
    // Convert internal links to Next.js Link components.
    const isInternalLink =
    name === "a" && attribs["data-internal-link"] === "true";
    if (isInternalLink) {
    return (
    <Link href={attribs.href} {...attribs}>
    {domToReact(children, options)}
    </Link>
    );
    } else if (name === "img") {
    attribs["width"] = "250";
    attribs["height"] = "150";
    return (
    <img {...attribs}/>
    );
    }
    },
    };
    return parse(content, options);
    }

    fixInternalLinks()  函数使用正则表达式从 HTML 字符串中查找 WordPress 网站中的文章链接。在原始 HTML 中,您可以看到文章包含一个 List 标签,其中包含指向网站上其他文章的链接,将这些链接替换为指向静态网站中路由的内部链接。

    parseHTML()  函数会发现多个多余的换行符 \n ,并将其替换为 <br /> 标记。它还能找到内部链接,并将锚标记转换为 Link 标记。然后,该函数使用标签属性调整图片大小。

  8. 要为每个动态路由生成主用户界面,请添加以下代码:
    export default async function Post({ params }) {
    const post = await getPost(params.id);
    const content = parseHtml(post.content.rendered);
    return (
    <>
    <h1>
    {post.title.rendered}
    </h1>
    <div>{content}</div>
    </>
    );
    }

    从 Gutenberg 数据中解析原始 HTML 后,代码会返回代表页面格式化用户界面的 JSX。

最后,当您运行项目时,主页将显示 WordPress 上的文章列表。此外,当您点击单个文章时,您将看到正确呈现的 Gutenberg 解析内容。

小结

本指南介绍了如何通过 WordPress API 将 Gutenberg 区块内容有效地整合并解析为 HTML。这样,当您使用无头 WordPress 时,就可以在前端呈现任何类型的内容。

评论留言