Zsens Admin 插件前端性能三板斧:我把首屏资源从 1.8MB 砍到 180KB 的"按需掠夺"策略
之前帖子聊过查询合并和缓存,这次换个战场——前端静态资源的"精准投放"。很多插件开发者有个惯性:功能一多,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,但插件场景特殊——用户环境不可控、缓存策略混乱、多插件资源冲突,有时候"手动的精准"比"自动的聪明"更可靠。你们怎么处理的?有没有被某个缓存策略坑过的经历?

