Last active
January 16, 2026 15:06
-
-
Save quietcactus/559c07eb8683132523b77db415315a29 to your computer and use it in GitHub Desktop.
Enqueues scripts and styles based on if the template part is called on a template
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php /** | |
| * Discovers and registers template part hooks | |
| * Uses transient caching for performance | |
| */ | |
| add_action('after_setup_theme', function () { | |
| $tp_dir = get_stylesheet_directory() . '/template-parts'; | |
| if (! is_dir($tp_dir)) { | |
| return; | |
| } | |
| $cache_key = 'tp_registered_parts_v3'; | |
| $cached_parts = get_transient($cache_key); | |
| $is_debug_mode = defined('WP_DEBUG') && WP_DEBUG; | |
| // Use cached data if available and not in debug mode | |
| if ($cached_parts !== false && !$is_debug_mode) { | |
| $template_parts = $cached_parts; | |
| } else { | |
| // Scan directory and build list of template parts | |
| $template_parts = tp_discover_template_parts($tp_dir); | |
| // Cache for 24 hours (DAY_IN_SECONDS = 86400) | |
| if (!$is_debug_mode) { | |
| set_transient($cache_key, $template_parts, DAY_IN_SECONDS); | |
| } | |
| } | |
| // Register hooks for each discovered template part | |
| if (is_array($template_parts)) { | |
| foreach ($template_parts as $slug) { | |
| add_action("get_template_part_{$slug}", 'tp_enqueue_template_part_assets', 10, 2); | |
| } | |
| } | |
| }); | |
| /** | |
| * Scans template-parts directory and returns array of slugs to register | |
| * | |
| * @param string $tp_dir Path to template-parts directory | |
| * @return array Array of template part slugs | |
| */ | |
| function tp_discover_template_parts($tp_dir) { | |
| $template_parts = []; | |
| // Walk every file under /template-parts/, skipping "." and ".." | |
| $it = new RecursiveIteratorIterator( | |
| new RecursiveDirectoryIterator($tp_dir, RecursiveDirectoryIterator::SKIP_DOTS) | |
| ); | |
| foreach ($it as $file_info) { | |
| /** @var SplFileInfo $file_info */ | |
| if ($file_info->getExtension() !== 'php') { | |
| continue; | |
| } | |
| $full_path = $file_info->getPathname(); | |
| // Normalize path separators for cross-platform compatibility | |
| $full_path = str_replace('\\', '/', $full_path); | |
| $tp_dir_normalized = str_replace('\\', '/', $tp_dir); | |
| // Strip off the ".../template-parts/" prefix: | |
| $relative = str_replace($tp_dir_normalized . '/', '', $full_path); | |
| $path_parts = pathinfo($relative); | |
| $dirname = $path_parts['dirname']; // e.g. "section-intro" or "." | |
| $base = $path_parts['filename']; // e.g. "section-intro" | |
| // Register the full filename path (e.g., template-parts/section-intro/section-intro) | |
| $full_slug = 'template-parts/' . ($dirname !== '.' ? $dirname . '/' : '') . $base; | |
| $template_parts[] = $full_slug; | |
| // Also register the split version for backward compatibility | |
| // Split on first hyphen: | |
| $segments = explode('-', $base, 2); | |
| $slug_base = $segments[0]; // e.g. "section" | |
| // Rebuild the hook's "slug" including sub-folder(s): | |
| $slug_short = ($dirname !== '.' ? $dirname . '/' : '') . $slug_base; | |
| $slug = 'template-parts/' . $slug_short; | |
| $template_parts[] = $slug; | |
| } | |
| return array_unique($template_parts); | |
| } | |
| /** | |
| * Clears template parts cache when theme is switched or updated | |
| */ | |
| function tp_clear_cache() { | |
| delete_transient('tp_registered_parts_v3'); | |
| } | |
| add_action('switch_theme', 'tp_clear_cache'); | |
| add_action('after_switch_theme', 'tp_clear_cache'); | |
| /** | |
| * Enqueues CSS/JS assets for template parts | |
| * Supports modern script attributes and intelligent versioning | |
| * | |
| * Supports both calling syntaxes: | |
| * - get_template_part('template-parts/section-intro/section-intro') | |
| * - get_template_part('template-parts/section-intro/section', 'intro') | |
| * | |
| * Checks for assets in locations (in priority order): | |
| * 1. Co-located: template-parts/section-intro/section-intro.css | |
| * 2. Legacy: css/template-parts/section-intro.css | |
| * | |
| * @param string $slug Template part slug (e.g., 'template-parts/section-intro/section') | |
| * @param string $name Template part name (optional, e.g., 'intro') | |
| */ | |
| function tp_enqueue_template_part_assets($slug, $name) { | |
| // Self-contained versioning - no external dependencies | |
| $cacheVersion = '1.0.0'; | |
| $slug_short = preg_replace('#^template-parts/#', '', $slug); | |
| $filename = $slug_short . ($name ? "-{$name}" : ''); | |
| $dir = get_stylesheet_directory(); | |
| $uri = get_stylesheet_directory_uri(); | |
| // Determine versioning strategy | |
| // Use filemtime for development, static version for production | |
| $use_filemtime = defined('WP_DEBUG') && WP_DEBUG; | |
| // Build asset paths to check | |
| // When $name is provided (e.g., 'section', 'intro'), we need to check both: | |
| // 1. The combined filename: template-parts/section-intro/section-intro.css | |
| // 2. The slug as-is: template-parts/section-intro/section.css | |
| $asset_paths = []; | |
| if ($name) { | |
| // Split syntax: get_template_part('template-parts/section-intro/section', 'intro') | |
| // Extract directory and reconstruct full filename | |
| $path_info = pathinfo($slug); | |
| $dir_path = $path_info['dirname']; | |
| $base_name = $path_info['filename']; | |
| // Try the combined version first (e.g., section-intro/section-intro.css) | |
| $combined_filename = $base_name . '-' . $name; | |
| $asset_paths[] = '/' . $dir_path . '/' . $combined_filename; | |
| // Then try the slug as-is (e.g., section-intro/section.css) | |
| $asset_paths[] = '/' . $slug; | |
| } else { | |
| // Full filename syntax: get_template_part('template-parts/section-intro/section-intro') | |
| $asset_paths[] = '/' . $slug; | |
| } | |
| // === CSS LOADING === | |
| $css_rel = null; | |
| // Check co-located paths | |
| foreach ($asset_paths as $asset_path) { | |
| $css_test = $asset_path . '.css'; | |
| if (file_exists($dir . $css_test)) { | |
| $css_rel = $css_test; | |
| break; | |
| } | |
| } | |
| // Fallback to legacy location | |
| if (!$css_rel) { | |
| $css_legacy = "/css/template-parts/{$filename}.css"; | |
| if (file_exists($dir . $css_legacy)) { | |
| $css_rel = $css_legacy; | |
| } | |
| } | |
| if ($css_rel) { | |
| $version = $use_filemtime ? filemtime($dir . $css_rel) : $cacheVersion; | |
| wp_enqueue_style( | |
| "tp-{$filename}", | |
| $uri . $css_rel, | |
| [], | |
| $version | |
| ); | |
| } | |
| // === JS LOADING === | |
| $js_rel = null; | |
| // Check co-located paths | |
| foreach ($asset_paths as $asset_path) { | |
| $js_test = $asset_path . '.js'; | |
| if (file_exists($dir . $js_test)) { | |
| $js_rel = $js_test; | |
| break; | |
| } | |
| } | |
| // Fallback to legacy location | |
| if (!$js_rel) { | |
| $js_legacy = "/js/template-parts/{$filename}.js"; | |
| if (file_exists($dir . $js_legacy)) { | |
| $js_rel = $js_legacy; | |
| } | |
| } | |
| if ($js_rel) { | |
| $version = $use_filemtime ? filemtime($dir . $js_rel) : $cacheVersion; | |
| wp_enqueue_script( | |
| "tp-{$filename}", | |
| $uri . $js_rel, | |
| ['jquery'], | |
| $version, | |
| true | |
| ); | |
| } | |
| } | |
| /** | |
| * Adds modern script attributes (defer, async, type="module") to template part scripts | |
| * | |
| * @param string $tag Script tag HTML | |
| * @param string $handle Script handle | |
| * @param string $src Script source URL | |
| * @return string Modified script tag | |
| */ | |
| function tp_add_script_attributes($tag, $handle, $src) { | |
| // Only apply to template part scripts | |
| if (strpos($handle, 'tp-') !== 0) { | |
| return $tag; | |
| } | |
| // Default: add defer to all template part scripts for better performance | |
| $attributes = ['defer']; | |
| // Check for .module.js files to add type="module" | |
| if (strpos($src, '.module.js') !== false) { | |
| $attributes[] = 'type="module"'; | |
| // Remove defer for modules (not needed, modules are deferred by default) | |
| $attributes = ['type="module"']; | |
| } | |
| // Build new script tag with attributes | |
| $tag = '<script src="' . $src . '"'; | |
| foreach ($attributes as $attr) { | |
| if (strpos($attr, '=') !== false) { | |
| // Attribute with value (e.g., type="module") | |
| $tag .= ' ' . $attr; | |
| } else { | |
| // Boolean attribute (e.g., defer, async) | |
| $tag .= ' ' . $attr; | |
| } | |
| } | |
| $tag .= '></script>' . "\n"; | |
| return $tag; | |
| } | |
| add_filter('script_loader_tag', 'tp_add_script_attributes', 10, 3); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
WordPress Template Parts Auto-Loader
A smart WordPress system that automatically discovers and loads CSS/JS assets for template parts. No more manual enqueuing - just drop your assets next to your PHP templates and they load automatically!
Features
template-parts/directory and registers hooks automaticallydeferattributes and ES6 module supportFile Organization
Organize your template parts with their assets in the same folder:
Usage
Basic Template Part Call
Automatically loads:
section-intro.cssandsection-intro.jsSplit Syntax (Alternative)
Also loads:
section-intro.cssandsection-intro.jsComponent Variations
Loads:
component-hero-slide-1.cssandcomponent-hero-slide-1.jsPerformance Features
Caching
WP_DEBUGis enabledSmart Versioning
filemtime()for instant cache busting when files change'1.0.0'for optimal browser cachingThe system automatically chooses the appropriate versioning strategy based on your environment:
Modern Script Loading
deferattribute automatically*.module.jsfortype="module"Configuration
Cache Management
Debug Mode
When
WP_DEBUGis enabled:Custom Dependencies
Scripts automatically depend on jQuery. To customize:
Requirements
template-parts/directoryAsset Naming Conventions
CSS Files
section-intro.css→ Loads withsection-introtemplatecomponent-hero-slide-1.css→ Loads withcomponent-hero-slide-1templateJavaScript Files
section-intro.js→ Standard script withdefersection-intro.module.js→ ES6 module withtype="module"Legacy Support
Assets in legacy locations still work:
css/template-parts/section-intro.cssjs/template-parts/section-intro.jsBenefits
wp_enqueue_style()calls neededHow It Works
template-parts/directory on theme setupget_template_part_{slug}hooks