WordPress JavaScript
From jQuery, Backbone and Gulp, to React, Lodash and webpack, JavaScript in WordPress has evolved a lot over time – and sometimes the old and the new both need to coexist.
This guide primarily covers the more modern JavaScript in WordPress.
Not the JavaScript way, the WordPress JavaScript way
Many traditional modern JavaScript projects manage assets and loading techniques differently than how JavaScript is handled inside WordPress.
A few key differences:
Abstractions
WordPress ships with React, a Redux-like store, and a babel runtime, but you don’t import React from 'react'
, the Redux-like store is @wordpress/data
and the babel runtime is called wp-polyfill
.
Most notably, WordPress has abstracted React as @wordpress/element
. In part due to past licensing issues, in part to add some WordPress-specific features and exclusions (no createClass
and PropTypes
) and in part to mitigate migration risk if the project ever chose to pivot to Preact, Vue or other future frameworks.
Dependency management
Often, vendor dependencies in a traditional JavaScript application are handled entirely by webpack or a build process. This build process might create a vendor bundle or isolate groups of dependencies by other methodologies. However, in WordPress, many isolated codebases need to coexist and interact.
In WordPress vendor dependencies are managed in PHP by a system of methods for registering and enqueing scripts, styles and webfonts. This allows Core, Plugins and Themes to share scripts and styles without tons of duplicate copies. In JavaScript, modules and methods are made available to the window
global. Often WordPress-specific code is available from window.wp
– so window.wp.element.render()
is where you can tap into React.render()
.
In precompiled source files, solutions like @wordpress/dependency-extraction-webpack-plugin
translate ES5 JavaScript import
statements into a PHP array()
of strings that map to the registered asset in WordPress – so you can write nice ES5 JavaScript that compiles-down to build files ready for WordPress’ unique JavaScript environment.
import { render } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { filter } from 'lodash';
Will get extracted as…
array(
'dependencies' => array(
'wp-element',
'wp-i18n',
'lodash',
)
)
Then…
- A script that uses WordPress’ registration system declares that dependency array.
- WordPress automatically enqueues those assets prior to loading the custom script.
- webpack will have resolved
@wordpress/element
, lodash and other bundled vendor packages using webpackglobal
where it can tap into the module.
Included WordPress packages and third-party vendor dependencies over bring-your-own
One natural tension in WordPress JavaScript development is whether to use included WordPress packages and vendor dependencies, or whether to add custom ones.
Often the wisest and safest path is to leverage what’s included, even if you think you can bring something that improves on the WordPress version.
Sometimes when WordPress doesn’t include something, or there’s a strong reason to use another solution, but in this case, it’s important to inspect scripts and stylesheets for potential collision. Particularly with stylesheets, WordPress applies CSS to top-level element selectors like <section>
, <h1>
, <h2>
that may conflict with components using a class-based selector system and expect to run in a more-pristine environment.
REFERNECE: WordPress/gutenberg packages
Caching
WordPress’ component library is loaded in instances of the Block Editor, and often cached in a user’s browser or perhaps an edge CDN the site is leveraging.
Using WordPress assets makes your application and WordPress itself faster and lighter because the asset gets cached on CDN and/or browser for future requests.
Compatibility
WordPress’ component library is designed to work within the WordPress Admin, an environment with 19 years of Core stylesheet specificity and potentially dozens of custom stylesheets. It’s never impossible to avoid conflict, but third-party authors often test with WordPress where they don’t test with all third-party WordPress solutions.
From a script standpoint, WordPress’ components are tested to be compatible with WordPress and avoid conflicts like two scripts tapping into the global namespace – i.e. expecting window.apiUrl
to be available.
Performance/File size
Due to the nature of shared hosting and creating distributed products, a 100kb dependency gets multiplied millions of times, so using something existing is a microoptimization that translates into a measurable impact on general delivery, update package payloads as well as disk space.
Making lazy-loaded assets work in WordPress
Modern React has some great features like <Suspense>
and React.lazy()
that allow us to build code-split monoliths with isolated code. These features can pretty easily be made to work in WordPress, but they don’t work in @wordpress/scripts
out-of-the-box. These assets also fall outside WordPress’ dependency system and need a way to be cache-busted between releases.
1. Add a webpack.config.js
to your project
Change the output directory to a versioned folder and use webpack-merge
or object spreading to rebuild to config.
{root}/webpack.config.js
const path = require('path');
const wpScriptsConfig = require('@wordpress/scripts/config/webpack.config');
const version = require('./package.json').version; // never require the entire config
const { merge } = require('webpack-merge');
const newConfig = {
output: {
path: path.resolve(process.cwd(), 'build/' + version)
}
}
module.exports = merge(wpScriptsConfig, newConfig);
2. Register your assets with WordPress
In addition to registration, we’ll need to make the runtime URL for the versioned build folder available.
$build_dir = PRODUCT_DIR . '/build/' . PRODUCT_VERSION;
$build_url = PRODUCT_URL . '/build/' . PRODUCT_VERSION;
$asset_file = $build_dir '/admin.asset.php';
if ( is_readable( $asset_file ) ) {
$asset = include_once $asset_file;
} else {
return;
}
wp_register_script(
'nfd-admin',
$build_url . '/admin.js',
$asset['dependencies'],
$asset['version'],
true
);
wp_add_inline_script(
'nfd-admin',
'var nfdProductPublicPath =' . wp_json_encode( $build_url ) . ';',
'before'
);
3. Before rendering your React App, set the webpack public path variable
if( 'undefined' !== typeof window.nfdProductPublicPath ) {
__webpack_public_path__ = window.nfdProductPublicPath;
}
// ...
render(
mountPoint,
<App />
);