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

[WIP] Enqueue only required frontend styles - V2 #6200

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c580ac9
Added class for managing the storage of block types parsed from the HTML
Apr 8, 2018
27c4964
Added @since version to WP_Parsed_Block_Types_Registry class
Apr 8, 2018
b91904b
Added logic to find block type while stripping block comments from th…
Apr 8, 2018
855bb0e
Added a function to prefix the core namespace to the block type
Apr 8, 2018
30a1438
Merge branch 'master' into add/enqueue-only-required-frontend-styles
Apr 9, 2018
1e27566
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 9, 2018
d9afec7
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 10, 2018
da22012
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 11, 2018
d2d119e
WIP: Just adds a function gutenberg_enqueue_required_block_styles()
Apr 11, 2018
f17746c
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 12, 2018
76752c1
Added documentation blocks to functions responsible for stripping blo…
Apr 12, 2018
8ad1ec1
Added logic for enqueueing required frontend styles if they need to b…
Apr 12, 2018
64c4e48
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 12, 2018
7763fae
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 13, 2018
c89713a
Added documentation for enqueue_required_frontend_block_styles()
Apr 13, 2018
e3275b6
Removed trailing and unintended spaces using EditorConfig plugin
Apr 14, 2018
4beb6a1
Syntax fixes and renamed function which normalized block type
Apr 14, 2018
4715f77
Merge branch 'master' into add/enqueue-only-required-frontend-styles
Apr 14, 2018
3cc30e4
Basic framework for enqueueing styles for required block types in <he…
Apr 14, 2018
53049cc
Added a class WP_Block_Type_Validator for validating block types
Apr 15, 2018
e96e861
Disabled stripping of dynamic block comments while rendering dynamic …
Apr 15, 2018
59b1cd1
Added a check to only add unique block types to parsed block types re…
Apr 15, 2018
5eeaf4c
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 15, 2018
f00369b
Fixed an error in get_last_error() function in Block Type Validator
Apr 15, 2018
65bcbd6
Fixed unit tests for dynamic block rendering and stripping block types
Apr 15, 2018
3669dc6
Removed _doing_it_wrong() notices from Block Type validator class
Apr 15, 2018
5552539
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 16, 2018
7960d22
Renamed gutenberg_strip_block_comments to gutenberg_process_block_com…
Apr 16, 2018
ec63723
Called gutenberg_trigger_block_comments_processing() from the functio…
Apr 16, 2018
93b61ee
Used is_singular() to check if current requested page is a page or a …
Apr 16, 2018
ec82881
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 16, 2018
5d43d13
Coding standard: Added space before closing the array bracket
Apr 16, 2018
647a095
Added $added_from_version field to Errors in Block type validator
Apr 16, 2018
b1370d5
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 17, 2018
5cd1866
Merge branch 'master' into add/enqueue-only-required-frontend-styles-v2
Apr 17, 2018
9b29c10
Disabled block type parsing from HTML for pages listing multiple posts
Apr 17, 2018
47424e2
Merge branch 'master' into add/enqueue-only-required-frontend-styles-v2
Apr 18, 2018
c25addc
Improved documentation for class WP_Parsed_Block_Types_Registry
Apr 18, 2018
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
112 changes: 101 additions & 11 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,27 @@ function gutenberg_render_block( $block ) {
return '';
}

/**
* Prefixes the core namespace ('core/') to the block type name if the namespace isn't found in the block type name
*
* @since 2.7.0
*
* @param string $block_type Name of the block type.
* @return string Name of the block type, prefixed by the core namespace if needed.
*/
function gutenberg_normalize_block_type( $block_type ) {
$block_type = trim( $block_type );

$index_of_slash = strpos( $block_type, '/' );

// If namespace isn't found in the block type name, prefix it with 'core/'.
if ( false === $index_of_slash ) {
return 'core/' . $block_type;
}

return $block_type;
}

/**
* Parses dynamic blocks out of `post_content` and re-renders them.
*
Expand All @@ -123,7 +144,8 @@ function gutenberg_render_block( $block ) {
* @param string $content Post content.
* @return string Updated post content.
*/
function do_blocks( $content ) {
function gutenberg_render_dynamic_blocks( $content ) {

$rendered_content = '';

$dynamic_block_names = get_dynamic_block_names();
Expand Down Expand Up @@ -154,15 +176,14 @@ function do_blocks( $content ) {
}

// Since content is a working copy since the last match, append to
// rendered content up to the matched offset...
$rendered_content .= substr( $content, 0, $offset );
// rendered content up to the matched offset (including the opening tag)...
$rendered_content .= substr( $content, 0, $offset + strlen( $opening_tag ) );

// ...then update the working copy of content.
$content = substr( $content, $offset + strlen( $opening_tag ) );

// Make implicit core namespace explicit.
$is_implicit_core_namespace = ( false === strpos( $block_name, '/' ) );
$normalized_block_name = $is_implicit_core_namespace ? 'core/' . $block_name : $block_name;
$normalized_block_name = gutenberg_normalize_block_type( $block_name );

// Find registered block type. We can assume it exists since we use the
// `get_dynamic_block_names` function as a source for pattern matching.
Expand All @@ -188,20 +209,89 @@ function do_blocks( $content ) {
break;
}

// Update content to omit text up to and including closing tag.
// Update content to omit text up to the closing tag.
$end_tag = $block_match_end[0][0];
$end_offset = $block_match_end[0][1];

$content = substr( $content, $end_offset + strlen( $end_tag ) );
$content = substr( $content, $end_offset );
}
}

// Append remaining unmatched content.
$rendered_content .= $content;

// Strip remaining block comment demarcations.
$rendered_content = preg_replace( '/<!--\s+\/?wp:.*?-->\r?\n?/m', '', $rendered_content );

return $rendered_content;
}
add_filter( 'the_content', 'do_blocks', 9 ); // BEFORE do_shortcode().

/**
* For a single post / page, this function parses `block types` while stripping
* `block comments` from the post's HTML and calls WP_Parsed_Block_Types_Registry
* to save those parsed block types. It registers gutenberg_process_block_comment()
* as a callback function for preg_replace_callback(). For each block comment
* (matched by the Regex) in the post's HTML, gutenberg_process_block_comment()
* will get called.
*
* For pages containing multiple posts like category / archive / home page, this only strips
* block comments (doesn't save the parsed block types in WP_Parsed_Block_Types_Registry).
* We don't need to save `parsed block types` for such pages since intelligent enqueuing of
* front-end styles (only enqueue styles for `block types` present in the post/page)
* only needs to happen for single posts / pages as of now.
*
* @since 2.7.0
*
* @param string $content Post content.
* @return string Updated post content (without block comments)
*/
function gutenberg_process_block_comments( $content ) {

// Checks if a single page/post is requested.
$is_post_or_page = is_singular( array( 'post', 'page' ) );

/*
* If a single page/post is requested, we need to parse and save `parsed block types` while stripping block comments.
* Otherwise, for category / archive / home page etc, we can simply strip block comments and return the HTML.
*/

if ( $is_post_or_page ) {
$content = preg_replace_callback( '/<!--\s+\/?wp:.*?-->\r?\n?/m', 'gutenberg_process_block_comment', $content );
} else {
$content = preg_replace( '/<!--\s+\/?wp:.*?-->\r?\n?/m', '', $content );
}

return $content;
}

/**
* Registered as a callback function to preg_replace_callback() and is called once for each
* block comment parsed from the post's HTML. It returns an empty string for each instantiation
* so that the post's HTML gets stripped of block comments.
*
* Also, it uses WP_Parsed_Block_Types_Registry to store block types parsed from the block comments.
* Those can be later used if needed.
*
* @since 2.7.0
*
* @param string $matches An array filled with the results of search.
* $matches[0] will contain the text that matched the full pattern,
* $matches[1] will have the text that matched the first captured parenthesized subpattern,
* and so on.
* @return string Returns an empty string to preg_replace_callback() for each of the block comments
*/
function gutenberg_process_block_comment( $matches ) {
$block_comment = $matches[0];

// Only process the block comment if it's not a closing tag for a block. If it's a closing tag, we can just return.
if ( preg_match( '/\/wp:/m', $block_comment ) !== 1 ) {

preg_match( '/wp:(.*?)\s+/m', $block_comment, $match );

$block_type_name = $match[1];
$block_type_name = gutenberg_normalize_block_type( $block_type_name );

WP_Parsed_Block_Types_Registry::get_instance()->add( $block_type_name );
}

return '';
}

add_filter( 'the_content', 'gutenberg_render_dynamic_blocks', 9 ); // BEFORE do_shortcode().
19 changes: 5 additions & 14 deletions lib/class-wp-block-type-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,13 @@ public function register( $name, $args = array() ) {
$name = $block_type->name;
}

if ( ! is_string( $name ) ) {
$message = __( 'Block type names must be strings.', 'gutenberg' );
_doing_it_wrong( __METHOD__, $message, '0.1.0' );
return false;
}
$block_type_validator = new WP_Block_Type_Validator();
$is_block_type_valid = $block_type_validator->validate( $name );

if ( preg_match( '/[A-Z]+/', $name ) ) {
$message = __( 'Block type names must not contain uppercase characters.', 'gutenberg' );
_doing_it_wrong( __METHOD__, $message, '1.5.0' );
return false;
}
if ( ! $is_block_type_valid ) {
$error = $block_type_validator->get_last_error();
+ _doing_it_wrong( __METHOD__, $error['error_text'], $error['added_from_version'] );

$name_matcher = '/^[a-z0-9-]+\/[a-z0-9-]+$/';
if ( ! preg_match( $name_matcher, $name ) ) {
$message = __( 'Block type names must contain a namespace prefix. Example: my-plugin/my-custom-block-type', 'gutenberg' );
_doing_it_wrong( __METHOD__, $message, '0.1.0' );
return false;
}

Expand Down
124 changes: 124 additions & 0 deletions lib/class-wp-block-type-validator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php
/**
* Blocks API: WP_Block_Type_Validator class
*
* @package gutenberg
* @since 2.7.0
*/

/**
* Core class used for validation of block type names
*
* @since 2.7.0
*/
class WP_Block_Type_Validator {

/**
* A container (array) for storing errors encountered by the validator.
*
* Each error is itself an array with `error_text` and `added_from_version` keys.
*
* @var array $errors Array for storing errors encountered by the Validator
*/
private $errors = array();

/**
* Validates a block type name
*
* @since 2.7.0
* @access public
*
* @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
* complete WP_Block_Type instance.
* @return true|false True on success, or false on failure.
*/
public function validate( $name ) {

if ( $name instanceof WP_Block_Type ) {
$name = $block_type->name;
}

if ( ! is_string( $name ) ) {
$message = __( 'Block type names must be strings.', 'gutenberg' );
$this->set_error( $message, '0.1.0' );

return false;
}

if ( preg_match( '/[A-Z]+/', $name ) ) {
$message = __( 'Block type names must not contain uppercase characters.', 'gutenberg' );
$this->set_error( $message, '1.5.0' );

return false;
}

$name_matcher = '/^[a-z0-9-]+\/[a-z0-9-]+$/';
if ( ! preg_match( $name_matcher, $name ) ) {
$message = __( 'Block type names must contain a namespace prefix. Example: my-plugin/my-custom-block-type', 'gutenberg' );
$this->set_error( $message, '0.1.0' );

return false;
}

return true;
}

/**
* Set an error in the validator
*
* This function can be used to set an error in the Validator
* Please note that this function adds the error to the existing $this->errors array
* It does not flush the existing errors object
*
* @param string $error_text A string denoting the error message.
* @param string $added_from_version A string denoting the version of WordPress where the error message was added.
*/
public function set_error( $error_text, $added_from_version = '' ) {
$this->errors[] = array(
'error_text' => $error_text,
'added_from_version' => $added_from_version,
);
}

/**
* Checks if the Validator encountered any errors in validation
*
* This function checks $this->errors array and returns true if there are any errors in it.
* If yes, it returns true. If no, it returns false.
*
* @return bool True/False
*/
public function has_errors() {
return ! empty( $this->errors ) ? true : false;
}

/**
* Get errors stored in the Validator
*
* This function returns the $this->errors array
*
* @return array An array is returned containing the errors stored in the Validator.
* The returned array is an array of errors.
* Each individual error is an array with `error_text` and `added_from_version` keys.
* If there are no errors stored, an empty array is returned
*/
public function get_errors() {
return $this->errors;
}

/**
* Get the last error encountered by the Validator
*
* @return array|bool An array containing `error_text` and `added_from_version` keys
* If there are no errors stored in the validator, FALSE (boolean) is returned
*/
public function get_last_error() {
$error_count = count( $this->errors );

if ( 0 === $error_count ) {
return false;
} else {
return $this->errors[ $error_count - 1 ];
}
}
}
Loading