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

Tree-shaking block styles on the frontend #41020

Open
wants to merge 11 commits into
base: trunk
Choose a base branch
from
10 changes: 7 additions & 3 deletions bin/packages/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,12 @@ function createStyleEntryTransform() {
// block-library package also need rebuilding.
if (
packageName === 'block-library' &&
[ 'style.scss', 'editor.scss', 'theme.scss' ].includes(
path.basename( file )
)
[
'style.scss',
'editor.scss',
'theme.scss',
'styles/*.scss',
].includes( path.basename( file ) )
) {
entries.push( file );
}
Expand Down Expand Up @@ -217,6 +220,7 @@ if ( files.length ) {
`${ PACKAGES_DIR }/block-library/src/*/style.scss`,
`${ PACKAGES_DIR }/block-library/src/*/theme.scss`,
`${ PACKAGES_DIR }/block-library/src/*/editor.scss`,
`${ PACKAGES_DIR }/block-library/src/*/styles/*.scss`,
`${ PACKAGES_DIR }/block-library/src/*.scss`,
],
{
Expand Down
107 changes: 107 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,110 @@ function gutenberg_register_legacy_social_link_blocks() {
}

add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' );

if ( ! function_exists( 'wp_maybe_inline_block_style_parts' ) ) {
/**
* Inlines tree-shaked CSS for blocks, instead of a single file.
*
* @param array $metadata Metadata provided for registering a block type.
*/
function wp_maybe_inline_block_style_parts( $metadata ) {

// Bail early if style is empty or not an array.
if ( ! isset( $metadata['style'] ) || ! is_array( $metadata['style'] ) ) {
return $metadata;
}

// Compile an array of style-parts.
$styled_parts = array();
foreach ( $metadata['style'] as $key => $style ) {
// Skip item if "parts" and "style" are not set, or empty.
if ( empty( $style['parts'] ) || empty( $style['handle'] ) ) {
continue;
}

// Add the stylesheet to the array to be used below.
$styled_parts[ $style['handle'] ] = $style['parts'];

// Convert $metadata['style'] to an array removing the "parts" and "handle" keys.
$metadata['style'][ $key ] = $style['handle'];
}

// Bail early if wp_should_load_separate_core_block_assets() is false.
if ( ! wp_should_load_separate_core_block_assets() ) {
return $metadata;
}

// Bail early if there are no styled parts.
if ( empty( $styled_parts ) ) {
return $metadata;
}

/**
* Callback to add the style-parts to the block.
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
$callback = static function( $block_content, $block ) use ( $metadata, $styled_parts ) {
// Check that we're on the right block.
if ( $block['blockName'] !== $metadata['name'] ) {
return $block_content;
}

// Use a static variable to avoid adding the same part more than once.
static $style_parts_added = array();
if ( ! isset( $style_parts_added[ $block['blockName'] ] ) ) {
$style_parts_added[ $block['blockName'] ] = array();
}

// Add inline styles for the class-names that exist in the content.
foreach ( $styled_parts as $handle => $styled_parts ) {

global $wp_styles;
// Remove the default style. We'll be adding style-parts depending on the block content.
$wp_styles->registered[ $handle ]->src = '';
// Get the block's folder path which will be later used to get the individual files.
// Use the folder-path of the style.css file if available, otherwise fallback to the block.json parent folder.
$block_path = dirname( $metadata['file'] );
if ( ! empty( $wp_styles->registered[ $handle ]->extra['path'] ) ) {
$block_path = dirname( $wp_styles->registered[ $handle ]->extra['path'] );
}

// Unset the default style's path to prevent inlining the whole file.
unset( $wp_styles->registered[ $handle ]->extra['path'] );

// Add the style-parts to the block.
foreach ( $styled_parts as $part ) {

// Make sure this part has not already been added.
if ( in_array( $part, $style_parts_added[ $block['blockName'] ], true ) ) {
continue;
}

// Skip item if the block does not contain the defined string.
if ( false === strpos( $block_content, $part ) ) {
continue;
}

$file = $block_path . "/styles/{$part}.css";
if ( is_rtl() && file_exists( $block_path . "/styles/{$part}-rtl.css" ) ) {
$file = $block_path . "/styles/{$part}-rtl.css";
}
wp_add_inline_style( $handle, file_get_contents( $file ) );

// Add the part to the array of added parts.
$style_parts_added[ $block['blockName'] ][] = $part;
}
}
return $block_content;
};
add_filter( 'render_block', $callback, 10, 2 );

return $metadata;
}
}
// Add the filter. Using a priority of 1 ensures that this filter runs before others,
// so the "style" metadata can be properly formatted for subsequent filters.
add_filter( 'block_type_metadata', 'wp_maybe_inline_block_style_parts', 1, 2 );
15 changes: 14 additions & 1 deletion packages/block-library/src/paragraph/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,18 @@
"__unstablePasteTextInline": true
},
"editorStyle": "wp-block-paragraph-editor",
"style": "wp-block-paragraph"
"style": [
{
"handle": "wp-block-paragraph",
"parts": [
"has-background",
"has-drop-cap",
"has-text-color",
"is-large-text",
"is-larger-text",
"is-regular-text",
"is-small-text"
]
}
]
}
53 changes: 7 additions & 46 deletions packages/block-library/src/paragraph/style.scss
Original file line number Diff line number Diff line change
@@ -1,46 +1,7 @@
.is-small-text {
font-size: 0.875em;
}

.is-regular-text {
font-size: 1em;
}

.is-large-text {
font-size: 2.25em;
}

.is-larger-text {
font-size: 3em;
}

// Don't show the drop cap when editing the paragraph's content. It causes a
// number of bugs in combination with `contenteditable` fields. The caret
// cannot be set around it, caret position calculation fails in Chrome, and
// typing at the end of the paragraph doesn't work.
.has-drop-cap:not(:focus)::first-letter {
float: left;
font-size: 8.4em;
line-height: 0.68;
font-weight: 100;
margin: 0.05em 0.1em 0 0;
text-transform: uppercase;
font-style: normal;
}

// Prevent the dropcap from breaking out of the box when a background is applied.
p.has-drop-cap.has-background {
overflow: hidden;
}

p.has-background {
padding: $block-bg-padding--v $block-bg-padding--h;
}

// Use :where to contain the specificity of this rule
// so it's easily overrideable by any theme that targets
// links using the a element.
// For example, this is what global styles does.
:where(p.has-text-color:not(.has-link-color)) a {
color: inherit;
}
@import "./styles/is-small-text.scss";
@import "./styles/is-regular-text.scss";
@import "./styles/is-large-text.scss";
@import "./styles/is-larger-text.scss";
@import "./styles/has-drop-cap.scss";
@import "./styles/has-background.scss";
@import "./styles/has-text-color.scss";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
p.has-background {
padding: $block-bg-padding--v $block-bg-padding--h;
}
18 changes: 18 additions & 0 deletions packages/block-library/src/paragraph/styles/has-drop-cap.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Don't show the drop cap when editing the paragraph's content. It causes a
// number of bugs in combination with `contenteditable` fields. The caret
// cannot be set around it, caret position calculation fails in Chrome, and
// typing at the end of the paragraph doesn't work.
.has-drop-cap:not(:focus)::first-letter {
float: left;
font-size: 8.4em;
line-height: 0.68;
font-weight: 100;
margin: 0.05em 0.1em 0 0;
text-transform: uppercase;
font-style: normal;
}

// Prevent the dropcap from breaking out of the box when a background is applied.
p.has-drop-cap.has-background {
overflow: hidden;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Use :where to contain the specificity of this rule
// so it's easily overrideable by any theme that targets
// links using the a element.
// For example, this is what global styles does.
:where(p.has-text-color:not(.has-link-color)) a {
color: inherit;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.is-large-text {
font-size: 2.25em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.is-larger-text {
font-size: 3em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.is-regular-text {
font-size: 1em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.is-small-text {
font-size: 0.875em;
}
23 changes: 22 additions & 1 deletion schemas/json/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,28 @@
{
"type": "array",
"items": {
"type": "string"
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"handle": {
"description": "The stylesheet handle.",
"type": "string"
},
"parts": {
"description": "An array of class names that have individual stylesheets. If defined, overrides the 'handle' property on the frontend, loading styles only when the class is present.",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [ "handle", "parts" ]
}
]
}
}
]
Expand Down
9 changes: 9 additions & 0 deletions tools/webpack/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ module.exports = {
},
transform: stylesTransform,
} ) ),
{
from: `./packages/block-library/build-style/*/styles/*.css`,
to( { absoluteFilename } ) {
const parts = absoluteFilename.split( sep );
const dirname = parts[ parts.length - 3 ];
return `build/block-library/blocks/${ dirname }/styles/[name].css`;
},
transform: stylesTransform,
},
Object.entries( {
'./packages/block-library/src/':
'build/block-library/blocks/',
Expand Down