2019-04-26 23:12:00 +03:00
|
|
|
#!/usr/bin/env node
|
|
|
|
const fetch = require('node-fetch')
|
|
|
|
const filesize = require('filesize')
|
2019-09-05 22:42:54 +03:00
|
|
|
const cssstats = require('cssstats')
|
2019-04-26 23:12:00 +03:00
|
|
|
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'
|
2019-04-26 23:56:26 +03:00
|
|
|
// 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
|
2019-04-26 23:12:00 +03:00
|
|
|
|
|
|
|
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')
|
|
|
|
|
2019-04-26 23:56:26 +03:00
|
|
|
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) {
|
2019-09-10 21:21:42 +03:00
|
|
|
bundles = META_BUNDLES ? bundles.filter(isMetaBundle) : bundles.filter(bundle => !isMetaBundle(bundle))
|
2019-04-26 23:56:26 +03:00
|
|
|
}
|
|
|
|
|
2019-04-26 23:12:00 +03:00
|
|
|
Promise.all(
|
2019-04-26 23:56:26 +03:00
|
|
|
bundles.map(bundle => {
|
2019-04-26 23:12:00 +03:00
|
|
|
const entry = {
|
2019-04-26 23:56:26 +03:00
|
|
|
name: bundle.name,
|
2019-04-26 23:12:00 +03:00
|
|
|
path: bundle.css,
|
2019-09-05 01:02:17 +03:00
|
|
|
local: require(`../${bundle.stats}`),
|
|
|
|
remote: {}
|
2019-04-26 23:12:00 +03:00
|
|
|
}
|
|
|
|
return fetch(unpkgBaseURL + bundle.stats)
|
|
|
|
.then(res => res.json())
|
2019-09-10 21:21:42 +03:00
|
|
|
.catch(() => {
|
2019-09-05 22:42:54 +03:00
|
|
|
console.warn(`Unable to fetch old "${bundle.name}" stats from unpkg; assuming it's new!`)
|
|
|
|
return cssstats('')
|
2019-09-05 01:02:17 +03:00
|
|
|
})
|
2019-04-26 23:12:00 +03:00
|
|
|
.then(stats => (entry.remote = stats))
|
|
|
|
.then(() => entry)
|
|
|
|
})
|
|
|
|
).then(entries => {
|
|
|
|
const columns = [
|
2019-04-26 23:56:26 +03:00
|
|
|
{title: 'name', value: get(d => d.name), alignment: 'left'},
|
2019-04-26 23:12:00 +03:00
|
|
|
|
|
|
|
// CSS selector count
|
2019-04-26 23:56:26 +03:00
|
|
|
{title: 'selectors', value: get(d => d.local.selectors.total)},
|
2019-04-26 23:12:00 +03:00
|
|
|
{title: DELTA, value: delta(d => d.selectors.total), id: 'selector-delta'},
|
|
|
|
|
|
|
|
// gzipped size (bytes)
|
2019-04-26 23:56:26 +03:00
|
|
|
{title: 'gzip size', value: get(d => d.local.gzipSize, prettySize), id: 'gzip'},
|
2019-04-26 23:12:00 +03:00
|
|
|
{title: DELTA, value: delta(d => d.gzipSize, prettySize), id: 'gzip-delta'},
|
|
|
|
|
|
|
|
// raw size (bytes)
|
2019-04-26 23:56:26 +03:00
|
|
|
{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'}
|
2019-04-26 23:12:00 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
for (const [index, column] of Object.entries(columns)) {
|
|
|
|
column.index = index
|
|
|
|
}
|
|
|
|
|
|
|
|
const header = columns.map(c => c.title)
|
2019-04-26 23:56:26 +03:00
|
|
|
let data = entries.map(entry => columns.map(c => c.value(entry)))
|
2019-04-26 23:12:00 +03:00
|
|
|
|
|
|
|
if (SORT) {
|
2019-04-26 23:56:26 +03:00
|
|
|
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))
|
2019-04-26 23:12:00 +03:00
|
|
|
} else {
|
|
|
|
console.warn(`No such sort column: "${SORT}"! Output will not be sorted.`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 23:56:26 +03:00
|
|
|
if (QUIET) {
|
|
|
|
data = data.filter(cells => {
|
|
|
|
return cells.filter((cell, i) => columns[i].title === DELTA).every(cell => cell.value !== 0)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-26 23:12:00 +03:00
|
|
|
const rows = data.map(cells => cells.map(String))
|
|
|
|
|
2019-04-26 23:56:26 +03:00
|
|
|
console.log(
|
|
|
|
table([header].concat(rows), {
|
|
|
|
columns,
|
|
|
|
columnDefault: {
|
|
|
|
alignment: 'right'
|
|
|
|
},
|
|
|
|
border: getBorderCharacters('norc'),
|
|
|
|
drawHorizontalLine(index, size) {
|
|
|
|
return index <= 1 || index === size
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
2019-04-26 23:12:00 +03:00
|
|
|
})
|
|
|
|
|
2019-04-26 23:56:26 +03:00
|
|
|
function get(getter, format = String) {
|
|
|
|
return entry => {
|
|
|
|
const value = getter(entry)
|
|
|
|
return {
|
|
|
|
value,
|
|
|
|
toString: () => format(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 23:12:00 +03:00
|
|
|
function delta(getter, format = String, options = {}) {
|
2019-04-26 23:56:26 +03:00
|
|
|
const {moreIsGood = false, badThreshold = 1000} = options
|
2019-04-26 23:12:00 +03:00
|
|
|
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)}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-26 23:56:26 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|