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 - V1 #6166

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 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
872f4c3
Fixed linting errors reported by PHP CodeSniffer
Apr 14, 2018
fa50524
Added a class WP_Block_Type_Validator for validating block types
Apr 15, 2018
7af6736
Disabled stripping of dynamic block comments while rendering dynamic …
Apr 15, 2018
d0ca801
Changed text in a comment to reflect the new dynamic block rendering …
Apr 15, 2018
ac3a60a
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 15, 2018
a56cd90
Added a check to only add unique block types to parsed block types re…
Apr 15, 2018
173d19b
Fixed unit tests for dynamic block rendering and stripping block types
Apr 15, 2018
2e271ea
Fixed spaces around Concat operator in unit tests
Apr 15, 2018
f919a9f
Fixed an error in get_last_error() function in Block Type Validator
Apr 15, 2018
a97737e
Removed _doing_it_wrong notices from Block Type Validator class
Apr 15, 2018
0db02bd
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 16, 2018
8d6ae65
Used is_singular() to check if current requested page is a page or a …
Apr 16, 2018
d162365
Coding standard: Added space before closing the array bracket
Apr 16, 2018
013ffad
Assigned PHP_INT_MAX priority to functions for stripping block commen…
Apr 16, 2018
37d8123
Added $added_from_version field to Errors in Block type validator
Apr 16, 2018
e58d51f
Improved documentation for get_errors() function
Apr 16, 2018
a938396
Added error logging for - Not able to enqueue styles for block type i…
Apr 16, 2018
b0aab53
Merge remote-tracking branch 'upstream/master' into add/enqueue-only-…
Apr 17, 2018
6c79ec4
Merge branch 'master' into add/enqueue-only-required-frontend-styles
Apr 17, 2018
9e2696b
Moved a comment to a better suited place for it
Apr 17, 2018
5b19356
Disabled block type parsing from HTML for pages listing multiple posts
Apr 17, 2018
b9bc4d4
Merge branch 'master' into add/enqueue-only-required-frontend-styles
Apr 18, 2018
f6e5750
Improved documentation block for 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
120 changes: 109 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,97 @@ 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().

/*
* This needs to run as late as possible so as to let plugins apply filters to content first.
* Have subtracted 1 from the priority since `enqueue_required_frontend_block_styles()`
* needs to run after this. Have given that filter PHP_INT_MAX priority.
*/
add_filter( 'the_content', 'gutenberg_process_block_comments', PHP_INT_MAX - 1 );
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