Zsens Admin 插件前端性能三板斧:我把首屏资源从 1.8MB 砍到 180KB 的"按需掠夺"策略

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 47 浏览 0 回复

之前帖子聊过查询合并和缓存,这次换个战场——前端静态资源的"精准投放"。很多插件开发者有个惯性:功能一多,CSS/JS 全量打包往 wp_enqueue_xxx 一塞,让用户浏览器吃哑巴亏。我重构 Zsens Admin 时走了遍极端,记录一下。

第一板斧:路由级资源切片,拒绝"一进来就全加载"

WordPress 的 admin_enqueue_scripts 钩子不区分页面,很多人写成这样:

add_action( 'admin_enqueue_scripts', function() {
    wp_enqueue_style( 'zsens-admin-all', plugin_dir_url( __FILE__ ) . 'assets/css/admin.css' );
    wp_enqueue_script( 'zsens-admin-all', plugin_dir_url( __FILE__ ) . 'assets/js/admin.js', ['jquery'], '1.0', true );
});

问题:用户点个"仪表盘小工具"也要加载 800KB 的配置页代码。我的改法是用 get_current_screen() 做路由指纹:

add_action( 'admin_enqueue_scripts', function( $hook ) {
    $screen = get_current_screen();
    if ( ! $screen ) return;
    
    // 基础壳子:所有后台页都需要的(约 12KB)
    wp_enqueue_style( 'zsens-shell', ZSENS_URL . 'assets/css/shell.css', [], '2.4.0' );
    
    // 按 "page" 参数做精确匹配,不是按 $hook 字符串硬编码
    $page = $_GET['page'] ?? '';
    
    $asset_map = [
        'zsens-dashboard' => ['dash.css', 'dash.js', ['chartjs']],
        'zsens-settings'  => ['settings.css', 'settings.js', ['wp-color-picker']],
        'zsens-logs'      => ['logs.css', 'logs.js', []],
    ];
    
    if ( isset( $asset_map[$page] ) ) {
        [$css, $js, $deps] = $asset_map[$page];
        wp_enqueue_style( "zsens-{$page}", ZSENS_URL . "assets/css/{$css}", ['zsens-shell'], '2.4.0' );
        wp_enqueue_script( "zsens-{$page}", ZSENS_URL . "assets/js/{$js}", array_merge(['jquery'], $deps), '2.4.0', true );
    }
});

关键细节:我把 wp-color-picker 这类 WP 内置依赖从自己的 bundle 里拆出去,让浏览器走 WP 的缓存,而不是每个插件各自打包一份 jQuery UI。

第二板斧:JS 的"延迟觉醒"——不是 defer,是逻辑级懒加载

有些功能不是页面级,是交互级。比如日志页的"实时过滤"需要 60KB 的复杂逻辑,但 90% 用户只是浏览。我不把它放进 logs.js,而是造了个微型加载器:

// logs.js 主文件,仅含骨架
window.ZsensLazy = function( moduleName, callback ) {
    const map = {
        'live-filter': ZSENS_URL + 'assets/js/modules/live-filter.js?v=' + ZSENS_VER,
        'export-csv':  ZSENS_URL + 'assets/js/modules/export-csv.js?v=' + ZSENS_VER,
    };
    
    if ( ! map[moduleName] ) return;
    
    // 检查是否已缓存
    if ( window.__zsensLoaded && window.__zsensLoaded[moduleName] ) {
        callback( window.__zsensLoaded[moduleName] );
        return;
    }
    
    const script = document.createElement('script');
    script.src = map[moduleName];
    script.onload = function() {
        window.__zsensLoaded = window.__zsensLoaded || {};
        window.__zsensLoaded[moduleName] = window.ZsensModules[moduleName];
        callback( window.ZsensModules[moduleName] );
    };
    document.head.appendChild( script );
};

// 绑定事件时触发加载
document.getElementById('log-filter-input').addEventListener('focus', function() {
    ZsensLazy('live-filter', function(mod) {
        mod.init( this );
    }.bind(this));
}, { once: true });

这里用了 { once: true },避免重复绑定。模块文件用 window.ZsensModules 做命名空间注册,防止全局污染。实测首屏 JS 从 340KB 降到 45KB,过滤功能首次点击延迟 80ms(本地缓存后归零)。

第三板斧:CSS 的"死亡变量"清理与运行时主题化

插件 CSS 最容易膨胀的是颜色变量。暗色模式、品牌色覆盖、用户自定义——很多人写两套完整 CSS,或者塞满 !important。我的解法是用 CSS 自定义属性 + 运行时注入:

// PHP 端:根据用户偏好生成最小化的内联样式
add_action( 'admin_head', function() {
    $theme = get_user_meta( get_current_user_id(), 'zsens_theme', true ) ?: 'light';
    
    $palettes = [
        'light' => ['--z-bg'=>'#f0f0f1','--z-text'=>'#1d2327','--z-accent'=>'#2271b1'],
        'dark'  => ['--z-bg'=>'#1e1e1e','--z-text'=>'#e0e0e0','--z-accent'=>'#72aee6'],
    ];
    
    $css = ':root{';
    foreach ( $palettes[$theme] ?? $palettes['light'] as $k => $v ) {
        $css .= esc_attr($k) . ':' . esc_attr($v) . ';';
    }
    $css .= '}';
    
    echo '<style>' . $css . '</style>';
});

CSS 文件里只保留 var(--z-bg) 这类引用,不写死颜色。这样一套 CSS 文件服务所有主题,浏览器缓存命中率最大化。暗色切换也不需要重新请求资源,改个 class 或变量就行。

一个踩坑:版本号缓存与 CDN 的博弈

之前用 filemtime() 做版本号,本地开发很爽,但上了对象存储后 filemtime 返回 false,导致版本号变成当前时间戳,缓存彻底失效。现在的兜底:

function zsens_asset_ver( $file ) {
    if ( defined( 'ZSENS_DEV' ) && ZSENS_DEV ) {
        return filemtime( ZSENS_PATH . $file ) ?: time();
    }
    // 生产环境:手动维护的常量,发版时批量递增
    return ZSENS_VER;
}

开发/生产双轨,避免"本地没问题,上线全崩"的经典剧本。

这三板斧下来,Network 面板清爽很多。不是不能用构建工具搞 tree-shaking,但插件场景特殊——用户环境不可控、缓存策略混乱、多插件资源冲突,有时候"手动的精准"比"自动的聪明"更可靠。你们怎么处理的?有没有被某个缓存策略坑过的经历?

评论0
回复 · 0
还没有回复
微信客服 微信客服