本文约 4,710 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:点完“开始安装”后,后台多了七个插件
启用新主题后弹出一个提示框,列出一串“为了正常使用本主题,建议安装以下插件”:某某 Core、某某 Companion、某某 Shortcodes、某某 Widgets、还有两个 Page Builder。点完确认,站点多了七个插件,其中三个你永远不会用。
这背后的机制是 TGMPA(TGM Plugin Activation)——一个被大量商用主题引用的 PHP 类库,作用是给用户一个统一的插件安装入口。它本身没什么问题,问题是作者为了“演示站效果”往往把一堆插件设为推荐,甚至把本可以由主题自身实现的小功能拆出去做成插件。
本文解决的是:如何判断这些依赖里,哪些必须留,哪些可以拆,以及拆掉之后用什么替代。 关于如何避免与主题自身功能重复,另见插件与主题功能重复时的处理原则。
原理分析:TGMPA 的三种推荐强度
TGMPA 在注册的插件数组里给每个插件三个关键开关:
| 字段 | 含义 | 用户能不能拒绝 |
|---|---|---|
required |
是否是必需 | true 时强烈提示,但 TGMPA 本身不强制阻止主题运行;真正“强制”靠主题自己的检查 |
force_activation |
是否在主题激活时自动启用 | true 会在主题启用时被激活,且用户在插件列表里无法停用 |
force_deactivation |
是否随主题切换停用 | true 时切主题会自动停用该插件,常用来绑定 Licensing 插件 |
需要特别注意的是:required 与“缺了就白屏”不是一回事。真正让站点在缺插件时出问题的,是主题代码里的裸调用——比如直接调用某个插件的类、函数或短代码而没有做 function_exists / class_exists 保护。
所以“能不能拆掉某个依赖”这个问题的答案,取决于主题在哪里、以何种方式用到了它。
保姆级实操
第一步:列出主题声明的全部依赖。
# 找到 TGMPA 的注册位置(通常在 functions.php 或 inc/ 下)
grep -rn "tgmpa_register\|tgmpa(" wp-content/themes/当前主题/ --include="*.php" | head -5
grep -rln "class-tgm-plugin-activation" wp-content/themes/当前主题/
打开那个文件,逐个记录:name、slug、required、force_activation、source(source 非空表示是随主题打包的外部/付费插件,这类无法从官方目录获取,风险最高)。
第二步:反查主题对每个插件的真实依赖点。
# 检查主题是否直接调用了插件的类
grep -rnE "new (Elementor|RevSlider|WooCommerce|Vc_)[A-Za-z]*\\\\?[A-Za-z_]*" --include="*.php" wp-content/themes/当前主题/ | head -20
# 检查是否直接 do_shortcode 了某个插件的短代码
grep -rno "do_shortcode( *'\[[a-zA-Z0-9_-]*" --include="*.php" wp-content/themes/当前主题/ | sort -u | head -20
# 检查是否有防御性判断(有的话说明拆掉通常安全)
grep -rn "class_exists\|function_exists\|is_plugin_active\|defined( *'" --include="*.php" wp-content/themes/当前主题/ | head -30
判断规则:有 class_exists/function_exists 保护的调用 = 可安全剥离;裸调用 = 必须先补上一层保护。
第三步:在子主题里做“软替换”。
以最常见的“主题依赖某个短代码/小工具插件实现 CTA 区块”为例:
<?php
// functions.php —— 替代某个主题依赖插件的具体功能
add_action( 'init', function () {
// 若原插件仍在,就让它继续干活,避免冲突
if ( shortcode_exists( 'vendor_cta' ) ) {
return;
}
add_shortcode( 'vendor_cta', function ( $atts ) {
$atts = shortcode_atts( array(
'title' => '联系我们',
'link' => home_url( '/contact/' ),
), $atts, 'vendor_cta' );
return sprintf(
'<div class="cta-box"><a class="cta-btn" href="%s">%s</a></div>',
esc_url( $atts['link'] ),
esc_html( $atts['title'] )
);
} );
} );
这样即使原插件停用,正文里已有的 [vendor_cta] 也不会变成裸露文本。
第四步:处理 force_activation 的强制项。
// 子主题 functions.php —— 解除 TGMPA 的自动检测
add_action( 'after_setup_theme', function () {
remove_action( 'tgmpa_register', '当前主题注册TGMPA的函数名' );
}, 9 );
其中函数名要到主题源码里找(tgmpa_register 的 add_action 第二个参数)。移除后 force_activation 不再生效,但你必须自己保证替代实现到位。
第五步:验证剥离后的前台完整性。
# 逐个停用候选依赖,先看前台有无 PHP 错误
wp plugin deactivate 候选插件
curl -s -o /dev/null -w "HTTP=%{http_code}\n" https://example.com/
# 同时盯紧 PHP 错误日志(这是最重要的一步)
tail -n 50 wp-content/debug.log 2>/dev/null || tail -n 50 /var/log/php-fpm/error.log
开启调试日志(wp-config.php 里 WP_DEBUG + WP_DEBUG_LOG),任何 Call to undefined function / Class not found 都会暴露真实的依赖点。
第六步:把非必需的 companion 类插件换成原生能力。
很多 “某某 Companion / Core / Addons” 插件提供的其实是:自定义文章类型、一堆短代码、以及演示数据的必需支撑。替代路径:
// 用原生方式注册 CPT,替换 companion 插件里的那部分
add_action( 'init', function () {
if ( post_type_exists( 'team' ) ) {
return;
}
register_post_type( 'team', array(
'public' => true,
'label' => '团队成员',
'supports' => array( 'title', 'editor', 'thumbnail' ),
'show_in_rest' => true,
'has_archive' => true,
) );
} );
CPT 换成原生注册之后,内容不会因为插件停用而消失;模板文件则由你自己在子主题里补 single-team.php / archive-team.php。
第七步:留一份记录。
# 记录下每次剥离操作前的插件状态,方便回滚
wp plugin list --format=json > plugin-state-$(date +%F).json
常见踩坑
- 直接停用然后前台一片空白。裸调用没有 fallback,务必先在 staging 上逐个试并看
debug.log。 - 忘了清理正文里的短代码。停用插件只是输出源消失,正文里几十处短代码还在,应先用短代码占位实现接管。
required没当回事。TGMPA 提示的 required 有时只是作者设定,不等于技术必要性;反过来,标记为recommended的也可能是演示数据的必需项。force_deactivation绑定的 License 插件。切主题后它们会被停用,可能影响仍在使用的其他商用主题,要逐个确认来源。- 付费/bundled 插件(
source非空)无法从官方更新。这类依赖长期没安全更新,是剥离优先级最高的对象。 - 拆了之后不删除 option 残留。参考本文连带的清理流程,把残留设置一并删掉,否则
wp_options继续膨胀。 - 案例:某企业站主题强制要求装“某某 Core”才能用 team/FAQ/testimonial 三个 CPT。团队改用原生 CPT + 自定义字段后卸载了 Core 插件,结果所有旧文章的 meta 丢失——因为 Core 插件把数据存在自定义表里,卸载时表也被删了。正确顺序应是:先迁移数据(meta → 原生 postmeta/terms)→ 确认前台正常 → 最后才删除插件。
总结
剥离的判断标准,按优先级排序:先拆 source 非空的 bundled 付费插件(无更新风险)→ 再拆各类有 class_exists 保护的重复功能插件 → 最后才是一动全身的 core/companion 类。
每条都遵循同一个流程(若发现某些“依赖”其实只是 demo 站的必需项,按为什么导入演示数据后和演示站不一样的清单处理即可):grep 找依赖点 → 写短代码/CPT 替代 → staging 停用并看 debug.log → 前台验证 → 记录 JSON 状态便于回滚。
最后一个实用建议:把“本站点需要哪些插件”写成一份文档,每个插件标注“为什么需要它”。凡是答不上来理由的,通常就是下一次可以清理的目标——这就是长期维护里最有效的一次性减负动作。
