WordPress has native PHP JavaScript management functions. Using these functions are better than manually adding <script>
tags in the <head>
or footer of the page. In this article, we will discuss:
- PHP Constants for Debugging JavaScript
- Registering and Deregistering Scripts
- Enqueueing and Dequeueing Scripts
- Localizing Scripts
- Initializing Scripts
- Modifying Script Tags on Print
PHP Constants for Debugging JavaScript
Most WordPress developers are familiar with WP_DEBUG
and know how to use that to debug WordPress. WP_DEBUG
is designed as a global for turning debugging on and off, but WP_DEBUG
has no affect on JavaScript files. Fewer developers know about SCRIPT_DEBUG
. While WP_DEBUG
helps debug the PHP code, SCRIPT_DEBUG
helps with JavaScript. SCRIPT_DEBUG
will output the normal, non-minified version of the JavaScript files. For example, with SCRIPT_DEBUG
will output wp-embed.js
while normally WordPress will output wp-embed.min.js
. You can use this constant to output minified and non-minified your plugin/theme JavaScript files dynamically. For example:
<?php | |
namespace MyPrefix\MyPluginOrTheme; | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// JavaScript File | |
$filename = "myscript{$suffix}.js"; | |
// CSS File | |
$filename = "myscript{$suffix}.css"; |
Alternatively, you could use a function that will do this for you:
<?php | |
namespace MyPrefix\MyPluginOrTheme; | |
/** | |
* Gets the proper JavaScript filename based on SCRIPT_DEBUG. | |
* | |
* @param string $filename Filename without the extension. | |
* | |
* @return string JavaScript filename. | |
*/ | |
function get_js_filename( $filename ) { | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
return "{$filename}{$suffix}.js"; | |
} | |
$filename = get_js_filename( 'myscript' ); |
Or, you can use a constant or multiple constants:
<?php | |
namespace MyPrefix\MyPluginOrTheme; | |
// Define MULTIPLE Suffixes | |
define( 'JS_SUFFIX', ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.js' : '.min.js' ) ); | |
define( 'CSS_SUFFIX', ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '.css' : '.min.css' ) ); | |
// JavaScript file | |
$filename = 'myscript' . JS_SUFFIX; | |
// CSS file | |
$filename = 'mystyle' . CSS_SUFFIX; | |
// Define a SINGLE Suffix | |
define( 'SCRIPT_SUFFIX', ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min' ) ); | |
// JavaScript file | |
$filename = 'myscript' . SCRIPT_SUFFIX . '.js'; | |
// CSS file | |
$filename = 'mystyle' . SCRIPT_SUFFIX . '.css'; |
Or (just for fun) create a function for the suffix only:
<?php | |
namespace MyPrefix\MyPluginOrTheme; | |
/** | |
* Gets the proper JavaScript filename based on SCRIPT_DEBUG. | |
* | |
* @param string $filename Filename without the extension. | |
* | |
* @return string JavaScript filename. | |
*/ | |
function get_suffix( $filename ) { | |
static $suffix; | |
if ( null !== $suffix ) { | |
return $suffix; | |
} | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
return $suffix; | |
} | |
$filename = 'myscript' . get_suffix() . 'js'; |
If SCRIPT_DEBUG
is on, the filename will be my-js-file.js
while if SCRIPT_DEBUG
is false, the filename will be my-js-file.min.js
. So if you have a Gulp/Grunt script or a PHPStorm File Watcher auto-minifying your JavaScript, WordPress will properly output the correct JavaScript file.
Registering and Deregistering Scripts
Why register your scripts?
- DRY: The script manager allows you to re-use and output the script in various places of your code.
- Integrations: The script manager allows for anyone to integrate, optimize, and build upon your script.
- Prevents Duplication: A script using the script manager will only be printed on the page once.
- Client Focused & Client Friendly: It allows your customers/clients to remove the scripts easily.
DRY
The first reason (and one which I agree) is perfect for coders who subscribe to DRY (Don't Repeat Yourself) coding, which most of us prefer. It optimizes your code and makes your code clean and readable. WordPress Core registers a ton of JavaScript files. Most notable of these are:
jquery
(core and other parts—jquery-ui
,jquery-effects
)thickbox
underscrore
backbone
media-element
If you are using any of these, please use the WordPress core version. It ensures the best experience.
Client Focused & Client Friendly
Many developers do not like the second reason. They want to control every aspect of their code because they are afraid that their client/customer or another developer will break their plugin. However, to me, this is not a good reason. If a customer/client breaks the plugin/site because they chose to copy/paste code and place it in their functions.php
site, it is their fault, and they can pay you to fix it. But by providing the client/customer code that is changeable, it makes you look like a better developer, and you will have return customers/clients because they can trust that you have their best interests in mind.
Integrations & Prevents Duplication
Plugins like WP Rocket and other optimization plugins can detect and handled registered scripts much easier than functions that echo script
elements at random places throughout the document. WordPress filters, like script_loader_src
and script_loader_tag
, can be used to amend how one or more registered scripts are loaded in the DOM by amending the tag attributes (see below for the advanced examples), and finally other scripts can use those scripts as dependencies themselves.
By using a script manager, WordPress tries to prevent duplication of scripts. For example, if one plugin outputs underscore
via a script
tag and the other developer uses WordPress's version, then multiple underscore
scripts will be printed on the page, which obviously will lead to problems.
The Code: Registering/Deregistering Scripts
There is a function for registering a JavaScript file (wp_register_script
) and one for deregistering a JavaScript file (wp_deregister_script
). Now, many developers, and even those on developers.wordpress.org, recommend that you register scripts within the wp_enqueue_scripts
hook. However, it is my opinion that you should always register scripts on init
(for frontend scripts wrapped with ! is_admin()
conditional) and admin_init
(for admin scripts) hooks. For the purposes of this article, we will be focusing all of the examples to frontend JavaScript, so we won't be using the admin_init
hook. This allows your customers/clients to manipulate the scripts however they need, if they need.
Registering JavaScript Files
wp_register_script($handle, $src, $deps, $ver, $in_footer)
takes a couple parameters which are obvious: $handle
(slug) $src
(the script URL), $in_footer
(whether to output in the head
or footer). It is always recommended that $in_footer
be true
unless (1) there is a very specific reason for the script to be in the head
(other than "my code doesn't work in the footer") or (2) you use defer
. Two really good examples are Google Analytics and New Relic—both require to be run in the head
at the top. IMHO Google Analytics should have the async
attribute added to it. The two remaining parameters—dependencies ($deps
) and version ($ver
) are more fascinating.
Script Version ($ver
)
Most people use a simple version number to identify the version (e.g., 0.0.1 or 2.1). In fact, most developers are either ignorant or lazy and only use the version of the theme or plugin via a constant or something hardcoded that never seems to change. So if you are doing development with an aggressively caching browser it becomes problematic and frustrating to ensure that your changes are being reflected in the DOM, even with browsersync or gulp-livereload. However, the best option is to do both the version of the plugin and the file version. If you are like me, changing the version of the file every time in the JavaScript file and the correlating PHP file is such a hassle. Being a stickler on this yet also lazy about versioning myself, I need something simple that would be cacheable and yet also cache bust when needed (on update/change). My goal is to load the JavaScript file via WordPress and forget about that part and work on the JavaScript file only. So, I use filemtime
for file versioning within my PHP and maintain semantic versioning within the JavaScript file properly. filemtime
helps me accomplish this. It returns the epoch time (e.g., 1529330834
) of when the file was written/changed. So as long as the file does not change or update, the file will be cached.
So here is how I do versioning for my scripts:
<?php | |
namespace MyPrefix\MyPluginOrTheme; | |
define( 'PLUGIN_VERSION', '1.0.0' ); | |
// Parent Theme Version | |
$ver = PLUGIN_VERSION . '-' . filemtime( get_template_directory() . "/myscript$suffix.js" ); | |
// Child Theme Version | |
$ver = PLUGIN_VERSION . '-' . filemtime( get_stylesheet_directory() . "/myscript$suffix.js" ); | |
// Plugin Version, assumes this PHP file is in the root of the plugin folder (e.g., ./wp-content/plugins/myplugin/script-dependencies.php) | |
$ver = PLUGIN_VERSION . '-' . filemtime( get_plugin_dir( __FILE__ ) . "/myscript$suffix.js" ); |
The resulting filename will be something like myscript.js?ver=1.0.0-1528229721
. This will make it easy to identify the version of the plugin while also ensuring that the correct version of the script is on the frontend. Even WordPress.com does something similar outputting files with the version of the file and a date (e.g., https://s1.wp.com/wp-content/js/mustache.js?ver=6.1.1-201824
).
Script Dependencies ($deps
)
Finally, $deps
are really cool because when I output the file, WordPress will also output all the dependencies without me having to do it...but more on that in the next section.
Registering Scripts in Themes
For themes, you register files like this (using the previous best practice with the dynamic suffix):
<?php | |
namespace MyPrefix\MyTheme; | |
define( 'MY_THEME_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the theme folder. | |
wp_register_script( | |
'myscript', // file slug | |
get_stylesheet_directory_uri() . "/js/myscript$suffix.js", // file URL | |
array(), // dependencies | |
MY_THEME_VERSION . '-' . filemtime( get_stylesheet_directory() . "/js/myscript$suffix.js" ), // version | |
true // in footer or not | |
); | |
wp_register_script( | |
'jquery-myscript', // file slug | |
get_stylesheet_directory_uri() . "/js/jquery.myscript$suffix.js", // file URL | |
array( 'jquery' ), // dependencies | |
MY_THEME_VERSION . '-' . filemtime( get_stylesheet_directory() . "/js/jquery.myscript$suffix.js" ), // version | |
true // in footer or not | |
); | |
} |
Alternatively, you could use a theme script register helper:
<?php | |
namespace MyPrefix\MyTheme; | |
define( 'MY_THEME_VERSION', '1.0.0' ); | |
/** | |
* Register my scripts. | |
* | |
* @param string $filename Filename. | |
* @param string $theme_relative_path Relative path to file within theme folder. | |
*/ | |
function register_theme_script( $filename, $theme_relative_path = '/', $dependencies = array(), $footer = true ) { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
wp_register_script( | |
str_replace( '.', '-', $filename ), // file slug without js extension | |
get_stylesheet_directory_uri() . "{$theme_relative_path}{$filename}{$suffix}.js", // file URL | |
$dependencies, // dependencies | |
MY_THEME_VERSION . '-' . filemtime( get_stylesheet_directory() . "{$theme_relative_path}{$filename}{$suffix}.js" ), // version | |
$footer // in footer or not | |
); | |
} |
Registering Scripts in Plugins
For plugins, you register files like this (using the previous best practice with the dynamic suffix):
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'myscript', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/myscript{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/myscript{$suffix}.js" ), // version | |
true | |
); | |
wp_register_script( | |
'jquery-myscript', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/jquery.myscript{$suffix}.js", // file URL | |
array( 'jquery' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/jquery.myscript{$suffix}.js" ), // version | |
true | |
); | |
} |
Alternatively, you could use a plugin script register helper:
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
// Should be placed at the root of the plugin folder | |
define( 'MY_PLUGIN_FILE', __FILE__ ); | |
/** | |
* Register my scripts. | |
* | |
* @param string $filename Filename. | |
* @param string $theme_relative_path Relative path to file within theme folder. | |
*/ | |
function register_plugin_script( $filename, $theme_relative_path = '/', $dependencies = array(), $footer = true ) { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
wp_register_script( | |
str_replace( '.', '-', $filename ), // file slug without js extension | |
get_plugin_dir( MY_PLUGIN_FILE ) . "{$theme_relative_path}{$filename}{$suffix}.js", // file URL | |
$dependencies, // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "{$theme_relative_path}{$filename}{$suffix}.js" ), // version | |
$footer // in footer or not | |
); | |
} |
Deregistering JavaScript Files
Deregistering is extremely straight forward as you can see here:
<?php | |
namespace MyPrefix\MyPluginOrTheme; | |
// Deregister script by handle | |
wp_deregister_script( 'myscript' ); |
However, actually deregistering a script can be quite tricky. The main problem with deregistering scripts is deregistering AFTER it has been registered. For plugins, this is why registering a script and enqueueing (or just enqueueing) a script in the same function (without a do_action
between them) is an unfriendly and bad idea.
For example, here are three bad ways to do it:
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function wp_enqueue_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_enqueue_script( | |
'plugin-script', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js" ), // version | |
true | |
); | |
} |
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function wp_enqueue_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'plugin-script', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js" ), // version | |
true | |
); | |
wp_enqueue_script( 'plugin-script' ); | |
} |
This last one looks that most right but the problem is that they both are hooked at the same priority and there is no way to hook in between the functions to do anything.
Here is a better way:
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'plugin-script', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js" ), // version | |
true | |
); | |
} | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts', 99 ); | |
/** | |
* Register my scripts. | |
*/ | |
function wp_enqueue_scripts() { | |
wp_enqueue_script( 'plugin-script' ); | |
} |
This way, I can hook in between the functions within wp_enqueue_scripts
hook to make any changes necessary.
Here is the best way (IMHO):
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'plugin-script', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js" ), // version | |
true | |
); | |
} | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function wp_enqueue_scripts() { | |
wp_enqueue_script( 'plugin-script' ); | |
} |
So when do you deregister a script? You deregister a script for two primary reasons:
- If you want to re-register the script with different arguments (primarily
$src
URL). - If you want to ensure the script never gets placed on the frontend period.
Warning: Whenever you deregister a script (e.g., myscript) without re-registering a new version of that script, any script dependent upon that script (e.g., myscript) will not output either.
So, consider this example. In wp-config.php
, we have define( 'SCRIPT_DEBUG', true );
. Now, let's say we have this in an active plugin:
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'plugin-script-base', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script-base{$suffix}.js", // file URL | |
array( 'underscore' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script-base{$suffix}.js" ), // version | |
true | |
); | |
wp_register_script( | |
'plugin-script', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js", // file URL | |
array( 'plugin-script-base' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js" ), // version | |
true | |
); | |
wp_register_script( | |
'plugin-script-ext', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script-ext{$suffix}.js", // file URL | |
array( 'plugin-script' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script-ext{$suffix}.js" ), // version | |
true | |
); | |
} | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts', 999 ); | |
/** | |
* Outputs our plugin scripts | |
*/ | |
function wp_enqueue_scripts() { | |
wp_enqueue_script( 'plugin-script-ext' ); | |
} |
With a single statement, our use of the active plugin outputs three files on the frontend in this order:
js/plugin-script-base.js
js/plugin-script.js
js/plugin-script-ext.js
Now in our theme, say we have this in the functions.php
file:
<?php | |
namespace MyPrefix\MyTheme; | |
define( 'MY_THEME_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\deregister_scripts', 999 ); | |
/** | |
* Register my scripts. | |
*/ | |
function deregister_scripts() { | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_deregister_script( 'plugin-script-base' ); | |
} |
This function within functions.php
will cause plugin-script
and plugin-script-ext
NOT to be placed on the frontend either because the WordPress script manager assumes that since plugin-script
and plugin-script-ext
are dependent upon plugin-script-base
, they also won't work if plugin-script-base
is missing. So WordPress will not output those files.
A Note
Many seasoned non-WordPress PHP developers will want to use their own version of jQuery. So they will deregister WordPress's jQuery and register their own. This may be a great idea for your own project ONLY that you will always control, but it is a terrible idea for any free/premium plugins and/or themes. Here's why:
- Other themes/plugins depend on the same version of that script WordPress provides, so you may break the theme or plugins accidentally. So if you deregister it and register your own (even if the same version), when WordPress upgrades that script (i.e., jQuery), it could break everything. Alternatively, if you deregister jQuery (for example) and register another specific version (older or newer), then you may break current plugins.
- WordPress may upgrade that script for security reasons and now you have opened a security hole. This will cause a massive amount of support requests if you sell a premium theme/plugin and cause harm to your brand/reputation. Or, you may not even be around to fix the problem for your client.
This is not client/customer friendly at all. As a developer, we need to get over our own opinions that using a CDN version or a specific version of a script is better and accept this part of WordPress and the WordPress ecosystem. Simply, play nice!
Enqueueing and Dequeueing Scripts
So far, we have only properly registered (or deregistered) the scripts within WordPress but that does not output the script tags on the frontend. To output the scripts on the frontend, you need to queue the script to be outputted by the WordPress script manager. To do this, you will need to use wp_enqueue_script
.
wp_enqueue_script
does two things:
- Registers the script if it has not been registered already.
- Outputs any files on the frontend during the
wp_head
hook viaprint_head_scripts
(which callswp_print_scripts
hook) andwp_footer
hook via_wp_footer_scripts
which callsprint_late_styles()
andprint_footer_scripts()
.
Sometimes, however, plugins and themes will just enqueue all their scripts even if the page doesn't use that particular script. They do this mostly because they provide their functionality through a shortcode and don't know how to properly test the page to determine whether that shortcode exists or not (blog post coming soon). In these cases, to increase the performance of your site, you may want to remove them. To do this, you will need to use wp_dequeue_script
. Let's say that our script was "properly" registered on init
and enqueued on wp_enqueue_scripts
hooks. To dequeue the script, you just need to do this:
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\remove_script' ); | |
/** | |
* Register my scripts. | |
*/ | |
function remove_script() { | |
// Always dequeue the script | |
wp_dequeue_script( 'plugin-script' ); | |
// Conditionally dequeue the script on archive pages | |
if ( is_archive() ) { | |
wp_dequeue_script( 'plugin-script' ); | |
} | |
} |
Localizing Scripts
Sometimes, our scripts need some additional information or configuration for whatever reason (localizing strings, configuring a JavaScript object, etc). This is done via wp_localize_script
. wp_localize_script
takes three parameters: $handle
(string), $object_name
(string), and $l10n
(array).
When you localize your script, the object name (the second parameter) is the name of your JavaScript object, so best practice is that this parameter string would be camelCase. There are no checks within WordPress, so if you pass something like 'my-object'
, your JavaScript will fail.
Whatever PHP array is passed to wp_localize_script
will be placed ahead of your external script tag. You can organize the $l10n
array however you'd like, but over time, I have learned and now prefer to do something like this:
$plugin_l10n = array( | |
'strings' => array( | |
'firstLabel' => __( 'Some string to translate', 'textdomain' ), | |
'secondLabel' => __( 'Some string to translate', 'textdomain' ), | |
), | |
'config' => array( | |
'something' => 'some-value', | |
'somethingElse' => 3, | |
), | |
'debug' => ( ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) || ( ! defined( 'SCRIPT_DEBUG' ) && defined( 'WP_DEBUG' ) && WP_DEBUG ) ), | |
); |
Again, please note that the string properties are camelCased.
Your JavaScript object will be placed in the Global Namespace. In our example, the JavaScript object is named pluginScriptExt
. Therefore, our object is available globally via window.pluginScriptExt
, so you must always prefix your objects unless you overwrite another object (if you need more information, see Mozilla's Global Object definition).
For example, if we take our script plugin-script-base
from above, it is dependent on underscore
. Underscore has a global JavaScript object named _
. If we were to do this, we would render Underscore useless and _.each
would not be available; instead _.strings
would be.
// Overwrites underscore | |
wp_localize_script( 'plugin-script-ext', '_', $plugin_script_base_l10n ); |
Alternatively, calling wp_localize_script
twice on the same script with the same object name will overwrite the object, not extend the object. For example,
// Create empty pluginScriptExt object -- won't work | |
wp_localize_script( 'plugin-script-ext', 'pluginScriptExt', array() ); | |
// Add .config to pluginScriptExt object -- won't work | |
wp_localize_script( 'plugin-script-ext', 'pluginScriptExt', array( | |
'strings' => array( | |
'firstLabel' => __( 'Some string to translate', 'textdomain' ), | |
'secondLabel' => __( 'Some string to translate', 'textdomain' ), | |
), | |
) ); | |
// Add .strings to pluginScriptExt object -- won't work | |
wp_localize_script( 'plugin-script-ext', 'pluginScriptExt', array( | |
'config' => array( | |
'something' => 'some-value', | |
'somethingElse' => 3, | |
), | |
) ); |
With this example, you will have:
// Overwritten | |
var pluginScriptExt = { | |
config: { | |
something: 'some-value', | |
somethingElse: 3 | |
} | |
}; |
So, if you want to "expand" an object, it is better to create two objects (until WP_Scripts expands its capabilities).
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'plugin-script-base', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script-base{$suffix}.js", // file URL | |
array( 'underscore' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script-base{$suffix}.js" ), // version | |
true | |
); | |
wp_register_script( | |
'plugin-script', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js", // file URL | |
array( 'plugin-script-base' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js" ), // version | |
true | |
); | |
wp_register_script( | |
'plugin-script-ext', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script-ext{$suffix}.js", // file URL | |
array( 'plugin-script' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script-ext{$suffix}.js" ), // version | |
true | |
); | |
// Create base object for pluginScriptExt object. | |
wp_localize_script( 'plugin-script-ext', 'pluginScriptExtStrings', array( | |
'firstLabel' => __( 'Some string to translate', 'textdomain' ), | |
'secondLabel' => __( 'Some string to translate', 'textdomain' ), | |
) ); | |
// Create base object for pluginScriptExt object. | |
wp_localize_script( 'plugin-script-ext', 'pluginScriptExtConfig', array( | |
'something' => 'some-value', | |
'somethingElse' => 3, | |
) ); | |
} | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts', 999 ); | |
/** | |
* Outputs our plugin scripts | |
*/ | |
function wp_enqueue_scripts() { | |
wp_enqueue_script( 'plugin-script-ext' ); | |
} |
With this example, you will have:
// As 2 objects | |
var pluginScriptExtStrings = { | |
firstLabel: 'Some string to translate', // localized | |
secondLabel: 'Some string to translate', // localized | |
}; | |
var pluginScriptExtConfig = { | |
something: 'some-value', | |
somethingElse: 3 | |
}; |
It is important to note that the script should either be registered (via wp_register_script
) or enqueued (via wp_enqueue_script
) before it can be localized.
Initializing Scripts
There are several scripts, like sliders, that require some sort of initialization. Take waypoints, for example. It requires initialization before it can be used on the page. For example:
var waypoint = new Waypoint({ | |
element: document.getElementById('waypoint'), | |
handler: function(direction) { | |
console.log('Scrolled to waypoint!') | |
} | |
}); |
To do this properly within WordPress, you need to use wp_add_inline_script
.
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my frontend scripts. | |
*/ | |
function register_scripts() { | |
if ( is_admin() ) { | |
return; | |
} | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'waypoints', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/waypoints{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/waypoints{$suffix}.js" ), // version | |
true | |
); | |
wp_register_script( | |
'my-waypoints', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/my-waypoints{$suffix}.js", // file URL | |
array( 'waypoints' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/my-waypoints{$suffix}.js" ), // version | |
true | |
); | |
} | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function wp_enqueue_scripts() { | |
wp_enqueue_script( 'my-waypoints' ); | |
wp_add_inline_script( 'my-waypoints', "var waypoint = new Waypoint({ | |
element: document.getElementById('waypoint'), | |
handler: function(direction) { | |
console.log('Scrolled to waypoint!') | |
} | |
})" ); | |
} | |
// Result | |
//<script type="text/javascript" src="https://domain.com/wp-content/plugins/myplugin/js/my-waypoints.js"></script> | |
//<script type="text/javascript"> | |
// var waypoint = new Waypoint({element: document.getElementById('waypoint'), | |
// handler: function(direction) { | |
// console.log('Scrolled to waypoint!') | |
// } | |
// }) | |
//</script> |
wp_add_inline_script
can also output JavaScript before a specific script tag. To do this:
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my frontend scripts. | |
*/ | |
function register_scripts() { | |
if ( is_admin() ) { | |
return; | |
} | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'waypoints', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/waypoints{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/waypoints{$suffix}.js" ), // version | |
true | |
); | |
wp_register_script( | |
'my-waypoints', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/my-waypoints{$suffix}.js", // file URL | |
array( 'waypoints' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/my-waypoints{$suffix}.js" ), // version | |
true | |
); | |
} | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function wp_enqueue_scripts() { | |
wp_enqueue_script( 'my-waypoints' ); | |
wp_add_inline_script( 'my-waypoints', 'console.log("before my-waypoints file")', 'before' ); | |
} | |
// Result | |
//<script type="text/javascript"> | |
// console.log("before my-waypoints file") | |
//</script> | |
//<script type="text/javascript" src="https://domain.com/wp-content/plugins/myplugin/js/my-waypoints.js"></script> |
If you use wp_localize_script
and wp_add_inline_script
, wp_localize_script
will always output before wp_add_inline_script
.
Using wp_add_inline_script
is a great way to keep your code clean using native WordPress functions and the script manager avoiding yet another hook and manually outputting this yourself.
It is important to note that the script should either be registered (via wp_add_inline_script
) or enqueued (via wp_add_inline_script
) before it can be initialized.
Extending the previous Waypoints example, let's only do something if waypoints
is not already registered. You do this by using wp_script_is
.
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Do nothing if the script is registered | |
if ( wp_script_is( $handle, 'registered' ) ) { | |
return; | |
} | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'waypoints', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/waypoints{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/waypoints{$suffix}.js" ), // version | |
true | |
); | |
} |
With wp_script_is
, you can check whether a script has been:
registered
enqueued
/queued
to_do
/to_print
done
/printed
wp_script_is
is quite powerful in debugging scripts and in trying to find when a script was registered, enqueued, and printed on the page.
For example, with wp_script_is( $handle, 'enqueued' )
, you can know that you can either deregister (if false) or dequeue the script (if true) in order to prevent that script from being printed on the page.
Finally (though this is becoming much less important), you can make your scripts conditional by adding conditional
data to the script. You can do this by using
<?php | |
namespace MyPrefix\MyPlugin; | |
define( 'MY_PLUGIN_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my frontend scripts. | |
*/ | |
function register_scripts() { | |
if ( is_admin() ) { | |
return; | |
} | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the plugin folder. | |
wp_register_script( | |
'plugin-script', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js", // file URL | |
array(), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script{$suffix}.js" ), // version | |
true | |
); | |
// IE only Script | |
wp_register_script( | |
'plugin-script-ie', | |
get_plugin_dir( MY_PLUGIN_FILE ) . "js/plugin-script-ie{$suffix}.js", // file URL | |
array( 'plugin-script' ), // dependencies | |
MY_PLUGIN_VERSION . '-' . filemtime( plugin_dir_url( MY_PLUGIN_FILE ) . "js/plugin-script-ie{$suffix}.js" ), // version | |
true | |
); | |
} | |
add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\wp_enqueue_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function wp_enqueue_scripts() { | |
wp_enqueue_script( 'plugin-script' ); | |
wp_script_add_data( 'plugin-script', 'conditional', 'lt IE 9' ); | |
} |
Modifying Script Tags on Print
Adding Additional Attributes to script
tag—async
and defer
WordPress does not automatically add async
or defer
to your script tags, nor is there an "easy" way to add it to the script manager (which is still being actively discussed in core (tickets 22249 and 12009).
First, what is the difference between defer and async? bitsofcode has two great diagrams that explains the difference between the two:
Simply stated both defer
and async
download/fetch the script immediately and do not pause/prevent further HTML parsing. The difference comes at JavaScript execution. defer
executes after the HTML parsing is completed and DOM is ready. Because of this adding defer
to scripts in the footer makes little sense. async
executes when the script comes ready.
To improve the performance of the page, you need to add these yourself. You can do this by using the script_loader_tag
filter, which gives you the HTML of the script being printed on the page, including the conditional HTML and any added inline script.
To add async or defer to all, you can do this:
<?php | |
namespace MyPrefix\MyTheme; | |
add_filter( 'script_loader_tag', __NAMESPACE__ . '\script_loader_tag_add_async', 10 ); | |
/** | |
* Filters the HTML script tag of an enqueued script. | |
* | |
* @param string $tag The `<script>` tag for the enqueued script. | |
* @param string $handle The script's registered handle. | |
* @param string $src The script's source URL. | |
* | |
* @return string Modified script tags including `async="async"`. | |
*/ | |
function script_loader_tag_add_async( $tag ) { | |
if ( false === strpos( $tag, 'async' ) ) { | |
return str_replace( ' src', ' async="async" src', $tag ); | |
} | |
} | |
add_filter( 'script_loader_tag', __NAMESPACE__ . '\script_loader_tag_add_defer', 10 ); | |
/** | |
* Filters the HTML script tag of an enqueued script. | |
* | |
* @param string $tag The `<script>` tag for the enqueued script. | |
* @param string $handle The script's registered handle. | |
* @param string $src The script's source URL. | |
* | |
* @return string Modified script tags including `async="async"`. | |
*/ | |
function script_loader_tag_add_defer( $tag ) { | |
if ( false === strpos( $tag, 'defer' ) ) { | |
return str_replace( ' src', ' defer="defer" src', $tag ); | |
} | |
} |
Alternatively, you can be more specific and do something like this:
<?php | |
namespace MyPrefix\MyTheme; | |
add_filter( 'script_loader_tag', __NAMESPACE__ . '\script_loader_tag', 10 ); | |
/** | |
* Filters the HTML script tag of an enqueued script. | |
* | |
* @param string $tag The `<script>` tag for the enqueued script. | |
* @param string $handle The script's registered handle. | |
* @param string $src The script's source URL. | |
* | |
* @return string Modified script tags including `async="async"`. | |
*/ | |
function script_loader_tag( $tag ) { | |
// Load AFTER a page has finished loading completely | |
$deferred = array( 'comment-reply', 'wp-emoji' ); | |
foreach ( $deferred as $script ) { | |
if ( false !== strpos( $tag, $script ) && false === strpos( $tag, 'defer' ) ) { | |
return str_replace( ' src', ' defer="defer" src', $tag ); | |
} | |
} | |
// Load asynchronously while page is loading | |
$async = array( 'wp-embed', 'myscript' ); | |
foreach ( $async as $script ) { | |
if ( false !== strpos( $tag, $script ) && false === strpos( $tag, 'async' ) ) { | |
return str_replace( ' src', ' async="async" src', $tag ); | |
} | |
} | |
return $tag; | |
} |
If you use the Genesis Framework, this is already built-in for you. All you need to do is register your script with either ?async=true
or ?defer=true
and Genesis will automagically add async or defer to the script tag for you!!
<?php | |
namespace MyPrefix\MyTheme; | |
define( 'MY_THEME_VERSION', '1.0.0' ); | |
add_action( 'init', __NAMESPACE__ . '\register_scripts' ); | |
/** | |
* Register my scripts. | |
*/ | |
function register_scripts() { | |
// Suffix | |
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; | |
// Assumes the JS file is located in the js folder within the theme folder. | |
wp_register_script( | |
'myscript', // file slug | |
get_stylesheet_directory_uri() . "/js/myscript$suffix.js?defer=true", // file URL with ?async=true added | |
array(), // dependencies | |
MY_THEME_VERSION . '-' . filemtime( get_stylesheet_directory() . "/js/myscript$suffix.js" ), // version | |
true // in footer or not | |
); | |
wp_register_script( | |
'jquery-myscript', // file slug | |
get_stylesheet_directory_uri() . "/js/jquery.myscript$suffix.js?async=true", // file URL with ?defer=true added | |
array( 'jquery' ), // dependencies | |
MY_THEME_VERSION . '-' . filemtime( get_stylesheet_directory() . "/js/jquery.myscript$suffix.js" ), // version | |
true // in footer or not | |
); | |
} |
Changing the Script Source—Cache Busting
You can use the script_loader_src
filter to cache bust everything.
DO NOT USE THIS IN PRODUCTION.
<?php | |
namespace MyPrefix\MyTheme; | |
add_filter( 'script_loader_src', __NAMESPACE__ . '\script_loader_src', 10, 2 ); | |
/** | |
* Cache bust all the scripts. | |
* | |
* @param string $src Script loader source path. | |
* @param string $handle Script handle. | |
* | |
* @return string | |
*/ | |
function script_loader_src( $src, $handle ) { | |
if ( 'plugin-script' !== $handle ) { | |
return $src; | |
} | |
return add_query_arg( 'wps', time(), $src ); | |
} |
Summary
With this overview of the native WordPress Script functions for script management, you should be able to use any type of JavaScript file in any plugin or theme extending parent theme or plugin JavaScript files.
- PHP Constants for Debugging JavaScript:
SCRIPT_DEBUG
allows you to manage whether your script outputs minified or not. You can also pass the value ofSCRIPT_DEBUG
towp_localize_script
to help you add debug messages within the console. - Registering and Deregistering Scripts: Use
wp_register_script
to register a script oninit
hook andwp_deregister_script
only if you plan to register an alternative to avoid any unintended effects. - Enqueueing and Dequeueing Scripts: Use
wp_enqueue_script
to output the script on the page andwp_dequeue_script
to prevent the script's output. - Localizing Scripts: Use
wp_localize_script
to output any configuration and translations of any strings used within your script. - Initializing Scripts: Use
wp_add_inline_script
to add additional inline scripts to initialize external scripts or any thing else than adding a configuration object. - Modifying Script Tags on Print: Use
script_loader_src
andscript_loader_tag
to make more advanced changes to thescript
tag including adding attributes likeasync
ordefer
, which Genesis makes easy for you.
Thank you to both Gary Jones and Mike Hemberger for pre-reviewing and providing some great insights!
Leave a Reply