Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactivity API: add wp_store #51191

Merged
merged 3 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php
/**
* WP_Interactivity_Store class
*
* Manages the initial state of the Interactivity API store in the server and
* its serialization so it can be restored in the browser upon hydration.
*
* @package Gutenberg
* @subpackage Interactivity API
*/

/**
* Manages the initial state of the Interactivity API store in the server and
* its serialization so it can be restored in the browser upon hydration.
*
* It's a private class, exposed by other functions, like `wp_store`.
*
* @access private
*/
class WP_Interactivity_Store {
/**
* Store.
*
* @var array
*/
private static $store = array();

/**
* Get store data.
*
* @return array
*/
static function get_data() {
return self::$store;
}

/**
* Merge data.
*
* @param array $data The data that will be merged with the exsisting store.
*/
static function merge_data( $data ) {
self::$store = array_replace_recursive( self::$store, $data );
}

/**
* Serialize store data to JSON.
*
* @return string|false Serialized JSON data.
*/
static function serialize() {
// TODO: Escape?
return wp_json_encode( self::$store );
}

/**
* Reset the store data.
*/
static function reset() {
self::$store = array();
}

/**
* Render the store data.
*/
static function render() {
if ( empty( self::$store ) ) {
return;
}

// TODO: decide the final id name.
luisherranz marked this conversation as resolved.
Show resolved Hide resolved
$store = self::serialize();
echo "<script id=\"wp-interactivity-store-data\" type=\"application/json\">$store</script>";
}
}
28 changes: 28 additions & 0 deletions lib/experimental/interactivity-api/scripts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
/**
* Utils to optimize the interactive scripts.
*
* @package Gutenberg
* @subpackage Interactivity API
*/

/**
* Move interactive scripts to the footer. This is a temporary measure to make
* it work with `wp_store` and it should be replaced with deferred scripts or
* modules.
*/
function gutenberg_interactivity_move_interactive_scripts_to_the_footer() {
// Move the @wordpress/interactivity package to the footer.
wp_script_add_data( 'wp-interactivity', 'group', 1 );

// Move all the view scripts of the interactive blocks to the footer.
$registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();
foreach ( array_values( $registered_blocks ) as $block ) {
if ( isset( $block->supports['interactivity'] ) && $block->supports['interactivity'] ) {
foreach ( $block->view_script_handles as $handle ) {
wp_script_add_data( $handle, 'group', 1 );
}
}
}
}
add_action( 'wp_enqueue_scripts', 'gutenberg_interactivity_move_interactive_scripts_to_the_footer', 11 );
26 changes: 26 additions & 0 deletions lib/experimental/interactivity-api/store.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* The functions that expose the store of the WP_Interactivity_Store class.
*
* @package Gutenberg
* @subpackage Interactivity API
*/

/**
* Merge data with the exsisting store.
*
* @param array $data Data that will be merged with the exsisting store.
*
* @return $data The current store data.
*/
function wp_store( $data = null ) {
if ( $data ) {
WP_Interactivity_Store::merge_data( $data );
}
return WP_Interactivity_Store::get_data();
}

/**
* Render the Interactivity API store in the frontend.
*/
add_action( 'wp_footer', array( 'WP_Interactivity_Store', 'render' ), 8 );
3 changes: 3 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( gutenberg_is_experiment_enabled( 'gutenberg-interactivity-api-core-blocks' ) ) {
require __DIR__ . '/experimental/interactivity-api/blocks.php';
}
require __DIR__ . '/experimental/interactivity-api/class-wp-interactivity-store.php';
require __DIR__ . '/experimental/interactivity-api/store.php';
require __DIR__ . '/experimental/interactivity-api/scripts.php';

// Fonts API.
if ( ! class_exists( 'WP_Fonts' ) ) {
Expand Down
2 changes: 1 addition & 1 deletion packages/interactivity/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const deepMerge = ( target, source ) => {
const getSerializedState = () => {
// TODO: change the store tag ID for a better one.
luisherranz marked this conversation as resolved.
Show resolved Hide resolved
const storeTag = document.querySelector(
`script[type="application/json"]#store`
`script[type="application/json"]#wp-interactivity-store-data`
);
if ( ! storeTag ) return {};
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php
/**
* `WP_Interactivity_Store` class test.
*
* @package Gutenberg
* @subpackage Interactivity API
*/

/**
* Tests for the `WP_Interactivity_Store` class.
*
* @group directives
* @covers WP_Interactivity_Store
*/
class WP_Interactivity_Store_Test extends WP_UnitTestCase {
public function set_up() {
// Clear the state before each test.
WP_Interactivity_Store::reset();
}

public function test_store_should_be_empty() {
$this->assertEmpty( WP_Interactivity_Store::get_data() );
}

public function test_store_can_be_merged() {
$data = array(
'state' => array(
'core' => array(
'a' => 1,
'b' => 2,
'nested' => array(
'c' => 3,
),
),
),
);
WP_Interactivity_Store::merge_data( $data );
$this->assertSame( $data, WP_Interactivity_Store::get_data() );
}

public function test_store_can_be_extended() {
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'a' => 1,
),
),
)
);
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'b' => 2,
),
'custom' => array(
'c' => 3,
),
),
)
);
$this->assertSame(
array(
'state' => array(
'core' => array(
'a' => 1,
'b' => 2,
),
'custom' => array(
'c' => 3,
),
),
),
WP_Interactivity_Store::get_data()
);
}

public function test_store_existing_props_should_be_overwritten() {
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'a' => 1,
),
),
)
);
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'a' => 'overwritten',
),
),
)
);
$this->assertSame(
array(
'state' => array(
'core' => array(
'a' => 'overwritten',
),
),
),
WP_Interactivity_Store::get_data()
);
}

public function test_store_existing_indexed_arrays_should_be_replaced() {
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'a' => array( 1, 2 ),
),
),
)
);
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'a' => array( 3, 4 ),
),
),
)
);
$this->assertSame(
array(
'state' => array(
'core' => array(
'a' => array( 3, 4 ),
),
),
),
WP_Interactivity_Store::get_data()
);
}

public function test_store_should_be_correctly_rendered() {
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'a' => 1,
),
),
)
);
WP_Interactivity_Store::merge_data(
array(
'state' => array(
'core' => array(
'b' => 2,
),
),
)
);
ob_start();
WP_Interactivity_Store::render();
$rendered = ob_get_clean();
$this->assertSame(
'<script id="wp-interactivity-store-data" type="application/json">{"state":{"core":{"a":1,"b":2}}}</script>',
$rendered
);
}
}