JavaScript Modules in  WordPress

Loading JS modules in WordPress with the new wp_enqueue_script_module function.

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

Simple Demo

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.

PHP

There are few important things to know about this code demo:

  1. Assumes you’re working inside a plugin. If you’re working in a theme, replace plugin_dir_url with get_stylesheet_directory_uri.
  2. Modules always load <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.

HTML

<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>

JS

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.`);
});

Console

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.

Conditionally Loaded Dynamic Modules

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.

JS

import { moduleOne } from "module-1";
import { moduleThree } from "module-3";

moduleOne();
moduleThree();

Console

Module #1 made this.
Module #3 made this.

Summary

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.

Sources, Citations & Other Resources

Related Articles

Meet the Author

Kevin Leary, WordPress Consultant

I'm a custom WordPress web developer and analytics consultant in Boston, MA with 16 years of experience building websites and applications. View a portfolio of my work or request an estimate for your next project.