[wp-trac] [WordPress Trac] #60647: Script Modules: Allow modules to depend on existing WordPress scripts
WordPress Trac
noreply at wordpress.org
Fri Mar 1 12:42:29 UTC 2024
#60647: Script Modules: Allow modules to depend on existing WordPress scripts
-----------------------------+------------------------------
Reporter: jonsurrell | Owner: jonsurrell
Type: feature request | Status: assigned
Priority: normal | Milestone: Awaiting Review
Component: Script Loader | Version: trunk
Severity: normal | Resolution:
Keywords: | Focuses: javascript
-----------------------------+------------------------------
Comment (by jonsurrell):
I've spent a good amount of considering this and I'll share my ideas.
First some concepts and constraints followed by ideas for implementation.
I welcome questions, challenges, and feedback.
-----
Backwards compatibility is very important. Therefore, all of the currently
available scripts need to remain available for the foreseeable future.
Many existing scripts have side effects when they're loaded, such as
initialization of a store. They may also have state or singletons that do
not behave as expected when duplicated. See
[https://github.com/WordPress/gutenberg/issues/8981 Gutenberg issue #8981]
for details.
Scripts share functionality by attaching variables to the `window` object
to expose them, they're accessible through the global namespace. For
example. `wp-api-fetch` is accessible through `window.wp.apiFetch`.
Scripts can use modules through [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Operators/import "dynamic import"
(`import()`)]. `import()` is an async function (returns a `Promise`),
implying that scripts using modules necessarily become async.
Modules can trivially access script functionality as long as the order of
evaluation is correct (`window.wp.apiFetch` cannot be accessed before the
`wp-api-fetch` script has been fetched and evaluated).
-----
Given
- Scripts need to remain.
- Scripts should not be duplicated.
- Modules can use scripts easily via globals.
- Scripts using modules requires promises.
Some possible approaches:
First, modules ''could'' depend on scripts. Modules can access scripts
through globals, so all we need is for dependencies to correctly enqueue
scripts. This should be a simple change but sacrifices the developer
experience. Folks need to know whether they're using a module or a script,
and they need to include the correct dependency and use it in the correct
way: module dependencies are imported, script dependencies are used
through globals.
{{{
// Our dependent module's has a complicated dependencies array:
$dependencies = array(
'@wordpress/interactivity',
// Somehow we declare a script dependency (this form is not currently
valid)
array( 'import' => 'script', 'id' => 'wp-api-fetch' ),
);
// In the dependent module, we have a mix of imports and globals:
import * as interactivity from '@wordpress/interactivity';
const apiFetch = window.wp.apiFetch;
}}}
Another approach is to have proxy modules that export the globals provided
by a script. The proxy modules would still need to correctly enqueue the
associated scripts —there is a module->script dependency— but this would
be handled for Core scripts and not a public, maintaining a clear
separation between modules and scripts. The developer experience here is
improved, instead of depending on some scripts and some modules, modules
only depend on modules and developers would not need to access globals,
they'd only use `import`.
Here's what some proxy modules might look like:
{{{
// Module wrapper for wp-api-fetch script
// The @wordpress/api-fetch package uses a default export
const apiFetch = window.wp.apiFetch;
export default apiFetch;
// Module wrapper for wp-url script
// The @wordpress/url package uses named exports
export const addQueryArgs = window.wp.url.addQueryArgs
export const getPath = window.wp.url.getPath
export const isURL = window.wp.url.isURL
// etc…
}}}
The advantage to using these proxy modules is improved developer
experience:
{{{
// Our dependent module's dependencies are simpler:
$dependencies = array( '@wordpress/interactivity', '@wordpress/api-fetch'
);
// In the dependent module, we only use imports:
import apiFetch from '@wordpress/api-fetch';
import { addQueryArgs } from '@wordpress/url';
}}}
The proxy module approach also opens up a potential enhancement. WordPress
could include a proxy module and a full module version of scripts. When
preparing the modules, we could check whether the script is enqueued or
not. If the script is enqueued, we use the proxy module backed by the
script. If the script is not enqueued, we use the full module version and
the script does not need to be enqueued. This is a path where scripts can
remain available but are largely replaced by modules without sacrificing
backwards compatibility.
There are some drawbacks to the module proxy approach, mostly that we have
an additional request for the proxy module. One solution could be to print
the proxy module inline immediately after its associated module (or after
the importmap if that's not been printed yet). The proxy modules are
essentially lists of exports so are likely small in general.
Another potential downside is that this proposal focused on core scripts,
it's not focused on extenders. If the approach works well, we can consider
adding the appropriate extension points for extenders to follow the same
approach.
--
Ticket URL: <https://core.trac.wordpress.org/ticket/60647#comment:2>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list