本文约 3,903 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:改了 CSS,前台死活不生效
子主题的 style.css 改好了,本地看没问题,推到线上刷新却是老样式。清了浏览器缓存、清了缓存插件,CDN 那边还是旧文件。最后靠在 URL 后面手敲 ?v=2 才正常——这说明样式文件确实被 CDN 和浏览器按 URL 强缓存了,而 WordPress 默认生成的 <link> 地址里带着一串版本号,只要这串数字没变,缓存就认为文件没变。
这串数字来自 style.css 头部的 Version。但一个站点里其实存在两个版本号:父主题的和子主题的,很多人分不清该改哪一个。
原理分析:两个版本号,两套职责
WordPress 用 WP_Theme 对象解析 style.css 头部注释,Version 是其中一个字段:
/*
Theme Name: My Child
Template: parent-theme
Version: 1.2.0
Text Domain: my-child
*/
| 版本号 | 读取方式 | 主要作用 |
|---|---|---|
| 子主题 Version | wp_get_theme()->get( 'Version' ) |
主题列表显示、子主题自身资源的缓存串 |
| 父主题 Version | wp_get_theme( get_template() )->get( 'Version' ) |
判断父主题是否已升级,作为更新检查与兼容判断依据 |
关键点在于:wp_get_theme() 不带参数时返回的是当前启用的主题对象,启用子主题时它返回子主题。也就是说,如果你在子主题里用 wp_get_theme()->get('Version') 给 wp_enqueue_style() 传版本号,你拿到的是子主题版本,父主题更新时这个数字不会变,父主题资源就会出现”更新了但缓存没穿”的怪现象。
另一条链路是浏览器缓存:wp_enqueue_style() 输出的 <link> 会拼上 ?ver=版本号。版本号相同 → URL 相同 → 浏览器和 CDN 直接命中缓存。所以”改版本号”本质上是一次手动的缓存穿透(cache busting)。
保姆级实操
第 1 步:看清当前的两个版本号
wp theme list
输出里 Status 为 active 的是子主题,parent 是父主题,各自有独立 Version 列。也可以临时打印:
printf(
'子主题 %s / 父主题 %s',
esc_html( wp_get_theme()->get( 'Version' ) ),
esc_html( wp_get_theme( get_template() )->get( 'Version' ) )
);
第 2 步:子主题资源用子主题版本号
启用子主题后,get_stylesheet_uri() 指向子主题的 style.css,版本号就该用子主题自己的版本:
add_action( 'wp_enqueue_scripts', function () {
$theme = wp_get_theme(); // 当前启用主题 = 子主题
wp_enqueue_style(
'child-style',
get_stylesheet_uri(),
array(),
$theme->get( 'Version' ) // 子主题 style.css 的 Version
);
} );
之后再改 style.css,同步把 Version 从 1.2.0 加到 1.2.1,前台 URL 变成 ?ver=1.2.1,浏览器与 CDN 立即回源。
第 3 步:父主题资源用父主题版本号
如果子主题额外加载了父主题的资源(不推荐,但在覆盖式子主题里常见),版本号必须取父主题的,否则父主题升级后老资源仍被缓存:
add_action( 'wp_enqueue_scripts', function () {
$parent = wp_get_theme( get_template() ); // 按目录名取父主题对象
wp_enqueue_style(
'parent-style',
get_template_directory_uri() . '/style.css',
array(),
$parent->get( 'Version' ) // 父主题版本,随升级自动变化
);
}, 8 );
第 4 步:高频改动场景改用文件修改时间
开发阶段一天改十几次样式,每次手改版本号太痛苦。用 filemtime() 让版本号跟着文件时间戳自动变:
add_action( 'wp_enqueue_scripts', function () {
$css = get_stylesheet_directory() . '/style.css';
wp_enqueue_style(
'child-style',
get_stylesheet_uri(),
array(),
file_exists( $css ) ? (string) filemtime( $css ) : wp_get_theme()->get( 'Version' )
);
} );
注意:filemtime() 每次请求都要做一次磁盘 stat,生产环境建议只在开发模式启用,或者把它缓存进 transient。
第 5 步:确认缓存插件与 CDN 会跟随变化
WP Rocket、LiteSpeed Cache 这类插件在检测到资源 URL 变化后会自动清理相关缓存;但 CDN 侧的缓存规则需要自己确认——Cloudflare 默认会缓存 ?ver= 查询串,版本号变了就会重新回源。若 CDN 设置了”忽略查询字符串”,那版本号方案失效,需要改用文件名哈希或手动 purge。相关清理顺序见主题更新前必做的三件事里的缓存一节。
第 6 步:数据库侧的缓存同步刷新
主题版本号变化时,如果站上用了主题版本号做 transient 缓存键,需要一并失效:
add_action( 'after_switch_theme', function () {
delete_transient( 'mytheme_menu_cache' );
wp_cache_flush(); // 有外部对象缓存时,通知它整体失效
} );
常见踩坑
- 版本号写成常量字符串。
wp_enqueue_style( 'x', $src, array(), '1.0' )写死等于永久缓存,改一百次文件前台也不动。 - 传
false反而更糟。$ver = false时WP_Styles会用 WordPress 自身的版本号兜底,只有核心升级才变,等于把样式缓存周期拉到最长。 - 父主题更新后子主题资源版本号不变。 子主题
style.css里@import了父主题样式时尤其明显——@import引入的文件不参与版本号机制,应改用wp_enqueue_style显式加载。 - 版本号与更新检查器冲突。 商业主题用自定义更新器比对
Version,手改子主题版本号不影响父主题更新提示,但把父主题style.css的 Version 乱改会让更新器反复提示”有更新”。 - 案例: 某站启用了子主题,运营改了首页 Banner 的 CSS 后投诉”改了没用”。排查发现子主题
functions.php里用的是wp_get_theme( get_template() )->get('Version')——取的是父主题版本,父主题半年没更新,版本号一直是3.1.4,?ver=3.1.4被 CDN 缓存了半年。改成子主题版本后一次生效。教训是:谁的资源,用谁的版本。 - 忘了
Version头必须写在style.css最前面的注释块里。 写在文件中间或第二个注释块里,WP_Theme解析不到,会返回空字符串。 - 缓存插件的”合并 CSS”功能把版本号抹掉了。 合并后的文件名由插件自己生成,不受主题 Version 控制,需要在插件里手动点一次”清除合并文件”。
- 子主题里改了模板却看不到效果。 先确认覆盖文件放在了子主题目录的正确层级,可对照子主题覆盖父主题模板的正确姿势逐项核对。
总结
判断标准很简单:加载谁的文件,就用谁的版本号。子主题资源用 wp_get_theme()->get('Version'),父主题资源用 wp_get_theme( get_template() )->get('Version'),开发期高频改动用 filemtime() 兜底。上线前确认 CDN 没有”忽略查询字符串”。版本号不是给更新器看的装饰,它是最廉价、最可控的缓存穿透开关——把它写进你的发布流程,比每次教客户按 Ctrl + F5 有效得多。
