DRYing out WordPress Asset Enqueuing

WordPress has several handy functions for granular control over external assets. Specifically wp_enqueue_script(), wp_register_script(), wp_enqueue_style(), wp_register_style(). These functions allow us to register assets so they’re available and can be added to the page where applicable.

While these functions give a fair amount of control, if you have a complex site you can end up repeating the same four functions over and over again. This not only contributes to visual clutter it increases the likelihood of making a mistake like a misplaced argument or typo.

Realizing this at 3.7 DESIGNS, we started looking for a better way to handle asset registering and enqueuing. What we came up with is as follows.

Define once

Rather than repeating wp_register_ and wp_enqueue_ for each individual asset, we decided defining all the assets once via a multidimensional array would cut down on needless repetition and code bloat.

Each asset is defined using a nested array with keys for the relevant arguments passed into wp_enqueue__ and wp_register_. Let’s look and see what’s required to register a script.

wp_register_script( string $handle, string $src, array $deps = array(), string|bool|null $ver = false, bool $in_footer = false );

We need a handle, file path, an array of dependencies, an optional version number, and whether it should show up in the footer or not.

So our scripts array could look something like this:

apply_filters( 'threeseven_global_scripts', $global_scripts = array(
	'global'	=>	array(
		'handle'	=>	'global',
		'uri'		=>	get_template_directory_uri() . '/dist/js/global.min.js',
		'deps'		=>	array( 'jquery' ),
		'ver'		=>	THEME_VER,
		'footer'	=>	true,
	),
	'vendor'	=>	array(
		'handle'	=>	'vendor',
		'uri'		=>	get_template_directory_uri() . '/dist/js/vendor.min.js',
		'deps'		=>	array(),
		'ver'		=>	THEME_VER,
		'footer'	=>	true,
	),
	'customizer'	=>	array(
		'handle'	=>	'propagate_customizer',
		'uri'		=>	get_template_directory_uri() . '/dist/js/customizer.js',
		'deps'		=>	array( 'customize-preview' ),
		'ver'		=>	THEME_VER,
		'footer'	=>	true,
		'condition' =>  is_customize_preview(),
	),
	'comment-reply'	=>	array(
		'handle'	=>	'comment-reply',
		'condition' =>  is_singular() && comments_open() && get_option( 'thread_comments' ),
		'register'  =>  false
		),
) );

Each asset has keys that correspond to arguments passed into the register function. You may have noticed we’ve added the additional keys of condition and register. We’ll use these to determine if the script needs to be registered and where it should be enqueued. More on this later.

Now we probably want to set some defaults so we don’t have to add unnecessary keys to our array. For example, most scripts will be registered before being enqueued. We can store those defaults in a separate array:

$default = 	array(
			'condition' =>  true,
			'register'  =>  true,
			'footer'    =>  false,
	);

Here we’re setting that scripts should be enqueued, registered and included in the HEAD by default.

Now that we’ve defined our defaults and scripts, we need to parse the array.

Parse once

Our variables are then passed into a function that will parse the array.

Our parse script looks like this:

three_seven_enqueue_scripts( $global_scripts , $default);

function three_seven_enqueue_scripts( $scripts , $default ) {
	foreach( $scripts as $script ) {
		
		$script = array_merge( $default , $script);
		
		if( $script['register'] !== false ){
			// Register the script so we can conditionally use it elsewhere
			wp_register_script( $script[ 'handle' ], $script[ 'uri' ], $script[ 'deps' ], $script[ 'ver' ], $script[ 'footer' ] );
		}
		
		if( $script['condition'] && $script['print'] !== false ){
			// These are global, so enqueue
			wp_enqueue_script( $script[ 'handle' ] );
		}
	}
}

First we merge each script with our $default array. Because the $script array is passed into array_merge second, the $script array will have priority on duplicate keys.

This is also where our register and condition keys come into play.

The register key indicates if the script needs to be registered (duh.) This allows us to use scripts that are bundled and already registered through WordPress like jQuery UI. The condition key allows us to dictate where the script should be loaded.

You probably already do this by putting if statements in your asset enqueue function, limiting assets to specific situations like only enqueuing on the homepage. If you look at our array, you’ll notice we’re essentially using the same approach, just streamlining it.

'condition' =>  is_singular() && comments_open() && get_option( 'thread_comments' ),

In the case of the comment-reply script, we only want to enqueue it on single posts that have comments open if threaded comments are enabled. When all three conditions are met, this will return true and the script will be enqueued.

And we’re done! This is our first iteration and I’m sure there are ways to improve it (we’d love to hear your suggestions!). The result is cleaner code, less repetition. and fewer chances for simple mistakes.

Here is the entire code for those interested:

function threeseven_theme_assets() {

        define( THEME_VER, '1.0' );

	// Setup global scripts
	wp_enqueue_script( 'jquery' );
	
	$default = 	array(
			'condition' =>  true,
			'register'  =>  true,
			'print'     =>  true,
	);
	
	apply_filters( 'threeseven_global_scripts', $global_scripts = array(
		'global'	=>	array(
			'handle'	=>	'global',
			'uri'		=>	get_template_directory_uri() . '/dist/js/global.min.js',
			'deps'		=>	array( 'jquery' ),
			'ver'		=>	THEME_VER,
			'footer'	=>	true,
		),
		'vendor'	=>	array(
			'handle'	=>	'vendor',
			'uri'		=>	get_template_directory_uri() . '/dist/js/vendor.min.js',
			'deps'		=>	array(),
			'ver'		=>	THEME_VER,
			'footer'	=>	true,
		),
		'customizer'	=>	array(
			'handle'	=>	'propagate_customizer',
			'uri'		=>	get_template_directory_uri() . '/dist/js/customizer.js',
			'deps'		=>	array( 'customize-preview' ),
			'ver'		=>	THEME_VER,
			'footer'	=>	true,
			'condition' =>  is_customize_preview(),
		),
		'comment-reply'	=>	array(
			'handle'	=>	'comment-reply',
			'condition' =>  is_singular() && comments_open() && get_option( 'thread_comments' ),
			'register'  =>  false
		),
	) );

	three_seven_enqueue_scripts( $global_scripts , $default);

	// Setup global styles
	apply_filters( 'threeseven_global_styles', $global_styles = array(
		'global'	=>	array(
			'handle'	=>	'global',
			'uri'		=>	get_stylesheet_directory_uri() . '/dist/css/style.min.css',
			'ver'		=>	THEME_VER,
		),
	) );


	three_seven_enqueue_styles( $global_styles , $default );

}
add_action( 'wp_enqueue_scripts', 'threeseven_theme_assets', 0 );

function three_seven_enqueue_scripts( $scripts , $default ) {

	foreach( $scripts as $script ) {
		
		$script = array_merge( $default , $script);
		
		if( $script['register'] !== false ){
			// Register the script so we can conditionally use it elsewhere
			wp_register_script( $script[ 'handle' ], $script[ 'uri' ], $script[ 'deps' ], $script[ 'ver' ], $script[ 'footer' ] );
		}
		
		if( $script['condition'] && $script['print'] !== false ){
			// These are global, so enqueue
			wp_enqueue_script( $script[ 'handle' ] );
		}

	}

}

function three_seven_enqueue_styles( $styles , $default ) {
	
	foreach( $styles as $style ) {
		$style = array_merge( $default , $style);
		
		if( $style['register'] !== false ){
			// Register the stle so we can conditionally load / unload
			wp_register_style( $style[ 'handle' ], $style[ 'uri' ], $style[ 'ver' ] );
		}
		if( $style['condition'] && $style['print'] !== false){
			// These are global so enqueue
			wp_enqueue_style( $style[ 'handle' ] );
		}
	}

}