[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