diff --git a/build/bundle-sizes.js b/build/bundle-sizes.js new file mode 100644 index 00000000..0e621d8a --- /dev/null +++ b/build/bundle-sizes.js @@ -0,0 +1,92 @@ +import path from 'node:path'; +import postcss from 'postcss'; +import fs from 'node:fs/promises'; +import pkg from '../postcss.config.cjs'; +import { createRequire } from 'node:module'; +import { sync as brotli } from 'brotli-size'; +import { gzipSizeSync as gzip } from 'gzip-size'; + +// Comes in handy later when we run postcss +const { plugins } = pkg; + +// @ts-ignore +const require = createRequire(import.meta.url); +const { scripts } = require('../package.json'); +/** + * We build up an object with script:command pairs from package.json + * + * @type {Object.} + */ +const filtered = Object.keys(scripts) + .filter((key) => key.startsWith('lib:')) + .reduce((obj, key) => { + obj[key] = scripts[key]; + return obj; + }, {}); + +/** + * The regex captures a filepath and filename group from an npm command. + * + * Captures for the command `postcss src/extra/normalize.light.css -o normalize.light.min.css` yields + * { + * groups: { + * filepath: 'src/extra/normalize.light.css', + * filename: 'normalize.light.min.css' + * } + * } + */ +const regex = /postcss\s(?\S+)\s\-[o]\s(?.*\.css)(?:.*$)/; + +/** + * @typedef {Object} Size + * @propety {number} raw - Unminified size in bytes + * @property {string} size - Unminified size in KiB + * @property {string} minified - Minified size in KiB + * @property {string} brotli - Brotli compressed minified size in KiB + * @property {string} gzip - Gzip compressed minified size in KiB + */ + /** @type {Object.} sizes */ +let sizes = {} + +for (const [_, script] of Object.entries(filtered)) { + const found = script.match(regex); + + if (!found) continue; + + /** + * @typedef {object} CaptureGroup + * @property {string} filepath + * @property {string} filename + */ + /** @type {CaptureGroup} */ + const { filepath, filename } = found.groups; + + /** + * @type {import('postcss').ProcessOptions} + */ + const options = { from: path.resolve(`../${filepath}`), to: undefined }; + const css = await fs.readFile(path.resolve(`../${filepath}`), 'utf-8'); + /** + * Run the css through PostCSS (just like Open-Props). + * plugins.slice(0, -1) remove `cssnano` plugin so we can get the size of the unminified code + */ + const code = await postcss(plugins.slice(0, -1)).process(css, options); + /** + * This time we want to get the minified size. + */ + const minified = await postcss(plugins).process(css, options); + + /** + * Build the sizes object. + * Strip `.min` from the filename + */ + sizes[filename.replace('.min', '')] = { + raw: code.css.length, // in bytes + size: (code.css.length / 1024).toFixed(2), // in KiB + minified: (minified.css.length / 1024).toFixed(2), // KiB + brotli: (brotli(minified.css) / 1024).toFixed(2), // in KiB + gzip: (gzip(minified.css) / 1024).toFixed(2), // in KiB + } +} + +await fs.writeFile('bundle-sizes.json', JSON.stringify(sizes, null, 2), { encoding: 'utf8' }); diff --git a/package-lock.json b/package-lock.json index 1efa6ab6..02ffb787 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,20 @@ { "name": "open-props", - "version": "1.6.8", + "version": "1.6.19", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "open-props", - "version": "1.6.8", + "version": "1.6.19", "license": "MIT", "devDependencies": { "ava": "^3.15.0", + "brotli-size": "^4.0.0", "colorjs.io": "^0.4.1-patch.1", "concurrently": "^7.2.2", "cssnano": "^5.1.10", + "gzip-size": "^7.0.0", "json": "^11.0.0", "open-color": "^1.9.1", "postcss": "^8.3.9", @@ -695,6 +697,18 @@ "node": ">=8" } }, + "node_modules/brotli-size": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", + "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "dev": true, + "dependencies": { + "duplexer": "0.1.1" + }, + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/browserslist": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", @@ -1766,6 +1780,12 @@ "node": ">=8" } }, + "node_modules/duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q==", + "dev": true + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -2113,6 +2133,27 @@ "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, + "node_modules/gzip-size": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz", + "integrity": "sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gzip-size/node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6810,6 +6851,15 @@ "fill-range": "^7.0.1" } }, + "brotli-size": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", + "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "dev": true, + "requires": { + "duplexer": "0.1.1" + } + }, "browserslist": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", @@ -7581,6 +7631,12 @@ "is-obj": "^2.0.0" } }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q==", + "dev": true + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -7841,6 +7897,23 @@ "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, + "gzip-size": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz", + "integrity": "sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + }, + "dependencies": { + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + } + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", diff --git a/package.json b/package.json index 7490fe8f..8d501795 100644 --- a/package.json +++ b/package.json @@ -186,6 +186,7 @@ "build": "concurrently npm:gen:op npm:gen:shadowdom && npm run gen:types", "test": "ava test/basic.test.cjs", "bundle": "concurrently npm:lib:* -m 25 && concurrently npm:shadow:* -m 25", + "bundle:sizes": "cd build && node bundle-sizes.js", "gen:op": "cd build && node props.js \"\" true", "gen:nowhere": "cd build && node props \"\" false", "gen:shadowdom": "cd build && node props \"\" false \":host\" \"shadow\"", @@ -313,9 +314,11 @@ }, "devDependencies": { "ava": "^3.15.0", + "brotli-size": "^4.0.0", "colorjs.io": "^0.4.1-patch.1", "concurrently": "^7.2.2", "cssnano": "^5.1.10", + "gzip-size": "^7.0.0", "json": "^11.0.0", "open-color": "^1.9.1", "postcss": "^8.3.9",