使用 WordPress 数据创建 Gatsby 网站

Avatar of Ganesh Dahal
Ganesh Dahal 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

在我 上一篇文章 中,我提到了创建一个部分移植的 WordPress-Gatsby 网站。本文将继续介绍其底层机制,并提供逐步演练。

Gatsby 是一个基于 React 的静态网站框架,它不仅吸引了 JavaScript 开发人员的关注,也吸引了 WordPress 开发人员和用户的关注。许多 WordPress 用户发现其功能很有吸引力,例如超快的图像处理和增强的黑客攻击安全防护,但他们希望在继续使用 WordPress 管理员和编辑器管理内容的同时使用这些功能。

Chris 之前在 CSS-Tricks 上介绍过 将 Gatsby 和 WordPress 结合使用 的想法。作为一名 WordPress 爱好者,我决定尝试一下。本文基于我在此过程中学习到的知识和记录。

请注意,WPGraphQLgatsby-cli 处于持续开发中,后续版本可能会出现重大更改。此项目使用 WPGraphQL 0.8.3、gatsby-source-wpgraphql 2.5.1 和 gatsby-cli 2.12.21 完成。与 WordPress 不同,较新的 WPGraphQL 版本不支持向后兼容性。请查阅官方 WPGraphQLGatsby 文档,了解最新的更改,并在使用前谨慎操作。

Gatsby 启动器 库中有一些现成的项目。两个很好的例子是 Alexandra Spalato 的 gatsby-wordpress-theme-blog 和 Zac Gordon 和 Muhammad Muhsin 的 twenty-nineteen-gatsby-theme

先决条件

如果您想一起学习,以下是一些您需要准备的条件:

资产和资源

因为我之前已经完成了一些 Gatsby 学习项目,所以我有一些资产,例如排版、布局和其他可重用组件,可以应用在这里。我还学习了以下最新的教程指南,这帮助我为这个项目做好了准备。

Henrick Wirth 的指南非常全面且透彻。Jason 的分步文章是一个很好的资源,甚至包括非常有用的视频,可以帮助您了解整个过程。Muhammad 的文章帮助解释了如何使用 Gatsby 的 createPages API 创建静态页面,并分解了各种函数、模板文件和 React 组件。

我主要遵循了 Henrik 的指南,并将本文分成了类似的部分。Henrik 的指南包括 图像处理 和添加 使用 ACF Flexible Content 功能的页面构建器,这些内容我们这里不涉及。

部分 1:设置 WordPress 和 Gatsby

首先,让我们为数据源设置一个 WordPress 网站。这可以是已经存在的网站,也可以是新网站。即使是本地 WordPress 安装也可以。我决定使用 WordPress 自带的 Twenty Twenty 主题 为此项目创建一个新的测试 WordPress 网站。

安装 WPGraphQL 和 WPGraphiQL 插件

让我们从在 WordPress 中安装几个插件开始。我们将使用 WPGraphQL 在 WordPress 中启用 GraphQL API,并打开 WordPress 作为数据源。我们还将使用 WPGraphiQL(注意名称中的“i”)。这实际上是可选的,但它创建了一个界面,可以直接在 WordPress 仪表板中测试 GraphQL 查询,这非常方便。您可能会注意到,我链接到插件的 GitHub 仓库 而不是 WordPress 插件目录,这是有意的——在撰写本文时,这两个插件都未在目录中提供。因此,您需要下载 ZIP 文件,并通过 /wp-content/plugins 目录在 WordPress 中手动安装它们。

激活后,GraphiQL API 将显示在 WordPress 仪表板中。

GraphiQL API 提供了一个游乐场,用于从 WordPress 网站测试 GraphQL 查询。

Screenshot of the GraphQL playground interface in the WordPress dashboard.
GraphiQL 屏幕提供了三个面板:一个用于在不同对象之间导航(左侧)、一个用于查询数据(中间)和一个用于可视化返回的数据(右侧)。

设置本地 Gatsby 网站

我们将通过在项目的 wordpress-gatsby 目录中安装 Gatsby 的默认启动器 来设置本地 Gatsby 网站,并在命令行中执行以下操作:

#! create a new Gatsby site using the default starter
gatsby new wordpress-gatsby https://github.com/gatsbyjs/gatsby-starter-default

使用 gatsby develop 重新启动服务器,然后在新的浏览器选项卡中导航到 localhost:8000。我们应该在浏览器中看到一个启动页面。

Gatsby 文档中提供了有关 如何在本地创建 Gatsby 网站 的链接。

接下来,我们将安装和配置 gatsby-source-graphql 插件。就像我们在设置 WordPress 时所做的那样,我们必须在 Gatsby 网站中安装和配置 WPGraphQL

#! install wpgraphql plugin
#! add with yarn
yarn add gatsby-source-graphql
#! install with npm
npm install --save gatsby-source-graphql

好的,现在是时候配置 gatsby-source-graphql 插件了。打开 gatsby-config.js 文件,让我们使用以下设置:

// plugin configuration
module.exports = {
  plugins: [
    {
      resolve: "gatsby-source-graphql",
      options: {
        typeName: "WPGraphQL",
        fieldName: "wpcontent",
        // GraphQL endpoint, relative to your WordPress home URL.
        url: "https://tinjurewp.com/wp-gatsby/graphql",
        // GraphQL endpoint using env variable
       // url: "${process.env.WORDPRESS_URL}/graphql",
      },
    },
  ],
}

我如何得到这个确切的配置?我严格遵循了Gatsby 文档中的描述。通过指定 GraphQL 端点的 URL(上面突出显示的部分)和两个配置选项,将插件添加到了 Gatsby 实例中:typeName,一个远程模式查询类型,以及 fieldName,它在 Gatsby 查询中可用。请注意,最新的 WPGraphQL 文档建议使用 fieldName: "wpcontent" 而不是 "wpgraphql",如指南中所述。

替代设置:使用 dotenv 模块

或者,我们可以使用 dotenv npm 模块 来定义环境变量,这些变量用于自定义开发环境。Henrik 在他的指南中也使用了这种方法。

如果您使用此方法,则可以在插件配置文件(如 .env.production)中定义一个变量,例如 WORDPRESS_URL,并使用它来代替公开 WordPress URL。

# .env.production
# Don't put any sensible data here!!!
WORDPRESS_URL=https://tinjurewp.com/wp-gatsby/

我的测试环境同样将 WordPress 实例和数据暴露给 WPGraphQL

Colby Fayock 提供了一个关于在 Gatsby 和 Netlify 中使用环境变量的分步指南

重新启动开发服务器后,WPGraphQL API 可用于 Gatsby 查询并检索从 WordPress 站点查询的特定数据,并通过本地主机 GraphQL URL(https//localhost:8000/___graphql/)将其显示在 Gatsby 站点上。

Screenshot showing the GraphQL query interface with the explorer on the left, the query in the center, and the returned data on the right.
请注意,与 WordPress 站点本身不同,这里的数据暴露给了 WPGraphQL。我们可以针对 WPGraphQL API 进行查询,以显示 WordPress 站点中的任何字段。

第 2 部分:从 WordPress 移植文章和页面

在 Gatsby 中,可以通过使用 GraphQL 查询数据并将查询结果映射到文章或页面模板,在构建时创建文章和页面。这个过程在Gatsby 教程中进行了描述,该教程介绍了如何通过编程方式从数据创建页面。Gatsby 使用了两个 API,onCreateNodecreatePages,教程包含了关于如何实现它们的详细解释。

此处的代码片段来自Henrik 的指南。由于 WordPress 以不同的数据类型和类别存储其数据库中的数据,因此移植所有内容并非易事。但是,凭借之前使用 Gatsby createPages API 和Node API创建页面和文章的知识,我能够继续学习。还有许多真实的入门网站可以作为示例参考。

步骤 1:在 WordPress 站点中添加文章和页面内容

如果还没有,请在 WordPress 站点中添加一些文章和页面。在为该内容创建页面之前,我们需要从 Gatsby 站点的 pages 文件夹中删除 index.jspage-2.js。这两个文件似乎干扰了移植的 WordPress 数据。

步骤 2:创建页面和文章模板

我们将为我们的内容创建两个模板文件,一个用于文章(/src/templates/posts/index.js),一个用于页面(/src/templates/pages/index.js)。

这是我们的文章模板。基本上,我们两次使用了文章标题(一次作为 SEO 页面标题,一次作为文章标题),并将文章内容用作文章组件。

// src/templates/post/index.js
import React  from "react"
import Layout from "../../components/layout"
import SEO from "../../components/SEO"


const Post = ({ pageContext }) => {
  const post = pageContext.post


  return (
    <Layout>
      <SEO title={post.title} />


      <h1> {post.title} </h1>
      <div dangerouslySetInnerHTML={{__html: post.content}} />


    </Layout>
  )
}


export default Post

对于页面模板,我们将执行几乎相同的事情。

//src/templates/pages/index.js

import React  from "react"
import Layout from "../../components/layout"
import SEO from "../../components/seo"


const Page = ({ pageContext }) => {
  const page = pageContext.page


  return (
    <Layout>
      <SEO title={page.title} />


      <h1>{page.title}</h1>
      <div dangerouslySetInnerHTML={{__html: page.content}} />


    </Layout>
  )
}


export default Page

步骤 3:使用 createPages API 创建静态文章和页面

请注意,我们在这里介绍的整个代码都可以写在 node.js 文件中。但是,为了提高可读性,文章和页面在项目根目录下的名为 create 的文件夹中进行了分离,遵循Henrik 的指南

我们将开始使用 GraphQL createPages API!我们将从向 gatsby-node.js 添加以下内容开始。

// gatsby-node.js
const createPages = require("./create/createPages")
const createPosts = require("./create/createPosts")


 exports.createPagesStatefully = async ({ graphql, actions, reporter }, options) => {
  await createPages({ actions, graphql, reporter }, options)
  await createPosts({ actions, graphql, reporter }, options)
 }

Muhammad 的文章提到了一个值得注意的要点:

createPages API 是 Gatsby 公开的 Node API 的一部分。它本质上指示 Gatsby 添加页面。在其中,我们使用 async/await(ECMAScript 2017 的一项功能)调用了一些方法。

换句话说:这两个函数都创建了相关的静态页面。考虑到这一点,让我们定义我们想要使用的数据,并在 create/createPages.js 文件中获取这些数据。抱歉代码有点多,但 Henrik 的注释有助于解释正在发生的事情。

//create/createPages.js
const pageTemplate = require.resolve('../src/templates/page/index.js');


const GET_PAGES = `
  query GET_PAGES($first:Int $after:String) {
    wpgraphql {
      pages(
        first: $first
        after: $after
        # This will make sure to only get the parent nodes and no children
        where: {
          parent: null
         }
      ) {
        pageInfo {
          hasNextPage
          endCursor
        }
        nodes {
          id
          title
          pageId
          content
          uri
          isFrontPage
        }
      }
    }
  }
`


const allPages = []
let pageNumber = 0
const itemsPerPage = 10


/** This is the export which Gatbsy will use to process.
 * @param { actions, graphql }
 * @returns {Promise<void>} */
module.exports = async ({ actions, graphql, reporter }, options) => {


  /** This is the method from Gatsby that we're going
   * to use to create pages in our static site. */
  const { createPage } = actions
  /** Fetch pages method. This accepts variables to alter
   * the query. The variable `first` controls how many items to
   * request per fetch and the `after` controls where to start in
   * the dataset.
   * @param variables
   * @returns {Promise<*>} */
  const fetchPages = async (variables) =>
    /** Fetch pages using the GET_PAGES query and the variables passed in. */
    await graphql(GET_PAGES, variables).then(({ data }) => {
      /** Extract the data from the GraphQL query results */
      const {
        wpgraphql: {
          pages: {
            nodes,
            pageInfo: { hasNextPage, endCursor },
          },
        },
      } = data


      /** Map over the pages for later creation */
      nodes
      && nodes.map((pages) => {
        allPages.push(pages)
      })


      /** If there's another page, fetch more
       * so we can have all the data we need. */
      if (hasNextPage) {
        pageNumber++
        reporter.info(`fetch page ${pageNumber} of pages...`)
        return fetchPages({ first: itemsPerPage, after: endCursor })
      }


      /** Once we're done, return all the pages
       * so we can create the necessary pages with
       * all the data on hand. */
      return allPages
    })


  /** Kick off our `fetchPages` method which will get us all
   * the pages we need to create individual pages. */
  await fetchPages({ first: itemsPerPage, after: null }).then((wpPages) => {


    wpPages && wpPages.map((page) => {
      let pagePath = `${page.uri}`


      /** If the page is the front page, the page path should not be the uri,
       * but the root path '/'. */
      if(page.isFrontPage) {
        pagePath = '/'
      }


      createPage({
        path: pagePath,
        component: pageTemplate,
        context: {
          page: page,
        },
      })


      reporter.info(`page created: ${page.uri}`)
    })


    reporter.info(`# -----> PAGES TOTAL: ${wpPages.length}`)
  })
}

同样,Muhammad 的文章非常有用,因为它分解了 createPages.jscreatePosts.js 函数可以执行的操作。Henrik 的指南还为每个步骤提供了有用的注释。

步骤 4:创建文章

createPosts.js 文件与 createPages.js 几乎相同。唯一的区别是在路径前添加 blog/,并在整个代码中将“page”替换为“posts”。

如果我们在这里停止并在终端中使用 gatsby develop 重新启动开发服务器,开发日志将显示页面构建过程。

现在,如果我们在浏览器中打开 localhost:8000,我们会得到一个 404 错误。

这可能令人沮丧,但没关系。点击 404 页面上的任何链接都会显示来自 WordPress 数据源的正确页面或文章。例如,如果点击示例页面链接,它将在浏览器中显示来自 WordPress 的示例页面内容。


第 3 部分:使用导航

让我们继续讨论我们网站的导航菜单。WordPress 具有一个导航管理功能,允许我们使用指向页面、文章、档案、分类法甚至自定义链接的链接来构建菜单。我们希望为 WordPress 中的主菜单创建导航,并将其发送到 GraphQL,以便我们可以在自己的站点中查询它。

导航链接(包括页面和文章链接)在 Gatsby 中是使用Gatsby Link API创建的,该 API 使用内置的 <Link> 组件和 navigate 函数。<Link> 组件用于链接到内部页面,但不链接到外部链接。

将导航菜单从 WordPress 移植到 Gatsby 站点是一个棘手的任务,需要创建 <Menu><MenuItems> 组件并相应地重构 <Layout> 组件。以下是工作原理。

本节中使用的代码片段直接取自Henrik 的指南,以保证完整性,但是这些代码片段在其他Gatsby WordPress 入门教程中也经常使用,变化很小。

步骤 1:创建一个 WordPress 菜单

如指南中所述,创建一个名为“PRIMARY”的菜单非常重要,该菜单在 Twenty Twenty 主题中定义。我们将向其中添加三个链接。

  • 首页:指向我们主页的链接,它将是一个指向我们站点索引的自定义链接。
  • 示例页面:WordPress 在新安装时创建的默认页面。
  • 首页:这通常是 WordPress 中主页的名称。您需要在编辑器中创建此页面。

步骤 2:使用 GraphiQL 资源管理器查询菜单项

接下来,我们将从 GraphiQL 接口编写菜单项的查询。请注意,我们可以使用资源管理器通过勾选几个复选框来实际编写查询。

query MyQuery {
  menuItems(where: {location: PRIMARY}) {
    nodes {
      label
      url
      title
      target
    }
  }
}

看到数据中的 URL 是绝对路径,显示完整地址了吗?我们需要一个实用程序函数将这些 URL 转换为相对 URL,同样是因为 <Link> 组件支持相对 URL。

Henrik 的指南 提供了以下实用程序函数,用于将 WordPress 的绝对 URL 转换为 Gatsby 所需的相对 URL

// src/utils/index.js
/** Parses a menu item object and returns Gatsby-field URI.
 * @param {object} menuItem a single menu item
 * @param wordPressUrl
 * @param blogURI */
export const CreateLocalLink = (menuItem, wordPressUrl, blogURI='blog/') => {
  const { url, connectedObject } = menuItem;


  if (url === '#') {
    return null;
  }
  /** Always want to pull of our API URL */
  let newUri = url.replace(wordPressUrl, '');


  /** If it's a blog link, respect the users blogURI setting */
  if (connectedObject && connectedObject.__typename === 'WPGraphQL_Post') {
    newUri = blogURI + newUri;
  }


  return newUri;
};

步骤 4:创建一个菜单项组件

下一步是创建一个 <MenuItem> 组件,该组件利用上一步创建的实用程序函数。结果是一个完整的链接,供 Gatsby 站点菜单使用。

// src/components/MenuItem.js
import React from "react"
import { CreateLocalLink } from "../utils"
import { Link } from "gatsby"


const MenuItem = ({ menuItem, wordPressUrl }) => {
  return (
    <Link style={{marginRight: '20px' }}
     to={CreateLocalLink(menuItem, wordPressUrl)}>
     {menuItem.label}
     </Link>
  )
}


export default MenuItem

步骤 5:创建菜单组件

好的,我们创建了 URL 和一个功能性的 <MenuItem> 组件。让我们创建一个新的 <Menu> 组件,我们的 <MenuItem> 组件可以放在这里。Gatsby 的 StaticQuery API 用于使用 GraphQL 查询所有主菜单项。

// src/components/Menu.js
import React from "react"
import { StaticQuery, graphql } from "gatsby"
import MenuItem from "./MenuItem"


/** Define MenuItem fragment and get all primary menu items */
const MENU_QUERY = graphql`
  fragment MenuItem on WPGraphQL_MenuItem {
    id
    label
    url
    title
    target
  }


  query GETMAINMENU {
    wpgraphql {
      menuItems(where: {location: PRIMARY}) {
        nodes {
          ...MenuItem
        }
      }
      generalSettings {
        url
      }
    }
  }
`


const Menu = () => {
  return (
    <StaticQuery
      query={MENU_QUERY}
      render={(data) => {
        if (data.wpgraphql.menuItems) {
          const menuItems = data.wpgraphql.menuItems.nodes
          const wordPressUrl = data.wpgraphql.generalSettings.url


       return (
         <div style={{ marginBottom: "20px" }}>
           {
             menuItems &&
             menuItems.map((menuItem) => (
               <MenuItem key={menuItem.id}
               menuItem={menuItem} wordPressUrl={wordPressUrl}/>
             )
           )}
         </div>
       )
      }
      return null
   }}
  />
  )
}


export default Menu

步骤 6:将菜单添加到布局组件

此时,我们拥有构建 Gatsby 站点菜单所需的所有内容。我们只需要将 <Menu> 组件放到我们的 <Layout> 组件中即可。

// src/components/layout.js
import React from "react"
import PropTypes from "prop-types"
import useSiteMetadata from '../components/siteMetadata';
import Header from "./Header"
import Footer from "./Footer"
import Menu from "./Menu"
import "./layout.css"


const Layout = ({ children }) => {
  const { title, description } = useSiteMetadata();


  return (
    <section>
      <Header siteTitle={title} description={description} />
      <div
      style={{ margin: `0 auto`, maxWidth: 960,
               padding: `0 1.0875rem 1.45rem`,}}>
        <Menu />
        <main>{children}</main>
        <Footer />
      </div>
    </section>
  )
}


Layout.propTypes = {
  children: PropTypes.node.isRequired,
}


export default Layout

Gatsby 的 <Link> 组件文档 解释说,来自外部 CMS(如 WordPress)的数据应该理想情况下由 <Link> 组件进行检查,并根据情况使用 Gatsby 的 <Link> 或常规 <a> 标签进行渲染。这确保了 WordPress 端的任何真正外部链接保持绝对路径,而不会与 <Link> 组件冲突。

这需要——你猜对了——另一个执行此操作的组件。在 Gatsby 文档中,它被称为 <UniversalLink>,它返回 Gatsby 兼容的 <Link> 组件或传统的 <a> 元素。

//src/components/UniversalLink.js
import React from "react"
import { Link as GatsbyLink } from "gatsby"


const UniversalLink = ({ children, to, activeClassName, partiallyActive, ...other }) => {
  const internal = /^\/(?!\/)/.test(to)
  // Use Gatsby Link for internal links, and <a> for others
  if (internal) {
    return (
      <GatsbyLink
        to={to}
        activeClassName={activeClassName}
        partiallyActive={partiallyActive}
        {...other}
      >
        {children}
      </GatsbyLink>
    )
  }
  return (
    <a href={to} {...other}>
      {children}
    </a>
  )
}
export default UniversalLink

现在,让我们回到我们的 <MenuItem> 组件并更新它以使用 <UniversalLink>

/ src/components/MenuItem.js
import React from "react"
import { CreateLocalLink } from "../utils"
import UniversalLink from "./UniversalLink"


const MenuItem = ({ menuItem, wordPressUrl }) => {
  return (
    <UniversalLink style={{marginRight: '20px' }}
      to={CreateLocalLink(menuItem, wordPressUrl)}>
      {menuItem.label}
    </UniversalLink>
  )
}


export default MenuItem

准备好查看结果了吗?使用 gatsby develop 重新启动本地服务器,浏览器应该会显示一个导航菜单,其中包含指向相对页面路径的链接的菜单项。

Screenshot showing the Same Page title and content under the site navigation, which includes the site title, and menu items.
在 WordPress 中创建,在 Gatsby 中显示。

第 4 节:在 Gatsby 中显示博文

到目前为止,我们的情况已经相当不错了,但还有一块重要的内容需要处理:在网站上显示页面。在本节中,我们将逐步介绍实现这一目标的步骤,具体来说,我们将创建博文模板以及一些新的帖子图片组件,然后在 createPages.jscreatePosts.js 中将所有内容整合在一起。

您是否已经在 WordPress 中创建了页面和帖子?如果没有,现在是时候进入 WordPress 并创建它们了。

步骤 1:在项目目录的根目录添加一个全局变量文件

// global variable
const Globals = {
  blogURI: ''
}
module.exports = Globals

当 WordPress 管理员(设置阅读设置)中的首页设置设置为“最新文章”选项时,将使用 blogURI = ' ' URL 路径。

如果您计划改用“静态页面”选项,则应在全局变量文件中使用 blogURI= 'blog'

步骤 2:在 templates 文件夹中创建博文模板

此模板将处理所有已发布帖子的显示。请注意,这里使用了两个组件——PostEntryPagination,这两个组件目前尚不存在。我们稍后会介绍它们。

// src/templates/post/blog.js
import React from "react"
import Layout from "../../components/Layout"
import PostEntry from "../../components/PostEntry"
import Pagination from "../../components/Pagination"
import SEO from "../../components/SEO"


const Blog = ({ pageContext }) => {
  const { nodes, pageNumber, hasNextPage, itemsPerPage, allPosts }
  = pageContext


  return (
    <Layout>
      <SEO
        title="Blog"
        description="Blog posts"
        keywords={[`blog`]}
      />
      {nodes && nodes.map(post => <PostEntry key={post.postId}
        post={post}/>)}
      <Pagination
        pageNumber={pageNumber}
        hasNextPage={hasNextPage}
        allPosts={allPosts}
        itemsPerPage={itemsPerPage}
      />
    </Layout>
  )
}


export default Blog

步骤 3. 创建帖子条目组件

此组件用于 archive.js 和其他组件中,用于遍历帖子,显示帖子条目标题、特色图片(如果有)、摘要和 URL(在 WordPress 中称为“slug”)。

// src/components/PostEntry.js
import React from "react"
import { Link } from "gatsby"
import Image from "./Image"
import { blogURI } from "../../globals"


const PostEntry = ({ post }) => {
  const { uri, title, featuredImage, excerpt } = post

  return (
    <div style={{ marginBottom: "30px" }}>
      <header>
        <Link to={`${blogURI}/${uri}/`}>
          <h2 style={{ marginBottom: "5px" }}>{title}</h2>
          <Image image={featuredImage} style={{ margin: 0 }}/>
        </Link>
      </header>


      <div dangerouslySetInnerHTML={{ __html: excerpt }}/>
    </div>
  )
}

export default PostEntry

步骤 4:创建一个(可选)图片组件

Gatsby 默认启动器带有一个 Image 组件,在大多数情况下都能正常工作。在本例中,我们获取用作 WordPress 中帖子特色图片的图片文件,并在没有特色图片的情况下为其分配一个备用图片,如 Henrik 的指南 中所述。

// src/components/Image.js
import React from "react"
import { useStaticQuery, graphql } from "gatsby"


const Image = ({ image, withFallback = false, ...props }) => {
  const data = useStaticQuery(graphql`
    query {
      fallBackImage: file(relativePath: { eq: "fallback.svg" }) {
        publicURL
      }
    }
  `)


  /* Fallback image */
  if (!image) {
    return withFallback ? <img src={data.fallBackImage.publicURL}
      alt={"Fallback"} {...props}/> : null
  }


  return <img src={image.sourceUrl} alt={image.altText} {...props}/>
}


export default Image

如果 withFallback 设置为 false(如 Gatsby 组件文件的默认设置),则它根本不会渲染 DOM 元素。

步骤 5:创建分页组件

分页组件允许我们在帖子索引中每页显示指定数量的帖子。WordPress 有两种类型的分页 一种返回“下一页”和“上一页”链接 以便一次浏览一页,另一种提供链接的页码。我们在这个组件中使用的是前者。

// src/components/Pagination.js
import React from "react"
import { Link } from "gatsby"
import { blogURI } from "../../globals"


const Pagination = ({ pageNumber, hasNextPage }) => {
  if (pageNumber === 1 && !hasNextPage) return null


  return (
    <div style={{ margin: "60px auto 20px", textAlign: "center" }}>
      <div className="nav-links">
        {
          pageNumber > 1 && (
            <Link
              className="prev page-numbers"
              style={{
                padding: "8px 8px 5px 4px",
              }}
           to={pageNumber > 2 ? `${blogURI}/page/${pageNumber - 1}`: `${blogURI}/`}
            >
              ← <span> Previous</span>
            </Link>
          )
        }
          <span className="meta-nav screen-reader-text"></span>
          {pageNumber}
        </span>


        {
          hasNextPage && (
            <Link
              style={{
                padding: "4px 8px 5px 8px",
              }}
              className="next page-numbers"
              to={`${blogURI}/page/${pageNumber + 1}`
              }
            >
              <span>Next </span> →
            </Link>
          )
        }
      </div>
    </div>
  )
}


export default Pagination

第 7 行有一个条件语句,如果 pageNumber === 1 && !hasNextPage,则返回 null。换句话说,如果当前页面的 hasPageNumber 大于 1,则“上一页”按钮(第 13-24 行)将显示。类似地,当当前页面的 hasNextPage 至少为 1 时,“下一页”按钮(第 30-42 行)将显示。

步骤 6:重构 createPages

我们需要清理 createPages.js 文件以反映自创建文件以来完成的所有工作。随着跟踪的内容越来越多,该文件变得过于庞大。为了保持代码的组织性和结构化,我们可以使用**GraphQL 片段**,它允许我们“将复杂的查询分解成更小、更容易理解的组件”,根据文档

GraphQL 片段是可重用的单元,允许构建字段集,然后在需要时将其包含在查询中。

如果我们遵循Henrik 的指南,帖子模板和帖子预览的 GraphQL 查询字段存储在 data.js 文件中。

// src/templates/posts/data.js
const PostTemplateFragment = `
  fragment PostTemplateFragment on WPGraphQL_Post {
    id
    postId
    title
    content
    link
    featuredImage {
      sourceUrl
    }
    categories {
      nodes {
        name
        slug
        id
      }
    }
    tags {
      nodes {
        slug
        name
        id
      }
    }
    author {
      name
      slug
    }
  }
`


const BlogPreviewFragment = `
  fragment BlogPreviewFragment on WPGraphQL_Post {
    id
    postId
    title
    uri
    date
    slug
    excerpt
    content
    featuredImage {
      sourceUrl
    }
    author {
      name
      slug
    }
  }
`


module.exports.PostTemplateFragment = PostTemplateFragment
module.exports.BlogPreviewFragment = BlogPreviewFragment

接下来,根据指南重构 create/createPosts.js 文件需要在 createPosts.js 的顶部部分(第 2-10 行)添加以下代码,正好位于第 4 行的 const = GET_POSTS=` 查询语句之上。

// create/createPosts.js
const {
  PostTemplateFragment,
  BlogPreviewFragment,
} = require("../src/templates/posts/data.js")


const { blogURI } = require("../globals")


const postTemplate = require.resolve("../src/templates/posts/index.js")
const blogTemplate = require.resolve("../src/templates/posts/blog.js")


const GET_POSTS = `
  # Here we make use of the imported fragments which are referenced above
  ${PostTemplateFragment}
  ${BlogPreviewFragment}
  query GET_POSTS($first:Int $after:String) {
    wpgraphql {
      posts(
       first: $first
       after: $after
       # This will make sure to only get the parent nodes and no children
       where: {
         parent: null
       }
      ) {
         pageInfo {
           hasNextPage
           endCursor
         }
         nodes {
           uri


           # This is the fragment used for the Post Template
           ...PostTemplateFragment


           #This is the fragment used for the blog preview on archive pages
          ...BlogPreviewFragment
        }
      }
    }
 }
`

在这里,在前面的步骤中创建的片段字符串(第 9-10 行)在 GET_POSTS 查询(第 12 行)之外导入并注册,并在 GET_POSTS($first:Int $after:String) 查询(第 34 行和第 37 行)内部用作片段。

createPosts.js 文件的底部,使用全局 blogURI 变量定义了 blogPage 路径(第 36-41 行),并且我们添加了创建分页博客页面的代码(第 99-111 行)。

// create/createPosts.js
// Previous code excluded


const allPosts = []
const blogPages = [];
let pageNumber = 0;
const itemsPerPage = 10;


/** This is the export which Gatbsy will use to process.
 * @param { actions, graphql }
 * @returns {Promise<void>} */
module.exports = async ({ actions, graphql, reporter }, options) => {


  /** This is the method from Gatsby that we're going
   * to use to create posts in our static site */
  const { createPage } = actions


  /** Fetch posts method. This accepts variables to alter
   * the query. The variable `first` controls how many items to
   * request per fetch and the `after` controls where to start in
   * the dataset.
   * @param variables
   * @returns {Promise<*>} */
  const fetchPosts = async (variables) =>
    /** Fetch posts using the GET_POSTS query and the variables passed in */
    await graphql(GET_POSTS, variables).then(({ data }) => {
      /** Extract the data from the GraphQL query results */
      const {
        wpgraphql: {
          posts: {
            nodes,
            pageInfo: { hasNextPage, endCursor },
          },
        },
      } = data


      /** Define the path for the paginated blog page.
       * This is the url the page will live at
       * @type {string} */
      const blogPagePath = !variables.after
        ? `${blogURI}/`
        : `${blogURI}/page/${pageNumber + 1}`


      /** Add config for the blogPage to the blogPage array for creating later
       * @type {{
       *   path: string,
       *   component: string,
       *   context: {nodes: *, pageNumber: number, hasNextPage: *} }} */
      blogPages[pageNumber] = {
        path: blogPagePath,
        component: blogTemplate,
        context: {
          nodes,
          pageNumber: pageNumber + 1,
          hasNextPage,
          itemsPerPage,
          allPosts,
        },
      }


      /** Map over the posts for later creation */
      nodes
      && nodes.map((posts) => {
        allPosts.push(posts)
      })


     /** If there's another post, fetch more so we can have all the data we need */
      if (hasNextPage) {
        pageNumber++
        reporter.info(`fetch post ${pageNumber} of posts...`)
        return fetchPosts({ first: itemsPerPage, after: endCursor })
      }


      /** Once we're done, return all the posts so we can
       * create the necessary posts with all the data on hand */
      return allPosts
    })


  /** Kick off our `fetchPosts` method which will get us all
   * the posts we need to create individual posts */
  await fetchPosts({ first: itemsPerPage, after: null }).then((wpPosts) => {


    wpPosts && wpPosts.map((post) => {
      /** Build post path based of theme blogURI setting */
      const path = `${blogURI}${post.uri}`


      createPage({
        path: path,
        component: postTemplate,
        context: {
          post: post,
        },
      })


      reporter.info(`post created:  ${path}`)
    })


    reporter.info(`# -----> POSTS TOTAL: ${wpPosts.length}`)


    /** Map over the `blogPages` array to create the
     * paginated blog pages */
    blogPages
    && blogPages.map((blogPage) => {
      if (blogPage.context.pageNumber === 1) {
        blogPage.context.publisher = true;
        blogPage.context.label = blogPage.path.replace('/', '');
      }
      createPage(blogPage);
      reporter.info(`created blog archive page ${blogPage.context.pageNumber}`);
    });
  })
}

最终更新的 create/createPosts.jscreate/createPage.js 文件在此 GitHub 存储库中可用

在 Muhammad 的Twenty Nineteen 移植教程文章中,他详细描述了如何使用 Gatsby 的 createPage 创建的静态页面使用与本示例中使用的几乎相同的代码和文件结构。很高兴看到我们的参考之间形成了一些一致性。

使用 gatsby develop 重新启动本地服务器后,我们应该在浏览器中显示一个屏幕,该屏幕现在显示已发布帖子的循环,其中包含帖子标题和摘要。


第 5 节:样式和部署

虽然样式、排版和部署都超出了我们在这里讨论的范围,但我们可以稍微谈谈它们。Gatsby 的文档提供了关于样式部署/托管选项的优秀资源。

基本网站样式

Gatsby 的文档按全局 CSS 文件模块化样式表CSS-in-JS进行分组。还有其他样式选项可用,包括Typograpgy.jsSassJSSStylusPostCSS

在将 Twenty Nineteen WordPress 主题移植到 Gatsby 时,Muhammad 包含了主题的样式,以便可以在 Gatsby 站点上使用它们。他警告说,由于某些单位和值与 Gatsby 不兼容,因此需要进行一些调整。例如,他必须调整 CSS 中的 vw 单位以与某些组件的 flexbox 一起使用。类似地,在将Twenty Twenty 主题移植到 Gatsby时,Henrik 在其Gatsby 启动器 - Twenty Twenty中遵循了类似的过程,通过移植Twenty Twenty 样式表以及字体

我决定在我的项目中使用 Sass。这需要安装gatsby-plugin-sass及其所需的 node-sass 依赖项。

#! install node-sass & gatsby-sass
yarn add node-sass gatsby-plugin-sass
#! or with npm
npm install --save node-sass gatsby-plugin-sass

然后可以将插件添加到 gatsby-config.js配置,如下所示。

// gatsby-config.js
module.exports = {
  siteMetadata: {
    plugins: [
      `gatsby-plugin-sass`
    ],
  }
}

现在,我们可以在 .scss 文件中编写样式,并像在任何其他 Sass 项目中一样导入它们。

// using import in a component file
import("./src/styles/global.scss")


// using require in the gatsby-browser.js file
require('./src/styles/global.scss')

.scss 样式表可以通过全局 <Layout> 组件导入,或者使用 require 语句添加到 gatsby-browser.js 中。对于此演示项目,我使用 Gatsby 的默认样式作为主页面,并且我简单地保留了帖子内容。我对 Header.js 文件进行了一些重构,并添加了一些非常基本的样式。

//src/components/Header.js
import { Link } from "gatsby"
import PropTypes from "prop-types"
import React from "react"
import useSiteMetadata from '../components/siteMetadata';
import Menu from "./Menu"
import "../styles/header.css"


const Header = () =>{
  const { title } = useSiteMetadata();


  return (
    <header className="header">
      <div className="nav-container brand">
        <Link  to="/"> {title} </Link>
        {/* Menu here */}
        <Menu />
      </div>
    </header>
  )
}


Header.propTypes = {
  siteTitle: PropTypes.string,
  description: PropTypes.string,
}


Header.defaultProps = {
  siteTitle: ``,
  description: ``,
}


export default Header

使用 gatsby develop 重新启动服务器后,这应该会显示网站页眉。

支持 WordPress 块样式

我假设如果你已经走到这一步,那么你已经非常熟悉 WordPress 块编辑器,并且知道块通常是如何工作的。自发布块编辑器以来,WordPress 一直维护着一组独立的样式,专门用于块内容。

这意味着我们需要一个额外的步骤才能将它们与主题样式一起移植到 Gatsby。Jason Lengstorf 在他的教程指南中进行了演示。首先,安装 WordPress 块软件包。

# install wordpress/block-library
npm install @wordpress/block-library
# with yarn add
yarn add @wordpress/block-library

然后我们可以将这些样式导入 Gatsby 组件。让我们使用 <Layout> 组件。

// src/components/layout.js
import React from "react"
  import { Link } from "gatsby"


import "@wordpress/block-library/build-style/style.css"
  import "../styles/layout.css"


const Layout = ({ children }) => {
  return (
    <section>
      <header>
        <Link to="/" className="home">
          Gatsby + WP
        </Link>
      </header>
      <main>{children}</main>
    </section>
  )
}


export default Layout

块编辑器仍然处于积极开发阶段,这意味着事情可能会发生变化,甚至可能出乎意料。因此,如果你计划使用它们,请务必谨慎行事。

站点部署

在解释我为什么选择Netlify时,我谈到了部署,我选择它是因为它连接到项目的 GitHub 存储库,并在推送到特定分支时自动部署,这要归功于 Netlify Functions。

Netlify 有一个不错的分步指南,介绍了如何将 Gatsby 站点连接到 Netlify。Gatsby 文档还描述了部署到 Netlify

最后,链接到我自己的 Netlify 部署的演示站点

同样,这为我们提供了持续部署,其中站点在将更改推送到存储库时会自动重建。如果我们希望在 WordPress 中进行更改时(例如发布帖子或编辑页面)执行类似的过程,则可以使用JAMstack 部署插件,如Jason 的指南中所述。


这仍然在进行中!

虽然我在将 WordPress 主题移植到 Gatsby 的过程中学到的知识非常适合构建博客的基本构建块,但我意识到仍然有很多工作要做。我的意思是,WordPress 存储了如此多的数据,包括作者、类别、标签、帖子状态、自定义帖子类型等等,所有这些都需要额外考虑。

但是,越来越多的解耦的 Gatsby WordPress 站点示例,我将在下面列出一些供参考。Henrik 的WordPress-Gatsby 资源精选列表对于了解有关 WordPress-Gatsby 解耦的更多信息非常有帮助。

鸣谢

我知道我在整篇文章中都提到了这一点,但还是要特别感谢Henrick WirthJason LengstorfMuhammad Muhsin为将 WordPress 移植到 Gatsby 所做的所有工作。我在这里介绍的所有内容仅仅是他们出色工作的积累,我感谢他们每个人都创建了如此有用的指南,即使像我这样的新手也能理解。我还要特别感谢 CSS-Tricks 的 Geoff Graham 编辑这篇文章。