wp_enqueue_scripts: правильное подключение CSS и JS в WordPress

Одна из самых частых ошибок начинающих WordPress-разработчиков — подключение CSS и JS файлов напрямую через хардкод в header.php. Это ломает совместимость с плагинами, дублирует ресурсы и усложняет отладку. В WordPress есть правильный механизм — система enqueue, которая решает все эти проблемы. Разберём, как ей пользоваться на практике.

Основы: wp_enqueue_style и wp_enqueue_script

Все скрипты и стили в WordPress подключаются через хук wp_enqueue_scripts. Этот хук срабатывает на фронтенде сайта. Для админки используется admin_enqueue_scripts, для страницы логина — login_enqueue_scripts.

add_action( 'wp_enqueue_scripts', 'mytheme_enqueue_assets' );

function mytheme_enqueue_assets() {
    // Подключение CSS
    wp_enqueue_style(
        'mytheme-main',                              // handle (уникальный идентификатор)
        get_stylesheet_directory_uri() . '/css/main.css', // URL файла
        array(),                                     // зависимости
        '1.2.0',                                     // версия
        'all'                                        // media
    );

    // Подключение JS
    wp_enqueue_script(
        'mytheme-app',                               // handle
        get_stylesheet_directory_uri() . '/js/app.js', // URL файла
        array( 'jquery' ),                           // зависимости
        '1.2.0',                                     // версия
        true                                         // загрузка в footer
    );
}

Параметр handle — это уникальное имя ресурса. Если два плагина подключат стиль с одним handle, WordPress загрузит его только один раз. Поэтому используйте префикс темы или плагина.

Последний параметр в wp_enqueue_script — булево значение $in_footer. Если true, скрипт загрузится перед закрывающим </body>, что улучшает скорость рендеринга страницы. Всегда ставьте true, если скрипт не нужен в <head>.

Зависимости и порядок загрузки

Третий параметр — массив зависимостей. WordPress автоматически выстроит правильный порядок загрузки. Если ваш скрипт использует jQuery и другую библиотеку, укажите их:

add_action( 'wp_enqueue_scripts', 'mytheme_slider_assets' );

function mytheme_slider_assets() {
    // Сначала регистрируем библиотеку (не подключаем)
    wp_register_script(
        'swiper',
        get_stylesheet_directory_uri() . '/js/swiper-bundle.min.js',
        array(),
        '11.0.0',
        true
    );

    wp_register_style(
        'swiper-css',
        get_stylesheet_directory_uri() . '/css/swiper-bundle.min.css',
        array(),
        '11.0.0'
    );

    // Подключаем свой скрипт с зависимостями
    wp_enqueue_script(
        'mytheme-slider',
        get_stylesheet_directory_uri() . '/js/slider-init.js',
        array( 'swiper' ),  // Swiper загрузится первым
        '1.0.0',
        true
    );

    // Стиль слайдера зависит от стилей Swiper
    wp_enqueue_style(
        'mytheme-slider-css',
        get_stylesheet_directory_uri() . '/css/slider.css',
        array( 'swiper-css' ),
        '1.0.0'
    );
}

Разница между wp_register_script и wp_enqueue_script: register только регистрирует ресурс в системе, enqueue — регистрирует и сразу ставит в очередь на подключение. Регистрация полезна, когда библиотека может понадобиться другим скриптам как зависимость.

Передача данных из PHP в JavaScript: wp_localize_script

Часто нужно передать в JS переменные из PHP — URL для AJAX-запросов, настройки, переводы. Для этого есть wp_localize_script и более новая wp_add_inline_script:

add_action( 'wp_enqueue_scripts', 'mytheme_localize' );

function mytheme_localize() {
    wp_enqueue_script(
        'mytheme-app',
        get_stylesheet_directory_uri() . '/js/app.js',
        array(),
        '1.0.0',
        true
    );

    // wp_localize_script — создаёт глобальный JS-объект
    wp_localize_script( 'mytheme-app', 'MyThemeData', array(
        'ajaxUrl'  => admin_url( 'admin-ajax.php' ),
        'nonce'    => wp_create_nonce( 'mytheme_nonce' ),
        'siteUrl'  => home_url( '/' ),
        'i18n'     => array(
            'loading' => __( 'Загрузка...', 'mytheme' ),
            'error'   => __( 'Произошла ошибка', 'mytheme' ),
        ),
    ) );

    // Альтернатива — wp_add_inline_script (с WP 4.5)
    wp_add_inline_script( 'mytheme-app',
        'const SITE_CONFIG = ' . wp_json_encode( array(
            'restUrl' => rest_url( 'mytheme/v1/' ),
            'userId'  => get_current_user_id(),
        ) ) . ';',
        'before'  // вставить ДО основного скрипта
    );
}

В JavaScript эти данные доступны так:

// После wp_localize_script
console.log( MyThemeData.ajaxUrl );  // /wp-admin/admin-ajax.php
console.log( MyThemeData.i18n.loading ); // Загрузка...

// После wp_add_inline_script
console.log( SITE_CONFIG.restUrl );  // /wp-json/mytheme/v1/

wp_localize_script всегда преобразует значения в строки, что может быть проблемой с числами и булевыми значениями. wp_add_inline_script с wp_json_encode сохраняет типы данных.

Условная загрузка: подключаем ресурсы только там, где нужно

Загружать все скрипты на всех страницах — плохая практика. Слайдер нужен только на главной, скрипт комментариев — только на одиночных записях. Условная загрузка экономит ресурсы и ускоряет сайт:

add_action( 'wp_enqueue_scripts', 'mytheme_conditional_assets' );

function mytheme_conditional_assets() {
    // Слайдер только на главной
    if ( is_front_page() ) {
        wp_enqueue_script( 'mytheme-slider', get_stylesheet_directory_uri() . '/js/slider.js', array(), '1.0', true );
        wp_enqueue_style( 'mytheme-slider-css', get_stylesheet_directory_uri() . '/css/slider.css', array(), '1.0' );
    }

    // Галерея только в записях с шорткодом 
    if ( is_singular() ) {
        global $post;
        if ( has_shortcode( $post->post_content, 'gallery' ) ) {
            wp_enqueue_script( 'mytheme-gallery', get_stylesheet_directory_uri() . '/js/gallery.js', array(), '1.0', true );
        }
    }

    // Скрипт для WooCommerce-страниц
    if ( function_exists( 'is_woocommerce' ) && is_woocommerce() ) {
        wp_enqueue_script( 'mytheme-shop', get_stylesheet_directory_uri() . '/js/shop.js', array( 'jquery' ), '1.0', true );
    }

    // Стили для страницы контактов (по slug)
    if ( is_page( 'contacts' ) ) {
        wp_enqueue_style( 'mytheme-contacts', get_stylesheet_directory_uri() . '/css/contacts.css', array(), '1.0' );
    }
}

Для ещё более гибкого управления ассетами на уровне каждой страницы используют плагины вроде Gonzales (Asset CleanUp) — он позволяет отключать ненужные CSS/JS прямо из редактора записи.

Отключение стандартных и плагинных ресурсов

Иногда нужно убрать лишние стили или скрипты, добавленные ядром или плагинами. Для этого используются функции wp_dequeue_style и wp_dequeue_script:

add_action( 'wp_enqueue_scripts', 'mytheme_dequeue_unwanted', 100 );

function mytheme_dequeue_unwanted() {
    // Убираем стили блоков Gutenberg (если не используете)
    wp_dequeue_style( 'wp-block-library' );
    wp_dequeue_style( 'wp-block-library-theme' );

    // Убираем emoji-скрипты WordPress
    remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
    remove_action( 'wp_print_styles', 'print_emoji_styles' );

    // Убираем jQuery Migrate (если не нужен)
    if ( ! is_admin() ) {
        wp_deregister_script( 'jquery' );
        wp_register_script( 'jquery', includes_url( '/js/jquery/jquery.min.js' ), array(), null, true );
    }

    // Убираем стили Contact Form 7 со страниц без формы
    if ( ! is_page( array( 'contacts', 'feedback' ) ) ) {
        wp_dequeue_style( 'contact-form-7' );
        wp_dequeue_script( 'contact-form-7' );
    }
}

// Убираем глобальные стили (Global Styles inline CSS)
remove_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' );
remove_action( 'wp_body_open', 'wp_global_styles_render_svg_filters' );

Приоритет 100 важен — он гарантирует, что наша функция выполнится после того, как плагины подключат свои ресурсы. Стандартный приоритет 10 может не сработать.

CDN fallback: загрузка с CDN с локальным запасным вариантом

Популярные библиотеки выгодно загружать с CDN — они могут быть закешированы браузером. Но CDN может быть недоступен, поэтому нужен fallback:

add_action( 'wp_enqueue_scripts', 'mytheme_cdn_with_fallback' );

function mytheme_cdn_with_fallback() {
    // jQuery с CDN
    wp_deregister_script( 'jquery' );
    wp_register_script(
        'jquery',
        'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js',
        array(),
        '3.7.1',
        true
    );
    wp_enqueue_script( 'jquery' );

    // Fallback если CDN не загрузился
    wp_add_inline_script( 'jquery',
        'window.jQuery || document.write(\'<script src="' .
        esc_url( includes_url( '/js/jquery/jquery.min.js' ) ) .
        '"><\/script>\')'
    );
}

Атрибуты async и defer

Начиная с WordPress 6.3 появилась встроенная поддержка стратегий загрузки скриптов через параметр $args:

// WordPress 6.3+: нативная поддержка strategy
wp_register_script(
    'mytheme-analytics',
    get_stylesheet_directory_uri() . '/js/analytics.js',
    array(),
    '1.0.0',
    array(
        'in_footer' => true,
        'strategy'  => 'defer',  // или 'async'
    )
);
wp_enqueue_script( 'mytheme-analytics' );

// Для старых версий WordPress — фильтр
add_filter( 'script_loader_tag', 'mytheme_add_async_defer', 10, 3 );

function mytheme_add_async_defer( $tag, $handle, $src ) {
    $async_scripts = array( 'mytheme-analytics', 'mytheme-tracking' );
    $defer_scripts = array( 'mytheme-comments', 'mytheme-lazy' );

    if ( in_array( $handle, $async_scripts, true ) ) {
        return str_replace( ' src=', ' async src=', $tag );
    }

    if ( in_array( $handle, $defer_scripts, true ) ) {
        return str_replace( ' src=', ' defer src=', $tag );
    }

    return $tag;
}

Версионирование и сброс кеша

Параметр версии добавляется к URL файла как ?ver=1.2.0. При обновлении файла меняйте версию, чтобы браузер загрузил новую копию. Можно автоматизировать это через filemtime:

wp_enqueue_style(
    'mytheme-main',
    get_stylesheet_directory_uri() . '/css/main.css',
    array(),
    filemtime( get_stylesheet_directory() . '/css/main.css' )  // время изменения файла
);

Это гарантирует, что при каждом изменении файла версия обновится автоматически, и браузер загрузит свежую версию.

Частые ошибки

  • Хардкод в header.php — вместо <link rel="stylesheet"> и <script> всегда используйте enqueue. Иначе плагины не смогут управлять вашими ресурсами.
  • Забытый wp_head / wp_footer — без вызова этих функций в шаблоне enqueue не работает. Проверьте, что они есть в header.php и footer.php.
  • Неправильный хукwp_enqueue_scripts для фронтенда, admin_enqueue_scripts для админки. Не путайте, иначе стили попадут не туда.
  • Подключение в init — некоторые разработчики используют хук init для enqueue. Это работает, но неправильно: условные теги типа is_page() ещё недоступны на init.
  • Удаление jQuery без проверки — многие плагины зависят от jQuery. Перед удалением убедитесь, что ничего не сломается.

Система enqueue — один из фундаментальных механизмов WordPress. Правильное её использование не только ускорит сайт, но и обеспечит совместимость с другими плагинами и темами. Привыкайте использовать wp_enqueue_scripts для всех ресурсов — это стандарт разработки, который сэкономит время при отладке и масштабировании проекта.