本文约 5,515 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:改了什么?没人说得清
一个自建主题维护了三年,某天首页样式突然错位。三个人分别回忆:设计师说上周改过卡片间距,后端说上个月动过 functions.php 的查询,运营说前天在「自定义 → 额外 CSS」里加了一段。现场翻服务器文件,主题目录里的 style.css 版本号还写着 1.0.0——从建站那天起就没变过。没有任何记录,只能靠二分法逐段注释代码。
这不是技术问题,是工程习惯问题。主题一旦进入「自己长期维护」的状态,它就从一份交付物变成了一个软件项目,需要版本号、需要变更记录、需要可回滚。WordPress 本身对这块很宽容(它只要求 style.css 头部声明版本),宽容的另一面是没有约束——全靠自己建规矩。
好消息是,这套规矩不需要重量级工具。一个 Git 仓库、一个 CHANGELOG.md、一条「改版本号的规则」,就能覆盖 90% 的场景。
原理分析:WordPress 的版本号在哪、怎么用
WordPress 读取主题版本有两个来源:
| 来源 | 位置 | 用途 |
|---|---|---|
style.css 头部 Version: |
主题根目录 style.css | 后台显示、缓存 busting 基础值 |
wp_get_theme()->get('Version') |
运行时读取 | 常用于 wp_enqueue_style() 第 4 参数 |
| 主题选项里的 db version | 自定义 option | 触发升级例程 |
| Git tag | 仓库 | 人类可读的发布点 |
最常见的用法是把版本号当缓存破坏参数:wp_enqueue_style( 'main', $url, array(), wp_get_theme()->get('Version') )。版本号一变,浏览器就会重新拉取 CSS/JS。这意味着版本号不只是记录,它直接影响线上行为——版本号不改,用户可能一直拿着旧缓存。
版本号规则建议直接采用语义化版本(SemVer):主版本.次版本.修订号。破坏性变更(模板文件结构变、钩子签名变、需要用户手动操作)进主版本;新增功能且向后兼容进次版本;纯修 bug 进修订号。WordPress 自身以前不是 SemVer,但主题是独立项目,用 SemVer 没有冲突。
保姆级实操
步骤 1:初始化仓库与目录约定
cd wp-content/themes/my-theme
git init
git branch -M main
echo "node_modules/" > .gitignore
echo "*.log" >> .gitignore
echo ".DS_Store" >> .gitignore
echo "/dist/*.map" >> .gitignore
git add -A && git commit -m "chore: init theme baseline"
约定:main 永远是可发布状态;改动都在 feat/xxx、fix/xxx、chore/xxx 分支;合并进 main 时打 tag。
步骤 2:把版本号写进 style.css 头
/*
Theme Name: Mimangfei Base
Theme URI: https://mimangfei.com/
Author: Mimangfei
Author URI: https://mimangfei.com/
Description: 自建长期维护基线主题
Version: 1.4.0
Requires at least: 6.4
Tested up to: 6.8
Requires PHP: 8.0
Text Domain: mimangfei-base
*/
Tested up to 要跟着每次 WordPress 大版本更新同步改,这是 主题与WordPress新版本的前瞻性适配 里提到的兼容声明,也会在后台更新页显示。Version 每次发布必须递增,这是缓存 busting 的开关。
步骤 3:建立 changelog 模板
主题根目录建 CHANGELOG.md,固定使用 Keep a Changelog 的格式:
# Changelog
本文件记录 Mimangfei Base 主题的所有显著变更。
版本号遵循语义化版本规范。
## [1.4.0] - 2026-10-10
### Added
- 产品中心模板 `template-parts/content-product.php`,支持规格表格
- `theme.json` 新增 `spacingScale` 自定义间距档位
### Changed
- 移动端导航热区提升至 44px(WCAG 2.2 SC 2.5.5)
- `mmf_enqueue_assets()` 改为按页面类型条件加载脚本
### Fixed
- 修复归档页第二页 `pre_get_posts` 未排除 `product` 类型导致分页 404
- 修复 `esc_attr()` 缺失导致的属性上下文 XSS(见 header.php:47)
### Deprecated
- `mmf_get_product_meta()` 将在 2.0.0 移除,请改用 `mmf_get_product_spec()`
### Removed
- 移除对 `wp_get_sites()` 的调用(WordPress 6.1 已删除该函数)
六个分类(Added / Changed / Fixed / Deprecated / Removed / Security)覆盖绝大多数改动,写不出分类的改动说明它太小,可以合并到下一条。
步骤 4:自动生成 changelog 的前半段
规范化提交信息后,changelog 可以半自动生成:
# 提交信息约定:type(scope): subject
git commit -m "fix(pagination): 排除 product 类型避免归档分页 404"
git commit -m "feat(product): 新增规格表格模板"
# 导出两个 tag 之间的提交列表
git log --pretty=format:"- %s (%h)" v1.3.0..v1.4.0
把输出粘进 CHANGELOG 对应分类,人工补上为什么要改。自动化的部分是不遗漏,人工的部分是说清意图。
步骤 5:需要数据库迁移时写升级例程
主题改了选项结构、改了自定义表名,必须有升级逻辑,否则老站点更新后直接出错:
<?php
define( 'MMF_THEME_DB_VERSION', '3' );
add_action( 'after_switch_theme', 'mmf_theme_activate' );
function mmf_theme_activate() {
if ( get_option( 'mmf_db_version' ) !== MMF_THEME_DB_VERSION ) {
mmf_theme_upgrade();
}
}
add_action( 'init', 'mmf_maybe_upgrade' );
function mmf_maybe_upgrade() {
if ( get_option( 'mmf_db_version' ) !== MMF_THEME_DB_VERSION ) {
mmf_theme_upgrade();
}
}
function mmf_theme_upgrade() {
$current = (int) get_option( 'mmf_db_version', 0 );
if ( $current < 1 ) {
// 1: 引入默认设置
add_option( 'mmf_settings', array( 'layout' => 'grid', 'per_page' => 12 ) );
}
if ( $current < 2 ) {
// 2: 把旧的单选项合并进数组
$old = get_option( 'mmf_layout_mode' );
if ( $old ) {
$s = get_option( 'mmf_settings', array() );
$s['layout'] = sanitize_key( $old );
update_option( 'mmf_settings', $s );
delete_option( 'mmf_layout_mode' );
}
}
if ( $current < 3 ) {
// 3: 为产品 CPT 补 rewrite 规则,需刷新固定链接
flush_rewrite_rules();
}
update_option( 'mmf_db_version', MMF_THEME_DB_VERSION );
}
这种「逐级 if」的写法比 switch 更好维护:从 1 升到 3 会自动经过 2,不会出现跳级漏迁移。
步骤 6:发布与回滚
# 发布:改 style.css 的 Version,写 CHANGELOG,打 tag
git checkout main
git pull --ff-only
git tag -a v1.4.0 -m "1.4.0 产品中心模板 + 移动端热区"
git push origin main --tags
# 回滚:先回滚文件,再回滚数据
git checkout v1.3.0
回滚要注意:代码可以 git checkout 回上一版,但数据库迁移做过的事回不去。flush_rewrite_rules()、选项结构变更这类操作,需要写对应的 down 逻辑或至少在 CHANGELOG 里标明「此版本不可逆」。
步骤 7:上线前的固定检查清单
每次发布前三件事:① style.css 的 Version 已递增;② CHANGELOG 有对应条目;③ 预发布环境打开 WP_DEBUG_LOG 跑一遍主要页面(参考主题调试的标准做法),日志里无新增 deprecated 与 PHP notice。
常见踩坑
- 改了代码不改版本号:用户浏览器缓存旧 CSS,前台看起来「改了没生效」,反复刷新无效,最后被当成灵异事件。
- 直接在生产主题目录里改文件:没有版本控制,改错了只能靠记忆恢复。至少要做每日文件备份。
- CHANGELOG 只写「优化若干」:出事回查时毫无价值。每条要能定位到文件与动机。
flush_rewrite_rules()写在init上:每次请求都刷新,性能极差,正确做法是只在激活/升级时调用一次。- 案例: 某主题 2.0 版本把自定义字段从
product_price改名为_product_price(加下划线变成隐藏字段)。代码更新后前台全部读不到价格,显示为 0。因为 CHANGELOG 里只在「Changed」写了「优化字段命名」,没有标注需要数据迁移,运维按常规流程发布后才发现,回滚代码期间站点展示了两小时的错误价格。教训:涉及数据结构变更必须在 CHANGELOG 里单独标「需迁移」,并提供迁移脚本。 - tag 打在错误的提交上:
git tag默认打在当前 HEAD,发布流程里应先git pull --ff-only确认分支最新再打。 - 子主题与父主题版本号混用:
wp_get_theme()在子主题下返回子主题版本,若用父主题版本做缓存 busting,子主题改动不会刷新缓存。应统一用wp_get_theme()->get('Version')并确保改的是当前启用主题。
总结
自己维护主题的版本管理,最低可行配置是四件套:style.css 里的语义化版本号(每次发布必改,兼作缓存 busting)、一个按 Added/Changed/Fixed 分类的 CHANGELOG.md、Git tag 作为发布点、以及带版本号常量的数据库升级例程。
判断做得是否到位的标准只有一条:任意一次线上故障,能否在五分钟内定位到「哪个版本引入的、改了哪几行、能否安全回滚」。做不到就说明记录还不够细。这套规范与 主题二次开发的成本评估 配合使用效果最好——维护成本能不能量化,取决于变更记录是否完整;而评估后决定重构时,也要先按 主题迁移到新主题时内容保全方案 规划好数据迁移。
