mcpField
W
wp-performance-review

wp-performance-review

github.com/elvismdev测试与质量237186 次安装48 周前更新
代码仓库

关于 wp-performance-review

用于审查 WordPress 主题、插件和自定义代码性能的技能,先检查关键风险,再按严重程度和行号整理发现。

主要功能

  1. 01优先扫描关键性能问题
  2. 02检查 PHP、JavaScript 和数据库代码
  3. 03覆盖 AJAX、资源、缓存与 Cron
  4. 04按严重程度和行号报告

适用场景

  1. 01审查 WordPress 主题或插件变更
  2. 02排查加载缓慢、超时或 500 错误
  3. 03高流量活动前审计代码

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/elvismdev/claude-wordpress-skills --skill wp-performance-review

安装后在 Claude Code 中输入/wp-performance-review确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
anti-patterns.md
caching-guide.md
measurement-guide.md
wp-query-guide.md

详细介绍

WordPress 性能审查技能

概述

针对 WordPress 主题、插件和自定义代码进行系统化性能代码审查。核心原则: 先扫描关键问题(OOM、无界查询、绕过缓存),再检查警告,最后关注优化项。报告问题时注明行号和严重级别。

何时使用

适用场景:

  • 审查 WordPress 主题或插件的 PR/代码
  • 用户反馈页面加载缓慢、超时或出现 500 错误
  • 高流量活动(发布、促销、流量爆发)前进行审计
  • 优化 WP_Query 或数据库操作
  • 排查内存耗尽或数据库锁

不适用场景:

  • 仅进行安全审计(如有可用的 wp-security-review,请使用它)
  • Gutenberg 区块开发模式(如有可用的 wp-gutenberg-blocks,请使用它)
  • 与 WordPress 无关的一般 PHP 代码审查

代码审查工作流

  1. 识别文件类型,并应用下方相关检查项
  2. 优先扫描关键模式(OOM、无界查询、绕过缓存)
  3. 检查警告(效率低下但不会造成灾难性后果)
  4. 记录优化项(锦上添花的改进)
  5. 使用下方输出格式注明行号并报告

特定文件类型的检查项

插件/主题 PHP 文件(functions.php、plugin.php、*.php)

扫描以下内容:

  • query_posts() → 严重:切勿使用——会破坏主查询
  • posts_per_page.*-1 或 numberposts.*-1 → 严重:无界查询
  • session_start() → 严重:绕过页面缓存
  • add_action.*init.* 或 add_action.*wp_loaded → 检查是否每次请求都会运行开销较大的代码
  • 非管理后台环境中的 update_option 或 add_option → 警告:页面加载时写入数据库
  • 未使用缓存的 wp_remote_get 或 wp_remote_post → 警告:阻塞式 HTTP 请求
WP_Query / 数据库代码

扫描以下内容:

  • 缺少 posts_per_page 参数 → 警告:默认使用博客设置
  • 使用 'value' 比较的 'meta_query' → 警告:扫描未建立索引的列
  • 包含大型数组的 post__not_in → 警告:排除操作缓慢
  • LIKE '%term%'(前置通配符)→ 警告:全表扫描
  • 不进行分页时缺少 no_found_rows => true → 信息:不必要的计数
AJAX 处理程序(wp_ajax_*、REST 端点)

扫描以下内容:

  • 使用 admin-ajax.php → 信息:可考虑改用 REST API
  • 使用 POST 方法执行读取操作 → 警告:绕过缓存
  • setInterval 或轮询模式 → 严重:存在自我 DDoS 风险
  • 缺少 nonce 验证 → 安全问题(与性能无关,但需标记)
模板文件(主题中的 *.php)

扫描以下内容:

  • 在循环中使用 get_template_part → 警告:可考虑缓存输出
  • 在循环中执行数据库查询(N+1)→ 严重:查询数量倍增
  • 在模板中使用 wp_remote_get → 警告:阻塞渲染
JavaScript 文件

扫描以下内容:

  • 使用 $.post( 执行读取操作 → 警告:为支持缓存,请改用 GET
  • setInterval.*fetch\|ajax → 严重:轮询模式
  • import _ from 'lodash' → 警告:导入整个库会使打包文件膨胀
  • 页面加载时发起 AJAX 请求的内联 <script> → 检查其必要性
区块编辑器 / Gutenberg 文件(block.json、blocks/ 中的 *.js)

扫描以下内容:

  • 多次调用 registerBlockStyle() → 警告:每次调用都会创建预览 iframe
  • 在渲染回调中使用 wp_kses_post($content) → 警告:会破坏 InnerBlocks
  • 没有 render_callback 的静态区块 → 信息:可考虑改为动态区块,以便于维护
资源注册(functions.php、*.php)

扫描以下内容:

  • wp_enqueue_script 未指定版本 → 信息:可能导致缓存失效问题
  • wp_enqueue_script 未指定 defer/async 策略 → 信息:阻塞渲染
  • 缺少 THEME_VERSION 常量 → 信息:版本管理
  • wp_enqueue_script 未进行条件检查 → 警告:仅特定页面需要的资源会在全站加载
临时数据与选项

扫描以下内容:

  • 使用动态键(例如 user_{$id})的 set_transient → 警告:没有对象缓存时会导致数据表膨胀
  • 对频繁变化的数据使用 set_transient → 警告:违背缓存的目的
  • 在共享主机上将大型数据存入临时数据 → 警告:没有对象缓存时会导致数据库膨胀
WP-Cron

扫描以下问题:

  • 缺少 DISABLE_WP_CRON 常量 → INFO:Cron 在页面请求时运行
  • 运行时间过长的 Cron 回调(遍历所有用户/文章)→ CRITICAL:阻塞 Cron 队列
  • wp_schedule_event 未检查事件是否已安排 → WARNING:重复安排

快速检测的搜索模式

# Critical issues - scan these first
grep -rn "posts_per_page.*-1\|numberposts.*-1" .
grep -rn "query_posts\s*(" .
grep -rn "session_start\s*(" .
grep -rn "setInterval.*fetch\|setInterval.*ajax\|setInterval.*\\\$\." .

# Database writes on frontend
grep -rn "update_option\|add_option" . | grep -v "admin\|activate\|install"

# Uncached expensive functions
grep -rn "url_to_postid\|attachment_url_to_postid\|count_user_posts" .

# External HTTP without caching
grep -rn "wp_remote_get\|wp_remote_post\|file_get_contents.*http" .

# Cache bypass risks
grep -rn "setcookie\|session_start" .

# PHP code anti-patterns
grep -rn "in_array\s*(" . | grep -v "true\s*)" # Missing strict comparison
grep -rn "<<<" .  # Heredoc/nowdoc syntax
grep -rn "cache_results.*false" .

# JavaScript bundle issues
grep -rn "import.*from.*lodash['\"]" .  # Full lodash import
grep -rn "registerBlockStyle" .  # Many block styles = performance issue

# Asset loading issues
grep -rn "wp_enqueue_script\|wp_enqueue_style" . | grep -v "is_page\|is_singular\|is_admin"

# Transient misuse
grep -rn "set_transient.*\\\$" .  # Dynamic transient keys
grep -rn "set_transient" . | grep -v "get_transient"  # Set without checking first

# WP-Cron issues
grep -rn "wp_schedule_event" . | grep -v "wp_next_scheduled"  # Missing schedule check

平台背景

不同的托管环境需要采用不同的方法:

托管式 WordPress 主机(WP Engine、Pantheon、Pressable、WordPress VIP 等):

  • 通常开箱即提供对象缓存
  • 可能提供平台专属的辅助函数(例如 VIP 上的 wpcom_vip_*)
  • 查看主机文档,了解推荐模式

自托管 / 标准托管:

  • 为开销较大的函数手动实现对象缓存包装器
  • 考虑使用 Redis 或 Memcached 插件实现持久化对象缓存
  • 需要承担更多缓存层配置工作

共享托管:

  • 对无界查询和外部 HTTP 请求格外谨慎
  • 资源有限,性能问题会更快显现
  • 可能完全不提供持久化对象缓存

快速参考:严重反模式

数据库查询
// ❌ CRITICAL: Unbounded query.
'posts_per_page' => -1

// ✅ GOOD: Set reasonable limit, paginate if needed.
'posts_per_page' => 100,
'no_found_rows'  => true, // Skip count if not paginating.

// ❌ CRITICAL: Never use query_posts().
query_posts( 'cat=1' ); // Breaks pagination, conditionals.

// ✅ GOOD: Use WP_Query or pre_get_posts filter.
$query = new WP_Query( array( 'cat' => 1 ) );
// Or modify main query:
add_action( 'pre_get_posts', function( $query ) {
    if ( $query->is_main_query() && ! is_admin() ) {
        $query->set( 'cat', 1 );
    }
} );

// ❌ CRITICAL: Missing WHERE clause (falsy ID becomes 0).
$query = new WP_Query( array( 'p' => intval( $maybe_false_id ) ) );

// ✅ GOOD: Validate ID before querying.
if ( ! empty( $maybe_false_id ) ) {
    $query = new WP_Query( array( 'p' => intval( $maybe_false_id ) ) );
}

// ❌ WARNING: LIKE with leading wildcard (full table scan).
$wpdb->get_results( "SELECT * FROM wp_posts WHERE post_title LIKE '%term%'" );

// ✅ GOOD: Use trailing wildcard only, or use WP_Query 's' parameter.
$wpdb->get_results( $wpdb->prepare(
    "SELECT * FROM wp_posts WHERE post_title LIKE %s",
    $wpdb->esc_like( $term ) . '%'
) );

// ❌ WARNING: NOT IN queries (filter in PHP instead).
'post__not_in' => $excluded_ids

// ✅ GOOD: Fetch all, filter in PHP (faster for large exclusion lists).
$posts = get_posts( array( 'posts_per_page' => 100 ) );
$posts = array_filter( $posts, function( $post ) use ( $excluded_ids ) {
    return ! in_array( $post->ID, $excluded_ids, true );
} );
钩子与操作
// ❌ WARNING: Code runs on every request via init.
add_action( 'init', 'expensive_function' );

// ✅ GOOD: Check context before running expensive code.
add_action( 'init', function() {
    if ( is_admin() || wp_doing_cron() ) {
        return;
    }
    // Frontend-only code here.
} );

// ❌ CRITICAL: Database writes on every page load.
add_action( 'wp_head', 'prefix_bad_tracking' );
function prefix_bad_tracking() {
    update_option( 'last_visit', time() );
}

// ✅ GOOD: Use object cache buffer, flush via cron.
add_action( 'shutdown', function() {
    wp_cache_incr( 'page_views_buffer', 1, 'counters' );
} );

// ❌ WARNING: Using admin-ajax.php instead of REST API.
// Prefer: register_rest_route() - leaner bootstrap.
PHP 代码
// ❌ WARNING: O(n) lookup - use isset() with associative array.
in_array( $value, $array ); // Also missing strict = true.

// ✅ GOOD: O(1) lookup with isset().
$allowed = array( 'foo' => true, 'bar' => true );
if ( isset( $allowed[ $value ] ) ) {
    // Process.
}

// ❌ WARNING: Heredoc prevents late escaping.
$html = <<<HTML
<div>$unescaped_content</div>
HTML;

// ✅ GOOD: Escape at output.
printf( '<div>%s</div>', esc_html( $content ) );
缓存问题
// ❌ WARNING: Uncached expensive function calls.
url_to_postid( $url );
attachment_url_to_postid( $attachment_url );
count_user_posts( $user_id );
wp_oembed_get( $url );

// ✅ GOOD: Wrap with object cache (works on any host).
function prefix_cached_url_to_postid( $url ) {
    $cache_key = 'url_to_postid_' . md5( $url );
    $post_id   = wp_cache_get( $cache_key, 'url_lookups' );

    if ( false === $post_id ) {
        $post_id = url_to_postid( $url );
        wp_cache_set( $cache_key, $post_id, 'url_lookups', HOUR_IN_SECONDS );
    }

    return $post_id;
}

// ✅ GOOD: On WordPress VIP, use platform helpers instead.
// wpcom_vip_url_to_postid(), wpcom_vip_attachment_url_to_postid(), etc.

// ❌ WARNING: Large autoloaded options.
add_option( 'prefix_large_data', $data ); // Add: , '', 'no' for autoload.

// ❌ INFO: Missing wp_cache_get_multiple for batch lookups.
foreach ( $ids as $id ) {
    wp_cache_get( "key_{$id}" );
}
AJAX 与外部请求
// ❌ WARNING: AJAX POST request (bypasses cache).
$.post( ajaxurl, data ); // Prefer: $.get() for read operations.

// ❌ CRITICAL: Polling pattern (self-DDoS).
setInterval( () => fetch( '/wp-json/...' ), 5000 );
// ❌ WARNING: Synchronous external HTTP in page load.
wp_remote_get( $url ); // Cache result or move to cron.

// ✅ GOOD: Set timeout and handle errors.
$response = wp_remote_get( $url, array( 'timeout' => 2 ) );
if ( is_wp_error( $response ) ) {
    return get_fallback_data();
}
WP Cron
// ❌ WARNING: WP Cron runs on page requests.
// Add to wp-config.php:
define( 'DISABLE_WP_CRON', true );
// Run via server cron: * * * * * wp cron event run --due-now

// ❌ CRITICAL: Long-running cron blocks entire queue.
add_action( 'my_daily_sync', function() {
    foreach ( get_users() as $user ) { // 50k users = hours.
        sync_user_data( $user );
    }
} );

// ✅ GOOD: Batch processing with rescheduling.
add_action( 'my_batch_sync', function() {
    $offset = (int) get_option( 'sync_offset', 0 );
    $users  = get_users( array( 'number' => 100, 'offset' => $offset ) );

    if ( empty( $users ) ) {
        delete_option( 'sync_offset' );
        return;
    }

    foreach ( $users as $user ) {
        sync_user_data( $user );
    }

    update_option( 'sync_offset', $offset + 100 );
    wp_schedule_single_event( time() + 60, 'my_batch_sync' );
} );

// ❌ WARNING: Scheduling without checking if already scheduled.
wp_schedule_event( time(), 'hourly', 'my_task' ); // Creates duplicates!

// ✅ GOOD: Check before scheduling.
if ( ! wp_next_scheduled( 'my_task' ) ) {
    wp_schedule_event( time(), 'hourly', 'my_task' );
}
绕过缓存问题
// ❌ CRITICAL: Plugin starts PHP session on frontend (bypasses ALL page cache).
session_start(); // Check plugins for this - entire site becomes uncacheable!

// ❌ WARNING: Unique query params create cache misses.
// https://example.com/?utm_source=fb&utm_campaign=123&fbclid=abc
// Each unique URL = separate cache entry = cache miss.
// Solution: Strip marketing params at CDN/edge level.

// ❌ WARNING: Setting cookies on public pages.
setcookie( 'visitor_id', $id ); // Prevents caching for that user.
Transient 误用
// ❌ WARNING: Dynamic transient keys create table bloat (without object cache).
set_transient( "user_{$user_id}_cart", $data, HOUR_IN_SECONDS );
// 10,000 users = 10,000 rows in wp_options!

// ✅ GOOD: Use object cache for user-specific data.
wp_cache_set( "cart_{$user_id}", $data, 'user_carts', HOUR_IN_SECONDS );

// ❌ WARNING: Transients for frequently-changing data defeats purpose.
set_transient( 'visitor_count', $count, 60 ); // Changes every minute.

// ✅ GOOD: Use object cache for volatile data.
wp_cache_set( 'visitor_count', $count, 'stats' );

// ❌ WARNING: Large data in transients on shared hosting.
set_transient( 'api_response', $megabytes_of_json, DAY_IN_SECONDS );
// Without object cache = serialized blob in wp_options.

// ✅ GOOD: Check hosting before using transients for large data.
if ( wp_using_ext_object_cache() ) {
    set_transient( 'api_response', $data, DAY_IN_SECONDS );
} else {
    // Store in files or skip caching on shared hosting.
}
资源加载
// ❌ WARNING: Assets load globally when only needed on specific pages.
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_script( 'contact-form-js', ... );
    wp_enqueue_style( 'contact-form-css', ... );
} );

// ✅ GOOD: Conditional enqueue based on page/template.
add_action( 'wp_enqueue_scripts', function() {
    if ( is_page( 'contact' ) || is_page_template( 'contact-template.php' ) ) {
        wp_enqueue_script( 'contact-form-js', ... );
        wp_enqueue_style( 'contact-form-css', ... );
    }
} );

// ✅ GOOD: Only load WooCommerce assets on shop pages.
add_action( 'wp_enqueue_scripts', function() {
    if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
        wp_dequeue_style( 'woocommerce-general' );
        wp_dequeue_script( 'wc-cart-fragments' );
    }
} );
外部 API 请求
// ❌ WARNING: No timeout set (default is 5 seconds).
wp_remote_get( $url ); // Set timeout: array( 'timeout' => 2 ).

// ❌ WARNING: Missing error handling for API failures.
$response = wp_remote_get( $url );
echo $response['body']; // Check is_wp_error() first!
站点地图与重定向
// ❌ WARNING: Generating sitemaps for deep archives (crawlers hammer these).
// Solution: Exclude old post types, cache generated sitemaps.

// ❌ CRITICAL: Redirect loops consuming CPU.
// Debug with: x-redirect-by header, wp_debug_backtrace_summary().
文章 Meta 查询
// ❌ WARNING: Searching meta_value without index.
'meta_query' => array(
    array(
        'key'   => 'color',
        'value' => 'red',
    ),
)
// Better: Use taxonomy or encode value in meta_key name.

// ❌ WARNING: Binary meta values requiring value scan.
'meta_key'   => 'featured',
'meta_value' => 'true',
// Better: Presence of 'is_featured' key = true, absence = false.

**如需深入了解任何模式:**加载 references/anti-patterns.md

严重性定义

严重性说明
Critical会在规模增大时导致故障(OOM、500 错误、数据库锁)
Warning在高负载下会降低性能
Info存在优化机会

输出格式

按以下结构整理发现:

## Performance Review: [filename/component]

### Critical Issues
- **Line X**: [Issue] - [Explanation] - [Fix]

### Warnings  
- **Line X**: [Issue] - [Explanation] - [Fix]

### Recommendations
- [Optimization opportunities]

### Summary
- Total issues: X Critical, Y Warnings, Z Info
- Estimated impact: [High/Medium/Low]

常见错误

进行性能审查时,请避免以下错误:

错误错误原因修正方法
标记仅用于管理后台代码中的 posts_per_page => -1管理后台查询不受公开访问规模的影响检查上下文——管理后台、CLI、Cron 的风险较低
漏掉插件中隐藏的 session_start()绕过缓存会影响整个站点始终在所有代码中搜索 session_start
忽略非分页查询中的 no_found_rows虽然优化幅度不大,但影响会累积标记为 INFO,而非 WARNING
建议在共享托管环境中使用对象缓存许多共享主机不提供持久化缓存先检查托管环境
只审查 PHP,漏掉 JS 轮询JS setInterval + fetch = 自我 DDoS检查 .js 文件中的轮询模式

深入参考资料

根据任务加载以下参考资料:

任务要加载的参考资料
检查 PHP 代码中的问题references/anti-patterns.md
优化 WP_Query 调用references/wp-query-guide.md
实施缓存references/caching-guide.md
为高流量活动做准备references/measurement-guide.md

注意:对于标准代码审查,anti-patterns.md 包含所需的所有模式。其他参考资料则在专门优化查询、实施缓存策略或为流量活动做准备时提供更深入的背景信息。

评论

支持 Markdown