ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从目录到加载:WordPress块主题Twenty Twenty-Five全解析

从目录到加载:WordPress块主题Twenty Twenty-Five全解析 从二十多年前的经典主题一路用过来再回头看 WordPress 6.7 默认带的 Twenty Twenty-Five我最大的感受是做主题这件事已经从“写 PHP 模板”变成了“组织一份设计系统”。TT5 是官方默认主题里最新的块主题样本目录干净、加载链路清晰非常适合拿来研究块主题到底怎么运作。这篇文章我就从目录结构讲到加载机制把 twentytwentyfive 每个文件夹负责什么、WordPress 又是怎么把它跑起来的一次讲清楚后面再补上我实际调主题时反复踩过的几个坑。1. 开工前必须搞懂的三个概念块主题、theme.json 与模板层级第一次接触块主题的人最容易懵的点在于模板目录里全是.html文件没有header.php、single.php样式也不写在style.css里那主题到底靠什么驱动答案就是三个东西块主题本身、theme.json、模板层级。先把这三个概念盘明白后面的目录和加载流程才能看进去。1.1 块主题与传统主题的差异传统经典主题的典型形态是一堆 PHP 模板文件在服务器端拼装 HTML循环输出文章列表样式由style.css手写维护functions.php里塞满了各种add_action和add_filter。用户看到的页面怎么做出来的得同时懂 PHP 和 CSS还得懂主题内部那一堆全局变量的约定。块主题则不一样。它的模板文件是纯 HTML 加区块标记组成的比如一个文章的single.html内容大致长这样!-- wp:template-part {slug:header,area:header} /-- !-- wp:group {layout:{type:constrained}} -- !-- wp:post-title {level:1} /-- !-- wp:post-content /-- !-- /wp:group -- !-- wp:template-part {slug:footer,area:footer} /--没有?php while ( have_posts() ) : the_post(); ?也没有get_header()。页面的骨架和区块结构直接在 HTML 里描述出来WordPress 渲染时把这些“块”翻译成真实的内容和样式。你可以把传统主题想象成装修队现场浇筑每一步都是 PHP 在服务器上敲出来的。块主题更像用乐高搭房子HTML 是积木说明书theme.json 是颜色和尺寸图纸编辑器就是拼积木的桌面。前者灵活但心智负担重后者约定清晰、更贴近“可视化编辑”的目标。1.2 theme.json 到底是什么theme.json 是整个块主题的设计系统配置文件。它统一管理颜色、字体、间距、布局、区块样式甚至模板和模板部件也能在里面注册。所有设计令牌design token集中在一份 JSON 里前端页面和后台编辑器都读它这是块主题“所见即所得”能成立的前提。最直接的理解方式以前设置主题颜色要去 functions.php 里注册add_theme_support(editor-color-palette)在前端还要单独写一套 CSS 变量。现在直接在 theme.json 的settings.color.palette里定义色板WordPress 会自动把它输出成--wp--preset--color--xxx这样的 CSS 变量前后端共用一套值。TT5 的 theme.json 遵循 WordPress 6.6 引入的 JSON 第三版规范结构上主要分settings可配置项、styles默认样式、templateParts模板部件、customTemplates自定义模板几大块。1.3 模板层级怎么在块主题里生效经典主题靠 template hierarchy 决定加载哪个 PHP 文件比如访问单篇文章就找single.php。块主题的层级逻辑一模一样只是加载的是templates目录下的 HTML 文件例如single.html、page.html、archive.html。解析顺序会从最具体的模板开始尝试找不到就往通用模板降级。访问一个标签归档页会依次尝试tag.html、taxonomy-tag.html、archive.html最后落到index.html。TT5 的templates目录里同时放了index.html和archive.html目的就是在各种类型的页面之间做合适的兜底。注意块主题模板文件里不需要也不应该写?php wp_head(); ?或?php wp_footer(); ?。WordPress 在渲染流程中会自动注入必要的 head 和 footer 资源这个差异是从经典主题迁过来时最容易犯的错误。2. Twenty Twenty-Five 目录结构逐层拆解把概念理清之后接下来直接看 TT5 的目录。下面的结构是正常安装后的简化版去掉了注释文件核心部分就是这些文件夹和文件twentytwentyfive/ ├── assets/ │ ├── fonts/ │ ├── images/ │ └── js/ ├── inc/ │ ├── block-patterns.php │ ├── block-styles.php │ ├── fonts.php │ └── functions.php ├── parts/ │ ├── footer.html │ ├── header.html │ └── post-meta.html ├── patterns/ ├── styles/ │ └── ... ├── templates/ │ ├── 404.html │ ├── archive.html │ ├── front-page.html │ ├── home.html │ ├── index.html │ ├── page.html │ ├── search.html │ └── single.html ├── functions.php ├── style.css └── theme.json先有整体印象再从顶层的三个文件开始一个个说。2.1 顶层三件套style.css、functions.php、theme.jsonstyle.css在块主题里的地位很特殊。它首先是主题的“身份证”文件开头的注释块包含主题名、版本、作者、文本域、依赖的 WordPress 版本等信息后台主题列表里的元数据就靠解析这段注释显示/* Theme Name: Twenty Twenty-Five Theme URI: https://wordpress.org/themes/twentytwentyfive/ Author: the WordPress team Version: 1.0 Requires at least: 6.7 Tested up to: 6.7 Requires PHP: 7.2 License: GNU General Public License v2 or later Text Domain: twentytwentyfive */块主题里style.css不一定需要写大量 CSS实际样式主要交给 theme.json但文件本身必须存在。TT5 的 style.css 里还是会放一部分基础重置和兜底样式避免某些极端情况下主题 JSON 没有覆盖到的元素“裸奔”。functions.php是主题的功能入口。在 TT5 里它没有把所有代码堆在一起而是通过require加载inc/下的模块文件这种做法在官方默认主题里已经很成熟目的就是让初始化逻辑按职责拆分方便阅读和维护。theme.json是设计系统的核心。毫不夸张地说TT5 一半的“灵魂”都在这份 JSON 里颜色、字体、间距、区块样式、模板部件注册、自定义模板注册全部集中在这里定义。后续所有自定义优先改它而不是去改 CSS。2.2 核心又核心的 templates/页面骨架templates/是页面的骨架层也是块主题中真正对应“页面模板”概念的地方。TT5 默认提供的模板覆盖了常规站点的所有典型场景index.html最终兜底模板其他模板都匹配不上时用它。front-page.html设为静态首页时优先使用。home.html文章列表首页。single.html单篇文章。page.html独立页面。archive.html归档页包括分类、标签、日期归档。search.html搜索结果页。404.html找不到页面时的提示页。这些 HTML 文件里都是区块标记。例如 TT5 的index.html一般会引用文章列表块single.html则用post-title、post-content、post-meta等区块拼出文章详情。它们的作用更像“默认值”——一旦用户在全站编辑器的模板面板里修改了某个模板数据库中就产生一个自定义版本优先于主题文件被加载。2.3 会被大量复用的 parts/模板部件parts/相当于经典主题里的header.php和footer.php是可以在多个模板之间复用的页面片段。TT5 的parts/默认有头部、底部、文章元信息等部件用wp:template-part区块在模板中引入!-- wp:template-part {slug:header,area:header} /--关键点是slug对应parts/下的文件名不含.htmlarea用来声明这个部件的区域界面上的拖拽和嵌套会参照这个属性。把头部底部抽成部件的好处是显而易见的几十个模板里重复引用同一个 header将来改导航时只需要动parts/header.html一个文件不用像经典主题那样在不同 PHP 模板里反复 get_header更不用怕漏改某个页面。2.4 patterns/ 与 styles/设计资产库patterns/目录存放块图案全称是 Block Patterns。图案是预先组合好的一组区块可以一键插入文章或页面正文例如“关于作者的介绍区”“带背景色的行动号召横幅”。TT5 内置了一批图案后台编辑器打开“添加区块 → 图案”就能看到它们。styles/目录则对应主题样式变体也就是后台“样式”面板里可以切换的不同视觉方案。每个变体在styles/下有一个自己的文件夹里面是一个覆盖性质的 theme.json 片段只写与默认不同的设置和样式。提示样式变体不是复制整个模板而是对主 theme.json 做增量覆盖。新增一个变体时可以复制默认 theme.json 里需要调整的那几段改颜色、改字体、改背景其余让默认值继续生效。TT5 自带几个风格差异明显的变体很值得拿来当“换肤模板”的入门案例研究。2.5 assets/ 与 inc/静态资源与 PHP 逻辑assets/负责存放静态资源字体文件在assets/fonts/图片在assets/images/如果主题有自己的前端脚本则放assets/js/。TT5 的字体是本地托管的不使用外部 CDN这是它在性能上做得比较干净的原因之一。inc/是 PHP 模块目录。TT5 把初始化逻辑拆成了多个子文件functions.php处理主题设置、导航注册、语言加载fonts.php管理字体资源和font-face输出block-patterns.php注册内置块图案block-styles.php注册自定义区块样式例如给某个核心块添加额外的样式选项。这种按职责拆分的结构非常值得学习。我的习惯是任何功能超过 100 行的主题都不要把所有代码塞进唯一的functions.php否则半年后回头维护光找函数就够你头疼。3. WordPress 到底怎么把一个块主题“跑起来”目录结构看明白了接下来是最核心的部分一次页面请求进来WordPress 是如何把 TT5 的 HTML 模板和 theme.json 变成浏览器里真实页面的这个过程分成五个环节一步步讲可能有点枯燥但把这五步打通了以后排查任何块主题问题你都会有清晰的方向。3.1 主题激活与被识别的过程网站在后台激活主题时WordPress 会扫描wp-content/themes/下的目录通过读取每个目录里style.css的头部注释生成主题列表。激活 TT5 时系统同时会读取它的theme.json确认它具备块主题能力。主题版本升级和兼容性检查也会在这一步完成。TT5 在style.css里声明了Requires at least: 6.7如果你的 WordPress 版本低于 6.7后台会提示不兼容。块主题对版本要求比经典主题更敏感原因就在于 theme.json 的解析逻辑、区块模板机制都是靠较新的核心代码支撑的。3.2 functions.php 和 inc/ 模块的职责边界主题一旦激活此后每次请求都会加载functions.php。TT5 在文件里会通过require引入inc/下的子模块并挂载若干after_setup_theme动作?php require get_template_directory() . /inc/functions.php; require get_template_directory() . /inc/fonts.php; require get_template_directory() . /inc/block-patterns.php; require get_template_directory() . /inc/block-styles.php; add_action( after_setup_theme, function () { load_theme_textdomain( twentytwentyfive ); add_theme_support( wp-block-styles ); // ... } );要注意的是functions.php是常驻内存的入口文件不在这个阶段做重型计算。所有需要条件加载的资源都应该放在具体函数里按需注册等真正输出时才入队。inc/fonts.php是 TT5 做得比较讲究的地方。字体文件一般比较大如果无脑塞进所有页面前端性能会明显下滑。TT5 的做法是按需注册字体样式并在输出时通过内联font-face让浏览器解析而且字体文件全部本地托管不依赖外部请求在隐私和稳定性上都更稳妥。3.3 从 theme.json 到前端 CSS 的完整链路这是块主题加载机制里最核心的一环。当页面准备输出时WordPress 会调用 WP_Theme_JSON_Resolver 和 WP_Theme_JSON 这两个核心类把主题的 theme.json 解析成一份“合并后的全局配置”。解析过程不是只读一个文件而是分层合并核心默认值Core defaults合并主题文件里的值再合并用户在编辑器里保存的自定义值。优先级从低到高用户自定义永远最高。这也解释了为什么你在编辑器里改了颜色前端会立刻变化而 theme.json 里同名的设置反而可能“失效”。合并完成后系统会把 styles 部分转换成 CSS 变量和选择器通过wp_enqueue_global_styles这类入口输出到页面头部。典型结果是页面源代码里会出现一段style idglobal-styles-inline-css里面是一行行--wp--preset--color--base这样的变量定义。编辑器和前端共用这份 CSS 变量是块主题实现所见即所得的技术底座。注意前端看到的 CSS 并不是原样把 theme.json 的 JSON 输出进去而是被转换成 CSS 自定义属性CSS Variables和具体的类选择器。修改 theme.json 后不生效最常见的两个原因一是缓存没清二是用户层自定义覆盖了主题层设置。3.4 模板解析顺序数据库优先于主题文件模板选择是页面渲染的前置步骤。WordPress 里头有一个解析函数做这件事按照请求类型确定模板层级然后从“数据库中的用户自定义模板”→“主题文件模板”→“核心回退模板”一路找下去。用户在全站编辑器里改动模板时WordPress 会在数据库 wp_posts 表里生成一条post_type wp_template的记录内容是模板的区块标记。此后前端解析这个模板时数据库记录优先返回主题文件里的同名模板反而靠后。同理模板部件wp_template_part也遵循同一套逻辑。这就是为什么“改了主题文件里的 single.html但前端毫无变化”往往不是没生效而是编辑器里已经存在一个自定义版本的 single 模板。判断方法很简单打开后台全站编辑器的模板列表看看有没有比主题文件多出来的模板有的话要么改它要么删除它。模板层级映射关系大致如下场景尝试顺序从具体到通用首页front-page.html → home.html → index.html单篇文章single.html → index.html独立页面page.html → index.html分类/标签归档taxonomy-{tax}.html → archive.html → index.html3.5 区块渲染与动态块服务端渲染模板文件找到之后HTML 里的区块标记还不能直接输出需要经过“块解析”流程。WordPress 会用parse_blocks()把标记解析成结构化的块数组然后逐块交给渲染器最终拼接成完整页面。每个区块具体怎么渲染取决于它是静态块还是动态块。静态块的渲染逻辑直接在前端 React 或服务端输出固定 HTML而动态块的渲染必须经过服务器端例如文章列表需要查询数据库、导航菜单需要读取菜单配置。动态块渲染时会调用服务端注册的 callback拿到数据库数据后拼装 HTML 再返回给页面。TT5 里常见的query-loop文章列表、navigation导航菜单、post-content文章内容都属于动态块。这种设计把“页面结构”和“数据逻辑”分开了模板只负责结构数据读取交给动态块这也是块主题能在编辑器里做到高度可视化的原因之一。4. 实战在 Twenty Twenty-Five 上做二次开发说完了加载机制下面进入实操部分。实际项目里我不会直接修改 TT5 源码而是以它为基座用子主题和 theme.json 做定制。下面是三种最常用也最安全的方式。4.1 该不该直接改主题先做一个子主题任何官方主题的更新都可能覆盖你的改动直接改 TT5 文件的结果就是升级一次丢一次改动所以子主题是第一选择。创建子主题只需要两步在wp-content/themes/下新建目录比如twentytwentyfive-child里面放一个带正确头部信息的style.css/* Theme Name: Twenty Twenty-Five Child Template: twentytwentyfive Version: 1.0 */然后建一个functions.php把父主题样式继承过来?php add_action( wp_enqueue_scripts, function () { wp_enqueue_style( parent-style, get_template_directory_uri() . /style.css ); wp_enqueue_style( child-style, get_stylesheet_uri(), array( parent-style ) ); } );注意Template: twentytwentyfive这一行是关键它告诉 WordPress 子主题继承哪个父主题。后台启用子主题后父主题的模板、theme.json 都会被自动继承你只需要写自己新增的部分。4.2 用 theme.json 调整全局颜色和字体子主题里如果放一个theme.json它会与父主题的 theme.json 深度合并。比如我想把 TT5 的主色换成自己的品牌色做法是在子主题的 theme.json 里覆盖styles段的 color 配置{ $schema: https://schemas.wp.org/trunk/theme.json, version: 3, styles: { color: { background: #f0efe7, text: #111111 } } }不需要把整个 theme.json 复制过来。合并时我只需要声明改动的键其余继续继承父主题。同理字体调整放在settings.typography.fontFamilies下面我可以先补充字体族定义再用styles.typography.fontFamily指定默认字体。实操心得设置完 theme.json先看编辑器预览再刷新前端页面。如果两边表现不一致优先检查缓存插件和对象缓存其次看数据库里的全局样式自定义是否有残留覆盖。4.3 给站点加一个自定义模板假设我想做一个“全宽页面模板”没有侧边栏干扰。在子主题里新建templates/landing.html内容按常规块模板结构写好!-- wp:template-part {slug:header,area:header} /-- !-- wp:group {layout:{type:constrained}} -- !-- wp:post-title /-- !-- wp:post-content /-- !-- /wp:group -- !-- wp:template-part {slug:footer,area:footer} /--然后需要在子主题的 theme.json 里注册它{ customTemplates: { landing: { title: Landing Page, postTypes: [ page ] } } }保存后编辑任意页面时右侧“页面模板”下拉面板里会出现 “Landing Page” 选项。使用的原理就是前面说的模板解析页面选择这个模板后WordPress 会尝试加载templates/landing.html。4.4 利用样式变体做快速换肤样式变体很适合给用户提供不同视觉风格。在子主题里新建styles/contrast目录里面放一个 theme.json{ styles: { color: { background: #000000, text: #ffffff } } }后台风格面板会自动识别这个变体用户可以选择切换到高对比版本。这比维护两套完整主题省力得多也很适合演示“同一个模板不同气质”。提示样式变体的覆盖是针对全局的如果用极端配色记得也检查链接色、按钮内边距等细节否则可能标题和按钮配色不协调。5. 踩坑实录与排查思路单独写一个章节聊聊实际问题。下面是块主题日常开发里我好几次遇到并记录下来的坑对号入座能省不少时间。5.1 在编辑器改了样式前端纹丝不动这是最典型的案例。原因基本就是数据优先级问题用户在编辑器里保存的样式是最高优先级任何 theme.json 或 CSS 覆盖都没办法越过它。如果你修改了主题文件发现前端无变化第一件事不是怀疑缓存而是排查是否有用户自定义的全局样式覆盖。去数据库看wp_posts表找post_type等于wp_global_styles的记录里面可能存有一份用户编辑后的全局样式。如果确认是残留自定义要么编辑器中手动把相关样式改回来要么删除这条记录让主题默认值重新生效。操作数据库前记得备份这属于常规操作但直接影响站点外观。5.2 语言包与中文显示问题TT5 的文本域是twentytwentyfive。想让后台编辑器和主题自带文案显示中文最简单的路径是在“设置 → 站点语言”里切换为简体中文。WordPress 会自动拉取对应语言包。手工方式也保留着把中文语言包文件放到wp-content/languages/themes/下文件名规范是twentytwentyfive-zh_CN.po和.mo。更换语言包后需要重新加载页面才会生效后台或前端有缓存的话务必清理一遍。5.3 模板加载顺序和你预期的不一样做自定义模板时经常遇到“明明建了archive.html归档页就是不用它”。此时先别急着改文件确认站点的固定链接设置再看看当前页面到底进入哪个模板层级。比如你在用page.html渲染文章列表其实是因为后台“设置 → 阅读”里选了某个页面作为文章页而文章列表页走的不是archive而是home模板。模板层级不是想当然的文件名匹配建议先查 Query Monitor或者用 WP CLI 命令把当前模板结构和渲染结果打出来确认实际选中了哪个模板再调整文件。5.4 上传大文件受限制这里要说的是一个容易被忽略的隐藏依赖全站编辑器和图案导入功能有时需要上传比较大的媒体文件如果php.ini里的upload_max_filesize和post_max_size太小会导致上传失败表现不光影响媒体库甚至影响图案和备份导入。修改上传限制属于常规操作记得同步调整这两项upload_max_filesize 64M post_max_size 80M修改之后在后台“媒体 → 添加媒体文件”页面刷新确认新值生效。这个操作与主题本身关系不大但块主题让人容易忽视传统 PHP 限制所以我总会提醒一次。5.5 调试块主题的好用工具兜底调试方案装上 Query Monitor 插件它能把当前页面解析了哪些模板、哪些块、哪些查询、哪些样式全部列出来是我排查模板解析和资源加载的第一入口。命令行走得通的话还可以用 WP CLIwp template list wp template part list这两条命令能列出当前站点所有可用的模板和模板部件标注来源是“主题”还是“自定义”。配合字符串搜索很快就能定位到底是谁在跟你的修改打架。我在实际项目里的工作流始终是“子主题 theme.json 数据库自定义三层分离”。先确认问题出在哪一层再决定动哪里。尤其是 TT5 这种官方主题它的整个设计就是为长期演进准备的你可以在子主题里放心叠加需求而不用怕升级踩坏站点。这套目录和加载逻辑放到任何新出的块主题上都通用建议你把 TWENTY TWENTY FIVE 当成语料库而不是终点多拆几个官方主题之后块主题对你来说就不再是玄学了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进