diff --git a/bin/packages/build.js b/bin/packages/build.js index 94db7f63eadeed..9ea7e3e38829e6 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -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 ); } @@ -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`, ], { diff --git a/lib/blocks.php b/lib/blocks.php index 3f0e8a547a8686..7b3999bef146c2 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -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 ); diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index 2323beff7ae595..58898f65905a51 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -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" + ] + } + ] } diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index 810787c64692ac..13bef62289b023 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -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"; diff --git a/packages/block-library/src/paragraph/styles/has-background.scss b/packages/block-library/src/paragraph/styles/has-background.scss new file mode 100644 index 00000000000000..2e50144a1019e5 --- /dev/null +++ b/packages/block-library/src/paragraph/styles/has-background.scss @@ -0,0 +1,3 @@ +p.has-background { + padding: $block-bg-padding--v $block-bg-padding--h; +} diff --git a/packages/block-library/src/paragraph/styles/has-drop-cap.scss b/packages/block-library/src/paragraph/styles/has-drop-cap.scss new file mode 100644 index 00000000000000..e1722b95096204 --- /dev/null +++ b/packages/block-library/src/paragraph/styles/has-drop-cap.scss @@ -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; +} diff --git a/packages/block-library/src/paragraph/styles/has-text-color.scss b/packages/block-library/src/paragraph/styles/has-text-color.scss new file mode 100644 index 00000000000000..a1ffc5ac260df4 --- /dev/null +++ b/packages/block-library/src/paragraph/styles/has-text-color.scss @@ -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; +} diff --git a/packages/block-library/src/paragraph/styles/is-large-text.scss b/packages/block-library/src/paragraph/styles/is-large-text.scss new file mode 100644 index 00000000000000..d23945669e5c2a --- /dev/null +++ b/packages/block-library/src/paragraph/styles/is-large-text.scss @@ -0,0 +1,3 @@ +.is-large-text { + font-size: 2.25em; +} diff --git a/packages/block-library/src/paragraph/styles/is-larger-text.scss b/packages/block-library/src/paragraph/styles/is-larger-text.scss new file mode 100644 index 00000000000000..a3ba88911f1799 --- /dev/null +++ b/packages/block-library/src/paragraph/styles/is-larger-text.scss @@ -0,0 +1,3 @@ +.is-larger-text { + font-size: 3em; +} diff --git a/packages/block-library/src/paragraph/styles/is-regular-text.scss b/packages/block-library/src/paragraph/styles/is-regular-text.scss new file mode 100644 index 00000000000000..24c1014a0496f2 --- /dev/null +++ b/packages/block-library/src/paragraph/styles/is-regular-text.scss @@ -0,0 +1,3 @@ +.is-regular-text { + font-size: 1em; +} diff --git a/packages/block-library/src/paragraph/styles/is-small-text.scss b/packages/block-library/src/paragraph/styles/is-small-text.scss new file mode 100644 index 00000000000000..78b494d6ca7b54 --- /dev/null +++ b/packages/block-library/src/paragraph/styles/is-small-text.scss @@ -0,0 +1,3 @@ +.is-small-text { + font-size: 0.875em; +} diff --git a/schemas/json/block.json b/schemas/json/block.json index 17e9ec519acf64..8c6205c2bbd1a8 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -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" ] + } + ] } } ] diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index c9a5af0b631ce1..c1d023321e8b8e 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -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/',