本文约 7,544 字,预计阅读 19 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:四十个请求里,真正需要的不到二十个
打开一个刚装好的企业站首页,Network 面板显示 41 个请求、1.8MB。逐条看下去会发现:wp-emoji-release.min.js、wp-embed.min.js、jquery-migrate.min.js、dashicons.min.css(一个 40KB 的字体图标表)、wp-block-library-theme.css、还有一堆 wp-block-*.css。而站点既没用表情,也没嵌入外部内容,主题是经典主题,前台只有一个搜索框和一个表单。
这些资源都是 WordPress 出于「开箱即用」默认注册的。它们在默认安装下合理,但对一个具体的站点来说,大部分是不需要的。每一条都带来三重成本:一个 HTTP 连接、一次解析执行、以及(对 CSS 而言)参与渲染阻塞。在移动端弱网环境下,十几个无用请求足以把 LCP 拖后几百毫秒。
精简的原则是「先确认、再移除、后验证」。因为有些资源看着没用,实际被某个插件依赖,一刀切会直接把功能弄坏。
原理分析:WordPress 的注册与加载是两个阶段
理解这一点,就不会写出无效的代码。WordPress 分两步:
| 阶段 | 函数 | 作用 |
|---|---|---|
| 注册 | wp_register_style/script() |
只登记,不输出 |
| 入队 | wp_enqueue_style/script() |
排队,输出时才打印 |
| 打印 | wp_print_styles() / wp_head |
真正输出 <link>/<script> |
所以移除也分两种:wp_dequeue_*() 只是把它从输出队列里拿掉(依赖仍在,别人还可以再入队);wp_deregister_*() 是彻底注销(依赖它的入队会失败,甚至报错)。实践中一律用 dequeue,不要用 deregister,这是安全边界。
第二个要点是时机。wp_enqueue_scripts 钩子里,核心注册资源的优先级是默认 10,主题的入队也是 10。要移除别人的东西,自己的钩子必须更晚执行——用优先级 100 是通行做法。
第三个要点是「按页面条件移除」。is_singular()、is_front_page()、is_archive() 这些条件标签可以精确控制,比如只让表单脚本出现在联系页。这才是收益最大的优化,因为大多数资源只在少数页面需要。
保姆级实操
步骤 1:先盘点前台到底加载了什么
# 用 WP-CLI 列出所有已注册的脚本与样式及依赖
wp eval 'global $wp_scripts; foreach($wp_scripts->registered as $h=>$o){ printf("%-32s deps=%s src=%s\n", $h, implode(",",$o->deps), basename($o->src)); }' | head -40
配合浏览器 Network 面板,把「已注册」和「实际加载」两件事分开看:注册了但没加载的不用管,加载了却没用的才是目标。Query Monitor 的「Scripts & Styles」面板会直接标出每个资源的 handle 和加载它的代码位置,定位最快。
步骤 2:移除 emoji 全套(绝大多数中文站用不上)
<?php
add_action( 'init', 'mmf_disable_emoji' );
function mmf_disable_emoji() {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
// 顺手去掉 s.w.org 的 DNS 预取
add_filter( 'wp_resource_hints', 'mmf_remove_emoji_dns_prefetch', 10, 2 );
}
function mmf_remove_emoji_dns_prefetch( $hints, $relation ) {
if ( 'dns-prefetch' === $relation ) {
$hints = array_filter( $hints, function( $hint ) {
return false === strpos( $hint, '//s.w.org' );
} );
}
return $hints;
}
注意:如果站点有留言功能且用户会发表情,别移除;用数据库字符集 utf8mb4 的站点已原生支持 emoji 存储,移除脚本只影响「把 :smile: 转成图片」这个转换功能,用户输入的表情仍会正常显示。
步骤 3:移除 wp-embed(oEmbed 前台脚本)
<?php
add_action( 'init', 'mmf_disable_embeds' );
function mmf_disable_embeds() {
remove_action( 'rest_api_init', 'wp_oembed_register_route' );
remove_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10 );
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
remove_action( 'wp_head', 'wp_oembed_add_host_js' );
add_filter( 'tiny_mce_plugins', function( $plugins ) {
return array_diff( $plugins, array( 'wpembed' ) );
} );
add_filter( 'rewrite_rules_array', function( $rules ) {
foreach ( $rules as $rule => $rewrite ) {
if ( false !== strpos( $rewrite, 'embed=true' ) ) {
unset( $rules[ $rule ] );
}
}
return $rules;
} );
}
add_action( 'wp_enqueue_scripts', 'mmf_dequeue_embed', 100 );
function mmf_dequeue_embed() {
wp_dequeue_script( 'wp-embed' );
}
移除后,粘贴 WordPress 文章链接到编辑器里不会再自动变成嵌入式卡片;粘贴 YouTube、B 站链接依旧可用(它们走的是 wp_oembed_get() 的内容解析,不依赖前台脚本)。
步骤 4:处理 jQuery 与 jquery-migrate
jquery-migrate 是为了兼容 jQuery 1.x 时代老代码而存在的兼容层,现代主题和插件基本不需要。但它风险最高——一旦某个插件依赖它,去掉后那块功能直接报错。稳妥做法是只在确认无依赖时移除:
<?php
add_action( 'wp_default_scripts', 'mmf_remove_jquery_migrate' );
function mmf_remove_jquery_migrate( $scripts ) {
if ( is_admin() ) {
return;
}
// 只有未登录用户才移除,管理员保留以便后台正常
if ( ! isset( $scripts->registered['jquery'] ) ) {
return;
}
$script = $scripts->registered['jquery'];
if ( ! empty( $script->deps ) ) {
$script->deps = array_diff( $script->deps, array( 'jquery-migrate' ) );
}
}
用 wp_default_scripts 过滤依赖数组,比直接 wp_deregister_script('jquery-migrate') 安全——后者会破坏任何显式声明了依赖的插件。
步骤 5:前台不加载 dashicons
dashicons 是后台菜单图标字体,前台只有在管理员登录(显示工具栏)时才需要:
<?php
add_action( 'wp_enqueue_scripts', 'mmf_dequeue_dashicons', 100 );
function mmf_dequeue_dashicons() {
if ( ! is_user_logged_in() ) {
wp_dequeue_style( 'dashicons' );
}
}
判断 is_user_logged_in() 很重要,否则管理员在前台会看到方块乱码。
步骤 6:精简区块样式与全局样式
<?php
add_action( 'wp_enqueue_scripts', 'mmf_trim_block_styles', 100 );
function mmf_trim_block_styles() {
// 经典主题用不到块库主题样式
if ( ! wp_is_block_theme() ) {
wp_dequeue_style( 'wp-block-library-theme' );
}
// 只用核心区块时,可以去掉 classic-theme-styles(块主题下无意义)
if ( wp_is_block_theme() ) {
wp_dequeue_style( 'classic-theme-styles' );
}
// 逐个移除未使用的区块样式(handle 形如 wp-block-<name>)
$unused = array(
'wp-block-archives',
'wp-block-calendar',
'wp-block-categories',
'wp-block-latest-comments',
'wp-block-rss',
'wp-block-search',
'wp-block-tag-cloud',
);
foreach ( $unused as $handle ) {
wp_dequeue_style( $handle );
}
}
wp_is_block_theme() 是判断块主题的正确函数,不要靠文件是否存在来猜。区块样式的 handle 命名规则是 wp-block- 加区块名,逐个确认后移除,不要一次性全清——按钮、图片、引用这几个几乎必用。
步骤 7:按页面条件加载,收益最大的一步
<?php
add_action( 'wp_enqueue_scripts', 'mmf_conditional_assets', 100 );
function mmf_conditional_assets() {
// 表单脚本只在联系页加载
if ( ! is_page( 'contact' ) ) {
wp_dequeue_script( 'contact-form-7' );
wp_dequeue_style( 'contact-form-7' );
wp_dequeue_script( 'google-recaptcha' );
}
// 轮播脚本只在首页加载
if ( ! is_front_page() ) {
wp_dequeue_script( 'mmf-swiper' );
wp_dequeue_style( 'mmf-swiper' );
}
// 评论回复脚本只在文章页且开启评论时加载
if ( ! is_singular( 'post' ) || ! comments_open() ) {
wp_dequeue_script( 'comment-reply' );
}
// 产品相关脚本只在产品相关页面加载
if ( ! is_singular( 'product' ) && ! is_tax( 'product_cat' ) ) {
wp_dequeue_style( 'mmf-gallery' );
wp_dequeue_script( 'mmf-gallery' );
}
}
步骤 8:给剩下的资源加 defer,并验证
<?php
add_filter( 'script_loader_tag', 'mmf_add_defer', 10, 2 );
function mmf_add_defer( $tag, $handle ) {
$skip = array( 'jquery-core' ); // 同步依赖,不能 defer
if ( in_array( $handle, $skip, true ) ) {
return $tag;
}
if ( false !== strpos( $tag, ' src=' ) && false === strpos( $tag, 'defer' ) ) {
$tag = str_replace( ' src=', ' defer src=', $tag );
}
return $tag;
}
验证方法:精简前后各跑一次 Network 面板,记录「请求数」「传输大小」「DOMContentLoaded」三项;再用 Lighthouse 移动端跑一轮确认 LCP 与 TBT 没有变差(度量方法见相关实践)。最重要的是逐个页面点一遍,确认表单、轮播、留言这些交互没坏。
常见踩坑
- 用
wp_deregister_script()而不是wp_dequeue_script():会破坏依赖关系树,任何声明依赖的插件入队时会静默失败,表现是「脚本没了但也没报错」。 - 钩子优先级没设成 100:默认 10 与主题入队同级,执行顺序取决于注册顺序,可能发生在对方入队之前,等于没移掉。
- 移除 emoji 后发现评论里的表情变成方框:数据库是
utf8mb4时表情本身能显示,方框通常意味着字符集是老的utf8,需要先修数据库而非加回脚本。 - 给 jQuery 加 defer:会让依赖 jQuery 的内联脚本提前执行,报
jQuery is not defined。必须排除jquery-core。 - 案例: 某站点一次性 dequeue 了所有
wp-block-*样式,前台按钮全部失去圆角和内边距,变成裸链接。原因是wp-block-button的样式也被清掉了,而主题的按钮正是用核心按钮区块渲染的。教训:区块样式要逐个确认,至少保留 button、image、quote、gallery、group 五项。 - 只在首页做条件加载,忘了搜索页和 404 页:条件标签要覆盖全站所有模板类型,漏掉的页面要么多加载、要么缺样式。
- 移除后没做回归测试:建议每次只移除一类,跑一次点击测试,确认无误再继续下一类。
总结
前台可安全精简的默认资源,按风险从低到高排序:emoji 全套(低)、wp-embed(低)、未使用的区块样式(中)、dashicons(中,需判断登录态)、jquery-migrate(高,需确认无依赖)。每移除一类都要跑一次功能回归。
收益最大的其实不是移除,而是「按页面条件加载」——把只在 1 个页面需要的脚本从全站 100 个页面上摘掉,效果远超零散移除几 KB。做完精简记得用 主题性能优化的度量方式 的方法做前后对比,确认请求数下降确实带来了 LCP 改善,而不是仅仅好看。
