From f7603ef6e3c31959039351a7f3eacfccd6ddbcc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:41:00 +0000 Subject: [PATCH 01/12] Add cssstats dependency Agent-Logs-Url: https://github.com/primer/react/sessions/990af679-0e96-40f6-97b2-ce1f9bf2830f Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- package-lock.json | 341 +++++++++++++++++++++++++++++++++++- packages/react/package.json | 1 + 2 files changed, 333 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7db0d43de0f..3479ad10040 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,8 +82,8 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@primer/react": "38.21.1", - "@primer/styled-react": "1.0.6", + "@primer/react": "38.22.0", + "@primer/styled-react": "1.0.7", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.3", @@ -96,8 +96,8 @@ "name": "example-nextjs", "version": "0.0.0", "dependencies": { - "@primer/react": "38.21.1", - "@primer/styled-react": "1.0.6", + "@primer/react": "38.22.0", + "@primer/styled-react": "1.0.7", "next": "^16.1.7", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -139,8 +139,8 @@ "version": "0.0.0", "dependencies": { "@primer/octicons-react": "^19.21.0", - "@primer/react": "38.21.1", - "@primer/styled-react": "1.0.6", + "@primer/react": "38.22.0", + "@primer/styled-react": "1.0.7", "clsx": "^2.1.1", "next": "^16.1.7", "react": "^19.2.0", @@ -12397,6 +12397,15 @@ "node": ">=4" } }, + "node_modules/css-color-names": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.1.tgz", + "integrity": "sha512-i7o8lqlrmiG/EUzlBftBncsrkYgBCfCI9X6plNxdyXMZlMNd4hPX7u/o7YLH9vwXPPPAr+BUs3R0oto+lzjbyA==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "dev": true, @@ -12492,6 +12501,43 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-selector-tokenizer": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", + "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "fastparse": "^1.1.2" + } + }, + "node_modules/css-shorthand-expand": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-shorthand-expand/-/css-shorthand-expand-1.2.0.tgz", + "integrity": "sha512-L3RS1VNYuXgMOfVGX4WzP9AFK6KL0JuioSoO8661egEac2eHX9/s4yFO8mgK6QEtm8UmU8IvuKzPgdQpU0DhpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-color-names": "0.0.1", + "css-url-regex": "0.0.1", + "hex-color-regex": "^1.0.1", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "map-obj": "^1.0.0", + "repeat-element": "^1.1.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/css-shorthand-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz", + "integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==", + "dev": true, + "license": "MIT" + }, "node_modules/css-to-react-native": { "version": "3.2.0", "license": "MIT", @@ -12513,6 +12559,13 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/css-url-regex": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-0.0.1.tgz", + "integrity": "sha512-nFtRgFyJUwz9pyMpyscglpHEFdEJ+y2Q8pK33I99gzhUV1OFzS3t5DtIop3VWLIoGFr4mWcM4hJuWPLXn1NXgA==", + "dev": true, + "license": "MIT" + }, "node_modules/css-what": { "version": "6.1.0", "license": "BSD-2-Clause", @@ -12666,6 +12719,70 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/cssstats": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/cssstats/-/cssstats-4.0.5.tgz", + "integrity": "sha512-Q5vVJAlR1OgZppst4Qkn0mYADVan/8fNgd6cGpANk2mC+jFKUWjaC0T7Byvr0yWWRWOTIv6Y2g1eL0csmorPbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.0", + "css-selector-tokenizer": "^0.7.3", + "css-shorthand-expand": "^1.2.0", + "gzip-size": "^6.0.0", + "has-class-selector": "^4.0.0", + "has-element-selector": "^4.0.0", + "has-id-selector": "^4.0.0", + "has-pseudo-class": "^4.0.0", + "has-pseudo-element": "^4.0.0", + "is-blank": "^2.1.0", + "is-css-shorthand": "^1.0.1", + "is-present": "^1.0.0", + "is-vendor-prefixed": "^4.0.0", + "lodash": "^4.17.20", + "postcss": "^8.1.4", + "postcss-custom-properties": "^12.1.6", + "postcss-safe-parser": "^5.0.2", + "specificity": "^0.4.1" + } + }, + "node_modules/cssstats/node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/cssstats/node_modules/postcss-safe-parser": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-5.0.2.tgz", + "integrity": "sha512-jDUfCPJbKOABhwpUKcqCVbbXiloe/QXMcbJ6Iipf3sDIihEzTqRCeMBfRaOHxhBuTYqtASrI1KJWxzztZU4qUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^8.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, "node_modules/csstype": { "version": "3.1.3", "license": "MIT" @@ -15130,6 +15247,13 @@ "node": ">= 4.9.1" } }, + "node_modules/fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fastq": { "version": "1.17.1", "dev": true, @@ -15891,6 +16015,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-class-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-class-selector/-/has-class-selector-4.0.0.tgz", + "integrity": "sha512-vHI2AQG8kvJAxcQCOdG8aUiTHhUnmGt40f/3KJtiWLFNvt3YlcbdbWJAoZIs0hirQoFN+P8NIwpJMb7LRkkuSA==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-element-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-element-selector/-/has-element-selector-4.0.0.tgz", + "integrity": "sha512-L85fbzBoV78AqC5X34wlfp3qev+hzXEEtqSOXoPDXFtIBmFn4sxVlsIUtTZQA/2hu7dt9xpuqWDB+GS4Y7tbRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-selector-tokenizer": "^0.7.3" + } + }, "node_modules/has-flag": { "version": "3.0.0", "license": "MIT", @@ -15898,6 +16039,13 @@ "node": ">=4" } }, + "node_modules/has-id-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-id-selector/-/has-id-selector-4.0.0.tgz", + "integrity": "sha512-JSCvmyVpsn4p4Bjt+u8vbydNAK3m5Ixu+cF/B1X9gRHBQan4Bkd/eE/jQ191O2KofObLHyeTWfrzfbTA/0NRIg==", + "dev": true, + "license": "MIT" + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "dev": true, @@ -15923,6 +16071,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-pseudo-class": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-pseudo-class/-/has-pseudo-class-4.0.0.tgz", + "integrity": "sha512-H9NPtMTs85zQ9drMtGqSdQcmqr4oprxCdUVyldwsHXHQO33fzIpX/X96iBovmu8YIdaQ6XGg9ZxPrBifjcfILg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pseudo-classes": "1.0.0" + } + }, + "node_modules/has-pseudo-element": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-pseudo-element/-/has-pseudo-element-4.0.0.tgz", + "integrity": "sha512-JibJn1za1U1ue/hxmVIwR+NdX0tYfpltzQNqLADKeyMlUbfCo16jUvX9ZmMgS3OpQw4WSSedTrTk9KokzswuxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pseudo-elements": "1.1.0" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "license": "MIT", @@ -15983,6 +16151,13 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true, + "license": "MIT" + }, "node_modules/history": { "version": "5.3.0", "license": "MIT", @@ -16028,6 +16203,20 @@ "dev": true, "license": "ISC" }, + "node_modules/hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==", + "dev": true, + "license": "MIT" + }, "node_modules/hsluv": { "version": "1.0.1", "license": "MIT" @@ -16389,6 +16578,17 @@ "node": ">=8" } }, + "node_modules/is-blank": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-blank/-/is-blank-2.1.0.tgz", + "integrity": "sha512-SOPvTu4ZRlJOSBBYV7+6D6wN+2UcN6IJCaQ2Yeu3BQ3oolsD4dqF95sz52TCSgMVCLR1osLOXIiFsO2TKp0GZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-empty": "latest", + "is-whitespace": "latest" + } + }, "node_modules/is-boolean-object": { "version": "1.2.2", "dev": true, @@ -16437,6 +16637,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-css-shorthand": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-css-shorthand/-/is-css-shorthand-1.0.1.tgz", + "integrity": "sha512-SXXTYSufuLvRBofGIlg7nGnD+a7eWePl6yKqoKsmYGN29RQL85AaNPr7lttF1JkGLQA7IBWvLnHxe/bAObRCOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-shorthand-properties": "^1.0.0" + } + }, "node_modules/is-data-view": { "version": "1.0.2", "dev": true, @@ -16689,6 +16899,33 @@ "dev": true, "license": "MIT" }, + "node_modules/is-present": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-present/-/is-present-1.0.0.tgz", + "integrity": "sha512-k3hcumGPxoqTO0fs5aoomkyDjViXgb7lWBB/iFIn+zg9EepNJwUJmi+BzD3k2i0fNTMWYRBHGLOTPtOEzFREVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-blank": "1.0.0" + } + }, + "node_modules/is-present/node_modules/is-blank": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-blank/-/is-blank-1.0.0.tgz", + "integrity": "sha512-TdhL1rVh1YmRNeVCEMXacXGTHNczcprPR1+jym5Hbnpa8qLoIMtMmjpU1d7Y0YdCcco2PAvARdnLQ6Thx/jaew==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-empty": "0.0.1", + "is-whitespace": "^0.3.0" + } + }, + "node_modules/is-present/node_modules/is-empty": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-0.0.1.tgz", + "integrity": "sha512-jYWXLEBmq8udg0gP7mw8tmyd9Yahzzp3kfLdcXj7ydkeVxjQkQ82U/Fx1sJRUMfkpO6vDGjWfke1tK8XYv+T5Q==", + "dev": true + }, "node_modules/is-promise": { "version": "4.0.0", "license": "MIT" @@ -16826,6 +17063,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-vendor-prefixed": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-vendor-prefixed/-/is-vendor-prefixed-4.0.0.tgz", + "integrity": "sha512-IOs6nB0cELr2AfldQbfGf5urbX74pYE2Z9sULu2yeQswqodxtQZwi+avzSGM6AVJ5KbvfStd8lH/ooZ+B5cdUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "vendor-prefixes": "1.0.0" + } + }, "node_modules/is-weakmap": { "version": "2.0.2", "dev": true, @@ -16871,6 +17118,16 @@ "dev": true, "license": "MIT" }, + "node_modules/is-whitespace": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz", + "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-windows": { "version": "1.0.2", "dev": true, @@ -17618,6 +17875,16 @@ "url": "https://github.com/fisker/make-synchronized?sponsor=1" } }, + "node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/markdown-it": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", @@ -21663,6 +21930,20 @@ "license": "MIT", "optional": true }, + "node_modules/pseudo-classes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pseudo-classes/-/pseudo-classes-1.0.0.tgz", + "integrity": "sha512-s3l2tOm0vTmDL4muvRfGMnAxJ0kYSeuZu+wOjNTHsm/4UtDGBZ8sMl0jPwwJgo+wRw2EQqVjqHdjIUcLzGgnJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pseudo-elements": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pseudo-elements/-/pseudo-elements-1.1.0.tgz", + "integrity": "sha512-+Lhs/odu0/h4slKf1/vvAIwrsl+1LNPb1cllAmVsf+yW/k3pE8wTZRqsdCToeu+zzeixGk+q3uuArFd0cl2Aiw==", + "dev": true, + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "dev": true, @@ -22287,6 +22568,16 @@ "type-fest": "^4.40.1" } }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "dev": true, @@ -22380,6 +22671,20 @@ "node": ">=0.10.0" } }, + "node_modules/rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==", + "dev": true, + "license": "MIT" + }, "node_modules/rimraf": { "version": "5.0.5", "dev": true, @@ -23420,6 +23725,16 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/specificity": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "dev": true, + "license": "MIT", + "bin": { + "specificity": "bin/specificity" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "dev": true, @@ -26336,6 +26651,13 @@ "node": ">= 0.8" } }, + "node_modules/vendor-prefixes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vendor-prefixes/-/vendor-prefixes-1.0.0.tgz", + "integrity": "sha512-oWOptgqBs948A3V9TmAUcVFvb0dJgmeHrcIcWq4rqtmCfaRs93t0+DfJu90V5n3drN0CKBYm4BTi9yvWyKXA+g==", + "dev": true, + "license": "MIT" + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -27709,7 +28031,7 @@ }, "packages/react": { "name": "@primer/react", - "version": "38.21.1", + "version": "38.22.0", "license": "MIT", "dependencies": { "@github/mini-throttle": "^2.1.1", @@ -27788,6 +28110,7 @@ "concurrently": "9.1.2", "copyfiles": "2.4.1", "cross-env": "7.0.3", + "cssstats": "^4.0.5", "fast-glob": "3.3.2", "filesize": "10.1.6", "front-matter": "4.0.2", @@ -28082,7 +28405,7 @@ }, "packages/styled-react": { "name": "@primer/styled-react", - "version": "1.0.6", + "version": "1.0.7", "dependencies": { "@styled-system/css": "^5.1.5", "@styled-system/props": "^5.1.5", @@ -28099,7 +28422,7 @@ "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@primer/primitives": "10.x || 11.x", - "@primer/react": "^38.20.0", + "@primer/react": "^38.22.0", "@rollup/plugin-babel": "^6.1.0", "@storybook/react-vite": "^10.3.3", "@types/react": "18.3.11", diff --git a/packages/react/package.json b/packages/react/package.json index 083ec949124..31072eef698 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -150,6 +150,7 @@ "concurrently": "9.1.2", "copyfiles": "2.4.1", "cross-env": "7.0.3", + "cssstats": "^4.0.5", "fast-glob": "3.3.2", "filesize": "10.1.6", "front-matter": "4.0.2", From 75c0cd8ada949167b2f281787e3089276388fc28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 20:52:19 +0000 Subject: [PATCH 02/12] Report CSS sizes in CI size summary Agent-Logs-Url: https://github.com/primer/react/sessions/990af679-0e96-40f6-97b2-ce1f9bf2830f Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 122 +++++++++++++++++++-- 1 file changed, 110 insertions(+), 12 deletions(-) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index 72388794319..c33f6a60f51 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -7,22 +7,39 @@ const commonjs = require('@rollup/plugin-commonjs') const {nodeResolve} = require('@rollup/plugin-node-resolve') const virtual = require('@rollup/plugin-virtual') const json = require('@rollup/plugin-json') +const cssstats = require('cssstats') const {filesize} = require('filesize') const {rollup} = require('rollup') const {minify} = require('terser') const gzipSize = require('gzip-size') -const noopCSSModules = { - name: 'empty-css-modules', +function createCSSModulesCollector(cssModules, cssImportsByImporter) { + return { + name: 'collect-css-modules', - transform(_code, id) { - if (!id.endsWith('.css')) { - return - } - return { - code: `export default {}`, - } - }, + resolveId(source, importer) { + if (!source.endsWith('.css') || !importer) { + return + } + + const id = path.resolve(path.dirname(importer), source) + const imports = cssImportsByImporter.get(importer) ?? new Set() + imports.add(id) + cssImportsByImporter.set(importer, imports) + }, + + async transform(_code, id) { + if (!id.endsWith('.css')) { + return + } + + cssModules.set(id, await fs.readFile(id, 'utf8')) + + return { + code: `export default {}`, + } + }, + } } async function main() { @@ -47,6 +64,8 @@ async function main() { core.info(`Analyzing entrypoint: ${entrypoint.entrypoint}`) const filepath = path.resolve(rootDirectory, entrypoint.filepath) + const cssModules = new Map() + const cssImportsByImporter = new Map() const bundle = await rollup({ input: filepath, external, @@ -56,7 +75,7 @@ async function main() { include: [/node_modules/], }), json(), - noopCSSModules, + createCSSModulesCollector(cssModules, cssImportsByImporter), ], onwarn: () => {}, }) @@ -64,6 +83,7 @@ async function main() { format: 'esm', }) const minified = await minify(output[0].code) + const css = await getCSSInfo(cssModules, cssImportsByImporter, output[0]) const exports = [] core.startGroup('Analyzing exports...') @@ -71,6 +91,8 @@ async function main() { for (const identifier of output[0].exports) { core.info(`Analyzing export: ${identifier}`) + const cssModules = new Map() + const cssImportsByImporter = new Map() const reexport = await rollup({ input: '__entrypoint__', external, @@ -79,7 +101,7 @@ async function main() { commonjs({ include: /node_modules/, }), - noopCSSModules, + createCSSModulesCollector(cssModules, cssImportsByImporter), json(), virtual({ __entrypoint__: `export { ${identifier} } from '${filepath}';`, @@ -91,6 +113,7 @@ async function main() { format: 'esm', }) const minified = await minify(output[0].code) + const css = await getCSSInfo(cssModules, cssImportsByImporter, output[0]) exports.push({ identifier, @@ -98,6 +121,7 @@ async function main() { minified: Buffer.byteLength(minified.code), gzipUnminified: await gzipSize(output[0].code), gzipMinified: await gzipSize(minified.code), + css, }) } @@ -109,6 +133,7 @@ async function main() { minified: Buffer.byteLength(minified.code), gzipUnminified: await gzipSize(output[0].code), gzipMinified: await gzipSize(minified.code), + css, exports, }) } @@ -126,6 +151,30 @@ async function main() { data: 'Export', header: true, }, + { + data: 'CSS Gzip', + header: true, + }, + { + data: 'CSS Size', + header: true, + }, + { + data: 'CSS Rules', + header: true, + }, + { + data: 'CSS Selectors', + header: true, + }, + { + data: 'CSS Declarations', + header: true, + }, + { + data: 'CSS Specificity (max)', + header: true, + }, { data: 'Gzip', header: true, @@ -152,6 +201,12 @@ async function main() { [ entrypoint.entrypoint === '.' ? '@primer/react' : path.join('@primer/react', entrypoint.entrypoint), '*', + filesize(entrypoint.css.gzip), + filesize(entrypoint.css.size), + String(entrypoint.css.stats.rules.total), + String(entrypoint.css.stats.selectors.total), + String(entrypoint.css.stats.declarations.total), + String(entrypoint.css.stats.selectors.specificity.max), filesize(entrypoint.gzipMinified), filesize(entrypoint.gzipUnminified), filesize(entrypoint.minified), @@ -165,6 +220,12 @@ async function main() { return [ '', exportInfo.identifier, + filesize(exportInfo.css.gzip), + filesize(exportInfo.css.size), + String(exportInfo.css.stats.rules.total), + String(exportInfo.css.stats.selectors.total), + String(exportInfo.css.stats.declarations.total), + String(exportInfo.css.stats.selectors.specificity.max), filesize(exportInfo.gzipMinified), filesize(exportInfo.gzipUnminified), filesize(exportInfo.minified), @@ -178,6 +239,43 @@ async function main() { } } +async function getCSSInfo(cssModules, cssImportsByImporter, chunk) { + const cssModuleIds = new Set() + + if (chunk.type === 'chunk') { + for (const moduleId of Object.keys(chunk.modules)) { + if (cssModules.has(moduleId)) { + cssModuleIds.add(moduleId) + } + + const imports = cssImportsByImporter.get(moduleId) + if (imports) { + for (const id of imports) { + cssModuleIds.add(id) + } + } + } + } + + const code = Array.from(cssModuleIds, id => { + return cssModules.get(id) + }).join('\n') + + if (code.length === 0) { + return { + size: 0, + gzip: 0, + stats: cssstats(code).toJSON(), + } + } + + return { + size: Buffer.byteLength(code), + gzip: await gzipSize(code), + stats: cssstats(code).toJSON(), + } +} + function getEntrypoints(packageJson) { if (packageJson.exports) { return getPackageExports(packageJson.exports, packageJson.type) From 7bd8bcbc8bf90b359b007582d5b276889029896f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 21:07:03 +0000 Subject: [PATCH 03/12] Minimize cssstats lockfile updates Agent-Logs-Url: https://github.com/primer/react/sessions/990af679-0e96-40f6-97b2-ce1f9bf2830f Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3479ad10040..267f2d2a643 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,8 +82,8 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@primer/react": "38.22.0", - "@primer/styled-react": "1.0.7", + "@primer/react": "38.21.1", + "@primer/styled-react": "1.0.6", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.3", @@ -96,8 +96,8 @@ "name": "example-nextjs", "version": "0.0.0", "dependencies": { - "@primer/react": "38.22.0", - "@primer/styled-react": "1.0.7", + "@primer/react": "38.21.1", + "@primer/styled-react": "1.0.6", "next": "^16.1.7", "react": "^19.2.0", "react-dom": "^19.2.0", @@ -139,8 +139,8 @@ "version": "0.0.0", "dependencies": { "@primer/octicons-react": "^19.21.0", - "@primer/react": "38.22.0", - "@primer/styled-react": "1.0.7", + "@primer/react": "38.21.1", + "@primer/styled-react": "1.0.6", "clsx": "^2.1.1", "next": "^16.1.7", "react": "^19.2.0", From 182261911c560018098aeb00f164b16d36e210b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 21:20:24 +0000 Subject: [PATCH 04/12] Address CSS size report review feedback Agent-Logs-Url: https://github.com/primer/react/sessions/990af679-0e96-40f6-97b2-ce1f9bf2830f Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index c33f6a60f51..a320a4bff26 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -91,8 +91,8 @@ async function main() { for (const identifier of output[0].exports) { core.info(`Analyzing export: ${identifier}`) - const cssModules = new Map() - const cssImportsByImporter = new Map() + const exportCSSModules = new Map() + const exportCSSImportsByImporter = new Map() const reexport = await rollup({ input: '__entrypoint__', external, @@ -101,7 +101,7 @@ async function main() { commonjs({ include: /node_modules/, }), - createCSSModulesCollector(cssModules, cssImportsByImporter), + createCSSModulesCollector(exportCSSModules, exportCSSImportsByImporter), json(), virtual({ __entrypoint__: `export { ${identifier} } from '${filepath}';`, @@ -113,7 +113,7 @@ async function main() { format: 'esm', }) const minified = await minify(output[0].code) - const css = await getCSSInfo(cssModules, cssImportsByImporter, output[0]) + const css = await getCSSInfo(exportCSSModules, exportCSSImportsByImporter, output[0]) exports.push({ identifier, @@ -265,7 +265,20 @@ async function getCSSInfo(cssModules, cssImportsByImporter, chunk) { return { size: 0, gzip: 0, - stats: cssstats(code).toJSON(), + stats: { + rules: { + total: 0, + }, + selectors: { + total: 0, + specificity: { + max: 0, + }, + }, + declarations: { + total: 0, + }, + }, } } From 0c56f9e6662bafb7798a364d8427fca3de94f40c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 21:30:42 +0000 Subject: [PATCH 05/12] Report CSS size stats in CI size summary Agent-Logs-Url: https://github.com/primer/react/sessions/990af679-0e96-40f6-97b2-ce1f9bf2830f Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index a320a4bff26..6871b5f20ac 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -19,13 +19,15 @@ function createCSSModulesCollector(cssModules, cssImportsByImporter) { resolveId(source, importer) { if (!source.endsWith('.css') || !importer) { - return + return null } const id = path.resolve(path.dirname(importer), source) const imports = cssImportsByImporter.get(importer) ?? new Set() imports.add(id) cssImportsByImporter.set(importer, imports) + + return null }, async transform(_code, id) { @@ -257,9 +259,7 @@ async function getCSSInfo(cssModules, cssImportsByImporter, chunk) { } } - const code = Array.from(cssModuleIds, id => { - return cssModules.get(id) - }).join('\n') + const code = Array.from(cssModuleIds, id => cssModules.get(id)).join('\n') if (code.length === 0) { return { From 53f73d7deea668371e0d3eb222ca7a758dfc93f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 15:03:14 +0000 Subject: [PATCH 06/12] chore: upload export size report artifact Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- .github/workflows/ci.yml | 6 + package-lock.json | 1 + packages/react/package.json | 1 + packages/react/script/get-export-sizes.cjs | 140 ++++++++++++++++++--- 4 files changed, 132 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eebead4144..f4d47ed9686 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -208,3 +208,9 @@ jobs: run: npm run build - name: Get export sizes run: node packages/react/script/get-export-sizes.cjs + - name: Upload export size report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: export-size-report + path: packages/react/dist/export-size-report.json + retention-days: 7 diff --git a/package-lock.json b/package-lock.json index 267f2d2a643..c2b0e4830f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28134,6 +28134,7 @@ "rimraf": "5.0.5", "rollup": "4.59.0", "rollup-plugin-import-css": "^0.0.0", + "specificity": "^0.4.1", "storybook": "^10.3.3", "terser": "5.36.0", "ts-toolbelt": "9.6.0", diff --git a/packages/react/package.json b/packages/react/package.json index 31072eef698..875040bf37f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -174,6 +174,7 @@ "rimraf": "5.0.5", "rollup": "4.59.0", "rollup-plugin-import-css": "^0.0.0", + "specificity": "^0.4.1", "storybook": "^10.3.3", "terser": "5.36.0", "ts-toolbelt": "9.6.0", diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index 6871b5f20ac..fd8637d1dc1 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -10,9 +10,13 @@ const json = require('@rollup/plugin-json') const cssstats = require('cssstats') const {filesize} = require('filesize') const {rollup} = require('rollup') +const specificity = require('specificity') const {minify} = require('terser') const gzipSize = require('gzip-size') +const CSS_SIZE_WARNING_BYTES = Number(process.env.CSS_SIZE_WARNING_BYTES ?? 10 * 1024) +const CSS_SELECTOR_SPECIFICITY_WARNING = Number(process.env.CSS_SELECTOR_SPECIFICITY_WARNING ?? 50) + function createCSSModulesCollector(cssModules, cssImportsByImporter) { return { name: 'collect-css-modules', @@ -85,7 +89,7 @@ async function main() { format: 'esm', }) const minified = await minify(output[0].code) - const css = await getCSSInfo(cssModules, cssImportsByImporter, output[0]) + const css = await getCSSInfo(rootDirectory, cssModules, cssImportsByImporter, output[0]) const exports = [] core.startGroup('Analyzing exports...') @@ -115,7 +119,7 @@ async function main() { format: 'esm', }) const minified = await minify(output[0].code) - const css = await getCSSInfo(exportCSSModules, exportCSSImportsByImporter, output[0]) + const css = await getCSSInfo(rootDirectory, exportCSSModules, exportCSSImportsByImporter, output[0]) exports.push({ identifier, @@ -140,6 +144,28 @@ async function main() { }) } + const reportPath = process.env.EXPORT_SIZE_REPORT_PATH + ? path.resolve(process.cwd(), process.env.EXPORT_SIZE_REPORT_PATH) + : path.join(rootDirectory, 'dist', 'export-size-report.json') + + await fs.mkdir(path.dirname(reportPath), {recursive: true}) + await fs.writeFile( + reportPath, + `${JSON.stringify( + { + generatedAt: new Date().toISOString(), + thresholds: { + cssSizeWarningBytes: CSS_SIZE_WARNING_BYTES, + cssSelectorSpecificityWarning: CSS_SELECTOR_SPECIFICITY_WARNING, + }, + ...data, + }, + null, + 2, + )}\n`, + ) + core.info(`Wrote export size report to ${reportPath}`) + if (process.env.CI) { await core.summary .addHeading('Sizes') @@ -241,7 +267,7 @@ async function main() { } } -async function getCSSInfo(cssModules, cssImportsByImporter, chunk) { +async function getCSSInfo(rootDirectory, cssModules, cssImportsByImporter, chunk) { const cssModuleIds = new Set() if (chunk.type === 'chunk') { @@ -265,30 +291,112 @@ async function getCSSInfo(cssModules, cssImportsByImporter, chunk) { return { size: 0, gzip: 0, - stats: { - rules: { - total: 0, - }, - selectors: { - total: 0, - specificity: { - max: 0, - }, - }, - declarations: { - total: 0, - }, + stats: getEmptyCSSStats(), + stylesheets: [], + warnings: { + stylesheetsOverSize: [], + highSpecificitySelectors: [], }, } } + const stylesheets = await Promise.all( + Array.from(cssModuleIds) + .sort() + .map(async id => { + const stylesheetCode = cssModules.get(id) + const size = Buffer.byteLength(stylesheetCode) + const stats = cssstats(stylesheetCode).toJSON() + const stylesheet = { + path: path.relative(rootDirectory, id), + size, + gzip: await gzipSize(stylesheetCode), + stats, + warnings: { + size: + size >= CSS_SIZE_WARNING_BYTES + ? { + threshold: CSS_SIZE_WARNING_BYTES, + actual: size, + } + : null, + highSpecificitySelectors: getHighSpecificitySelectors(stats.selectors.values ?? []), + }, + } + + return stylesheet + }), + ) + const stylesheetsOverSize = stylesheets + .filter(stylesheet => stylesheet.warnings.size) + .map(stylesheet => { + return { + path: stylesheet.path, + threshold: stylesheet.warnings.size.threshold, + actual: stylesheet.warnings.size.actual, + } + }) + const highSpecificitySelectors = stylesheets.flatMap(stylesheet => { + return stylesheet.warnings.highSpecificitySelectors.map(selector => { + return { + stylesheet: stylesheet.path, + ...selector, + } + }) + }) + return { size: Buffer.byteLength(code), gzip: await gzipSize(code), stats: cssstats(code).toJSON(), + stylesheets, + warnings: { + stylesheetsOverSize, + highSpecificitySelectors, + }, + } +} + +function getEmptyCSSStats() { + return { + rules: { + total: 0, + }, + selectors: { + total: 0, + specificity: { + max: 0, + }, + }, + declarations: { + total: 0, + }, } } +function getHighSpecificitySelectors(selectors) { + return selectors + .flatMap(selector => { + return specificity.calculate(selector).map(result => { + return { + selector: result.selector.trim(), + specificity: getSpecificityValue(result.specificityArray), + specificityArray: result.specificityArray, + } + }) + }) + .filter(result => { + return result.specificity >= CSS_SELECTOR_SPECIFICITY_WARNING + }) + .sort((a, b) => { + return b.specificity - a.specificity + }) +} + +function getSpecificityValue(specificityArray) { + return specificityArray[1] * 100 + specificityArray[2] * 10 + specificityArray[3] +} + function getEntrypoints(packageJson) { if (packageJson.exports) { return getPackageExports(packageJson.exports, packageJson.type) From ce436dcd7948529966a0ba7e61fb6f1370d11446 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 15:07:50 +0000 Subject: [PATCH 07/12] chore: clarify css report thresholds Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index fd8637d1dc1..029f385354d 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -14,8 +14,11 @@ const specificity = require('specificity') const {minify} = require('terser') const gzipSize = require('gzip-size') -const CSS_SIZE_WARNING_BYTES = Number(process.env.CSS_SIZE_WARNING_BYTES ?? 10 * 1024) -const CSS_SELECTOR_SPECIFICITY_WARNING = Number(process.env.CSS_SELECTOR_SPECIFICITY_WARNING ?? 50) +const CSS_SIZE_WARNING_THRESHOLD_BYTES = Number(process.env.CSS_SIZE_WARNING_BYTES ?? 10 * 1024) +const CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD = Number(process.env.CSS_SELECTOR_SPECIFICITY_WARNING ?? 50) +const SPECIFICITY_ID_WEIGHT = 100 +const SPECIFICITY_CLASS_WEIGHT = 10 +const SPECIFICITY_ELEMENT_WEIGHT = 1 function createCSSModulesCollector(cssModules, cssImportsByImporter) { return { @@ -155,8 +158,8 @@ async function main() { { generatedAt: new Date().toISOString(), thresholds: { - cssSizeWarningBytes: CSS_SIZE_WARNING_BYTES, - cssSelectorSpecificityWarning: CSS_SELECTOR_SPECIFICITY_WARNING, + cssSizeWarningBytes: CSS_SIZE_WARNING_THRESHOLD_BYTES, + cssSelectorSpecificityWarning: CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD, }, ...data, }, @@ -314,9 +317,9 @@ async function getCSSInfo(rootDirectory, cssModules, cssImportsByImporter, chunk stats, warnings: { size: - size >= CSS_SIZE_WARNING_BYTES + size >= CSS_SIZE_WARNING_THRESHOLD_BYTES ? { - threshold: CSS_SIZE_WARNING_BYTES, + threshold: CSS_SIZE_WARNING_THRESHOLD_BYTES, actual: size, } : null, @@ -386,7 +389,7 @@ function getHighSpecificitySelectors(selectors) { }) }) .filter(result => { - return result.specificity >= CSS_SELECTOR_SPECIFICITY_WARNING + return result.specificity >= CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD }) .sort((a, b) => { return b.specificity - a.specificity @@ -394,7 +397,12 @@ function getHighSpecificitySelectors(selectors) { } function getSpecificityValue(specificityArray) { - return specificityArray[1] * 100 + specificityArray[2] * 10 + specificityArray[3] + // specificityArray is [inline, ids, classes/attributes/pseudo-classes, elements/pseudo-elements]. + return ( + specificityArray[1] * SPECIFICITY_ID_WEIGHT + + specificityArray[2] * SPECIFICITY_CLASS_WEIGHT + + specificityArray[3] * SPECIFICITY_ELEMENT_WEIGHT + ) } function getEntrypoints(packageJson) { From f7f516f6e0c9a4535ec86ea059f770a8c86d8788 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 15:11:03 +0000 Subject: [PATCH 08/12] chore: clarify css warning env vars Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index 029f385354d..ced193a1096 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -14,8 +14,12 @@ const specificity = require('specificity') const {minify} = require('terser') const gzipSize = require('gzip-size') -const CSS_SIZE_WARNING_THRESHOLD_BYTES = Number(process.env.CSS_SIZE_WARNING_BYTES ?? 10 * 1024) -const CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD = Number(process.env.CSS_SELECTOR_SPECIFICITY_WARNING ?? 50) +const CSS_SIZE_WARNING_THRESHOLD_BYTES = Number( + process.env.CSS_SIZE_WARNING_THRESHOLD_BYTES ?? process.env.CSS_SIZE_WARNING_BYTES ?? 10 * 1024, +) +const CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD = Number( + process.env.CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD ?? process.env.CSS_SELECTOR_SPECIFICITY_WARNING ?? 50, +) const SPECIFICITY_ID_WEIGHT = 100 const SPECIFICITY_CLASS_WEIGHT = 10 const SPECIFICITY_ELEMENT_WEIGHT = 1 @@ -398,6 +402,7 @@ function getHighSpecificitySelectors(selectors) { function getSpecificityValue(specificityArray) { // specificityArray is [inline, ids, classes/attributes/pseudo-classes, elements/pseudo-elements]. + // CSS selectors cannot include inline style specificity, so index 0 is not included in this score. return ( specificityArray[1] * SPECIFICITY_ID_WEIGHT + specificityArray[2] * SPECIFICITY_CLASS_WEIGHT + From e14cfa24e5a89a16f55dde438db0dc31f8730e66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 15:16:36 +0000 Subject: [PATCH 09/12] chore: use canonical css warning env vars Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index ced193a1096..708b363e911 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -14,12 +14,8 @@ const specificity = require('specificity') const {minify} = require('terser') const gzipSize = require('gzip-size') -const CSS_SIZE_WARNING_THRESHOLD_BYTES = Number( - process.env.CSS_SIZE_WARNING_THRESHOLD_BYTES ?? process.env.CSS_SIZE_WARNING_BYTES ?? 10 * 1024, -) -const CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD = Number( - process.env.CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD ?? process.env.CSS_SELECTOR_SPECIFICITY_WARNING ?? 50, -) +const CSS_SIZE_WARNING_THRESHOLD_BYTES = Number(process.env.CSS_SIZE_WARNING_THRESHOLD_BYTES ?? 10 * 1024) +const CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD = Number(process.env.CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD ?? 50) const SPECIFICITY_ID_WEIGHT = 100 const SPECIFICITY_CLASS_WEIGHT = 10 const SPECIFICITY_ELEMENT_WEIGHT = 1 From f5dc7bf8eeeea9fa8e29db26fabee229e2598860 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 15:20:24 +0000 Subject: [PATCH 10/12] chore: document css report thresholds Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- .github/workflows/ci.yml | 1 + packages/react/script/get-export-sizes.cjs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4d47ed9686..1d05e21b425 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -209,6 +209,7 @@ jobs: - name: Get export sizes run: node packages/react/script/get-export-sizes.cjs - name: Upload export size report + # actions/upload-artifact@v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: export-size-report diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index 708b363e911..771cad0830c 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -14,6 +14,7 @@ const specificity = require('specificity') const {minify} = require('terser') const gzipSize = require('gzip-size') +// Default thresholds flag component-sized CSS files and selector scores that are high enough to review. const CSS_SIZE_WARNING_THRESHOLD_BYTES = Number(process.env.CSS_SIZE_WARNING_THRESHOLD_BYTES ?? 10 * 1024) const CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD = Number(process.env.CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD ?? 50) const SPECIFICITY_ID_WEIGHT = 100 @@ -399,6 +400,7 @@ function getHighSpecificitySelectors(selectors) { function getSpecificityValue(specificityArray) { // specificityArray is [inline, ids, classes/attributes/pseudo-classes, elements/pseudo-elements]. // CSS selectors cannot include inline style specificity, so index 0 is not included in this score. + // These weights follow CSS specificity scoring: IDs > classes/attributes/pseudo-classes > elements. return ( specificityArray[1] * SPECIFICITY_ID_WEIGHT + specificityArray[2] * SPECIFICITY_CLASS_WEIGHT + From e1708bb5a461635f9feb9833c289471ef0b9e909 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 15:24:18 +0000 Subject: [PATCH 11/12] chore: add css report error context Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 73 ++++++++++++++-------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index 771cad0830c..ba2aff727b9 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -152,22 +152,26 @@ async function main() { ? path.resolve(process.cwd(), process.env.EXPORT_SIZE_REPORT_PATH) : path.join(rootDirectory, 'dist', 'export-size-report.json') - await fs.mkdir(path.dirname(reportPath), {recursive: true}) - await fs.writeFile( - reportPath, - `${JSON.stringify( - { - generatedAt: new Date().toISOString(), - thresholds: { - cssSizeWarningBytes: CSS_SIZE_WARNING_THRESHOLD_BYTES, - cssSelectorSpecificityWarning: CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD, + try { + await fs.mkdir(path.dirname(reportPath), {recursive: true}) + await fs.writeFile( + reportPath, + `${JSON.stringify( + { + generatedAt: new Date().toISOString(), + thresholds: { + cssSizeWarningBytes: CSS_SIZE_WARNING_THRESHOLD_BYTES, + cssSelectorSpecificityWarning: CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD, + }, + ...data, }, - ...data, - }, - null, - 2, - )}\n`, - ) + null, + 2, + )}\n`, + ) + } catch (error) { + throw new Error(`Unable to write export size report to ${reportPath}`, {cause: error}) + } core.info(`Wrote export size report to ${reportPath}`) if (process.env.CI) { @@ -310,9 +314,10 @@ async function getCSSInfo(rootDirectory, cssModules, cssImportsByImporter, chunk .map(async id => { const stylesheetCode = cssModules.get(id) const size = Buffer.byteLength(stylesheetCode) - const stats = cssstats(stylesheetCode).toJSON() + const stylesheetPath = path.relative(rootDirectory, id) + const stats = getCSSStats(stylesheetCode, stylesheetPath) const stylesheet = { - path: path.relative(rootDirectory, id), + path: stylesheetPath, size, gzip: await gzipSize(stylesheetCode), stats, @@ -324,7 +329,7 @@ async function getCSSInfo(rootDirectory, cssModules, cssImportsByImporter, chunk actual: size, } : null, - highSpecificitySelectors: getHighSpecificitySelectors(stats.selectors.values ?? []), + highSpecificitySelectors: getHighSpecificitySelectors(stats.selectors.values ?? [], stylesheetPath), }, } @@ -352,7 +357,7 @@ async function getCSSInfo(rootDirectory, cssModules, cssImportsByImporter, chunk return { size: Buffer.byteLength(code), gzip: await gzipSize(code), - stats: cssstats(code).toJSON(), + stats: getCSSStats(code, 'combined CSS'), stylesheets, warnings: { stylesheetsOverSize, @@ -378,16 +383,30 @@ function getEmptyCSSStats() { } } -function getHighSpecificitySelectors(selectors) { +function getCSSStats(code, stylesheetPath) { + try { + return cssstats(code).toJSON() + } catch (error) { + throw new Error(`Unable to collect CSS stats for ${stylesheetPath}`, {cause: error}) + } +} + +function getHighSpecificitySelectors(selectors, stylesheetPath) { return selectors .flatMap(selector => { - return specificity.calculate(selector).map(result => { - return { - selector: result.selector.trim(), - specificity: getSpecificityValue(result.specificityArray), - specificityArray: result.specificityArray, - } - }) + try { + return specificity.calculate(selector).map(result => { + return { + selector: result.selector.trim(), + specificity: getSpecificityValue(result.specificityArray), + specificityArray: result.specificityArray, + } + }) + } catch (error) { + throw new Error(`Unable to calculate selector specificity for \`${selector}\` in ${stylesheetPath}`, { + cause: error, + }) + } }) .filter(result => { return result.specificity >= CSS_SELECTOR_SPECIFICITY_WARNING_THRESHOLD From 4ba6bf55a68b7cde50efa25c96997f45ad52fd47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 15:27:39 +0000 Subject: [PATCH 12/12] chore: document specificity score helper Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- packages/react/script/get-export-sizes.cjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react/script/get-export-sizes.cjs b/packages/react/script/get-export-sizes.cjs index ba2aff727b9..e6cf66c5c65 100644 --- a/packages/react/script/get-export-sizes.cjs +++ b/packages/react/script/get-export-sizes.cjs @@ -416,6 +416,10 @@ function getHighSpecificitySelectors(selectors, stylesheetPath) { }) } +/** + * @param {[number, number, number, number]} specificityArray [inline, ids, classes/attributes/pseudo-classes, elements/pseudo-elements] + * @returns {number} CSS selector specificity score without inline style specificity + */ function getSpecificityValue(specificityArray) { // specificityArray is [inline, ids, classes/attributes/pseudo-classes, elements/pseudo-elements]. // CSS selectors cannot include inline style specificity, so index 0 is not included in this score.