Quite often, you need to pass some values, references or some other pieces of data from the PHP side of your application to some JavaScript code running client side. Here are some quick tips on how to do it maybe slightly better.

Use the wp_add_inline_script Function

A lot of projects are using the wp_localize_script function to pass data from PHP to JavaScript; and that works just fine. However, this function was intended for UI translation texts only, and not for passing arbitrary data. And given that WordPress includes a function that allows you to add any kind of additional JavaScript (in the context of a specific asset handle), I would recommend using it.

The function is called wp_add_inline_script, and it has the following signature:

function wp_add_inline_script(
	string $handle,
	string $script,
	string $position = 'after'
): bool;

Instead of constructing your $data array/object first, and then passing script handle, variable name and data to wp_localize_script individually, you would specify the handle, and then pass a string containing the entire JavaScript code as second argument to wp_add_inline_script. The JavaScript code would have the JSON-encoded data inline, like so:

wp_add_inline_script(
	$handle,
	'var someNamespace = ' . json_encode( $data ) . ';'
);

Using wp_add_inline_script allows you not only to pass a data object (or scalar value) to JavaScript. Using it, you can also define functions, evaluate conditionals and perform other dynamic code logic. For one or the other use case, you would have had to use wp_add_inline_script anyway, so why not use it now, already? 🙂

There is one gotcha with wp_add_inline_script: the default value for the third argument, $position is set to 'after', which means that the inline script will come after the asset for the given handle. Depending on how you are accessing the data, this might be OK. If you are pulling this into a module file, in the file scope and not bound to something like domReady or so, this will not be OK, because the data has not yet been declared. You can easily get around that by explicitly passing 'before' as third argument to wp_add_inline_script.

Restrict Direct Access to a Single Place

Most of your (more modern) JavaScript code will make use of actual JavaScript modules, declared in individual files, which you then export and import. Accessing something via window, the global scope, should only be a rare thing to do. Yet, when we make data available via the above function(s), this is where it lands; available at window.someNamespace.

Depending on how many files need (pieces of) the data, you might end up with multiple files referencing window.someNamespace, which means directly accessing a global variable (which some people think is a bad thing to do).

Also depending on your JavaScript coding standards (or IDE configuration), you might get informed that you are accessing a potentially non-existing global variable. Of course, you can resolve that by either adding ESLint inline annotations such as /* global someNamespace */ to each of the files. Or, much better yet, by adding the someNamespace global to the globals section in your ESLint config.

Instead of the above, however, I recommend accessing the data in a single file only, from which you then export the settings as a regular module. This could look like so:

const { nextgenEditorSettings } = window;

export default nextgenEditorSettings;

In previous projects, I decided to call the file and module imports settings, just because this was what the majority of the individual pieces of data were. You can call it however you wish.

If you wanted, you could also decide to not export a single default namespace object, but instead export individual data items. To date, I haven’t felt the need to do this, but maybe it works best for you.

Given that this new file is essentially an abstraction layer for your data—abstracting consumer code accessing data from where that data actually lives—this means you can also alias or rename pieces of the data, in case you ever need to. Consumer code only cares about what this file/module exports, not what it internally does or references.

Wherever you need to access any of the data, you would then import the module, and destructure or directly access the things you need. For example, like so:

import settings from '../settings';

const { apiEndpoints, codeMetaKey } = settings;

// Do something with the data.

Also, turning the namespaced pieces of data into an actual JavaScript module defined in a single place, allows you to:

Add Inline Documentation to the Data

Another, in my opinion, huge benefit of exporting a settings module is that you can now actually add extensive documetation to the individual pieces of data. Thereby, the default module export is a (namespace) object, whereas any of the properties can be typed and explained as necessary, like so:

const { nextgenEditorSettings } = window;

/**
 * @type {object}
 * @property {object} apiEndpoints - Post API endpoint map.
 * @property {string} apiEndpoints.production - Production endpoint.
 * @property {string} apiEndpoints.staging - Staging endpoint.
 * @property {string} calculatorMarket - Site country option meta key
 * @property {string} codeMetaKey - Product Details code meta key.
 * @property {string} cptSlug - Product Details post type slug.
 * @property {number[]} disabledProducts - List of disabled product IDs.
 * @property {string} taxonomySlug - Product Category taxonomy slug.
 * @property {string} uploadsPath - NetStorage attachments upload path.
 */
export default nextgenEditorSettings;

Depending on your IDE, you get autocomplete and type support out of the box.

PhpStorm showing autocomplete information.

If you also document nested properties, for example, the apiEndpoints in the above example, this will get picked up, too:

Nested properties get picked up as well.

Any Other Tips?

Is there anything else you think should be mentioned (here) when talking about exposing PHP-accessible data to JavaScript?

Do you agree or disagree with any of the above?

Leave a Reply

Your email address will not be published. Required fields are marked *