Make WordPress Core

Changeset 60934


Ignore:
Timestamp:
10/15/2025 05:38:25 AM (6 weeks ago)
Author:
westonruter
Message:

Bundled Themes: Introduce stylesheet minification for core block themes.

This introduces a build process to provide minified stylesheets for the Twenty Twenty-Two and Twenty Twenty-Five themes. The built minified CSS is not committed to version control. When SCRIPT_DEBUG is disabled, the minified style.min.css file will be enqueued to improve performance, including reducing render-blocking resources as well as facilitating inlining. See #63007.

  • Stylesheets are minified via the new core cssmin:themes Grunt task, which is automatically run as part of build:css.
  • The same build process is added to the themes (via package.json and package-lock.json) with build and watch commands; the postcss and cssnano development dependencies are used to handle CSS minification.
  • The functions.php in each theme is updated to enqueue style.min.css unless SCRIPT_DEBUG is enabled, in which case style.css is enqueued.
  • A notice comment is added to style.css to warn against editing the stylesheet since the minified version will likely be served instead.
  • A new contributing.txt file is added to each theme to document the build process for developers.
  • The test-and-zip-default-themes GitHub workflow is updated to install npm dependencies and build the minified assets before packaging the themes.
  • New PHPUnit tests are added to verify version number consistency across style.css, readme.txt, package.json, and package-lock.json files in default themes.

Developed in https://github.com/WordPress/wordpress-develop/pull/10081.

Props b1ink0, westonruter, shyamgadde, jonsurrell, sabernhardt, jorbin, peterwilsoncc, poena.
Fixes #63012.

Location:
trunk
Files:
6 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/.github/dependabot.yml

    r60349 r60934  
    131131  # Monitor npm dependencies within default themes.
    132132  - package-ecosystem: "npm"
     133    directory: "/src/wp-content/themes/twentytwentyfive"
     134    schedule:
     135      interval: "weekly"
     136    open-pull-requests-limit: 20
     137    groups:
     138      twentytwentyfive-css:
     139        patterns:
     140          - "**browserslist*"
     141          - "*css*"
     142
     143  - package-ecosystem: "npm"
     144    directory: "/src/wp-content/themes/twentytwentytwo"
     145    schedule:
     146      interval: "weekly"
     147    open-pull-requests-limit: 20
     148    groups:
     149      twentytwentytwo-css:
     150        patterns:
     151          - "**browserslist*"
     152          - "*css*"
     153
     154  - package-ecosystem: "npm"
    133155    directory: "/src/wp-content/themes/twentytwentyone"
    134156    schedule:
  • trunk/.github/workflows/test-and-zip-default-themes.yml

    r60692 r60934  
    1515      - 'src/wp-content/themes/twentytwenty/**'
    1616      - 'src/wp-content/themes/twentytwentyone/**'
     17      - 'src/wp-content/themes/twentytwentytwo/**'
     18      - 'src/wp-content/themes/twentytwentyfive/**'
    1719      # Changes to this workflow file should always verify success.
    1820      - '.github/workflows/test-and-zip-default-themes.yml'
     
    3032      - 'src/wp-content/themes/twentytwenty/**'
    3133      - 'src/wp-content/themes/twentytwentyone/**'
     34      - 'src/wp-content/themes/twentytwentytwo/**'
     35      - 'src/wp-content/themes/twentytwentyfive/**'
    3236      # Changes to this workflow file should always verify success.
    3337      - '.github/workflows/test-and-zip-default-themes.yml'
     
    121125      matrix:
    122126        theme: [
     127            'twentytwentyfive',
     128            'twentytwentytwo',
    123129            'twentytwentyone',
    124130            'twentytwenty',
     
    222228          persist-credentials: false
    223229
     230      - name: Set up Node.js for themes needing minification
     231        if: matrix.theme == 'twentytwentytwo' || matrix.theme == 'twentytwentyfive'
     232        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
     233        with:
     234          node-version-file: '.nvmrc'
     235          cache: npm
     236          cache-dependency-path: src/wp-content/themes/${{ matrix.theme }}/package-lock.json
     237
     238      - name: Install npm dependencies
     239        if: matrix.theme == 'twentytwentytwo' || matrix.theme == 'twentytwentyfive'
     240        run: npm ci
     241        working-directory: src/wp-content/themes/${{ matrix.theme }}
     242
     243      - name: Build theme assets
     244        if: matrix.theme == 'twentytwentytwo' || matrix.theme == 'twentytwentyfive'
     245        run: npm run build
     246        working-directory: src/wp-content/themes/${{ matrix.theme }}
     247
    224248      - name: Upload theme ZIP as an artifact
    225249        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
    226250        with:
    227251          name: ${{ matrix.theme }}
    228           path: src/wp-content/themes/${{ matrix.theme }}
     252          path: |
     253            src/wp-content/themes/${{ matrix.theme }}
     254            !src/wp-content/themes/${{ matrix.theme }}/node_modules
    229255          if-no-files-found: error
    230256          include-hidden-files: true
  • trunk/.gitignore

    r60666 r60934  
    8989/src/wp-content/themes/twentytwentyone/node_modules
    9090/src/wp-content/themes/twentytwenty/node_modules
     91/src/wp-content/themes/twentytwentytwo/node_modules
     92/src/wp-content/themes/twentytwentyfive/node_modules
     93
     94# Minified files in bundled themes
     95/src/wp-content/themes/twentytwentytwo/*.min.css
     96/src/wp-content/themes/twentytwentyfive/*.min.css
    9197
    9298# Operating system specific files
  • trunk/Gruntfile.js

    r60899 r60934  
    567567                src: [
    568568                    'wp-admin/css/colors/*/*.css'
     569                ]
     570            },
     571            themes: {
     572                expand: true,
     573                cwd: WORKING_DIR,
     574                dest: WORKING_DIR,
     575                ext: '.min.css',
     576                src: [
     577                    'wp-content/themes/twentytwentytwo/style.css',
     578                    'wp-content/themes/twentytwentyfive/style.css',
    569579                ]
    570580            }
     
    15921602        'cssmin:rtl',
    15931603        'cssmin:colors',
     1604        'cssmin:themes',
    15941605        'usebanner'
    15951606    ] );
  • trunk/src/wp-content/themes/twentytwentyfive/functions.php

    r60424 r60934  
    4040add_action( 'after_setup_theme', 'twentytwentyfive_editor_style' );
    4141
    42 // Enqueues style.css on the front.
     42// Enqueues the theme stylesheet on the front.
    4343if ( ! function_exists( 'twentytwentyfive_enqueue_styles' ) ) :
    4444    /**
    45      * Enqueues style.css on the front.
     45     * Enqueues the theme stylesheet on the front.
    4646     *
    4747     * @since Twenty Twenty-Five 1.0
     
    5050     */
    5151    function twentytwentyfive_enqueue_styles() {
     52        $suffix = SCRIPT_DEBUG ? '' : '.min';
     53        $src    = 'style' . $suffix . '.css';
     54
    5255        wp_enqueue_style(
    5356            'twentytwentyfive-style',
    54             get_parent_theme_file_uri( 'style.css' ),
     57            get_parent_theme_file_uri( $src ),
    5558            array(),
    5659            wp_get_theme()->get( 'Version' )
  • trunk/src/wp-content/themes/twentytwentyfive/style.css

    r60921 r60934  
    1414Tags: one-column, custom-colors, custom-menu, custom-logo, editor-style, featured-images, full-site-editing, block-patterns, rtl-language-support, sticky-post, threaded-comments, translation-ready, wide-blocks, block-styles, style-variations, accessibility-ready, blog, portfolio, news
    1515*/
     16
     17/*
     18 * IMPORTANT: This file is only served on the frontend when `SCRIPT_DEBUG` is enabled;
     19 * in most instances, the `style.min.css` file will be served. It is not recommended that you
     20 * use the Theme File Editor to modify this stylesheet. Instead, add the necessary style
     21 * overrides via "Additional CSS" in the Site Editor.
     22 */
    1623
    1724/*
  • trunk/src/wp-content/themes/twentytwentytwo/functions.php

    r60538 r60934  
    4646
    4747        $version_string = is_string( $theme_version ) ? $theme_version : false;
     48
     49        $suffix = SCRIPT_DEBUG ? '' : '.min';
     50        $src    = 'style' . $suffix . '.css';
     51
    4852        wp_register_style(
    4953            'twentytwentytwo-style',
    50             get_template_directory_uri() . '/style.css',
     54            get_parent_theme_file_uri( $src ),
    5155            array(),
    5256            $version_string
  • trunk/src/wp-content/themes/twentytwentytwo/style.css

    r60921 r60934  
    1717Twenty Twenty-Two is distributed under the terms of the GNU GPL.
    1818*/
     19
     20/*
     21 * IMPORTANT: This file is only served on the frontend when `SCRIPT_DEBUG` is enabled;
     22 * in most instances, the `style.min.css` file will be served. It is not recommended that you
     23 * use the Theme File Editor to modify this stylesheet. Instead, add the necessary style
     24 * overrides via "Additional CSS" in the Site Editor.
     25 */
    1926
    2027/*
  • trunk/tests/phpunit/tests/theme.php

    r60729 r60934  
    315315     * @ticket 48566
    316316     *
    317      * @dataProvider data_year_in_readme
     317     * @dataProvider data_provider_default_themes
    318318     */
    319319    public function test_year_in_readme( $theme ) {
     
    337337    }
    338338
    339     public function data_year_in_readme() {
    340         return array_map(
    341             static function ( $theme ) {
    342                 return array( $theme );
    343             },
    344             $this->default_themes
    345         );
     339    /**
     340     * Data provider.
     341     *
     342     * @return array<string, array{ theme: string }>
     343     */
     344    public function data_provider_default_themes(): array {
     345        $data = array();
     346        foreach ( $this->default_themes as $default_theme ) {
     347            $data[ $default_theme ] = array( 'theme' => $default_theme );
     348        }
     349        return $data;
     350    }
     351
     352    /**
     353     * Tests that the version number in style.css, readme.txt, and package.json all match.
     354     *
     355     * @ticket 63012
     356     *
     357     * @dataProvider data_provider_default_themes
     358     */
     359    public function test_version_consistency_in_package_json( string $theme ) {
     360        $wp_theme = wp_get_theme( $theme );
     361
     362        $path_to_style_css = $wp_theme->get_theme_root() . '/' . $wp_theme->get_stylesheet() . '/style.css';
     363        $this->assertFileExists( $path_to_style_css );
     364        $this->assertSame( 1, preg_match( '/^Version: (\d+\.\d+(?:\.\d+)?)$/m', file_get_contents( $path_to_style_css ), $matches ), 'Expected Version in style.css' );
     365        $style_version = $matches[1];
     366
     367        // Version in style.css does not use patch versions, so supply one of 0.
     368        $version_with_patch = $matches[1];
     369        if ( 1 === substr_count( $version_with_patch, '.' ) ) {
     370            $version_with_patch .= '.0';
     371        }
     372
     373        $package_files         = array( 'package.json', 'package-lock.json' );
     374        $present_package_files = array();
     375        foreach ( $package_files as $package_file ) {
     376            $path_to_package_json = $wp_theme->get_theme_root() . '/' . $wp_theme->get_stylesheet() . '/' . $package_file;
     377            if ( file_exists( $path_to_package_json ) ) {
     378                $present_package_files[] = $package_file;
     379
     380                $data = json_decode( file_get_contents( $path_to_package_json ), true );
     381                $this->assertIsArray( $data, "Expected $package_file to be an array." );
     382                $this->assertArrayHasKey( 'name', $data, "Expected name key in $package_file." );
     383                $this->assertSame( $theme, $data['name'], "Expected name field to be the theme slug in $package_file." );
     384                $this->assertArrayHasKey( 'version', $data, "Expected version key in $package_file." );
     385                $this->assertSame( $version_with_patch, $data['version'], "Expected version from style.css to match version in $package_file." );
     386
     387                if ( 'package-lock.json' === $package_file && isset( $data['packages'][''] ) ) {
     388                    $this->assertArrayHasKey( 'version', $data['packages'][''], "Expected version key in packages[''] in package-lock.json." );
     389                    $this->assertSame( $version_with_patch, $data['packages']['']['version'], "Expected version from style.css to match packages[''].version in package-lock.json." );
     390                }
     391            }
     392        }
     393        if ( count( $present_package_files ) > 0 ) {
     394            $this->assertCount( 2, $present_package_files, 'Expected package-lock.json to be present when package.json is present, and vice versa.' );
     395        }
     396
     397        // TODO: The themes twentyfifteen, twentysixteen, and twentyseventeen lack a Stable Tag in the readme.txt.
     398        $path_to_readme_txt = $wp_theme->get_theme_root() . '/' . $wp_theme->get_stylesheet() . '/readme.txt';
     399        $this->assertFileExists( $path_to_readme_txt );
     400        if ( 1 === preg_match( '/^Stable [Tt]ag: (\d+\.\d+(?:\.\d+)?)$/m', file_get_contents( $path_to_readme_txt ), $matches ) ) {
     401            $readme_version = $matches[1];
     402            $this->assertSame( $style_version, $readme_version, 'Expected version in style.css and stable tag in readme.txt to match.' );
     403        }
    346404    }
    347405
Note: See TracChangeset for help on using the changeset viewer.