如何在Gatsby中创建自定义源插件
作为 Write for DOnations 计划的一部分,作者选择了 Internet Archive 来接收捐赠。
介绍
在构建网站或应用程序时,最困难的任务之一通常是从多个来源提取数据并将其整理成统一的输出。 解决此问题的一种常见方法是为站点的不同部分使用完全不同的构建系统,但这有时会增加复杂性,同时难以实现统一性。 这就是数据驱动的 Static Site Generator (SSG) Gatsby 可以提供解决方案的地方。
Gatsby 的核心目标之一就是为开发者解决这个问题,而源码插件是它这样做的主要方式。 源插件 是一组代码,用于处理将数据从给定源引入 Gatsby 生态系统。 源可以来自本地文件系统,如 Markdown 文件、 数据库、已发布的数据源,甚至是完全动态的远程数据源,如 API。
在本教程中,您将构建自己的自定义源插件,以将新数据从真实世界的 API 带入 Gatsby。 您还将格式化数据,以便可以在整个 Gatsby 中访问它,并且在本教程结束时,有一个工作项目可以从您的新动态数据源构建静态 HTML。
先决条件
在开始之前,这里有一些你需要的东西:
- Node.js 的本地安装,用于运行 Gatsby 并构建您的站点。 安装过程因操作系统而异,但 DigitalOcean 有 Ubuntu 20.04 和 macOS 的指南,您始终可以在 官方 Node.js 下载页面找到最新版本[ X213X]。
- 对 JavaScript 有一定的了解,以便在 Gatsby 中工作。 JavaScript 语言是一个广泛的话题,但一个很好的起点是我们的 如何在 JavaScript 系列中编码。
- 对 Web API、Node.js 和 JSON 有一定了解。
- 一个新的 Gatsby 项目,由
gatsby-starter-default
搭建。 为了满足这个要求并从头开始构建一个新的 Gatsby 项目,您可以参考 如何设置您的第一个 Gatsby 网站 教程的 Step 1。 - 如果您想自定义帖子的用户界面 (UI) 超出本教程的范围,则对 React 和 JSX 以及 HTML 元素 有一定的了解。
本教程在 Node.js v14.16.1、npm v6.14.12、Gatsby v3.13.0 和 node-fetch
v2.6.2 上进行了测试。
第 1 步 - 脚手架文件和安装依赖项
在构建某些东西时,第一步始终是整理好工具和零件。 在此步骤中,您将通过创建必要的文件结构并安装代码将依赖的依赖项来放置源插件的初始构建块。
由于这将是一个本地插件,因此请在 Gatsby 项目中创建一个目录,以将插件的源代码保存在根级 plugins
目录下。 您可以通过在文件浏览器中手动创建文件夹来执行此操作,或者使用 mkdir
命令从项目根目录中的命令行创建:
mkdir -p plugins/my-custom-source-plugin
注意: 如果你想在 Gatsby 项目目录之外开发你的插件,你可以这样做,但它需要一些额外的步骤才能让 Gatsby 将文件拉入你的站点。 有关这方面的更多信息,请查看官方 Gatsby 文档 。
接下来,您需要创建一个 package.json
文件,将此目录标记为 一个 Node.js 包 并具有自己的依赖项。 要创建此文件并预先填写一些必填字段,请使用以下命令:
cd plugins/my-custom-source-plugin npm init -y
此命令导航到您新创建的插件文件夹,然后使用 npm init
来初始化一个新包。 -y
标志会跳过一些与本项目无关的问题,并用所需的最小值填写 package.json
文件。
现在 package.json
存在,您可以将依赖项添加到插件中,这将使编写功能更容易。 继续安装本教程中唯一需要的额外依赖项,node-fetch,使用以下命令:
npm install node-fetch@^2
最后,创建 gatsby-node.js
文件,该文件将包含源插件的主要代码:
touch gatsby-node.js
注意: 如果您经常构建 Gatsby 插件,您可能会发现 他们的插件模板 很有帮助。
现在您已经创建了支持插件的文件结构并安装了初始依赖项,您将继续向 Gatsby 提供有关如何查找和加载插件的说明。
第 2 步 - 加载和配置插件
与任何 Gatsby 插件或主题一样,必须指示 Gatsby 从何处以及如何加载插件。 为此,您编辑 Gatsby 主配置文件 gatsby-config.js
,该文件位于 Gatsby 项目的根目录中。 在您选择的编辑器中打开文件并添加以下突出显示的行:
盖茨比-config.js
module.exports = { ... plugins: [ `gatsby-plugin-react-helmet`, `gatsby-plugin-image`, { resolve: `gatsby-source-filesystem`, options: { name: `images`, path: `${__dirname}/src/images`, }, }, `my-custom-source-plugin`, `gatsby-transformer-sharp`, ...
因为您的插件的源代码位于 plugins
目录中,所以让 Gatsby 加载它所需的只是传递可以在该文件夹中找到的子目录的名称。 您的插件目前也不接受任何选项,因此无需在 Gatsby 配置块中向其传递 options
对象。
保存 gatsby-config.js
并退出文件。
您现在已经配置 Gatsby 以加载您的自定义源插件,并准确告诉它在哪里可以找到它应该执行的源代码。 在下一步中,您将构建此源代码以将数据从您的自定义远程源提取到 Node.js 运行时。
第 3 步 — 将原始数据拉入 Node.js
在上一步中,您将 Gatsby 配置为加载和执行自定义源插件的代码,但您仍需要构建此代码以完成将新数据引入 Gatsby 生态系统的任务。 在此步骤中,您将编写执行此操作的代码,通过 node-fetch
获取远程数据并准备在以后的步骤中使用。
源插件可以从几乎任何地方(本地或远程)提取数据,但在本教程中,您的源插件将通过 他们的公共 API 专门从 维基百科 中的计算机编程书籍类别中提取标题和摘录]。
在 plugins
目录中打开 my-custom-source-plugin/gatsby-node.js
文件并添加以下代码:
插件/my-custom-source-plugin/gatsby-node.js
const fetch = require('node-fetch').default /** * Fetch a list of computer books from Wikipedia, with excerpts */ async function getWikiProgrammingBooks() { const BASE_ENDPOINT = "https://en.wikipedia.org/w/api.php?action=query&format=json&utf8=1&redirects=1"; // Get list of books const listEndpoint = new URL(BASE_ENDPOINT); listEndpoint.searchParams.append('list', 'categorymembers'); listEndpoint.searchParams.append("cmtitle", "Category:Computer_programming_books"); listEndpoint.searchParams.append("cmlimit", "10"); const listResults = await (await fetch(listEndpoint.toString())).json(); // Extract out the page IDs from the list const pageIds = listResults.query.categorymembers.map((listing) => listing.pageid); // Fetch details for page IDs const extractEndpoint = new URL(BASE_ENDPOINT); extractEndpoint.searchParams.append("pageids", pageIds.join("|")); extractEndpoint.searchParams.append("prop", "extracts|info"); extractEndpoint.searchParams.append("exintro", ""); extractEndpoint.searchParams.append("explaintext", ""); extractEndpoint.searchParams.append("inprop", "url"); const bookResult = await (await fetch(extractEndpoint.toString())).json(); return Object.values(bookResult.query.pages); }
在这段代码中,您创建了一个可重用的函数,可以调用该函数来返回计算机编程书籍的列表,以及它们的页面 ID(维基百科中的唯一 ID)和摘录/摘录。 在函数的第一部分,您构建了正确的 API URL,用于获取属于特定类别 (计算机编程书籍 )的标题和 ID 的初始列表。 URL 构造函数和接口 用于使修改查询字符串更具可读性和可管理性。
您使用 node-fetch 中的 fetch
方法向构造的 URL 发出 GET
请求,该 URL 将返回书名列表及其 ID。 然后将该响应转换为仅包含 pageid
值的 array,随后用于再次查询 Wikipedia API,这次请求 extracts 和 元信息 为给定的页面 ID 生成。 页面 ID 由竖线字符 (|
) 连接,因为 Wikipedia API 使用这种格式通过单个字符串值接受多个 ID。
最后,由于页面摘录的结果以 对象 的形式返回,其中每个书单都嵌套在其自己的 ID 下作为键,因此您可以使用 Object.values()
省略页面 ID 键并转换结果在返回它们之前放入一个数组。
如果你要记录这个函数的输出,它看起来像这样:
[ { "pageid": 379671, "ns": 0, "title": "The C Programming Language", "extract": "The C Programming Language (sometimes termed K&R, after its authors' initials) is a computer programming book written by Brian Kernighan and Dennis Ritchie...", "fullurl": "https://en.wikipedia.org/wiki/The_C_Programming_Language", ... }, ... ]
确保保存您的更改,但保持此文件打开,因为您将在下一步中添加更多代码。
在此步骤中,您使用 node-fetch
检索远程源内容并将其公开在 gatsby-node.js
文件中。 在下一步中,您将在使用内容创建新的 Gatsby 节点以在整个 Gatsby 项目中使用时对其进行规范化。
第 4 步 - 规范化数据和创建节点
在上一步中获取远程内容并将其带入 gatsby-node.js
并不意味着它现在可以在整个 Gatsby 中访问; 为了以通用的方式共享数据,Gatsby 使用了 节点 的概念,这些节点在 一个统一的 GraphQL 数据层 之间共享。 在此步骤中,您将创建这些节点,格式化您的新内容以匹配。
虽然您现在可以通过调用 getWikiProgrammingBooks()
从 Wikipedia 检索和访问结果,但您仍然需要添加代码以将其与 Gatsby 的节点系统 集成。 在上一步的同一个 gatsby-node.js
文件中,添加这个新的代码块来处理生成节点:
插件/my-custom-source-plugin/gatsby-node.js
const fetch = require('node-fetch').default ... exports.sourceNodes = async ({ actions, createContentDigest, createNodeId }) => { // Arbitrary node type constant const BOOK_TYPE = 'BookWikiPage'; // Get books const bookResults = await getWikiProgrammingBooks(); // Convert raw book results to nodes for (const book of bookResults) { actions.createNode({ ...book, id: createNodeId(`${BOOK_TYPE}-${book.pageid}`), parent: null, children: [], internal: { type: BOOK_TYPE, contentDigest: createContentDigest(book) } }) } };
在此代码块中,您将遍历 getWikiProgrammingBooks
返回的每本书,并通过 createNode 方法 为其创建一个 Gatsby 节点。 传入 createNode
的每个属性和值都具有重要性,值得考虑:
...book
用于将 Wikipedia API 对象中的键值对 传播 到您正在创建的 Gatsby 节点中。 这意味着稍后您可以访问node.title
,因为它将从book.title
复制。id
是 Gatsby 中的全局唯一值。 为了使每本书的 ID 在您自己的插件中唯一,您将书籍类型与 Wikipedia 页面 ID 组合以形成 ID 字符串。 但是,由于您无法确定其他插件使用的是什么 ID,因此您使用了将 ID 传递给createNodeId
的最佳实践,这是一个 Gatsby 辅助函数,可确保将 ID 转换为全局的东西独特。parent
是一个字段,可用于通过 ID 将您的节点链接到另一个节点,将此节点标记为子节点。 由于每本书都是它自己的实体,与其他节点没有连接,因此您将其保留为null
,表示它没有父节点。children
类似于parent
作为链接节点的一种方式,但需要一个 ID 数组。 由于每本书都没有孩子,因此您将数组留空。internal
是一个对象,它将高度特定于 Gatsby 的内部节点管理系统和其他插件的字段组合在一起。 它只能包含官方字段,这就是为什么您没有将book
对象传播到其中的原因。type
是一个全局唯一的字符串,描述了你正在创建的节点的类型,稍后将在通过 GraphQL 查询节点时使用。contentDigest
是一个哈希字符串,它由节点的内容和 GatsbycreateContentDigest
辅助实用程序构建而成。 此字段帮助 Gatsby 检测节点何时发生更改,因为如果book
对象的任何属性被修改,哈希字符串将发生更改。
您刚刚添加了获取源内容并使用它创建新的 Gatsby 节点的代码,在 Gatsby 环境中共享它们。 在下一步中,您将验证这些节点是否出现在 GraphQL 数据层中并且可以被查询。
第 5 步——(可选)使用 GraphQL API 检查节点输出
至此,您已将源内容拉入 Gatsby 并使用它来创建新节点。 作为使用断点或日志语句手动调试的替代方法,在此步骤中,您将使用交互式 GraphQL IDE 来验证这些新节点是否正在创建并且能够使用 GraphQL API 进行查询。
继续并通过从 Gatsby 项目的根目录运行以下命令来启动本地开发服务器:
npm run develop
注意: 在编写本教程时,Gatsby 的依赖链存在问题,导致在尝试启动开发服务器时会返回消息 Error: Cannot find module 'gatsby-core-utils'
。 如果遇到此错误,请运行以下命令:
npm install gatsby-core-utils
这将重新安装 Gatsby 核心实用程序并解决依赖性问题。 有关这方面的更多信息,请查看此 Gatsby 错误 的 GitHub 问题。
除了启动 Gatsby 站点的实时版本之外,develop
命令还公开了本地 GraphQL 服务器和 IDE。 要验证 gatsby-node.js
中的代码是否正在创建所有图书节点,您将使用此 GraphQL 查询来获取图书标题、页面 ID 和 Gatsby ID:
{ allBookWikiPage { edges { node { title pageid id } } } }
要运行此查询,请在 localhost:8000/___graphql
中打开交互式 GraphQL IDE,然后在执行前将查询粘贴到左侧,或者通过 cURL 查询:
curl --location --request POST 'http://localhost:8000/___graphql' \ --header 'Content-Type: application/json' \ --data-raw '{ "query": "{ allBookWikiPage { edges { node { title pageid id } } } }" }'
响应 JSON 看起来像这样:
{ "data": { "allBookWikiPage": { "edges": [ { "node": { "title": "The C Programming Language", "pageid": 379671, "id": "818771ca-40aa-5cfd-b9e7-fddff093d5ec" } }, ... ] } }, "extensions": {} }
验证您的新自定义源节点已创建并可在 GraphQL 数据层中访问后,下一步是使用它们为您的站点或应用程序的访问者创建可见内容。
第 6 步 - (可选)基于节点创建页面
到目前为止,前面的所有步骤都集中在创建内部 Gatsby 节点上,包括验证它们的创建和检索能力的最后一步。 但是,这些节点仅对 Gatsby 项目中运行的代码可见,对站点或应用程序的访问者不可见。 在这一步中,您将添加一个 React 页面模板文件并将其连接到您的节点,以便您的源插件内容变成实际的面向公众的网页。
有多种方法可以基于 Gatsby 节点创建页面,但在本教程中,您将使用 文件系统路由 API,它基于特殊的文件名语法创建页面。
首先,在 src/pages
中创建一个空文件,文件名为 {BookWikiPage.title}.js
。 花括号告诉 Gatsby 文件名正在使用文件系统路由 API,在花括号内,BookWikiPage.title
告诉 Gatsby 为每个唯一的书名创建一个页面。 请注意,您不再处理 plugins
目录中的文件,而是在 Gatsby 主项目中工作。
接下来,将代码添加到该文件中,该文件将采用 book 节点并将其显示为网页:
src/pages/{BookWikiPage.title}.js
import { graphql } from "gatsby"; import * as React from "react"; import Layout from "../components/layout"; import Seo from "../components/seo"; export default function BookPageTemplate({ data: { bookWikiPage } }) { const { title, extract, fullurl } = bookWikiPage; return ( <Layout> <Seo title={title} /> <h1>{title}</h1> <blockquote>{extract}</blockquote> <i>This article uses material from the Wikipedia article <a href={fullurl} target="_blank" rel="noreferrer">"{title}"</a>, which is released under the <a href="https://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share-Alike License 3.0</a>.</i> </Layout> ); } export const pageQuery = graphql` query ($id: String!) { bookWikiPage(id: { eq: $id }) { title extract fullurl } } `;
在您的代码末尾是一个名为 pageQuery
的导出变量,它使用 Gatsby GraphQL 标记 。 Gatsby 将评估 跟随它的 GraphQL 查询 ,将结果传递给 BookPageTemplate
函数。
BookPageTemplate
函数,它是一个 React 组件,然后获取 GraphQL 查询的结果,并通过将值嵌入到 JSX[X185X 中,将它们显示为网页的一部分] 它返回。 书名用作页面的主标题和标题,摘录显示为块引用,底部嵌入了指向完整维基百科条目页面的链接。
您还可以通过在声明之前使用 export default
将 BookPageTemplate
函数标记为默认导出,因为 Gatsby 期望找到负责生成最终呈现页面的 React 组件作为每个页面模板的默认导出文件。
将 React 模板代码添加到文件后,保存更改并关闭它。 导航到 http://localhost:8000/the-c-programming-language/
以呈现示例页面:
注意: 要更手动地基于节点创建页面,您可以使用 gatsby-node.js
内部的 createPages API。
要显示这些新节点及其相关页面的列表,您还将创建一个专用列表页面,该页面将在一个位置显示所有书籍。 在 src/pages
下,创建一个文件名为 books.js
的新文件。 接下来,将以下代码添加到其中:
src/pages/books.js
import { graphql, Link } from "gatsby"; import * as React from "react"; import Layout from "../components/layout"; import Seo from "../components/seo"; export default function BookListingsPageTemplate({ data: { allBookWikiPage } }) { return ( <Layout> <Seo title="Programming Books Listing" /> <p>Here are some computer programming books that have their own Wikipedia entries:</p> {allBookWikiPage.edges.map((edge) => { const node = edge.node; return ( <details key={node.title}> <summary>{node.title}</summary> <div className="details-body"> <p>{node.extract}</p> <div className="links"> <Link href={node.gatsbyPath}>Internal Page</Link> <a rel="noreferrer" href={node.fullurl}>Wikipedia Page</a> </div> </div> </details> ) })} </Layout> ); } export const pageQuery = graphql` query { allBookWikiPage { edges { node { title extract gatsbyPath(filePath: "/{BookWikiPage.title}") fullurl } } } } `;
与 {BookWikiPage.title}.js
页面模板类似,此文件也使用 GraphQL pageQuery
标签从 GraphQL 层拉入数据并将其传递给 React 组件。 但是,虽然之前的模板会根据 ID 呈现一本书,但此模板将呈现所有书籍的列表,同时链接到先前创建的各个书籍页面。
每个图书列表都使用 <details>
元素,它可以使列表展开以显示图书和链接的完整摘录,或者折叠以仅显示标题。 遵循最佳实践,您还可以在遍历数组时将唯一值传递给 key
,将 Gatsby Link 组件 用于内部链接,将 a
标记用于外部链接。
GraphQL 查询中的 gatsbyPath(filePath: "/{BookWikiPage.title}")
字符串使用特殊的 gatsbyPath()
函数来检索将根据传入的文件系统路由 API 文件名创建的公共路径。
保存并退出此文件。
注意: 更改组件的数据源时,热重载功能有时会返回如下错误:error Cannot query field "gatsbyPath" on type "BookWikiPage" graphql/template-strings
。 要修复此错误,请通过结束进程并再次运行 npm run develop
手动重新启动开发服务器。
所有书籍都在一页上,即使是可折叠的部分,事情也变得有点拥挤,所以最后一步是添加一些样式,让访问者更容易阅读列表。 在 src/styles/books.css
创建一个新的样式表文件。 您可以在文件浏览器中或使用 Gatsby 项目根目录中的命令行执行此操作:
mkdir -p ./src/styles touch ./src/styles/books.css
接下来,将以下 CSS 添加到文件中:
src/styles/books.css
details { border: 1px dotted black; margin: 6px; padding: 6px; } .details-body { background-color: #eedeff; margin: 4px 0px 2px 12px; padding: 4px; border-radius: 4px; } .links { display: flex; justify-content: space-evenly; }
这个 CSS 为每本书的列表、列表中的间距和边距以及内部和外部书籍链接之间的间距添加了一个边框。 将 CSS 添加到文件后,在继续之前保存并关闭它。
最后,更新图书列表页面模板以将这个 CSS 文件拉入:
src/pages/books.js
import { graphql, Link } from "gatsby"; import * as React from "react"; import Layout from "../components/layout"; import Seo from "../components/seo"; import "../styles/books.css";
使用新添加的 CSS 导入行保存并关闭此文件。
要查看结果,请再次运行 develop 命令以启动开发服务器并预览新页面:
npm run develop
您现在可以在 localhost:8000/books/
访问您的图书列表页面。
您现在不仅从头构建了一个 Gatsby 源插件,而且还使用它来生成基于 React 模板的页面。
结论
按照本教程中的步骤,您现在已经完成了构建一个自定义源插件,该插件将外部内容引入您的 Gatsby 项目并使用它来为您网站中的新页面提供动力。
源插件有很多深度。 如果您对遵循最佳实践和学习更高级的源插件概念感兴趣,以下是您可能感兴趣的一些领域:
- 如果您想与全世界分享您的插件,您可以通过将其发布到 npm 来实现。 您可以按照 这个发布 npm 包的指南 并按照 这个工作流程来提交到 Gatsby 插件库 。
- 对于在一段时间内保持不变的输入进行大量工作的源插件,您可以考虑 在构建之间利用 Gatsby 缓存机制 。
- 如果您的节点彼此相关,或者与其他插件创建的节点相关,您可以通过 relationships 将它们链接在一起。 本教程对此进行了简要介绍,但您可以在 Gatsby 文档 中找到更多详细信息 。
- transformer plugin 是一个插件,它只处理获取一种类型的节点并将它们转换为新的或修改的节点。 您可以在 Gatsby 转换器插件文档 中阅读有关它们的更多信息 。
如果您想了解有关 Gatsby 的更多信息,请查看 如何使用 Gatsby.js 系列创建静态网站 的其余部分。