diff --git a/package-lock.json b/package-lock.json index 4b5e895cee..35310211db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9190,6 +9190,12 @@ "json-schema-traverse": "^0.3.0" } }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -9302,6 +9308,20 @@ "requires": { "ansi-regex": "^3.0.0" } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } } } }, @@ -10461,9 +10481,9 @@ } }, "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-4.1.2.tgz", + "integrity": "sha512-iSWteWtfNcrWQTkQw8ble2bnonSl7YJImsn9OZKpE2E4IHhXI78eASpDYUljXZZdYj36QsEKjOs/CsiDqmKMJw==", "dev": true }, "fill-range": { @@ -11946,24 +11966,6 @@ "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==", "dev": true }, - "gzip-size": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", - "integrity": "sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==", - "dev": true, - "requires": { - "duplexer": "^0.1.1", - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, "h2x-core": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/h2x-core/-/h2x-core-1.1.1.tgz", @@ -13653,6 +13655,18 @@ "requires": { "node-fetch": "^1.0.1", "whatwg-fetch": ">=0.10.0" + }, + "dependencies": { + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } } }, "isstream": { @@ -15975,14 +15989,10 @@ } }, "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "dev": true, - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.4.0.tgz", + "integrity": "sha512-1mt8bw5JQWWTcwUM1FGjFJLFo5lB/jz6zbm+qwdEh2iqYobKS4aHWgz1d+mvho5cqCaShFDF+hnpgraIi/5tqA==", + "dev": true }, "node-gyp": { "version": "3.8.0", @@ -19831,6 +19841,12 @@ "path-type": "^3.0.0" } }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -19855,6 +19871,16 @@ "slash": "^1.0.0" } }, + "gzip-size": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz", + "integrity": "sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "pify": "^3.0.0" + } + }, "ignore": { "version": "3.3.10", "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", @@ -23574,6 +23600,22 @@ "sugarss": "^0.2.0", "svg-tags": "^1.0.0", "table": "^4.0.1" + }, + "dependencies": { + "table": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "dev": true, + "requires": { + "ajv": "^6.0.1", + "ajv-keywords": "^3.0.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + } } }, "sugarss": { @@ -23843,47 +23885,21 @@ } }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "is-fullwidth-code-point": { @@ -23892,29 +23908,35 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { + "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "strip-ansi": "^5.1.0" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -26446,6 +26468,12 @@ "ms": "^2.1.1" } }, + "filesize": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "dev": true + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", diff --git a/package.json b/package.json index 3bcfd8228c..0306999b9d 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "eslint": "4.19.1", "eslint-plugin-github": "1.0.0", "execa": "^0.10.0", + "filesize": "4.1.2", "fs-extra": "^4.0.2", "fx": "11.0.1", "gh-pages": "^1.0.0", @@ -86,7 +87,9 @@ "metalsmith-filter": "^1.0.2", "metalsmith-matters": "^1.2.0", "metalsmith-watch": "^1.0.3", + "minimist": "1.2.0", "next": "7.0.2", + "node-fetch": "2.4.0", "now": "^12.1.8", "npm-run-all": "4.1.5", "postcss": "7.0.14", @@ -116,6 +119,7 @@ "styled-components": "4.1.2", "stylelint": "9.10.1", "stylelint-config-primer": "7.0.0", + "table": "5.2.3", "title-case": "^2.1.1", "tree-model": "^1.0.7", "typographic-base": "^1.0.4", diff --git a/script/bundle-size-report b/script/bundle-size-report new file mode 100755 index 0000000000..e18b74a756 --- /dev/null +++ b/script/bundle-size-report @@ -0,0 +1,162 @@ +#!/usr/bin/env node +const fetch = require('node-fetch') +const filesize = require('filesize') +const minimist = require('minimist') +const {green, gray, yellow, red} = require('colorette') +const {table, getBorderCharacters} = require('table') + +const options = minimist(process.argv.slice(2)) + +const DELTA = '±' +const VERSION = options.version || 'latest' +const QUIET = options.quiet || options.q || 0 +const SORT = options.sort || options.s || 'gzip' +// the default is descending +const ASCENDING = options.asc || options.a +const ONLY_BUNDLES = options.only +const ALL_BUNDLES = !ONLY_BUNDLES && options.all +const META_BUNDLES = options.all || options.meta || false + +const {name} = require('../package.json') +const unpkgBaseURL = `https://unpkg.com/${name}@${VERSION}/` + +// ensure that K and B values line up vertically +const filesizeConfig = {symbols: {KB: 'K'}} +const prettySize = bytes => filesize(bytes, filesizeConfig) +const meta = require('../dist/meta.json') + +let bundles = Object.values(meta.bundles) + +// fitler out support bundles, since they don't generate CSS +bundles = bundles.filter(bundle => !isSupportBundleName(bundle.name)) + +if (ONLY_BUNDLES) { + const only = new Set(ONLY_BUNDLES.trim().split(/\s*,\s*/)) + bundles = bundles.filter(bundle => only.has(bundle.name)) +} else if (!ALL_BUNDLES) { + bundles = META_BUNDLES + ? bundles.filter(isMetaBundle) + : bundles.filter(bundle => !isMetaBundle(bundle)) +} + +Promise.all( + bundles.map(bundle => { + const entry = { + name: bundle.name, + path: bundle.css, + local: require(`../${bundle.stats}`) + } + return fetch(unpkgBaseURL + bundle.stats) + .then(res => res.json()) + .then(stats => (entry.remote = stats)) + .then(() => entry) + }) +).then(entries => { + const columns = [ + {title: 'name', value: get(d => d.name), alignment: 'left'}, + + // CSS selector count + {title: 'selectors', value: get(d => d.local.selectors.total)}, + {title: DELTA, value: delta(d => d.selectors.total), id: 'selector-delta'}, + + // gzipped size (bytes) + {title: 'gzip size', value: get(d => d.local.gzipSize, prettySize), id: 'gzip'}, + {title: DELTA, value: delta(d => d.gzipSize, prettySize), id: 'gzip-delta'}, + + // raw size (bytes) + {title: 'raw size', value: get(d => d.local.size, prettySize), id: 'size'}, + {title: DELTA, value: delta(d => d.size, prettySize), id: 'size-delta'}, + + // path goes last + {title: 'path', value: get(d => d.path), alignment: 'left'} + ] + + for (const [index, column] of Object.entries(columns)) { + column.index = index + } + + const header = columns.map(c => c.title) + let data = entries.map(entry => columns.map(c => c.value(entry))) + + if (SORT) { + const index = columns.findIndex(c => c.id === SORT || c.title === SORT) + if (index > -1) { + const compare = ASCENDING ? compareAscending : compareDescending + data.sort((a, b) => compare(a[index].value, b[index].value)) + } else { + console.warn(`No such sort column: "${SORT}"! Output will not be sorted.`) + } + } + + if (QUIET) { + data = data.filter(cells => { + return cells.filter((cell, i) => columns[i].title === DELTA).every(cell => cell.value !== 0) + }) + } + + const rows = data.map(cells => cells.map(String)) + + console.log( + table([header].concat(rows), { + columns, + columnDefault: { + alignment: 'right' + }, + border: getBorderCharacters('norc'), + drawHorizontalLine(index, size) { + return index <= 1 || index === size + } + }) + ) +}) + +function get(getter, format = String) { + return entry => { + const value = getter(entry) + return { + value, + toString: () => format(value) + } + } +} + +function delta(getter, format = String, options = {}) { + const {moreIsGood = false, badThreshold = 1000} = options + return entry => { + const local = getter(entry.local) + const remote = getter(entry.remote) + const value = local - remote + if (value === 0) { + return { + value, + toString: () => ` ${gray(0)}` + } + } else { + const sign = value > 0 ? '+' : '-' + const num = Math.abs(value) + const good = moreIsGood ? value > 0 : value < 0 + const color = good ? green : value >= badThreshold ? red : yellow + return { + value, + toString: () => color(`${sign} ${format(num)}`) + } + } + } +} + +function isMetaBundle(bundle) { + return !bundle.imports.every(isSupportBundleName) +} + +function isSupportBundleName(name) { + // "support", "marketing-support", and any future ones? + return name.endsWith('support') +} + +function compareAscending(a, b) { + return a - b +} + +function compareDescending(a, b) { + return b - a +} diff --git a/script/postpublish b/script/postpublish index d9fadb1e3b..0153c98c0c 100755 --- a/script/postpublish +++ b/script/postpublish @@ -13,3 +13,6 @@ if [[ "$GITHUB_REF" = "refs/heads/master" ]]; then echo "Whoops! Failed to publish Storybook. This is not a fatal error." ) fi + +# TODO: remove this in v13 +rm -rf build/ diff --git a/script/prepublish b/script/prepublish index 38942b2649..1d4568696f 100755 --- a/script/prepublish +++ b/script/prepublish @@ -2,10 +2,10 @@ set -e # generate the build directory -npm run dist +npm run --silent dist -# run the selector diff report -script/selector-diff-report +# run the bundle size and selector diff reports +script/run-reports # TODO: remove this in v13 mkdir -p build diff --git a/script/run-reports b/script/run-reports new file mode 100755 index 0000000000..7736edadfc --- /dev/null +++ b/script/run-reports @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +echo "Reporting bundle sizes..." +script/bundle-size-report --all + +echo "Reporting selector diffs..." +script/selector-diff-report diff --git a/script/selector-diff-report b/script/selector-diff-report index d8b05ae015..3e4a8cbab9 100755 --- a/script/selector-diff-report +++ b/script/selector-diff-report @@ -2,11 +2,11 @@ set -e function log() { - echo "$@" 1>&2 + echo "$@" 1>&2 } function warn() { - echo "$@" 1>&2 + echo "$@" 1>&2 } pkg="@primer/css" @@ -15,15 +15,12 @@ warn "Pulling the old $path from unpkg.com..." curl -sL "https://unpkg.com/$pkg/$path" > before.json warn "Building the stats locally..." -npm run --silent dist cp $path after.json key=".selectors.values[]" jq -r $key before.json > before.txt jq -r $key after.json > after.txt -warn "[selector report] diff:" -(diff before.txt after.txt | tee selector-diff.log) || log "(no diff!)" -warn "[selector report] end" +diff {before,after}.txt && echo 'no diff!' rm {before,after}.{json,txt}