How to Remove Unused CSS JS Files in WordPress

remove unused css js files in WordPress

When trying to optimize your site for speed, you really want to remove unused CSS JS files in WordPress .

With WordPress sites using a lot of plugins, when you try to optimize page loading times you’ll soon hit a roadblock.

You got a good fast host, enabled caching, optimized your images and removed any plugin that you don’t use.

Next on the list is to optimize or remove unused CSS JS files in WordPress.

We’ll go through:

80% of your CSS JS files probably don’t do anything 80% of the time

After the new redesign a few months back, I’ve made it a personal quest to limit the number of CSS and JS files here at Cozmoslabs.

After going through the scripts that load on our site, on our homepage we’ve managed to go from:

  • 15 JS scripts and 11 CSS files to:
  • 4 JS scripts and 2 CSS files

Why plugins load so many CSS JS files?

it makes a lot of sense to invest a bit of time to remove unused CSS JS files in WordPress.

If you take any plugin that offers a shortcode or Widget for the user, chances are it will load it’s CSS JS files site-wide like so:

1
2
3
4
5
6
7
8
9
10
function pluginslug_enqueue_style() {
	wp_enqueue_style( 'my-css-handle', 'style.css', false ); 
}
 
function pluginslug_enqueue_script() {
	wp_enqueue_script( 'my-js-handle', 'filename.js', false );
}
 
add_action( 'wp_enqueue_scripts', 'pluginslug_enqueue_style' );
add_action( 'wp_enqueue_scripts', 'pluginslug_enqueue_script' );

What this does is it loads the style.css and filename.js sitewide, regardless of the fact that you’re using a particular shortcode on a single page or a particular widget on a particular custom post type.

There’s no way to chose where that particular CSS/JS file gets loaded. So it makes a lot of sense to invest a bit of time to remove unused CSS JS files in WordPress.

See what scripts are getting loaded on our site

In order to remove unused scripts, we should first find our what exactly get’s loaded by our theme and plugins.

One can do that simply by looking at the source code, make a list of files and then search in each plugin where exactly they get loaded. But that’s boring and there are much better solutions.

Remove unused css js files in WordPress

Listing all loaded scripts with wp_enqueue_script

First thing first, we’ll create a simple plugin that will host all of our code.

Create a new file called wp_remove_assets.php and add the following plugin header:

1
2
3
4
5
6
7
8
9
<?php
/*
 * Plugin Name: WP Remove Assets
 * Plugin URI: https://www.cozmsolabs.com
 * Version: 0.1
 * Description: Filter particular scripts and style to load in posts or pages that don't need it.
 * Author: Cristian Antohe
 * Author URI: https://www.cozmoslabs.com/
*/

Before we can remove unused CSS JS files in WordPress, we’ll first look at what get’s loaded.

WordPress uses two global variables to store scripts and styles:

  • $wp_scripts
  • $wp_styles

If you do a var_dump of both global files in the front-end, you’ll be able to see all the registered scripts (by plugins, themes and WordPress it self) as well as the ones currently loaded on our page.

We’re mainly interested in the ones that get loaded.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// list loaded assets by our theme and plugins so we know what we're dealing with. This is viewed by admin users only.
add_action('wp_print_footer_scripts', 'wra_list_assets', 900000);
function wra_list_assets(){
    if ( !current_user_can('delete_users') ){
        return;
    }
 
    echo '<h2>List of all scripts loaded on this particular page.</h2>';
    echo '<p>This can differ from page to page depending of what is loaded in that particular page.</p>';
 
    // Print all loaded Scripts (JS)
    global $wp_scripts;
    wra_print_assets($wp_scripts);
 
    echo '<h2>List of all css styles loaded on this particular page.</h2>';
    echo '<p>This can differ from page to page depending of what is loaded in that particular page.</p>';
    // Print all loaded Styles (CSS)
    global $wp_styles;
    wra_print_assets($wp_styles);
}

We’re hooking into the wp_print_footer_scripts hook with a very high priority to make sure all scripts finish loading before listing them.

Also, this listing gets displayed only if you’re a logged in administrator. So your users or search engines will never see the listing.

There are two extra functions that we’ll need to create. The wra_print_assets and wra_asset_template.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// both $wp_styles and $wp_scripts are objects and store loaded CSS/JS files in $wp_styles->queue
function wra_print_assets($wp_asset){
    $nb_of_asset = 0;
    foreach( $wp_asset->queue as $asset ) :
        $nb_of_asset ++;
        $asset_obj = $wp_asset->registered[$asset];
        wra_asset_template($asset_obj, $nb_of_asset);
    endforeach;
}
 
// we're using inline css since this is only for admins to see and it's ok if it's a bit messy 
function wra_asset_template($asset_obj, $nb_of_asset){
    if( is_object( $asset_obj ) ){
        echo '<div class="wra_asset" style="padding: 2rem; font-size: 0.8rem; border-bottom: 1px solid #ccc;">';
 
        echo '<div class="wra_asset_nb"><span style="width: 150px; display: inline-block">Number:</span>';
        echo $nb_of_asset . '</div>';
 
 
        echo '<div class="wra_asset_handle"><span style="width: 150px; display: inline-block">Handle:</span>';
        echo $asset_obj->handle . '</div>';
 
        echo '<div class="wra_asset_src"><span style="width: 150px; display: inline-block">Source:</span>';
        echo $asset_obj->src . '</div>';
 
        echo '<div class="wra_asset_deps"><span style="width: 150px; display: inline-block">Dependencies:</span>';
        foreach( $asset_obj->deps as $deps){
            echo $deps . ' / ';
        }
        echo '</div>';
 
        echo '<div class="wra_asset_ver"><span style="width: 150px; display: inline-block">Version:</span>';
        echo $asset_obj->ver . '</div>';
 
        echo '</div>';
 
    }

We’ll be listing:

  • the current number of the CSS/JS file
  • the handle – this is the unique name used by wp_enqueue_scripts to load a script just once
  • source – the location of the file
  • dependencies – some files require other libraries, like jquery.
  • version – the current version of the CSS/JS file

Going forward, we’ll only need the handle of the CSS/JS files, however it’s nice to see this information listed to get an idea as to why some files are loaded.

Remove unused CSS JS files in WordPress

There are 4 main functions when you want to remove unused CSS JS files in WordPress front-end:

you can use WordPress conditional tags to target a particular page or an entire custom post type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// remove script handles we don't need, each with their own conditions
 
add_action('wp_print_scripts', 'wra_filter_scripts', 100000);
add_action('wp_print_footer_scripts',  'wra_filter_scripts', 100000);
 
function wra_filter_scripts(){
    #wp_deregister_script($handle);
    #wp_dequeue_script($handle);

    wp_deregister_script('bbpress-editor');
    wp_dequeue_script('bbpress-editor');
 
    // Device Pixels support
    // This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers. We only have gravatars so we should be ok without it.
    wp_deregister_script('devicepx');
    wp_dequeue_script('devicepx');
 
    if( !is_singular( 'docs' ) ){
        // the table of contents plugin is being used on documentation pages only
        wp_deregister_script('toc-front');
        wp_dequeue_script('toc-front');
    }
 
    if( !is_singular( array('docs', 'post' ) ) ){
        wp_deregister_script('codebox');
        wp_dequeue_script('codebox');
    }
}
 
// remove styles we don't need
add_action('wp_print_styles', 'wra_filter_styles', 100000);
add_action('wp_print_footer_scripts', 'wra_filter_styles', 100000);
function wra_filter_styles(){
 
    #wp_deregister_style($handle);
    #wp_dequeue_style($handle);

    //no more bbpress styles.
    wp_deregister_style('bbp-default');
    wp_dequeue_style('bbp-default');
 
    // download monitor is not used in the front-end.
    wp_deregister_style('wp_dlmp_styles');
    wp_dequeue_style('wp_dlmp_styles');
 
    if( !is_singular( 'docs' ) ){
        // the table of contents plugin is being used on documentation pages only
        wp_deregister_style('toc-screen');
        wp_dequeue_style('toc-screen');
    }
 
    if ( !( is_page( 'account' ) || is_page( 'edit-profile' )) ){
        // this should not be like this. Need to look into it.
        wp_deregister_style('wppb_stylesheet');
        wp_dequeue_style('wppb_stylesheet');
    }
 
    if( !is_singular( array('docs', 'post' ) ) ){
        wp_deregister_style('codebox');
        wp_dequeue_style('codebox');
    }
}

The really cool part of doing it like this is that you can use WordPress conditional tags to target a particular page or an entire custom post type. This gives us the flexibility we need to load our CSS/JS files exactly where they are needed.

Remove scripts that won’t work with wp_dequeue_script

Sometimes scripts are added differently. The most common way would be to simply output the script or style tag directly in the header or footer.

1
2
3
4
add_action('wp_head', 'pluginslug_add_script', 10);
function pluginslug_add_script(){
    echo '<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>';
}

Now, this is wrong for various reasons:

  • It’s not possible for other developers to deque assets
  • Not being part of $wp_scripts global variable, conflicts can appear since another plugin can load a local version of jQuery
  • It’s not best practices and should not be used unless you really know what you’re doing

To remove a script added like the above, you can do it by removing the entire hook from executing:

1
2
3
4
add_action('wp_print_footer_scripts', 'wra_remove_wphead_filter', 100000);
function wra_remove_wphead_filter(){
    remove_action('wp_head', 'pluginslug_add_script', 10);
}

We’re using the remove_action() function that WordPress provides. When removing actions, it’s best to remember:

  • The priority of the add_filter() declaration counts when doing remove_filter()
  • The remove_filter() function should be called on a hook after the add_filter() we want removed was added

In our particular case, when removing unused CSS/JS files, one of the scripts was jetpack.css that for some reason was added in a particular way that wp_dequeue_style() didn’t work.

1
2
3
4
5
6
7
8
9
10
/**
* This is the hack to concatinate all css files into one.
* For description and reasoning see the implode_frontend_css method
*
* Super late priority so we catch all the registered styles
*/
if( !is_admin() ) {
	add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first
	add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`
}

For some reason I couldn’t dequeue the jetpack.css file, however, there was a hook inside the Jetpack implode_frontend_css() function that allowed us to not load that particular file:

1
2
3
4
5
// Jetpack
add_action('jetpack_implode_frontend_css', 'wra_remove_jetpack');
function wra_remove_jetpack(){
    return false;
}

The entire plugin

That’s about it. You can copy / paste the entire plugin from here and make modifications to it to better suit your WordPress site.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<?php
/*
 * Plugin Name: WP Remove Assets
 * Plugin URI: https://www.cozmsolabs.com
 * Version: 0.1
 * Description: Filter particular scripts and style to load in posts or pages that don't need it.
 * Author: Cristian Antohe
 * Author URI: https://www.cozmoslabs.com/
*/
 
 
// remove script handles we don't need, each with their own conditions
 
add_action('wp_print_scripts', 'wra_filter_scripts', 100000);
add_action('wp_print_footer_scripts',  'wra_filter_scripts', 100000);
 
function wra_filter_scripts(){
    #wp_deregister_script($handle);
    #wp_dequeue_script($handle);

    wp_deregister_script('bbpress-editor');
    wp_dequeue_script('bbpress-editor');
 
    // Device Pixels support
    // This improves the resolution of gravatars and wordpress.com uploads on hi-res and zoomed browsers. We only have gravatars so we should be ok without it.
    wp_deregister_script('devicepx');
    wp_dequeue_script('devicepx');
 
    if( !is_singular( 'docs' ) ){
        // the table of contents plugin is being used on documentation pages only
        wp_deregister_script('toc-front');
        wp_dequeue_script('toc-front');
    }
 
    if( !is_singular( array('docs', 'post' ) ) ){
        wp_deregister_script('codebox');
        wp_dequeue_script('codebox');
    }
 
 
}
 
// Jetpack
add_action('jetpack_implode_frontend_css', 'wra_remove_jetpack');
function wra_remove_jetpack(){
    return false;
}
 
 
 
// remove styles we don't need
add_action('wp_print_styles', 'wra_filter_styles', 100000);
add_action('wp_print_footer_scripts', 'wra_filter_styles', 100000);
function wra_filter_styles(){
 
    #wp_deregister_style($handle);
    #wp_dequeue_style($handle);

    //no more bbpress styles.
    wp_deregister_style('bbp-default');
    wp_dequeue_style('bbp-default');
 
    // download monitor is not used in the front-end.
    wp_deregister_style('wp_dlmp_styles');
    wp_dequeue_style('wp_dlmp_styles');
 
    if( !is_singular( 'docs' ) ){
        // the table of contents plugin is being used on documentation pages only
        wp_deregister_style('toc-screen');
        wp_dequeue_style('toc-screen');
    }
 
    if ( !( is_page( 'account' ) || is_page( 'edit-profile' )) ){
        // this should not be like this. Need to look into it.
        wp_deregister_style('wppb_stylesheet');
        wp_dequeue_style('wppb_stylesheet');
    }
 
    if( !is_singular( array('docs', 'post' ) ) ){
        wp_deregister_style('codebox');
        wp_dequeue_style('codebox');
    }
}
 
 
 
 
// list loaded assets by our theme and plugins so we know what we're dealing with. This is viewed by admin users only.
add_action('wp_print_footer_scripts', 'wra_list_assets', 900000);
function wra_list_assets(){
    if ( !current_user_can('delete_users') ){
        return;
    }
 
    echo '<h2>List of all scripts loaded on this particular page.</h2>';
    echo '<p>This can differ from page to page depending of what is loaded in that particular page.</p>';
 
    // Print all loaded Scripts (JS)
    global $wp_scripts;
    wra_print_assets($wp_scripts);
 
    echo '<h2>List of all css styles loaded on this particular page.</h2>';
    echo '<p>This can differ from page to page depending of what is loaded in that particular page.</p>';
    // Print all loaded Styles (CSS)
    global $wp_styles;
    wra_print_assets($wp_styles);
}
 
function wra_print_assets($wp_asset){
    $nb_of_asset = 0;
    foreach( $wp_asset->queue as $asset ) :
        $nb_of_asset ++;
        $asset_obj = $wp_asset->registered[$asset];
        wra_asset_template($asset_obj, $nb_of_asset);
    endforeach;
}
 
function wra_asset_template($asset_obj, $nb_of_asset){
    if( is_object( $asset_obj ) ){
        echo '<div class="wra_asset" style="padding: 2rem; font-size: 0.8rem; border-bottom: 1px solid #ccc;">';
 
        echo '<div class="wra_asset_nb"><span style="width: 150px; display: inline-block">Number:</span>';
        echo $nb_of_asset . '</div>';
 
 
        echo '<div class="wra_asset_handle"><span style="width: 150px; display: inline-block">Handle:</span>';
        echo $asset_obj->handle . '</div>';
 
        echo '<div class="wra_asset_src"><span style="width: 150px; display: inline-block">Source:</span>';
        echo $asset_obj->src . '</div>';
 
        echo '<div class="wra_asset_deps"><span style="width: 150px; display: inline-block">Dependencies:</span>';
        foreach( $asset_obj->deps as $deps){
            echo $deps . ' / ';
        }
        echo '</div>';
 
        echo '<div class="wra_asset_ver"><span style="width: 150px; display: inline-block">Version:</span>';
        echo $asset_obj->ver . '</div>';
 
        echo '</div>';
 
    }
}

A better way for plugins to include CSS/JS files

Plugins load CSS/JS files globally for a variety of reasons. The main reason is that as a developer, there’s no standardized way of finding out if your shortcode is used in a page or not. So we load all assets globally.

Also, it’s what WordPress.org recommends you do on the Codex as an example.

With so many examples of loading scripts globally, it’s easy to understand why almost any WordPress site loads 15+ resources for each page, when only needing a a few of them.

Load assets only if shortcode is executed

We’ve been using this technique on almost all our plugin assets for quite some time.

Basically, we have a global variable that gets initiated in our shortcode, and if it’s set and true, we’re loading our assets like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// create a shortcode that registers a shortcode
function cozmoslabs_some_shortcode( $atts ) {
	global $cozmoslabs_some_shortcode;
        $cozmoslabs_some_shortcode = true;
        return 'We have a shortcode';
}
add_shortcode('cozmoslabs_short', 'cozmoslabs_some_shortcode');
 
function cozmoslabs_enqueue_script() {
	global $cozmoslabs_some_shortcode;
        if ( isset($cozmoslabs_some_shortcode) && $cozmoslabs_some_shortcode == true )	{
                wp_enqueue_script( 'my-js', 'filename.js', true );
        }
}
add_action( 'wp_footer', 'cozmoslabs_enqueue_script', 90000 );

Unfortunately, there are drawbacks to this as well:

  • the scripts need to be added to wp_footer with a late priority because if we add them to wp_enqueue_scripts, our global variable has a good chance not to be set
  • if you need something added in the header of the site, this won’t work
  • in wp_enqueue_script() you have to set $in_footer to true

Conclusions

If you want to remove unused CSS JS files in WordPress is really not that complicated.

The bigger issue here is the way assets are added in plugins and themes, a lot of them being added globally regardless of whether they are needed or not.

Other solutions to this problem can include combining assets, but even then, having a large CSS and JS file will slow down the loading time of your WordPress site, particularly on mobile devices because it has to render all those CSS files and execute all that JS code.

Content delivery networks also help, but you’re still loading unneeded resources.

I can’t think of an automated solution since each site is different, however there are plugins like WP Asset Clean Up. They don’t have the flexibility to target entire custom post types for example, however it’s a lot easier to use for non technical users then writing your own conditional rules.

If you have other suggestions on how plugins can load CSS/JS in a more mindful way and not globally, I would love to have your input in the comment section.

Subscribe to get early access

to new plugins, discounts and brief updates about what's new with Cozmoslabs!

7 thoughts on “How to Remove Unused CSS JS Files in WordPress

  1. Hey Cristian,

    Regarding your issue with the Jetpack actions you’re unable to remove… These are added as methods of a class instance, so to remove them, you need to get a reference to the class instance first.

    Fortunately, Jetpack provides a way to retrieve that instance through the badly named `Jetpack::init()` method: https://github.com/Automattic/jetpack/blob/257d473f022f2b75fe45719c22d2f655e3eafdee/class.jetpack.php#L308-L319

    So, to remove these actions, something like the following should work:

    https://gist.github.com/anonymous/8d6face1e4161172cd6d655ce739ba04#file-gistfile1-txt

  2. There’s actually no need to create a global variable (and you shouldn’t) if you want to only load scripts/styles on pages where the shortcode is used. One method is to use wp_register_script() to register the script in the wp_enqueue_scripts hook, and then to use wp_enqueue_script(‘script-hook’) in the plugin callback. The script will then only be loaded if the plugin callback is called.

    1. What is needed is a campaign to get every plugin owner to improve their plugins so that scripts and css files are only loaded if they are going to be used.

  3. With http2 combining multiple static files into one is no longer necessary and a bad idea. However, your point about the processing overhead of unused CSS and JS files is valid.

Leave a Reply

Your email address will not be published. Required fields are marked *