From 1d9a552dcf41be67d70fdacc6d52ba1ad88097c1 Mon Sep 17 00:00:00 2001 From: Liz Mitchell Date: Tue, 12 Dec 2023 17:25:03 -0800 Subject: [PATCH] docs: add size tracking --- docs/size.md | 87 ++++++++ package-lock.json | 323 ++++++++++++++++++++++++--- package.json | 27 ++- scripts/analyzer/element-docs-map.ts | 71 ++---- scripts/component-custom-elements.ts | 56 +++++ scripts/size/bundle-size.ts | 99 ++++++++ scripts/size/update-size.ts | 122 ++++++++++ 7 files changed, 696 insertions(+), 89 deletions(-) create mode 100644 docs/size.md create mode 100644 scripts/component-custom-elements.ts create mode 100644 scripts/size/bundle-size.ts create mode 100644 scripts/size/update-size.ts diff --git a/docs/size.md b/docs/size.md new file mode 100644 index 000000000..aee249d4a --- /dev/null +++ b/docs/size.md @@ -0,0 +1,87 @@ +# Sizes + + + + + + + +This doc tracks important size metrics for Material Web Components. + +- **gzip** - minified and compressed. This impacts download size, which can + take longer on slow networks. + +- **minified** - minified and unpacked. This impacts the time it takes a page + to be interactive, which can take longer on some devices. + + + +Last updated 2023-12-12. + + + +Component | gzip | minified | Import +--- | --- | --- | --- +**All** | **70.0kb** | 451.6kb *(66% CSS)* | *@material/web/all.js* +**Common** | **51.4kb** | 281.9kb *(54% CSS)* | *@material/web/common.js* +**Button** | **8.0kb** | 46.3kb *(66% CSS)* | + | 6.7kb | 27.4kb *(49% CSS)* | *@material/web/button/elevated-button.js* + | 6.6kb | 27.3kb *(49% CSS)* | *@material/web/button/filled-button.js* + | 6.7kb | 27.7kb *(49% CSS)* | *@material/web/button/filled-tonal-button.js* + | 6.4kb | 25.7kb *(48% CSS)* | *@material/web/button/outlined-button.js* + | 6.2kb | 24.2kb *(45% CSS)* | *@material/web/button/text-button.js* +**Checkbox** | **7.0kb** | 28.5kb *(43% CSS)* | *@material/web/checkbox/checkbox.js* +**Chips** | **9.9kb** | 60.1kb *(64% CSS)* | + | 4.8kb | 16.4kb *(22% CSS)* | *@material/web/chips/chip-set.js* + | 6.3kb | 26.8kb *(51% CSS)* | *@material/web/chips/assist-chip.js* + | 7.8kb | 36.7kb *(56% CSS)* | *@material/web/chips/filter-chip.js* + | 7.3kb | 33.6kb *(54% CSS)* | *@material/web/chips/input-chip.js* + | 6.4kb | 27.2kb *(51% CSS)* | *@material/web/chips/suggestion-chip.js* +**Dialog** | **4.2kb** | 15.2kb *(36% CSS)* | *@material/web/dialog/dialog.js* +**Divider** | **0.7kb** | 1.4kb *(39% CSS)* | *@material/web/divider/divider.js* +**Elevation** | **0.7kb** | 1.7kb *(62% CSS)* | *@material/web/elevation/elevation.js* +**Fab** | **6.9kb** | 37.1kb *(67% CSS)* | + | 6.6kb | 32.8kb *(64% CSS)* | *@material/web/fab/fab.js* + | 5.8kb | 24.6kb *(51% CSS)* | *@material/web/fab/branded-fab.js* +**Field** | **6.0kb** | 40.5kb *(83% CSS)* | + | 4.6kb | 24.8kb *(75% CSS)* | *@material/web/field/filled-field.js* + | 5.0kb | 27.1kb *(76% CSS)* | *@material/web/field/outlined-field.js* +**Focus** | **1.6kb** | 5.2kb *(46% CSS)* | *@material/web/focus/md-focus-ring.js* +**Icon** | **0.7kb** | 1.3kb *(46% CSS)* | *@material/web/icon/icon.js* +**Icon button** | **7.3kb** | 42.0kb *(65% CSS)* | + | 5.8kb | 23.1kb *(42% CSS)* | *@material/web/iconbutton/icon-button.js* + | 6.0kb | 25.0kb *(45% CSS)* | *@material/web/iconbutton/filled-icon-button.js* + | 6.0kb | 25.5kb *(46% CSS)* | *@material/web/iconbutton/filled-tonal-icon-button.js* + | 6.0kb | 24.6kb *(45% CSS)* | *@material/web/iconbutton/outlined-icon-button.js* +**List** | **6.9kb** | 27.2kb *(35% CSS)* | + | 1.6kb | 4.5kb *(5% CSS)* | *@material/web/list/list.js* + | 5.8kb | 23.0kb *(40% CSS)* | *@material/web/list/list-item.js* +**Menu** | **13.5kb** | 53.9kb *(23% CSS)* | + | 7.9kb | 28.8kb *(17% CSS)* | *@material/web/menu/menu.js* + | 6.5kb | 25.6kb *(37% CSS)* | *@material/web/menu/menu-item.js* + | 8.4kb | 31.9kb *(11% CSS)* | *@material/web/menu/sub-menu.js* +**Progress** | **3.5kb** | 13.9kb *(70% CSS)* | + | 2.6kb | 8.6kb *(64% CSS)* | *@material/web/progress/linear-progress.js* + | 2.2kb | 7.4kb *(57% CSS)* | *@material/web/progress/circular-progress.js* +**Radio** | **6.9kb** | 26.0kb *(31% CSS)* | *@material/web/radio/radio.js* +**Ripple** | **2.8kb** | 7.9kb *(14% CSS)* | *@material/web/ripple/ripple.js* +**Select** | **25.6kb** | 142.5kb *(57% CSS)* | + | 17.8kb | 89.3kb *(48% CSS)* | *@material/web/select/filled-select.js* + | 18.1kb | 89.9kb *(48% CSS)* | *@material/web/select/outlined-select.js* + | 6.6kb | 26.6kb *(36% CSS)* | *@material/web/select/select-option.js* +**Slider** | **9.7kb** | 45.0kb *(49% CSS)* | *@material/web/slider/slider.js* +**Switch** | **7.8kb** | 34.8kb *(53% CSS)* | *@material/web/switch/switch.js* +**Tabs** | **7.9kb** | 35.1kb *(50% CSS)* | + | 6.2kb | 21.9kb *(25% CSS)* | *@material/web/tabs/tabs.js* + | 6.3kb | 25.6kb *(48% CSS)* | *@material/web/tabs/primary-tab.js* + | 6.2kb | 25.2kb *(48% CSS)* | *@material/web/tabs/secondary-tab.js* +**Text field** | **13.7kb** | 93.0kb *(74% CSS)* | + | 10.7kb | 60.8kb *(62% CSS)* | *@material/web/textfield/filled-text-field.js* + | 10.9kb | 61.3kb *(62% CSS)* | *@material/web/textfield/outlined-text-field.js* + + + + diff --git a/package-lock.json b/package-lock.json index fb63df2b1..817a1c9ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,10 +17,14 @@ }, "devDependencies": { "@lit-labs/analyzer": "^0.9.2", + "@rollup/plugin-multi-entry": "^6.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", "@types/jasmine": "^4.0.3", "@web/test-runner": "^0.15.0", "@web/test-runner-playwright": "^0.9.0", "jasmine": "^4.5.0", + "rollup": "^2.79.1", "sass": "^1.52.3", "sass-true": "^6.1.0", "typescript": "5.1.6", @@ -746,6 +750,20 @@ "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", "dev": true }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", @@ -755,6 +773,25 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", @@ -1448,41 +1485,111 @@ } } }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", - "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", + "node_modules/@rollup/plugin-multi-entry": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-multi-entry/-/plugin-multi-entry-6.0.1.tgz", + "integrity": "sha512-AXm6toPyTSfbYZWghQGbom1Uh7dHXlrGa+HoiYNhQtDUE3Q7LqoUYdVQx9E1579QWS1uOiu+cZRSE4okO7ySgw==", "dev": true, "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "@types/resolve": "1.17.1", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.19.0" + "@rollup/plugin-virtual": "^3.0.0", + "matched": "^5.0.1" }, "engines": { - "node": ">= 10.0.0" + "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.42.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "dev": true, "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" }, "engines": { - "node": ">= 8.0.0" + "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, "node_modules/@sindresorhus/slugify": { @@ -1640,9 +1747,9 @@ "dev": true }, "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/express": { "version": "4.17.17", @@ -1826,13 +1933,10 @@ "dev": true }, "node_modules/@types/resolve": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", - "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true }, "node_modules/@types/send": { "version": "0.17.1", @@ -2030,6 +2134,64 @@ "node": ">=10.0.0" } }, + "node_modules/@web/dev-server-rollup/node_modules/@rollup/plugin-node-resolve": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz", + "integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^2.42.0" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "node_modules/@web/dev-server-rollup/node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@web/dev-server-rollup/node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, "node_modules/@web/dev-server-rollup/node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -2553,6 +2715,12 @@ "node": "*" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -3612,9 +3780,9 @@ } }, "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, "node_modules/etag": { @@ -5258,6 +5426,22 @@ "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", "dev": true }, + "node_modules/matched": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/matched/-/matched-5.0.1.tgz", + "integrity": "sha512-E1fhSTPRyhAlNaNvGXAgZQlq1hL0bgYMTk/6bktVlIhzUnX/SZs7296ACdVeNJE8xFNGSuvd9IpI7vSnmcqLvw==", + "dev": true, + "dependencies": { + "glob": "^7.1.6", + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/material-web-catalog": { "resolved": "catalog", "link": true @@ -6416,6 +6600,15 @@ } ] }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -6784,6 +6977,15 @@ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -6899,6 +7101,12 @@ "node": ">=8.0.0" } }, + "node_modules/smob": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", + "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", + "dev": true + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -6928,6 +7136,25 @@ "decode-uri-component": "^0.2.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -7115,6 +7342,36 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/package.json b/package.json index 03555fdd7..cea5ed20c 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,9 @@ "build:sass": "wireit", "test": "wireit", "build:catalog": "wireit", - "build:analyzer": "wireit", - "update-docs": "wireit" + "build:scripts": "wireit", + "update-docs": "wireit", + "update-size": "wireit" }, "type": "module", "files": [ @@ -56,10 +57,14 @@ }, "devDependencies": { "@lit-labs/analyzer": "^0.9.2", + "@rollup/plugin-multi-entry": "^6.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", "@types/jasmine": "^4.0.3", "@web/test-runner": "^0.15.0", "@web/test-runner-playwright": "^0.9.0", "jasmine": "^4.5.0", + "rollup": "^2.79.1", "sass": "^1.52.3", "sass-true": "^6.1.0", "typescript": "5.1.6", @@ -145,19 +150,19 @@ "./catalog:build:prod" ] }, - "build:analyzer": { + "build:scripts": { "command": "tsc -b scripts/tsconfig.json --pretty", "files": [ "scripts/tsconfig.json", - "scripts/analyzer/**/*.ts", + "scripts/**/*.ts", "!**/*.d.ts", "!**/*.css.ts" ], "output": [ "scripts/.tsbuildinfo", - "scripts/analyzer/**/*.js", - "scripts/analyzer/**/*.js.map", - "scripts/analyzer/**/*.d.ts" + "scripts/**/*.js", + "scripts/**/*.js.map", + "scripts/**/*.d.ts" ], "clean": "if-file-deleted" }, @@ -175,7 +180,13 @@ ], "output": [], "dependencies": [ - "build:analyzer" + "build:scripts" + ] + }, + "update-size": { + "command": "node scripts/size/update-size.js", + "dependencies": [ + "build:scripts" ] } } diff --git a/scripts/analyzer/element-docs-map.ts b/scripts/analyzer/element-docs-map.ts index 622adb0ee..a5f5a7f44 100644 --- a/scripts/analyzer/element-docs-map.ts +++ b/scripts/analyzer/element-docs-map.ts @@ -4,56 +4,31 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {COMPONENT_CUSTOM_ELEMENTS} from '../component-custom-elements.js'; + /** * A map of Markdown documentation file name to element entrypoints associated * with that documentation. */ -export const docsToElementMapping: {[key: string]: string[]} = { - 'button.md': [ - 'button/elevated-button.ts', - 'button/filled-button.ts', - 'button/filled-tonal-button.ts', - 'button/outlined-button.ts', - 'button/text-button.ts', - ], - 'checkbox.md': ['checkbox/checkbox.ts'], - 'chip.md': [ - 'chips/chip-set.ts', - 'chips/assist-chip.ts', - 'chips/filter-chip.ts', - 'chips/input-chip.ts', - 'chips/suggestion-chip.ts', - ], - 'dialog.md': ['dialog/dialog.ts'], - 'divider.md': ['divider/divider.ts'], - 'elevation.md': ['elevation/elevation.ts'], - 'fab.md': ['fab/fab.ts', 'fab/branded-fab.ts'], - 'focus-ring.md': ['focus/md-focus-ring.ts'], - 'icon-button.md': [ - 'iconbutton/icon-button.ts', - 'iconbutton/filled-icon-button.ts', - 'iconbutton/filled-tonal-icon-button.ts', - 'iconbutton/outlined-icon-button.ts', - ], - 'icon.md': ['icon/icon.ts'], - 'list.md': ['list/list.ts', 'list/list-item.ts'], - 'menu.md': ['menu/menu.ts', 'menu/menu-item.ts', 'menu/sub-menu.ts'], - 'progress.md': [ - 'progress/linear-progress.ts', - 'progress/circular-progress.ts', - ], - 'radio.md': ['radio/radio.ts'], - 'ripple.md': ['ripple/ripple.ts'], - 'slider.md': ['slider/slider.ts'], - 'switch.md': ['switch/switch.ts'], - 'tabs.md': ['tabs/tabs.ts', 'tabs/primary-tab.ts', 'tabs/secondary-tab.ts'], - 'text-field.md': [ - 'textfield/filled-text-field.ts', - 'textfield/outlined-text-field.ts', - ], - 'select.md': [ - 'select/filled-select.ts', - 'select/outlined-select.ts', - 'select/select-option.ts', - ], +export const docsToElementMapping: {[key: string]: readonly string[]} = { + 'button.md': COMPONENT_CUSTOM_ELEMENTS.button, + 'checkbox.md': COMPONENT_CUSTOM_ELEMENTS.checkbox, + 'chip.md': COMPONENT_CUSTOM_ELEMENTS.chips, + 'dialog.md': COMPONENT_CUSTOM_ELEMENTS.dialog, + 'divider.md': COMPONENT_CUSTOM_ELEMENTS.divider, + 'elevation.md': COMPONENT_CUSTOM_ELEMENTS.elevation, + 'fab.md': COMPONENT_CUSTOM_ELEMENTS.fab, + 'focus-ring.md': COMPONENT_CUSTOM_ELEMENTS.focus, + 'icon-button.md': COMPONENT_CUSTOM_ELEMENTS.iconButton, + 'icon.md': COMPONENT_CUSTOM_ELEMENTS.icon, + 'list.md': COMPONENT_CUSTOM_ELEMENTS.list, + 'menu.md': COMPONENT_CUSTOM_ELEMENTS.menu, + 'progress.md': COMPONENT_CUSTOM_ELEMENTS.progress, + 'radio.md': COMPONENT_CUSTOM_ELEMENTS.radio, + 'ripple.md': COMPONENT_CUSTOM_ELEMENTS.ripple, + 'slider.md': COMPONENT_CUSTOM_ELEMENTS.slider, + 'switch.md': COMPONENT_CUSTOM_ELEMENTS.switch, + 'tabs.md': COMPONENT_CUSTOM_ELEMENTS.tabs, + 'text-field.md': COMPONENT_CUSTOM_ELEMENTS.textField, + 'select.md': COMPONENT_CUSTOM_ELEMENTS.select, }; diff --git a/scripts/component-custom-elements.ts b/scripts/component-custom-elements.ts new file mode 100644 index 000000000..1f557629a --- /dev/null +++ b/scripts/component-custom-elements.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * A map of components and their custom element TypeScript entrypoints. + */ +export const COMPONENT_CUSTOM_ELEMENTS = { + button: [ + 'button/elevated-button.ts', + 'button/filled-button.ts', + 'button/filled-tonal-button.ts', + 'button/outlined-button.ts', + 'button/text-button.ts', + ], + checkbox: ['checkbox/checkbox.ts'], + chips: [ + 'chips/chip-set.ts', + 'chips/assist-chip.ts', + 'chips/filter-chip.ts', + 'chips/input-chip.ts', + 'chips/suggestion-chip.ts', + ], + dialog: ['dialog/dialog.ts'], + divider: ['divider/divider.ts'], + elevation: ['elevation/elevation.ts'], + fab: ['fab/fab.ts', 'fab/branded-fab.ts'], + field: ['field/filled-field.ts', 'field/outlined-field.ts'], + focus: ['focus/md-focus-ring.ts'], + icon: ['icon/icon.ts'], + iconButton: [ + 'iconbutton/icon-button.ts', + 'iconbutton/filled-icon-button.ts', + 'iconbutton/filled-tonal-icon-button.ts', + 'iconbutton/outlined-icon-button.ts', + ], + list: ['list/list.ts', 'list/list-item.ts'], + menu: ['menu/menu.ts', 'menu/menu-item.ts', 'menu/sub-menu.ts'], + progress: ['progress/linear-progress.ts', 'progress/circular-progress.ts'], + radio: ['radio/radio.ts'], + ripple: ['ripple/ripple.ts'], + select: [ + 'select/filled-select.ts', + 'select/outlined-select.ts', + 'select/select-option.ts', + ], + slider: ['slider/slider.ts'], + switch: ['switch/switch.ts'], + tabs: ['tabs/tabs.ts', 'tabs/primary-tab.ts', 'tabs/secondary-tab.ts'], + textField: [ + 'textfield/filled-text-field.ts', + 'textfield/outlined-text-field.ts', + ], +} as const; diff --git a/scripts/size/bundle-size.ts b/scripts/size/bundle-size.ts new file mode 100644 index 000000000..23e4f3786 --- /dev/null +++ b/scripts/size/bundle-size.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import multiEntry from '@rollup/plugin-multi-entry'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import {rollup} from 'rollup'; +import {promisify} from 'util'; +import {gzip} from 'zlib'; + +const gzipPromise = promisify(gzip); + +export interface Bundle { + name: string; + inputs: string[]; +} + +export interface BundleSize { + name: string; + size: Size; + inputs: InputSize[]; +} + +export interface InputSize { + input: string; + size: Size; +} + +export interface Size { + raw: number; + css: number; + gzip: number; +} + +export async function getBundleSize(bundle: Bundle): Promise { + const bundleSize = await computeBundleSize(bundle.inputs); + let inputSizes: InputSize[]; + if (bundle.inputs.length === 1) { + // If there's only one input, we don't need to re-generate the bundle. + inputSizes = [{input: bundle.inputs[0], size: bundleSize}]; + } else { + // Include computed bundle size for individual inputs. + inputSizes = await Promise.all( + bundle.inputs.map(async (input) => { + return { + input, + size: await computeBundleSize(input), + }; + }), + ); + } + + return { + name: bundle.name, + size: bundleSize, + inputs: inputSizes, + }; +} + +async function computeBundleSize(input: string | string[]): Promise { + const rollupBundle = await rollup({ + input, + external: [/node_modules/], + plugins: [multiEntry(), nodeResolve(), terser()], + }); + + let code = ''; + try { + const {output} = await rollupBundle.generate({}); + code = output[0].code; + } finally { + rollupBundle.close(); + } + + const litCssTagNameMatch = code.match(/css\s*as\s*(\w+)/); + if (!litCssTagNameMatch) { + throw new Error("Cannot find `import { css as X } from 'lit'`"); + } + + const litCssTagName = litCssTagNameMatch[1]; + const codeWithoutCss = code.replaceAll( + new RegExp(`${litCssTagName}\`([^\`]*)\``, 'g'), + '', + ); + + const encoder = new TextEncoder(); + const codeSize = encoder.encode(code).length; + const codeSizeWithoutCss = encoder.encode(codeWithoutCss).length; + const cssSize = codeSize - codeSizeWithoutCss; + const gzipSize = (await gzipPromise(code)).length; + return { + raw: codeSize, + css: cssSize, + gzip: gzipSize, + }; +} diff --git a/scripts/size/update-size.ts b/scripts/size/update-size.ts new file mode 100644 index 000000000..c3e069abd --- /dev/null +++ b/scripts/size/update-size.ts @@ -0,0 +1,122 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as fs from 'fs/promises'; + +import {MarkdownTable} from '../analyzer/markdown-tree-builder.js'; +import {COMPONENT_CUSTOM_ELEMENTS} from '../component-custom-elements.js'; +import {Bundle, Size, getBundleSize} from './bundle-size.js'; + +// The bundles to track sizes for. + +const bundles: Bundle[] = [ + { + name: 'all', + inputs: ['all.js'], + }, + { + name: 'common', + inputs: ['common.js'], + }, + ...( + Object.keys(COMPONENT_CUSTOM_ELEMENTS) as Array< + keyof typeof COMPONENT_CUSTOM_ELEMENTS + > + ).map((component) => { + const tsCustomElementPaths = COMPONENT_CUSTOM_ELEMENTS[component]; + const jsCustomElementPaths = tsCustomElementPaths.map((tsPath) => + tsPath.replace(/\.ts$/, '.js'), + ); + + return { + name: component, + inputs: jsCustomElementPaths, + }; + }), +]; + +// Compute bundle sizes. + +const bundleSizes = await Promise.all( + bundles.map((bundle) => getBundleSize(bundle)), +); + +// Create a markdown table with size data. + +const columns = ['Component', 'gzip', 'minified', 'Import']; +const rows: string[][] = []; +for (const {name, size, inputs} of bundleSizes) { + rows.push([ + `**${camelToSentenceCase(name)}**`, + `**${bytesToString(size.gzip)}**`, + getJsAndCss(size), + inputs.length === 1 ? `*${getImport(inputs[0].input)}*` : '', + ]); + + if (inputs.length > 1) { + rows.push( + ...inputs.map((input) => { + return [ + '', + `${bytesToString(input.size.gzip)}`, + `${getJsAndCss(input.size)}`, + `*${getImport(input.input)}*`, + ]; + }), + ); + } +} + +const markdownTable = new MarkdownTable(columns); +for (const row of rows) { + markdownTable.addRow(row); +} + +// Update markdown file. + +const markdownContent = await fs.readFile('docs/size.md', {encoding: 'utf8'}); +const updateTrackingStart = ''; +const updateTrackingEnd = ''; + +const now = new Date(); +const nowString = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; + +const newMarkdownContent = [ + markdownContent.substring(0, markdownContent.indexOf(updateTrackingStart)), + updateTrackingStart, + '\n\n', + `Last updated ${nowString}.\n\n`, + markdownTable.toString(), + '\n\n', + markdownContent.substring(markdownContent.indexOf(updateTrackingEnd)), +].join(''); + +await fs.writeFile('docs/size.md', newMarkdownContent); + +// Text formatting functions for markdown table. + +function getImport(input: string) { + return `@material/web/${input}`; +} + +function getCssPercent(size: Size) { + return `${Math.round((size.css / size.raw) * 100)}%`; +} + +function bytesToString(bytes: number) { + return `${(Math.round(bytes / 100) / 10).toFixed(1)}kb`; +} + +function getJsAndCss(size: Size) { + return `${bytesToString(size.raw)} *(${getCssPercent( + size, + )} CSS)*`; +} + +function camelToSentenceCase(value: string) { + const withSpaces = value.replaceAll(/([a-z])([A-Z])/g, '$1 $2'); + return withSpaces[0].toUpperCase() + withSpaces.slice(1).toLowerCase(); +}