WordPress 6.5 includes a new API that allows developers to finally work with JavaScript modules properly.
Using these two new functions, you can load JS modules in WordPress 6.5+:
wp_register_script_module
wp_enqueue_script_module
Both of these are pretty intuitiv and work just like their existing counterparts:
wp_register_script
wp_enqueue_script
The following demonstration will show how everything works, covering common real-world developer scenarios like working with jQuery. The demo loads four modules, three of which contain a simple function that outputs a message to the browser console. The final initialize script calls these methods in order.
There are few important things to know about this code demo:
plugin_dir_url
with get_stylesheet_directory_uri
. <script>
tags right before the </body>
tag. It’s operates like the in_footer
parameter is defaulting to true./**
* Modular JavaScript
*/
function enqueue_modules(): void {
$plugin_url = plugin_dir_url( __FILE__ );
$dependencies = [ [
'id' => 'jquery',
'import' => 'static', // Default
], [
'id' => 'module-1',
'import' => 'dynamic',
], [
'id' => 'module-2',
'import' => 'dynamic',
], [
'id' => 'module-3',
'import' => 'dynamic',
] ];
wp_register_script_module( 'module-1', $plugin_url . 'module-1.js' );
wp_register_script_module( 'module-2', $plugin_url . 'module-2.js' );
wp_register_script_module( 'module-3', $plugin_url . 'module-3.js' );
wp_enqueue_script_module( 'initialize', $plugin_url . 'initialize.js', $dependencies );
}
add_action( 'wp_enqueue_scripts', 'enqueue_modules' );
I’ve used wp_register_script_module
to register dependencies that will conditionally load. Conditionally loaded modules are possible with this pattern because I’ve set the import
parameter to dynamic
.
It also includes jquery
as a static dependency, which is a global script, not a module. I’ve included a demo to show how to handle this common real-world scenario.
<script type="module" src="https://www.kevinleary.net/wp-content/plugins/js-modules-demo/module-1.js" id="module-1-js-module"></script>
<script type="module" src="https://www.kevinleary.net/wp-content/plugins/js-modules-demo/module-2.js" id="module-2-js-module"></script>
<script type="module" src="https://www.kevinleary.net/wp-content/plugins/js-modules-demo/module-3.js" id="module-3-js-module"></script>
<script type="module" src="https://www.kevinleary.net/wp-content/plugins/js-modules-demo/initialize.js" id="initialize-js-module"></script>
Each individual module-*.js
contains a simple function export, and the initialize.js
serves as a bootstrap module that imports our other modules and executes them. Each one dumps a console.log("Module #* made this.")
in order.
module-1.js
export function moduleOne() {
console.log(`Module #1 made this.`);
}
module-2.js
export function moduleTwo() {
console.log(`Module #2 made this.`);
}
module-3.js
export function moduleThree() {
console.log(`Module #3 made this.`);
}
initialize.js
import { moduleOne } from "module-1";
import { moduleTwo } from "module-2";
import { moduleThree } from "module-3";
const $ = window.jQuery;
moduleOne();
moduleTwo();
moduleThree();
$(document).on("ready", () => {
console.log(`Good ol' DOM ready with jQuery.`);
});
The modules are executed sequentially, so these logs will always print in the same order.
Module #1 made this.
Module #2 made this.
Module #3 made this.
Good ol' DOM ready with jQuery.
Setting the dynamic
dependency property will conditionally load modules only when they’re imported. This is the real sweet sauce of working with JS modules, it allows us to loaded in only what we need on demand. It’s a great improvement over the outdated wp_enqueue_script
approach.
To provide an example: if the initialize.js
doesn’t use one of the dependent modules, then it’s not loaded. As an example, you can adjust the demo above to remove the use of moduleTwo
. This will not load module-2.js
, and will not add the associated message to the browser console.
import { moduleOne } from "module-1";
import { moduleThree } from "module-3";
moduleOne();
moduleThree();
Module #1 made this.
Module #3 made this.
This has been a large issue with numerous developers for some time. It took much longer than expected to get included in the core, but it’s finally here and is an exciting new addition.