Created
March 16, 2026 20:21
-
-
Save josephscott/254521a43dee3300448ef288dd2ea1f2 to your computer and use it in GitHub Desktop.
Gutenberg: prepend_to_selector() comparison
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Benchmark: prepend_to_selector() implementations. | |
| * | |
| * Usage: php bench-prepend-to-selector.php | |
| */ | |
| $data = array( | |
| 'single class selector' => array( | |
| 'selector' => '.inner', | |
| 'to_prepend' => '.wrapper ', | |
| 'expected' => '.wrapper .inner', | |
| ), | |
| 'single element selector' => array( | |
| 'selector' => 'p', | |
| 'to_prepend' => '.wrapper ', | |
| 'expected' => '.wrapper p', | |
| ), | |
| 'single id selector' => array( | |
| 'selector' => '#main', | |
| 'to_prepend' => '.wrapper ', | |
| 'expected' => '.wrapper #main', | |
| ), | |
| 'two comma-separated selectors without spaces' => array( | |
| 'selector' => 'h1,h2', | |
| 'to_prepend' => '.wrapper ', | |
| 'expected' => '.wrapper h1,.wrapper h2', | |
| ), | |
| 'three comma-separated selectors without spaces' => array( | |
| 'selector' => 'h1,h2,h3', | |
| 'to_prepend' => '.some-class ', | |
| 'expected' => '.some-class h1,.some-class h2,.some-class h3', | |
| ), | |
| 'comma-separated class selectors without spaces' => array( | |
| 'selector' => '.foo,.bar', | |
| 'to_prepend' => '.prefix ', | |
| 'expected' => '.prefix .foo,.prefix .bar', | |
| ), | |
| 'prepend without trailing space' => array( | |
| 'selector' => '.child', | |
| 'to_prepend' => '.parent', | |
| 'expected' => '.parent.child', | |
| ), | |
| 'compound selector without trailing space no comma spaces' => array( | |
| 'selector' => '.a,.b', | |
| 'to_prepend' => '.parent', | |
| 'expected' => '.parent.a,.parent.b', | |
| ), | |
| 'descendant selector prepended' => array( | |
| 'selector' => '.block .inner', | |
| 'to_prepend' => '.scope ', | |
| 'expected' => '.scope .block .inner', | |
| ), | |
| 'descendant selectors comma-separated without spaces' => array( | |
| 'selector' => '.block .inner,.block .alt', | |
| 'to_prepend' => '.scope ', | |
| 'expected' => '.scope .block .inner,.scope .block .alt', | |
| ), | |
| 'empty selector' => array( | |
| 'selector' => '', | |
| 'to_prepend' => '.prefix ', | |
| 'expected' => '.prefix ', | |
| ), | |
| 'empty prepend' => array( | |
| 'selector' => '.child', | |
| 'to_prepend' => '', | |
| 'expected' => '.child', | |
| ), | |
| 'both empty' => array( | |
| 'selector' => '', | |
| 'to_prepend' => '', | |
| 'expected' => '', | |
| ), | |
| 'attribute selector' => array( | |
| 'selector' => '[data-type="example"]', | |
| 'to_prepend' => '.scope ', | |
| 'expected' => '.scope [data-type="example"]', | |
| ), | |
| 'pseudo-class selector' => array( | |
| 'selector' => ':where(.is-layout-flex)', | |
| 'to_prepend' => '.editor ', | |
| 'expected' => '.editor :where(.is-layout-flex)', | |
| ), | |
| 'many comma-separated selectors without spaces' => array( | |
| 'selector' => 'h1,h2,h3,h4,h5,h6', | |
| 'to_prepend' => '.content ', | |
| 'expected' => '.content h1,.content h2,.content h3,.content h4,.content h5,.content h6', | |
| ), | |
| 'real world block element selector' => array( | |
| 'selector' => 'p', | |
| 'to_prepend' => '.wp-block-group ', | |
| 'expected' => '.wp-block-group p', | |
| ), | |
| 'real world compound block element selectors' => array( | |
| 'selector' => 'a,.wp-element-button', | |
| 'to_prepend' => '.wp-block-group ', | |
| 'expected' => '.wp-block-group a,.wp-block-group .wp-element-button', | |
| ), | |
| 'spaces after commas are preserved' => array( | |
| 'selector' => 'h1, h2, h3', | |
| 'to_prepend' => '.some-class ', | |
| 'expected' => '.some-class h1,.some-class h2,.some-class h3', | |
| ), | |
| 'spaces after commas preserved with class selectors' => array( | |
| 'selector' => '.foo, .bar', | |
| 'to_prepend' => '.prefix ', | |
| 'expected' => '.prefix .foo,.prefix .bar', | |
| ), | |
| 'mixed whitespace around commas preserved' => array( | |
| 'selector' => '.a , .b , .c', | |
| 'to_prepend' => '.pre ', | |
| 'expected' => '.pre .a ,.pre .b ,.pre .c', | |
| ), | |
| ); | |
| function prepend_current( string $selector, string $to_prepend ): string { | |
| if ( ! str_contains( $selector, ',' ) ) { | |
| return $to_prepend . $selector; | |
| } | |
| $new_selectors = array(); | |
| $selectors = explode( ',', $selector ); | |
| foreach ( $selectors as $sel ) { | |
| $new_selectors[] = $to_prepend . $sel; | |
| } | |
| return implode( ',', $new_selectors ); | |
| } | |
| function prepend_new( string $selector, string $to_prepend ): string { | |
| if ( ! str_contains( $selector, ',' ) ) { | |
| return $to_prepend . $selector; | |
| } | |
| return $to_prepend . str_replace( ',', ',' . $to_prepend, $selector ); | |
| } | |
| $loops = 200; | |
| $case_count = count( $data ); | |
| // --- Correctness check --- | |
| $mismatches = 0; | |
| foreach ( $data as $label => $case ) { | |
| $current_result = prepend_current( $case['selector'], $case['to_prepend'] ); | |
| $new_result = prepend_new( $case['selector'], $case['to_prepend'] ); | |
| if ( $current_result !== $case['expected'] ) { | |
| echo "FAIL [current] $label\n"; | |
| echo " expected: {$case['expected']}\n"; | |
| echo " got: $current_result\n"; | |
| ++$mismatches; | |
| } | |
| if ( $new_result !== $case['expected'] ) { | |
| echo "FAIL [new] $label\n"; | |
| echo " expected: {$case['expected']}\n"; | |
| echo " got: $new_result\n"; | |
| ++$mismatches; | |
| } | |
| } | |
| if ( $mismatches > 0 ) { | |
| echo "\n$mismatches correctness failure(s) - aborting benchmark.\n"; | |
| exit( 1 ); | |
| } | |
| echo "All $case_count cases produce identical results. Running benchmark...\n\n"; | |
| // --- Benchmark: current implementation --- | |
| $start = hrtime( true ); | |
| for ( $i = 0; $i < $loops; $i++ ) { | |
| foreach ( $data as $case ) { | |
| prepend_current( $case['selector'], $case['to_prepend'] ); | |
| } | |
| } | |
| $time_current = hrtime( true ) - $start; | |
| // --- Benchmark: new implementation --- | |
| $start = hrtime( true ); | |
| for ( $i = 0; $i < $loops; $i++ ) { | |
| foreach ( $data as $case ) { | |
| prepend_new( $case['selector'], $case['to_prepend'] ); | |
| } | |
| } | |
| $time_new = hrtime( true ) - $start; | |
| // --- Results --- | |
| $total_calls = $loops * $case_count; | |
| printf( "Total calls per implementation: %s\n", number_format( $total_calls ) ); | |
| printf( "Loops: %d x %d cases\n\n", $loops, $case_count ); | |
| $time_current_ms = $time_current / 1e6; | |
| $time_new_ms = $time_new / 1e6; | |
| printf( "Current : %.4f ms\n", $time_current_ms ); | |
| printf( "New : %.4f ms\n", $time_new_ms ); | |
| $diff_ms = $time_current_ms - $time_new_ms; | |
| $diff_pct = ( $diff_ms / $time_current_ms ) * 100; | |
| $direction = $diff_ms > 0 ? 'faster' : 'slower'; | |
| printf( "\nDifference: %.4f ms (%.2f%% %s)\n", abs( $diff_ms ), abs( $diff_pct ), $direction ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment