Drupal 与 Astro
Drupal 是一个开源的内容管理工具。
先决条件
首先,你需要具备以下条件:
一个 Astro 项目 - 如果你还没有 Astro 项目,我们的 安装指南 能够帮助你快速启动并运行。
一个 Drupal 站点 - 如果你还没有设置 Drupal 站点,可以按照官方指南 安装 Drupal。
将 Drupal 与 Astro 集成
安装 Drupal 的 JSON:API 模块
为了能够从 Drupal 获取内容,你需要启用 Drupal JSON:API 模块。
- 通过 Manage administrative(用户管理)菜单导航到扩展页面
admin/modules - 找到 JSON:API 模块并勾选它旁边的框
- 点击 Install(安装)以安装新模块
现在你可以通过 JSON:API 向你的 Drupal 应用程序发出 GET 请求。
在 .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 请求数据
你的内容是从 JSON:API URL 中获取的。
Drupal JSON:API URL 的结构
基本的 URL 结构为:/jsonapi/{entity_type_id}/{bundle_id}
URL 始终以 jsonapi 为前缀
entity_type_id指实体类型,例如节点、块、用户等。bundle_id指的是实体包。对于 Node 实体类型来说,捆绑包可以是文章。- 在本例中,要获取所有文章的列表,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 查询
Astro 组件可以使用 drupal-jsonapi-params 包从 Drupal 站点获取数据来构建查询。
以下示例显示了一个包含 “article” 内容类型查询的组件,该组件具有用于标题的文本字段和用于内容的富文本字段:
---
import {Jsona} from "jsona";
import {DrupalJsonApiParams} from "drupal-jsonapi-params";
import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
// 获取 Drupal 的根 URL。
export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
// 生成 JSON:API 查询。获取所有已发布文章的标题和正文。
const params: DrupalJsonApiParams = new DrupalJsonApiParams();
params.addFields("node--article", [
"title",
"body",
])
.addFilter("status", "1");
// 生成查询字符串。
const path: string = params.getQueryString();
const url: string = baseUrl + '/jsonapi/node/article?' + path;
// 获取文章。
const request: Response = await fetch(url);
const json: string | TJsonApiBody = await request.json();
// 初始化 Jsona。
const dataFormatter: Jsona = new Jsona();
// 对响应进行反序列化。
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>没有找到内容</h1></div> }
</body>你可以在 Drupal JSON:API 文档 中找到更多查询选项。
使用 Astro 和 Drupal 制作博客
经过上述的设置,你现在可以创建一个使用 Drupal 作为 CMS 的博客了。
先决条件
一个 Astro 项目 且安装了
JSONA和Drupal JSON-API Params。至少有一个条目的 Drupal 站点 - 对于本教程,我们建议从标准安装的新 Drupal 站点开始。
在 Drupal 站点的 Content(内容) 部分中,通过单击 Add(添加) 按钮创建一个新条目。然后,选择文章并填写字段:
- Title:
My first article for Astro! - Alias:
/articles/first-article-for astro - Description:
This is my first Astro article! Let's see what it will look like!
单击 Save(保存) 创建你的第一篇文章。请随意添加任意数量的文章。
- Title:
显示文章列表
如果
src/types.ts尚不存在的话,请创建一个,并使用以下代码来添加两个名为DrupalNode和Path的新接口。这些界面将与 Drupal 中文章内容类型的字段和路径字段相匹配。你将使用它来输入你的文章条目响应。export interface Path { alias: string pid: number langcode: string } export interface DrupalNode extends Record<string, any> { id: string type: string langcode: string status: boolean drupal_internal__nid: number drupal_internal__vid: number changed: string created: string title: string default_langcode: boolean sticky: boolean path: Path }你的 src 目录现在应该包含新的文件了:
- .env
- astro.config.mjs
- package.json
文件夹src/
- types.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"; // 获取 Drupal 的根 URL。 export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;这一步将导入所需的库,例如用于反序列化响应的
Jsona、用于格式化请求 URL、Node 和 Jsona 类型的DrupalJsonApiParams。它还将从.env文件中获取baseUrl。你的 src 目录现在应该包含新的文件了:
- .env
- astro.config.mjs
- package.json
文件夹src/
文件夹api/
- drupal.ts
- types.ts
在同一文件中,创建
fetchUrl函数来获取请求并反序列化响应。import {Jsona} from "jsona"; import {DrupalJsonApiParams} from "drupal-jsonapi-params"; import type {DrupalNode} from "../types.ts"; import type {TJsonApiBody} from "jsona/lib/JsonaTypes"; // 获取 Drupal 的根 URL。 export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL; /** * 从 Drupal 获取 url。 * * @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()函数来获取所有已发布的文章。import {Jsona} from "jsona"; import {DrupalJsonApiParams} from "drupal-jsonapi-params"; import type {DrupalNode} from "../types.ts"; import type {TJsonApiBody} from "jsona/lib/JsonaTypes"; // 获取 Drupal 的根 URL。 export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL; /** * 从 Drupal 获取 url。 * * @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); } /** * 获取所有已发布的文章。 * * @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()来获取所有已发布的文章,以及每个标题、正文、路径和创建日期的数据。转至 Astro 页面,你将从 Drupal 获取数据。以下示例在
src/pages/articles/index.astro处创建了一个文章落地页。导入必要的依赖项,并使用
getArticles()从 Drupal 获取内容类型为article的所有条目,同时传递DrupalNode接口来键入你的响应。--- 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"; // 获取所有已发布的文章。 const articles = await getArticles(); ---使用 getArticles() 的请求调用将返回一个类型化数组,该数组可在页面模板中使用。
如果你使用相同的页面文件,你的
src/pages/目录现在应该包含新文件:- .env
- astro.config.mjs
- package.json
文件夹src/
文件夹api/
- drupal.ts
文件夹pages/
文件夹articles/
- index.astro
- types.ts
向页面添加内容,例如标题。使用
articles.map()将你的 Drupal 条目显示为列表中的一行。--- 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"; // 获取所有已发布的文章。 const articles = await getArticles(); --- <html lang="en"> <head> <title>我的新网站</title> </head> <body> <h1>我的新网站</h1> <ul> {articles.map((article: DrupalNode) => ( <li> <a href={article.path.alias.replace("internal:en/", "")}> <h2>{article.title}</h2> <p>发布于 {article.created}</p> </a> </li> ))} </ul> </body> </html>
生成单个博客文章
使用与上面相同的方法从 Drupal 获取数据,但这一次,是在给每篇文章创建唯一的页面路由的页面上。
此示例使用 Astro 的默认静态模式,并使用 getStaticPaths() 函数创建 动态路由页面文件。该函数将在构建时被调用,以生成成为页面的路径列表。
创建一个新文件
src/pages/articles/[path].astro并从src/api/drupal.ts导入DrupalNode接口和getArticle()。在getStaticPaths()函数中获取数据,为你的博客创建路由。--- 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"; // 获取所有已发布的文章。 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 传递给页面组件。--- 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"; // 获取所有已发布的文章。 export async function getStaticPaths() { const articles = await getArticles(); return articles.map((article: DrupalNode) => { return { params: { // 选择 `path` 以匹配 `[path]` 路由值 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" }) } } }); } ---params内的属性必须与动态路由的名称匹配。由于文件名是[path].astro,因此传递给params的属性名称必须是path。在我们的示例中,
props对象将三个属性传递给页面:title:字符串,代表你文章的标题。body:字符串,代表你文章的内容。date:时间戳,基于你文件的创建日期。
使用页面的
props来显示你的博客文章。--- 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"; // 获取所有已发布的文章。 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>导航到你的开发服务器预览,并单击你的一篇文章以确保你的动态路由正常工作。
发布你的网站
要部署你的网站,请访问我们的 部署指南 并按照你首选的托管供应商说明进行操作。
社区资源
:::note[有想要分享的资源吗?]如果你找到(或制作)了关于使用 Drupal 与 Astro 的有用的视频或博客文章,请将其 添加到这个列表中!:::
更多 CMS 指南
精选 CMS 合作伙伴
-
CloudCannon
一个基于 Git 构建的 CMS,为速度、安全和零烦恼而生。