Skip to content

Commit 0deafae

Browse files
sirrealcarstingaxion
authored andcommitted
Script modules: Add server to client data passing (WordPress#61658)
Add a `scriptmoduledata_{ MODULE_ID }` filter that allows data to be embedded in page HTML for use by Script Modules. For example: add_filter( 'scriptmoduledata_MyScriptModuleID', function ( array $data ): array { $data['doesIt'] = 'it works'; return $data; } ); See the proposal for details: https://make.wordpress.org/core/2024/05/06/proposal-server-to-client-data-sharing-for-script-modules/
1 parent 623651f commit 0deafae

File tree

2 files changed

+99
-8
lines changed

2 files changed

+99
-8
lines changed

lib/experimental/script-modules.php

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,94 @@ function gutenberg_dequeue_module( $module_identifier ) {
197197
_deprecated_function( __FUNCTION__, 'Gutenberg 17.6.0', 'wp_dequeue_script_module' );
198198
wp_script_modules()->dequeue( $module_identifier );
199199
}
200+
201+
202+
/**
203+
* Print data associated with Script Modules in Script tags.
204+
*
205+
* This embeds data in the page HTML so that it is available on page load.
206+
*
207+
* Data can be associated with a given Script Module by using the
208+
* `scriptmoduledata_{$module_id}` filter.
209+
*
210+
* The data for a given Script Module will be JSON serialized in a script tag with an ID
211+
* like `wp-scriptmodule-data_{$module_id}`.
212+
*/
213+
function gutenberg_print_script_module_data(): void {
214+
$get_marked_for_enqueue = new ReflectionMethod( 'WP_Script_Modules', 'get_marked_for_enqueue' );
215+
$get_import_map = new ReflectionMethod( 'WP_Script_Modules', 'get_import_map' );
216+
217+
$modules = array();
218+
foreach ( array_keys( $get_marked_for_enqueue->invoke( wp_script_modules() ) ) as $id ) {
219+
$modules[ $id ] = true;
220+
}
221+
foreach ( array_keys( $get_import_map->invoke( wp_script_modules() )['imports'] ) as $id ) {
222+
$modules[ $id ] = true;
223+
}
224+
225+
foreach ( array_keys( $modules ) as $module_id ) {
226+
/**
227+
* Filters data associated with a given Script Module.
228+
*
229+
* Script Modules may require data that is required for initialization or is essential to
230+
* have immediately available on page load. These are suitable use cases for this data.
231+
*
232+
* This is best suited to a minimal set of data and is not intended to replace the REST API.
233+
*
234+
* If the filter returns no data (an empty array), nothing will be embedded in the page.
235+
*
236+
* The data for a given Script Module, if provided, will be JSON serialized in a script tag
237+
* with an ID like `wp-scriptmodule-data_{$module_id}`.
238+
*
239+
* The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID that
240+
* the data is associated with.
241+
*
242+
* @param array $data The data that should be associated with the array.
243+
*/
244+
$data = apply_filters( "scriptmoduledata_{$module_id}", array() );
245+
246+
if ( is_array( $data ) && ! empty( $data ) ) {
247+
/*
248+
* This data will be printed as JSON inside a script tag like this:
249+
* <script type="application/json"></script>
250+
*
251+
* A script tag must be closed by a sequence beginning with `</`. It's impossible to
252+
* close a script tag without using `<`. We ensure that `<` is escaped and `/` can
253+
* remain unescaped, so `</script>` will be printed as `\u003C/script\u00E3`.
254+
*
255+
* - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E.
256+
* - JSON_UNESCAPED_SLASHES: Don't escape /.
257+
*
258+
* If the page will use UTF-8 encoding, it's safe to print unescaped unicode:
259+
*
260+
* - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`).
261+
* - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when
262+
* JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was
263+
* before PHP 7.1 without this constant. Available as of PHP 7.1.0.
264+
*
265+
* The JSON specification requires encoding in UTF-8, so if the generated HTML page
266+
* is not encoded in UTF-8 then it's not safe to include those literals. They must
267+
* be escaped to avoid encoding issues.
268+
*
269+
* @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements.
270+
* @see https://www.php.net/manual/en/json.constants.php for details on these constants.
271+
* @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing.
272+
*/
273+
$json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS;
274+
if ( 'UTF-8' !== get_option( 'blog_charset' ) ) {
275+
$json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
276+
}
277+
278+
wp_print_inline_script_tag(
279+
wp_json_encode( $data, $json_encode_flags ),
280+
array(
281+
'type' => 'application/json',
282+
'id' => "wp-scriptmodule-data_{$module_id}",
283+
)
284+
);
285+
}
286+
}
287+
}
288+
289+
add_action( 'wp_footer', 'gutenberg_print_script_module_data' );
290+
add_action( 'admin_print_footer_scripts', 'gutenberg_print_script_module_data' );

packages/interactivity/src/store.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,15 @@ export function store(
319319
}
320320

321321
export const parseInitialData = ( dom = document ) => {
322-
const storeTag = dom.querySelector(
323-
`script[type="application/json"]#wp-interactivity-data`
324-
);
325-
if ( storeTag?.textContent ) {
322+
const jsonDataScriptTag =
323+
// Preferred Script Module data passing form
324+
dom.getElementById( 'wp-scriptmodule-data_@wordpress/interactivity' ) ??
325+
// Legacy form
326+
dom.getElementById( 'wp-interactivity-data' );
327+
if ( jsonDataScriptTag?.textContent ) {
326328
try {
327-
return JSON.parse( storeTag.textContent );
328-
} catch ( e ) {
329-
// Do nothing.
330-
}
329+
return JSON.parse( jsonDataScriptTag.textContent );
330+
} catch {}
331331
}
332332
return {};
333333
};

0 commit comments

Comments
 (0)