搜索全站
主题 19 分钟阅读

WordPress主题资源精简清单:可移除的默认加载与代码

前台加载了 40 个请求,一半是 WordPress 默认带上的。本文列出可安全移除的默认资源清单:emoji、wp-embed、jquery-migrate、dashicons、未用区块样式,并给出按页面条件加载的代码与验证方法。

U
UFO_PF 2019-10-16 · 阅读 19 分钟
Wo 主题
阅读提示
本文约 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 没有变差(度量方法见相关实践)。最重要的是逐个页面点一遍,确认表单、轮播、留言这些交互没坏。

常见踩坑

  1. 用 wp_deregister_script() 而不是 wp_dequeue_script():会破坏依赖关系树,任何声明依赖的插件入队时会静默失败,表现是「脚本没了但也没报错」。
  2. 钩子优先级没设成 100:默认 10 与主题入队同级,执行顺序取决于注册顺序,可能发生在对方入队之前,等于没移掉。
  3. 移除 emoji 后发现评论里的表情变成方框:数据库是 utf8mb4 时表情本身能显示,方框通常意味着字符集是老的 utf8,需要先修数据库而非加回脚本。
  4. 给 jQuery 加 defer:会让依赖 jQuery 的内联脚本提前执行,报 jQuery is not defined。必须排除 jquery-core。
  5. 案例: 某站点一次性 dequeue 了所有 wp-block-* 样式,前台按钮全部失去圆角和内边距,变成裸链接。原因是 wp-block-button 的样式也被清掉了,而主题的按钮正是用核心按钮区块渲染的。教训:区块样式要逐个确认,至少保留 button、image、quote、gallery、group 五项。
  6. 只在首页做条件加载,忘了搜索页和 404 页:条件标签要覆盖全站所有模板类型,漏掉的页面要么多加载、要么缺样式。
  7. 移除后没做回归测试:建议每次只移除一类,跑一次点击测试,确认无误再继续下一类。

总结

前台可安全精简的默认资源,按风险从低到高排序:emoji 全套(低)、wp-embed(低)、未使用的区块样式(中)、dashicons(中,需判断登录态)、jquery-migrate(高,需确认无依赖)。每移除一类都要跑一次功能回归。

收益最大的其实不是移除,而是「按页面条件加载」——把只在 1 个页面需要的脚本从全站 100 个页面上摘掉,效果远超零散移除几 KB。做完精简记得用 主题性能优化的度量方式 的方法做前后对比,确认请求数下降确实带来了 LCP 改善,而不是仅仅好看。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。