PluginProbe
Gutenberg / 17.3.1
Gutenberg v17.3.1
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
gutenberg / lib / experimental / modules / class-gutenberg-modules.php

class-gutenberg-modules.php in Gutenberg 17.3.1, at lib/experimental/modules/class-gutenberg-modules.php

219 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Modules API: Gutenberg_Modules class.
4 *
5 * Native support for ES Modules and Import Maps.
6 *
7 * @package Gutenberg
8 * @subpackage Modules
9 */
10
11 /**
12 * Gutenberg_Modules class
13 */
14 class Gutenberg_Modules {
15 /**
16 * An array of registered modules, keyed by module identifier.
17 *
18 * @var array
19 */
20 private static $registered = array();
21
22 /**
23 * An array of queued modules.
24 *
25 * @var string[]
26 */
27 private static $enqueued = array();
28
29 /**
30 * Registers the module if no module with that module identifier already
31 * exists.
32 *
33 * @param string $module_identifier The identifier of the module. Should be unique. It will be used in the final import map.
34 * @param string $src Full URL of the module, or path of the script relative to the WordPress root directory.
35 * @param array $dependencies Optional. An array of module identifiers of the static and dynamic dependencies of this module. It can be an indexed array, in which case all the dependencies are static, or it can be an associative array, in which case it has to contain the keys `static` and `dynamic`.
36 * @param string|bool|null $version Optional. String specifying module version number. It is added to the URL as a query string for cache busting purposes. If SCRIPT_DEBUG is true, a timestamp is used. If it is set to false, a version number is automatically added equal to current installed WordPress version. If set to null, no version is added.
37 */
38 public static function register( $module_identifier, $src, $dependencies = array(), $version = false ) {
39 // Register the module if it's not already registered.
40 if ( ! isset( self::$registered[ $module_identifier ] ) ) {
41 $deps = array(
42 'static' => isset( $dependencies['static'] ) || isset( $dependencies['dynamic'] ) ? $dependencies['static'] ?? array() : $dependencies,
43 'dynamic' => isset( $dependencies['dynamic'] ) ? $dependencies['dynamic'] : array(),
44 );
45
46 self::$registered[ $module_identifier ] = array(
47 'src' => $src,
48 'version' => $version,
49 'dependencies' => $deps,
50 );
51 }
52 }
53
54 /**
55 * Enqueues a module in the page.
56 *
57 * @param string $module_identifier The identifier of the module.
58 */
59 public static function enqueue( $module_identifier ) {
60 // Add the module to the queue if it's not already there.
61 if ( ! in_array( $module_identifier, self::$enqueued, true ) ) {
62 self::$enqueued[] = $module_identifier;
63 }
64 }
65
66 /**
67 * Returns the import map array.
68 *
69 * @return array Associative array with 'imports' key mapping to an array of module identifiers and their respective source strings.
70 */
71 public static function get_import_map() {
72 $imports = array();
73 foreach ( self::get_dependencies( self::$enqueued, array( 'static', 'dynamic' ) ) as $module_identifier => $module ) {
74 $imports[ $module_identifier ] = $module['src'] . self::get_version_query_string( $module['version'] );
75 }
76 return array( 'imports' => $imports );
77 }
78
79 /**
80 * Prints the import map.
81 */
82 public static function print_import_map() {
83 $import_map = self::get_import_map();
84 if ( ! empty( $import_map['imports'] ) ) {
85 echo '<script type="importmap">' . wp_json_encode( self::get_import_map(), JSON_HEX_TAG | JSON_HEX_AMP ) . '</script>';
86 }
87 }
88
89 /**
90 * Prints all the enqueued modules using <script type="module">.
91 */
92 public static function print_enqueued_modules() {
93 foreach ( self::$enqueued as $module_identifier ) {
94 if ( isset( self::$registered[ $module_identifier ] ) ) {
95 $module = self::$registered[ $module_identifier ];
96 wp_print_script_tag(
97 array(
98 'type' => 'module',
99 'src' => $module['src'] . self::get_version_query_string( $module['version'] ),
100 'id' => $module_identifier,
101 )
102 );
103 }
104 }
105 }
106
107 /**
108 * Prints the link tag with rel="modulepreload" for all the static
109 * dependencies of the enqueued modules.
110 */
111 public static function print_module_preloads() {
112 foreach ( self::get_dependencies( self::$enqueued, array( 'static' ) ) as $dependency_identifier => $module ) {
113 echo '<link rel="modulepreload" href="' . $module['src'] . self::get_version_query_string( $module['version'] ) . '" id="' . $dependency_identifier . '">';
114 }
115 }
116
117 /**
118 * Prints the necessary script to load import map polyfill for browsers that
119 * do not support import maps.
120 *
121 * TODO: Replace the polyfill with a simpler version that only provides
122 * support for import maps and load it only when the browser doesn't support
123 * import maps (https://github.com/guybedford/es-module-shims/issues/371).
124 */
125 public static function print_import_map_polyfill() {
126 $import_map = self::get_import_map();
127 if ( ! empty( $import_map['imports'] ) ) {
128 wp_print_script_tag(
129 array(
130 'src' => gutenberg_url( '/build/modules/importmap-polyfill.min.js' ),
131 'defer' => true,
132 )
133 );
134 }
135 }
136
137 /**
138 * Gets the module's version. It either returns a timestamp (if SCRIPT_DEBUG
139 * is true), the explicit version of the module if it is set and not false, or
140 * an empty string if none of the above conditions are met.
141 *
142 * @param array $version The version of the module.
143 * @return string A string presenting the version.
144 */
145 private static function get_version_query_string( $version ) {
146 if ( SCRIPT_DEBUG ) {
147 return '?ver=' . time();
148 } elseif ( false === $version ) {
149 return '?ver=' . get_bloginfo( 'version' );
150 } elseif ( null !== $version ) {
151 return '?ver=' . $version;
152 }
153 return '';
154 }
155
156 /**
157 * Returns all unique static and/or dynamic dependencies for the received modules. It's
158 * recursive, so it will also get the static or dynamic dependencies of the dependencies.
159 *
160 * @param array $module_identifiers The identifiers of the modules to get dependencies for.
161 * @param array $types The type of dependencies to retrieve. It can be `static`, `dynamic` or both.
162 * @return array The array containing the unique dependencies of the modules.
163 */
164 private static function get_dependencies( $module_identifiers, $types = array( 'static', 'dynamic' ) ) {
165 return array_reduce(
166 $module_identifiers,
167 function ( $dependency_modules, $module_identifier ) use ( $types ) {
168 if ( ! isset( self::$registered[ $module_identifier ] ) ) {
169 return $dependency_modules;
170 }
171
172 $dependencies = array();
173 foreach ( $types as $type ) {
174 $dependencies = array_merge( $dependencies, self::$registered[ $module_identifier ]['dependencies'][ $type ] );
175 }
176 $dependencies = array_unique( $dependencies );
177 $dependency_modules = array_intersect_key( self::$registered, array_flip( $dependencies ) );
178
179 return array_merge( $dependency_modules, $dependency_modules, self::get_dependencies( $dependencies, $types ) );
180 },
181 array()
182 );
183 }
184 }
185
186 /**
187 * Registers a JavaScript module. It will be added to the import map.
188 *
189 * @param string $module_identifier The identifier of the module. Should be unique. It will be used in the final import map.
190 * @param string $src Full URL of the module, or path of the script relative to the WordPress root directory.
191 * @param array $dependencies Optional. An array of module identifiers of the static and dynamic dependencies of this module. It can be an indexed array, in which case all the dependencies are static, or it can be an associative array, in which case it has to contain the keys `static` and `dynamic`.
192 * @param string|bool|null $version Optional. String specifying module version number. It is added to the URL as a query string for cache busting purposes. If SCRIPT_DEBUG is true, a timestamp is used. If it is set to false, a version number is automatically added equal to current installed WordPress version. If set to null, no version is added.
193 */
194 function gutenberg_register_module( $module_identifier, $src, $dependencies = array(), $version = false ) {
195 Gutenberg_Modules::register( $module_identifier, $src, $dependencies, $version );
196 }
197
198 /**
199 * Enqueues a JavaScript module. It will be added to both the import map and a
200 * script tag with the "module" type.
201 *
202 * @param string $module_identifier The identifier of the module. Should be unique. It will be used in the final import map.
203 */
204 function gutenberg_enqueue_module( $module_identifier ) {
205 Gutenberg_Modules::enqueue( $module_identifier );
206 }
207
208 // Prints the import map in the head tag.
209 add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_import_map' ) );
210
211 // Prints the enqueued modules in the head tag.
212 add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_enqueued_modules' ) );
213
214 // Prints the preloaded modules in the head tag.
215 add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_module_preloads' ) );
216
217 // Prints the script that loads the import map polyfill in the footer.
218 add_action( 'wp_footer', array( 'Gutenberg_Modules', 'print_import_map_polyfill' ), 11 );
219