将静态 Astro 模板转换为 WordPress 主题
通过 astro-wordpress 实现:src/pages/*.php.astro 下的页面会编译为真实的 WordPress 模板文件(index.php、page-template-*.php 等),而 npm run dev 则会代理到真实的本地 WordPress 实例,以便主题始终能够针对它进行实时预览。
这是一个经过验证的可靠配方,而不是对适配器 README 的转述:
以下步骤已应用于一个真实的静态 Astro 模板,并经过了端到端验证 —— 开发服务器实时代理到 WordPress、npm run build 生成可激活的主题、wp theme activate 成功执行。
前提条件
- 一个可通过 HTTP 访问的本地 WordPress 安装。有两个已验证的选项:
- Docker Compose(推荐 —— 完全不需要本地 PHP/MySQL/wp-cli,而且主机上很可能没有安装
wp-cli)。请参考步骤 0。 - 一个已经正在运行的本地 WP 安装(任何 PHP 服务器 + MySQL/MariaDB,由
devProxyTarget指定地址)。本技能无论如何只转换 Astro 这一侧。
- Docker Compose(推荐 —— 完全不需要本地 PHP/MySQL/wp-cli,而且主机上很可能没有安装
- 用于 Astro 项目的 Node/npm。
- 在本技能的全部步骤中,
wp-cli是 可选的 —— 下面的每个 wp-cli 命令都有一个不支持 wp-cli 的回退方案(通过浏览器/curl 提交表单,或使用 docker 化的 wp-cli 辅助容器)。不要将其作为硬性要求。
0. (Docker 选项) 使用 Docker Compose 启动 WordPress
这个精确的 compose.yaml(来自 astro-wordpress 启动器)无需修改即可使用 —— 已通过实际运行验证:
services:
wordpress:
image: wordpress
restart: always
ports:
- 8088:80
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- ./wp:/var/www/html
- ./public/:/var/www/html/public
db:
image: mysql:8.0
restart: always
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_RANDOM_ROOT_PASSWORD: "1"
volumes:
- db:/var/lib/mysql
volumes:
db:
docker compose up -d
默认情况下,wordpress 镜像中没有 wp-cli —— 通过浏览器(http://localhost:8088)完成安装和所有其他操作,或者使用一个共享相同网络和卷的一次性 docker 化 wp-cli 容器(请参阅验证/注意事项 —— 每当下面的某个步骤提到 wp ... 且本地未安装 wp-cli 时,都应该使用此模式):
docker run --rm --network container:<wordpress-container-name> \
--volumes-from <wordpress-container-name> -w /var/www/html \
-e WORDPRESS_DB_HOST=db -e WORDPRESS_DB_USER=exampleuser \
-e WORDPRESS_DB_PASSWORD=examplepass -e WORDPRESS_DB_NAME=exampledb \
wordpress:cli <wp-cli-command>
(-e 变量必须与 wordpress 服务的环境变量匹配 —— CLI 容器会读取相同的 wp-config.php,该文件通过 getenv() 解析这些变量,但不会继承另一个容器的环境。)
步骤
1. 安装适配器
npm install astro-wordpress
(下面使用的 passthroughImageService 随 Astro 核心一起提供 —— 不需要额外的包。)
2. 配置 astro.config.mjs
裸机 WP(Astro 和 WP 在同一个主机上,没有容器边界):
// @ts-check
import { defineConfig, fontProviders, passthroughImageService } from "astro/config";
import wordpress from "astro-wordpress";
export default defineConfig({
server: {
host: "127.0.0.1",
},
adapter: wordpress({
devProxyTarget: "http://localhost:7001", // 你的本地 WP URL
outDir: "./wp/wp-content/themes/<theme-name>", // 构建后的主题输出到这里
}),
image: {
service: passthroughImageService(), // 必需 —— WP 自行提供/优化图片
},
});
使用 Docker Compose WP(使用上面的 compose.yaml)需要额外三个选项 —— 每个都是经过实际验证,缺少哪一个都会导致问题(请参阅注意事项):
export default defineConfig({
server: {
host: "0.0.0.0", // 绑定所有接口,以便容器可以访问进来
allowedHosts: ["host.docker.internal"],
},
adapter: wordpress({
devProxyTarget: "http://localhost:8088",
outDir: "./wp/wp-content/themes/<theme-name>",
// WP 容器自身对绑定挂载的 public/ 目录的视图 ——
// 不是主机路径(请参阅注意事项)
publicDirPath: "/var/www/html/public",
// 让在 *容器内部* 运行的 PHP 开发模板存根能够
// 重新访问主机上运行的 Astro 开发服务器
devServerTarget: "http://host.docker.internal:4321",
}),
image: { service: passthroughImageService() },
});
devProxyTarget 必须与 WordPress 实际监听的地址匹配。outDir 是 npm run build 写入完成主题的位置;它不一定需要位于 Astro 项目内部。如果它位于内部(例如,一个完整的本地 WP 检出嵌套在 ./wp/ 下),请将该目录添加到 .gitignore —— WordPress 核心及其上传文件不属于 Astro 项目的仓库。
3. 添加主题样式表
WordPress 只识别包含包含此标头的 style.css 的主题目录 —— 创建 public/style.css(适配器会自动将其符号链接/复制到 outDir):
/*!
Theme Name: <Theme Name>
Version: 0.0.1
*/
4. 将页面转换为 .php.astro
将你想要由 WordPress 提供服务的每个 src/pages/*.astro 路由重命名为 src/pages/*.php.astro —— 文件的默认导出仍然通过 Astro/你现有的布局和组件进行渲染,无需更改;只有扩展名和最终的输出格式会变化(index.astro → index.php)。页面正确构建和服务不需要其他代码更改。
非索引的 WP 模板文件遵循相同的模式:page-template- 前缀会被自动检测并转换为 WordPress 识别的 /* Template Name: ... */ 注释,用于自定义页面模板,例如 src/pages/page-template-landing.php.astro → 一个可选择的“着陆页”模板。(在适配器的 README 中有文档说明;此配方未进行实践。)
对于必须输出原始 PHP 而没有任何 周围 HTML 文档的文件(例如用于注册钩子/字段的 functions.php.astro —— 请参阅 astro-wordpress-acf 技能),请在 frontmatter 中添加 export const partial = true;。如果没有它,Astro 会自动在输出前添加 <!DOCTYPE html> 并破坏 PHP 文件。
5. 激活主题
通用方法:WP 管理后台 → 外观 → 主题 → 激活。使用 wp-cli(本地或 docker 化,请参阅步骤 0):
npm run build
wp theme activate <theme-name> --path=<wp-dir>
验证
# 1. 开发服务器和 WP 都已启动?每个请求都通过 devProxyTarget 代理,
# 因此如果 WP 无法访问,即使是静态资源也会返回 502
npm run dev &
curl -o /dev/null -s -w "%{http_code}\n" http://127.0.0.1:4321/
# 2. 绕过 WP 代理,直接从开发服务器查看原始的、由 Astro 渲染的、PHP 风格的输出
# —— 有助于隔离“这是 Astro 的问题还是 WordPress 的问题”
curl -s -H "by-pass-proxy: 1" http://127.0.0.1:4321/index.php | head -c 300
# 3. 生产构建将真正的普通 PHP 文件输出到 outDir
npm run build
grep -c '<?php' wp/wp-content/themes/<theme-name>/index.php
# 4. 主题已激活 —— 通过 wp-cli,或者如果 wp-cli 不可用,只需加载首页并检查特定于主题的标记
wp theme list --path=<wp-dir> | grep <theme-name>
还没有可供测试的本地 WP 安装,并且想无头(无 wp-cli,无浏览器)确认 WP 安装本身是否正常?安装向导只是一个普通的表单 POST —— 这正是本技能验证全新 WP 安装的方式:
curl "http://localhost:8088/wp-admin/install.php?step=2" \
--data-urlencode "weblog_title=My Site" \
--data-urlencode "user_name=admin" \
--data-urlencode "admin_password=<password>" \
--data-urlencode "admin_password2=<password>" \
--data-urlencode "pw_weak=1" \
--data-urlencode "[email protected]" \
--data-urlencode "Submit=Install WordPress"
# 然后以相同的方式登录(使用 cookie jar)以用于后续的任何管理后台检查:
curl -c cookies.txt \
--data-urlencode "log=admin" --data-urlencode "pwd=<password>" \
--data-urlencode "wp-submit=Log In" \
--data-urlencode "redirect_to=http://localhost:8088/wp-admin/" \
--data-urlencode "testcookie=1" \
"http://localhost:8088/wp-login.php"
curl -b cookies.txt "http://localhost:8088/wp-admin/" | grep -o "<title>[^<]*</title>"
注意事项
- 每个开发模式请求都会代理到
devProxyTarget,不仅仅是.php页面 ——/src、/@、/node_modules以及任何带有by-pass-proxy标头的请求是唯一的绕过方式。如果 WordPress 未运行,整个站点都会返回 502,包括public/下的图片/字体。在调试 Astro 方面之前,请先检查 WP 是否实际启动。 - Astro 7 的开发服务器是一个持久化的后台守护进程。 第二次运行
npm run dev不会重新绑定或报错 —— 它只会报告已经运行的实例(使用npx astro dev status/stop/logs直接管理)。 export const partial = true;—— 请参阅步骤 4。这是整个适配器中唯一最不明显的要求;失败模式(PHP 文件顶部出现无关的<!DOCTYPE html>)看起来与 Astro 完全没有关系。publicDirPath必须设置为 WP 容器 所看到的路径,而不是主机路径。 适配器使用你提供的任何publicDirPath,将每个public/文件符号链接到主题目录中,这是字面上的操作。使用 Docker 时,请提供容器路径(/var/www/html/public)—— 通过双向检查生成的符号链接来确认:readlink wp-content/themes/<theme>/style.css→/var/www/html/public/style.css,这个链接在容器内通过docker exec <container> cat wp-content/themes/<theme>/style.css可以正常解析,但从主机 shell 访问会显示“No such file or directory”。如果你省略publicDirPath,适配器会回退到主机路径,这在容器内是无效的。astro dev的守护进程锁是针对每个项目目录的,而不是针对每个配置/端口的。 当同一个目录中已经有一个普通的astro dev在运行时,使用astro dev --config other.config.mjs --port 4325只会报告开发服务器已在原始的端口上运行 —— 它不会启动第二个实例。如果你需要在同一个项目中切换配置(例如,裸机 vs. Docker),请先使用astro dev stop停止现有的实例。- 此转换本身不会将
wp_head()/wp_footer()连接到布局中,因此由插件注入的<script>/<style>标签、SEO 插件元标签等不会出现。astro-wordpress 的 README 展示了如何在布局中添加<Fragment set:html={} />/<?php wp_footer(); ?>来实现这一点。如果布局独立构建自己的<head>(在移植现有的静态模板时很常见),请将有线连接这些作为后续的刻意操作,而不是本技能步骤已经为你提供的功能。 astro check(npm run build的一部分)可能会 OOM 崩溃,因为它试图对容器启动后落入./wp中的 WordPress 核心文件(wp-includes/js/dist/等目录下的数千个.js/.php文件)进行类型检查。在 Docker WP 已启动的情况下运行构建之前,请将"exclude": ["dist", "wp"]添加到tsconfig.json—— 否则,失败现象看起来像一个与 WordPress 适配器无关的 Node 堆崩溃。- Biome(或任何对整个仓库运行的 linter/formatter)也会尝试处理
./wp,原因相同 —— 在运行项目的 lint/format 命令之前,添加一个忽略条目(例如,在biome.json中添加"files": { "ignore": ["wp/**"] })。 - 动态 rest 参数路由无法转换。
src/pages/blog/[...slug].astro(或任何[...x].astro)不能变成[...x].php.astro—— Astro 的路由器会直接拒绝它(“rest parameter must be a standalone segment”),因为.php后缀破坏了段边界。将这些路由保留为普通的.astro并使其不包含在 WP 主题中,除非内容本身正在迁移到 WP。
下一步
一旦适配器连接好:
astro-wordpress-acf—— 通过 ACF 使页面的特定部分在 WP 管理后台中可编辑。astro-wordpress-menus—— 将页眉/页脚/导航组件连接到 WordPress 管理的菜单(外观 → 菜单),而不是硬编码的链接。astro-wordpress-global-settings—— 为不限特定页面的值添加一个站点范围的“主题选项”页面(需要 ACF Pro 或 Secure Custom Fields)。