Drupal & Astro
Drupal 是一个开源的内容管理工具。
先决条件
标题为“先决条件”的部分要开始,你需要具备以下条件
-
一个 Astro 项目 - 如果你还没有 Astro 项目,我们的安装指南将帮助你快速启动并运行。
-
一个 Drupal 站点 - 如果你还没有设置 Drupal 站点,可以按照官方指南安装 Drupal。
将 Drupal 与 Astro 集成
标题为“将 Drupal 与 Astro 集成”的部分安装 JSON:API Drupal 模块
标题为“安装 JSON:API Drupal 模块”的部分要从 Drupal 获取内容,你需要启用 Drupal JSON:API 模块。
- 通过“管理”管理菜单导航到“扩展”页面
admin/modules
- 找到 JSON:API 模块并勾选旁边的复选框
- 点击“安装”以安装新模块
现在你可以通过 JSON:API 向你的 Drupal 应用程序发出 GET
请求。
在 .env
中添加 Drupal URL
标题为“在 .env 中添加 Drupal URL”的部分要将你的 Drupal URL 添加到 Astro,请在项目根目录中创建一个 .env
文件(如果尚不存在),并添加以下变量
DRUPAL_BASE_URL="https://drupal.ddev.site/"
重新启动开发服务器以在你的 Astro 项目中使用此环境变量。
设置凭据
标题为“设置凭据”的部分默认情况下,Drupal JSON:API 端点可用于外部数据获取请求,无需身份验证。这允许你在没有凭据的情况下为你的 Astro 项目获取数据,但不允许用户修改你的数据或站点设置。
但是,如果你希望限制访问并要求身份验证,Drupal 提供了多种身份验证方法,包括
你可以将凭据添加到你的 .env
文件中。
DRUPAL_BASIC_USERNAME="editor"DRUPAL_BASIC_PASSWORD="editor"DRUPAL_JWT_TOKEN="abc123"...
.env
文件的信息。
你的根目录现在应包含这些新文件
- .env
- astro.config.mjs
- package.json
安装依赖
标题为“安装依赖”的部分JSON:API 请求和响应通常很复杂且深度嵌套。为了简化处理,你可以使用两个 npm 包来简化请求和响应的处理
JSONA
:用于服务器和浏览器的 JSON API v1.0 规范序列化器和反序列化器。Drupal JSON-API Params
:该模块提供了一个辅助类来创建所需的查询。在执行此操作时,它还尝试尽可能使用简短形式来优化查询。
npm install jsona drupal-jsonapi-params
pnpm add jsona drupal-jsonapi-params
yarn add jsona drupal-jsonapi-params
从 Drupal 获取数据
标题为“从 Drupal 获取数据”的部分你的内容从一个 JSON:API URL 获取。
Drupal JSON:API URL 结构
标题为“Drupal JSON:API URL 结构”的部分基本的 URL 结构是:/jsonapi/{entity_type_id}/{bundle_id}
URL 始终以 jsonapi
为前缀。
entity_type_id
指的是实体类型,例如 node、block、user 等。bundle_id
指的是实体包。对于 Node 实体类型,包可以是 article。- 在这种情况下,要获取所有文章的列表,URL 将是
[DRUPAL_BASE_URL]/jsonapi/node/article
。
要检索单个实体,URL 结构将是 /jsonapi/{entity_type_id}/{bundle_id}/{uuid}
,其中 uuid 是实体的 UUID。例如,获取特定文章的 URL 将采用 /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e
的形式。
仅检索某些字段
标题为“仅检索某些字段”的部分通过向请求添加查询字符串字段来仅检索某些字段。
GET: /jsonapi/{entity_type_id}/{bundle_id}?field[entity_type]=field_list
示例
/jsonapi/node/article?fields[node--article]=title,created
/jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title,created,body
通过添加筛选器查询字符串向你的请求添加筛选器。
最简单、最常见的筛选器是键值筛选器
GET: /jsonapi/{entity_type_id}/{bundle_id}?filter[field_name]=value&filter[field_other]=value
示例
/jsonapi/node/article?filter[title]=Testing JSON:API&filter[status]=1
/jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title&filter[title]=Testing JSON:API
你可以在 JSON:API 文档中找到更多查询选项。
构建 Drupal 查询
标题为“构建 Drupal 查询”的部分Astro 组件可以使用 drupal-jsonapi-params
包从你的 Drupal 站点获取数据来构建查询。
以下示例显示了一个组件,该组件对“文章”内容类型进行查询,该内容类型具有用于标题的文本字段和用于内容的富文本字段
---import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
// Get the Drupal base URLexport const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
// Generate the JSON:API Query. Get all title and body from published articles.const params: DrupalJsonApiParams = new DrupalJsonApiParams();params.addFields("node--article", [ "title", "body", ]) .addFilter("status", "1");// Generates the query string.const path: string = params.getQueryString();const url: string = baseUrl + '/jsonapi/node/article?' + path;
// Get the articlesconst request: Response = await fetch(url);const json: string | TJsonApiBody = await request.json();// Initiate Jsona.const dataFormatter: Jsona = new Jsona();// Deserialise the response.const articles = dataFormatter.deserialize(json);---<body> {articles?.length ? articles.map((article: any) => ( <section> <h2>{article.title}</h2> <article set:html={article.body.value}></article> </section> )): <div><h1>No Content found</h1></div> }</body>
你可以在 Drupal JSON:API 文档中找到更多查询选项
使用 Astro 和 Drupal 创建博客
标题为“使用 Astro 和 Drupal 创建博客”的部分通过上述设置,你现在可以创建一个使用 Drupal 作为 CMS 的博客。
先决条件
标题为“先决条件”的部分-
一个 Astro 项目,已安装
JSONA
和Drupal JSON-API Params
。 -
至少有一个条目的 Drupal 站点 - 对于本教程,我们建议从使用标准安装的新 Drupal 站点开始。
在你的 Drupal 站点的内容部分,通过点击添加按钮创建一个新条目。然后,选择文章并填写字段
- 标题:
My first article for Astro!
- 别名:
/articles/first-article-for astro
- 描述:
This is my first Astro article! Let's see what it will look like!
点击保存以创建你的第一篇文章。你可以随意添加任意数量的文章。
- 标题:
显示文章列表
标题为“显示文章列表”的部分-
如果
src/types.ts
不存在,请创建它,并使用以下代码添加两个名为DrupalNode
和Path
的新接口。这些接口将匹配 Drupal 中文章内容类型的字段和路径字段。你将使用它来为你的文章条目响应提供类型。src/types.ts export interface Path {alias: stringpid: numberlangcode: string}export interface DrupalNode extends Record<string, any> {id: stringtype: stringlangcode: stringstatus: booleandrupal_internal__nid: numberdrupal_internal__vid: numberchanged: stringcreated: stringtitle: stringdefault_langcode: booleansticky: booleanpath: Path}你的 src 目录现在应包含新文件
- .env
- astro.config.mjs
- package.json
目录src/
- types.ts
-
在
src/api
下创建一个名为drupal.ts
的新文件,并添加以下代码src/api/drupal.ts import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {DrupalNode} from "../types.ts";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";// Get the Drupal Base Url.export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;这将导入所需的库,例如用于反序列化响应的
Jsona
、用于格式化请求 URL 的DrupalJsonApiParams
以及 Node 和 Jsona 类型。它还将从.env
文件中获取baseUrl
。你的 src/api 目录现在应包含新文件
- .env
- astro.config.mjs
- package.json
目录src/
目录api/
- drupal.ts
- types.ts
-
在同一个文件中,创建
fetchUrl
函数以发出 fetch 请求并反序列化响应。src/api/drupal.ts import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {DrupalNode} from "../types.ts";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";// Get the Drupal Base Url.export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;/*** Fetch url from Drupal.** @param url** @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>*/export const fetchUrl = async (url: string): Promise<any> => {const request: Response = await fetch(url);const json: string | TJsonApiBody = await request.json();const dataFormatter: Jsona = new Jsona();return dataFormatter.deserialize(json);} -
创建
getArticles()
函数以获取所有已发布的文章。src/api/drupal.ts import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {DrupalNode} from "../types.ts";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";// Get the Drupal Base Url.export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;/*** Fetch url from Drupal.** @param url** @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>*/export const fetchUrl = async (url: string): Promise<any> => {const request: Response = await fetch(url);const json: string | TJsonApiBody = await request.json();const dataFormatter: Jsona = new Jsona();return dataFormatter.deserialize(json);}/*** Get all published articles.** @return Promise<DrupalNode[]>*/export const getArticles = async (): Promise<DrupalNode[]> => {const params: DrupalJsonApiParams = new DrupalJsonApiParams();params.addFields("node--article", ["title","path","body","created",]).addFilter("status", "1");const path: string = params.getQueryString();return await fetchUrl(baseUrl + '/jsonapi/node/article?' + path);}现在你可以在
.astro
组件中使用getArticles()
函数来获取所有已发布的文章,包括每篇文章的标题、正文、路径和创建日期数据。 -
转到你将从 Drupal 获取数据的 Astro 页面。以下示例在
src/pages/articles/index.astro
创建一个文章登陆页面。导入必要的依赖项,并使用
getArticles()
从 Drupal 获取所有内容类型为article
的条目,同时传递DrupalNode
接口来为你的响应提供类型。src/pages/articles/index.astro ---import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";import type { DrupalNode } from "../../types";import {getArticles} from "../../api/drupal";// Get all published articles.const articles = await getArticles();---这个使用 getArticles() 的 fetch 调用将返回一个类型化的条目数组,可以在你的页面模板中使用。
如果使用了相同的页面文件,你的
src/pages/
目录现在应包含新文件- .env
- astro.config.mjs
- package.json
目录src/
目录api/
- drupal.ts
目录pages/
目录articles/
- index.astro
- types.ts
-
向你的页面添加内容,例如标题。使用
articles.map()
将你的 Drupal 条目显示为列表中的行项目。src/pages/articles/index.astro ---import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";import type { DrupalNode } from "../types";import {getArticles} from "../api/drupal";// Get all published articles.const articles = await getArticles();---<html lang="en"><head><title>My news site</title></head><body><h1>My news site</h1><ul>{articles.map((article: DrupalNode) => (<li><a href={article.path.alias.replace("internal:en/", "")}><h2>{article.title}</h2><p>Published on {article.created}</p></a></li>))}</ul></body></html>
生成单篇博客文章
标题为“生成单篇博客文章”的部分使用与上面相同的方法从 Drupal 获取数据,但这次是在一个将为每篇文章创建唯一页面路由的页面上。
此示例使用 Astro 的默认静态模式,并使用 getStaticPaths()
函数创建动态路由页面文件。此函数将在构建时被调用,以生成将成为页面的路径列表。
-
创建一个新文件
src/pages/articles/[path].astro
,并从src/api/drupal.ts
导入DrupalNode
接口和getArticle()
。在getStaticPaths()
函数中获取你的数据,为你的博客创建路由。src/pages/articles/[path].astro ---import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";import type { DrupalNode } from "../../types";import {getArticles} from "../../api/drupal";// Get all published articles.export async function getStaticPaths() {const articles = await getArticles();}---你的 src/pages/articles 目录现在应包含新文件
- .env
- astro.config.mjs
- package.json
目录src/
目录api/
- drupal.ts
目录pages/
目录articles/
- index.astro
- [path].astro
- types.ts
-
在同一个文件中,将每个 Drupal 条目映射到一个具有
params
和props
属性的对象。params
属性将用于生成页面的 URL,而props
的值将作为 props 传递给页面组件。src/pages/articles/[path].astro ---import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";import type { DrupalNode } from "../../types";import {getArticles} from "../../api/drupal";// Get all published articles.export async function getStaticPaths() {const articles = await getArticles();return articles.map((article: DrupalNode) => {return {params: {// Choose `path` to match the `[path]` routing valuepath: article.path.alias.split('/')[2]},props: {title: article.title,body: article.body,date: new Date(article.created).toLocaleDateString('en-EN', {day: "numeric",month: "long",year: "numeric"})}}});}---params
内部的属性必须与动态路由的名称匹配。由于文件名是[path].astro
,因此传递给params
的属性名称必须是path
。在我们的示例中,
props
对象向页面传递三个属性title
:一个字符串,表示你帖子的标题。body
:一个字符串,表示你条目的内容。date
:一个时间戳,基于你文件的创建日期。
-
使用页面的
props
来显示你的博客文章。src/pages/articles/[path].astro ---import {Jsona} from "jsona";import {DrupalJsonApiParams} from "drupal-jsonapi-params";import type {TJsonApiBody} from "jsona/lib/JsonaTypes";import type { DrupalNode } from "../../types";import {getArticles} from "../../api/drupal";// Get all published articles.export async function getStaticPaths() {const articles = await getArticles();return articles.map((article: DrupalNode) => {return {params: {path: article.path.alias.split('/')[2]},props: {title: article.title,body: article.body,date: new Date(article.created).toLocaleDateString('en-EN', {day: "numeric",month: "long",year: "numeric"})}}});}const {title, date, body} = Astro.props;---<html lang="en"><head><title>{title}</title></head><body><h1>{title}</h1><time>{date}</time><article set:html={body.value} /></body></html> -
导航到你的开发服务器预览,然后点击你的一篇帖子,以确保你的动态路由正常工作。
发布你的站点
标题为“发布你的站点”的部分要部署你的网站,请访问我们的部署指南并按照你偏好的托管提供商的说明进行操作。
社区资源
标题为“社区资源”的部分如果你发现(或制作了!)一个关于使用 Drupal 和 Astro 的有用的视频或博客文章,请将其添加到此列表中!