| @@ -12,10 +12,12 @@ | ||
| 12 | 12 | |
| 13 | 13 | $style_depends = []; |
| 14 | 14 | $scripts_depends = []; |
| 15 | 15 | $aBlocks = []; |
| 16 | + $seen_refs = []; | |
| 17 | + $global_classes = []; | |
| 16 | 18 | |
| 17 | - self::recursive_block_parser( $parse_blocks_content, $aBlocks, $style_depends, $scripts_depends ); | |
| 19 | + self::recursive_block_parser( $parse_blocks_content, $aBlocks, $style_depends, $scripts_depends, $seen_refs, $global_classes ); | |
| 18 | 20 | |
| 19 | 21 | // Dedupe once, after the whole block tree has been walked. |
| 20 | 22 | $style_depends = array_unique( $style_depends ); |
| 21 | 23 | $scripts_depends = array_unique( $scripts_depends ); |
| @@ -54,18 +56,33 @@ | ||
| 54 | 56 | $library_js .= self::read_library_file( $register_scripts[ $script_depend ]['path'] ) . "\n"; |
| 55 | 57 | } |
| 56 | 58 | } |
| 57 | 59 | |
| 60 | + // Merge per-instance rules that share an identical body into a single | |
| 61 | + // grouped selector (see CssDedupe). Always applied during generation — | |
| 62 | + // it produces byte-for-byte equivalent styling, just smaller. Filterable | |
| 63 | + // as a code-level escape hatch (no UI setting). | |
| 64 | + if ( (bool) apply_filters( 'ablocks/perf/dedupe_css', true ) ) { | |
| 65 | + $dynamic_css = CssDedupe::process( $dynamic_css ); | |
| 66 | + } | |
| 67 | + | |
| 68 | + // Global classes, narrowed to the ones this page uses. They sit between | |
| 69 | + // the blocks' static CSS and their per-instance rules so a block's own | |
| 70 | + // styles still win over a class it carries. | |
| 71 | + $global_css = GlobalClasses::compiled_css_for( | |
| 72 | + array_values( array_unique( $global_classes ) ) | |
| 73 | + ); | |
| 74 | + | |
| 58 | 75 | $FileUpload = new FileUpload(); |
| 59 | 76 | $destination_folder = $FileUpload->get_upload_dir(); |
| 60 | 77 | self::copy_build_image_folder_to_uploads( $destination_folder ); |
| 61 | - $FileUpload->create_file( $file_name . '.min.css', $library_css . $static_css . $dynamic_css ); | |
| 78 | + $FileUpload->create_file( $file_name . '.min.css', $library_css . $static_css . $global_css . $dynamic_css ); | |
| 62 | 79 | $FileUpload->create_file( $file_name . '.min.js', $library_js . $static_js ); |
| 63 | 80 | |
| 64 | 81 | return $aBlocks; |
| 65 | 82 | } |
| 66 | 83 | |
| 67 | - public static function recursive_block_parser( $parse_content, &$aBlocks, &$style_depends, &$scripts_depends, &$seen_refs = [] ) { | |
| 84 | + public static function recursive_block_parser( $parse_content, &$aBlocks, &$style_depends, &$scripts_depends, &$seen_refs = [], &$global_classes = [] ) { | |
| 68 | 85 | if ( count( $parse_content ) > 0 ) { |
| 69 | 86 | foreach ( $parse_content as $item ) { |
| 70 | 87 | if ( ! empty( $item['blockName'] ) ) { |
| 71 | 88 | // Handle reusable blocks or patterns using "ref" |
| @@ -78,11 +95,21 @@ | ||
| 78 | 95 | $seen_refs[] = $ref_post_id; |
| 79 | 96 | $ref_post = get_post( $ref_post_id ); // Get the reusable block or pattern |
| 80 | 97 | if ( $ref_post ) { |
| 81 | 98 | $ref_content = parse_blocks( $ref_post->post_content ); // Parse the reusable block's content |
| 82 | - self::recursive_block_parser( $ref_content, $aBlocks, $style_depends, $scripts_depends, $seen_refs ); // Recursively parse the referenced block/pattern | |
| 99 | + self::recursive_block_parser( $ref_content, $aBlocks, $style_depends, $scripts_depends, $seen_refs, $global_classes ); // Recursively parse the referenced block/pattern | |
| 83 | 100 | } |
| 84 | 101 | } elseif ( strpos( $item['blockName'], 'ablocks' ) !== false ) { |
| 102 | + // Which global classes this page actually needs. Collected | |
| 103 | + // from the walk rather than from a stored per-post index: | |
| 104 | + // the walk is happening anyway, and an index would be one | |
| 105 | + // more thing to keep in sync with the content. | |
| 106 | + if ( ! empty( $item['attrs']['globalClasses'] ) && is_array( $item['attrs']['globalClasses'] ) ) { | |
| 107 | + foreach ( $item['attrs']['globalClasses'] as $global_class_id ) { | |
| 108 | + $global_classes[] = (string) $global_class_id; | |
| 109 | + } | |
| 110 | + } | |
| 111 | + | |
| 85 | 112 | $block_name_class = str_replace( ' ', '', ucwords( str_replace( '-', ' ', explode( '/', $item['blockName'] )[1] ) ) ); |
| 86 | 113 | |
| 87 | 114 | $dynamic_class = '\\ABlocks\\Blocks\\' . $block_name_class . '\\Block'; |
| 88 | 115 | if ( ! class_exists( $dynamic_class ) ) { |
| @@ -112,15 +139,29 @@ | ||
| 112 | 139 | // array_unique on every block). |
| 113 | 140 | $style_depends = array_merge( $style_depends, $instance->get_style_depends() ); |
| 114 | 141 | $scripts_depends = array_merge( $scripts_depends, $instance->get_script_depends() ); |
| 115 | 142 | |
| 116 | - // Capture dynamic CSS | |
| 143 | + // Capture dynamic CSS. | |
| 144 | + // | |
| 145 | + // A block can be reached twice in one walk: on an FSE theme | |
| 146 | + // `pre_render_block` collects EVERY block — parents and their | |
| 147 | + // children alike — and this parser also recurses into | |
| 148 | + // `innerBlocks`, so a nested block is seen once as a child and | |
| 149 | + // once as a collected top-level entry. Compiling it a second | |
| 150 | + // time is not idempotent for blocks whose CSS is emitted | |
| 151 | + // through a per-request dedupe (the atomic blocks' hashed | |
| 152 | + // `ablocks-s-*` rules): the repeat build returns an empty | |
| 153 | + // string because the rule was already emitted, and writing | |
| 154 | + // that over the first result stripped every nested atomic | |
| 155 | + // block's styles from the page. Keep the first result. | |
| 117 | 156 | if ( isset( $item['attrs']['ref'] ) || isset( $item['attrs']['block_id'] ) ) { |
| 118 | 157 | $block_id_or_ref = ! empty( $item['attrs']['block_id'] ) ? $item['attrs']['block_id'] : 'core_pattern_ref_' . $item['attrs']['ref']; |
| 119 | - $aBlocks[ $block_id_or_ref ] = [ | |
| 120 | - 'block_name' => $item['blockName'], | |
| 121 | - 'dynamic_style' => $instance->build_css( $attributes ), | |
| 122 | - ]; | |
| 158 | + if ( ! isset( $aBlocks[ $block_id_or_ref ]['dynamic_style'] ) ) { | |
| 159 | + $aBlocks[ $block_id_or_ref ] = [ | |
| 160 | + 'block_name' => $item['blockName'], | |
| 161 | + 'dynamic_style' => $instance->build_css( $attributes ), | |
| 162 | + ]; | |
| 163 | + } | |
| 123 | 164 | } |
| 124 | 165 | }//end if |
| 125 | 166 | }//end if |
| 126 | 167 | |
| @@ -125,9 +166,9 @@ | ||
| 125 | 166 | }//end if |
| 126 | 167 | |
| 127 | 168 | // Check for inner blocks and recursively process them |
| 128 | 169 | if ( is_array( $item['innerBlocks'] ) && count( $item['innerBlocks'] ) ) { |
| 129 | - self::recursive_block_parser( $item['innerBlocks'], $aBlocks, $style_depends, $scripts_depends, $seen_refs ); | |
| 170 | + self::recursive_block_parser( $item['innerBlocks'], $aBlocks, $style_depends, $scripts_depends, $seen_refs, $global_classes ); | |
| 130 | 171 | } |
| 131 | 172 | }//end if |
| 132 | 173 | }//end foreach |
| 133 | 174 | }//end if |