Script Modules in 6.5

A new “Script Modules” interface has been introduced to support native JavaScript modules in WordPress. JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a user’s browser. https://www.javascript.com/. modules use import and export and are often referred to as ECMAScript Modules or “ESM”.

This post will refer to “scripts” and “modules” for Scripts and Script Modules concepts, respectively.

JavaScript modules are a coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. web technology with a number of benefits like import and export for proper scoping, forced strict mode, and deferred loading. It’s important for WordPress to support modules and encourage their use as the future of JavaScript development.

Script Modules APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways.

The interface is modeled after the existing scripts interface and should feel familiar. Modules are identified by a unique identifier (ID), analogous to a script handle.

The registered module ID should be identical to the imported module name in JavaScript. For example, the @wordpress/interactivity module can be used in another module as:

import * as interactivity from '@wordpress/interactivity';

Registered modules have an array of module dependencies. Dependencies can be a simple string (the dependency module ID) or an associative array of the following form:

$dependencies = array(
'id' => '@wordpress/interactivity',
'import' => 'dynamic' // Optional.
);

The array form optionally includes an import key with the value 'static' (default) or 'dynamic'. Dependencies that may not be needed or should only be loaded under some circumstances should use 'import' => 'dynamic'.

Note that these dependencies are module dependencies, not script dependencies. Script handles should not be listed as module dependencies.

The following functions are the most relevant parts of the interface for developers working with modules:

  • wp_register_script_module( string $id, string $src, array $deps = array(), $version = false ): Registers the module.
  • wp_enqueue_script_module( string $id, string $src = '', array $deps = array(), $version = false ): Marks the module to be enqueued. An optional $src argument can be provided to enqueue and register the module.
  • wp_deregister_script_module( string $id ): Deregisters the module.
  • wp_dequeue_script_module( string $id ): Unmarks the module so it is not enqueued.

Important notes

It’s strongly recommended that developers currently utilizing JavaScript modules in their extensions migrate to the Script Modules API. This will standardize behavior, bring a number of benefits, and prevent some of the following potential issues:

  • Any code that is currently using import maps may run into compatibility issues because multiple import maps are not currently supported.
  • If a module appears before an import map, an error will be triggered that causes the import map to be ignored. This would break some critical functionality that the Script Modules API relies on.

It’s important to migrate to the new API to prevent these issues. If it’s unfeasible to migrate, moving modules script to the footer may mitigate some of the potential issues.

Available modules

Two core modules are provided with 6.5.

@wordpress/interactivity is the interface for building interactive frontends with the Interactivity API.

@wordpress/interactivity-router defines an Interactivity API store with the core/router namespace, exposing relevant state and actions for client-side navigation.

There are no other core modules available at this time. The JavaScript packages available as scripts before 6.5 are not available as modules. For example, the @wordpress/api-fetch package corresponds to the wp-api-fetch script and is not available in WordPress as a module.

Working with Script Modules

The most relevant functions for working with modules are wp_register_script_module to register and wp_enqueue_script_module to enqueue modules. For example:

// Registers a module, it can now appear as a dependency or be enqueued.
wp_register_script_module(
'@my-plugin/shared',
plugin_dir_url( __FILE__ ) . 'shared.js'
);

// Registers and enqueues a module.
wp_enqueue_script_module(
'@my-plugin/entry',
plugin_dir_url( __FILE__ ) . 'entry.js',
array( '@my-plugin/shared' )
);

// Enqueues a previously registered module.
wp_enqueue_script_module( '@my-plugin/a-registered-module' );

In this example, the @my-plugin/shared module will be available to the @my-plugin/entry module:

// "@my-plugin/entry" module at …/entry.js
import * as shared from '@my-plugin/shared';
shared.doSomethingInteresting();

Blocks

WordPress 6.5 introduces a new viewScriptModule block metadata field that will handle module registration and enqueue automatically analogous to viewScript. A full dev note about viewScriptModule can be read here.

Module registration

Developers familiar with modern JavaScript development will recognize the module system and may wonder, “Do I have to register all my modules with WordPress in order to use them?” The short answer is no, but the recommended approach is to register all the modules.

JavaScript modules can import be imported by URLs. It’s common to see imports like import { utility } from './utilities.js' in modern applications. Because the Script Modules API is built on top of native JavaScript modules, it’s possible for modules to import relative paths in WordPress. However, the recommended approach is to register modules and use the registered ID to import the module. This has a few benefits:

  • Registered modules are available globally. There’s no need to worry about the module URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org.
  • Registered modules have versions. The Script Modules API transparently handles associating a module ID with the correct URL and version.
  • Risk of module duplication is reduced. If a module is imported through different URLs, the browser may fetch and execute multiple copies of what is ostensibly the same module.
  • Dependencies that are imported statically will be preloaded when the module is enqueued. This allows the browser to fetch them in parallel.

Relative imports to unregistered modules may be useful for prototyping and exploration, but the best practice for production code is to register modules with the Script Modules API.

Limitations

Modules and scripts are not compatible at this time. Modules cannot depend on scripts and scripts cannot depend on modules. This means that modules cannot depend on scripts like wp-i18n, wp-api-fetch, etc.

There is a workaround to use scripts from modules. Script functionality is shared by adding variables to the window scope, for example wp-api-fetch can be accessed via window.wp.apiFetch. To use scripts from modules at this time, developers must:

  • Ensure that the script is enqueued: wp_enqueue_script( ‘wp-api-fetch’ )
  • Access the script through the global scope: const { apiFetch } = window.wp

There are a number of downsides to this workaround, but it can be an effective solution. Follow Core Trac ticket #60647 for work on script and module interoperability.

Compatibility

JavaScript modules have excellent support in most browsers today. In addition, the Script Modules API includes a polyfill for browsers with lacking or incomplete support to make the number of supported browsers even greater.

Technical details

A full explanation of how the Script Modules API works is beyond the scope of this note, but this section will provide a brief overview.

The Script Modules API will track registered and enqueued modules. For each enqueued module, a <script type="module"> tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.) will be added to load and execute the module.

A <script type="importmap"> tag will be added. The import map tells the browser how to map module IDs to URLs so that when it sees an import for '@wordpress/interactivity', it knows that the module should be fetched from the URL /wp-includes/js/dist/interactivity.min.js?ver=xyz. The import map includes all the dependencies of all enqueued modules, and all the dependencies of those dependencies, etc. The import map will not include dependencies of all the registered modules, only those that may be required on the page as dependencies, and it will not include the enqueued modules unless they are in the dependency graph of other enqueued modules.

A <link rel="modulepreload"> will be added for all static dependencies. Static dependencies are all dependencies that are not defined as array( 'id' => '…', 'import' => 'dynamic' ). This is an optimization hint for browsers, read more about it here.

Relevant links

Props

#6-5, #dev-notes, #dev-notes-6-5