@vernikr/size-report 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -81
- package/package.json +2 -2
- package/src/check.js +1 -1
- package/src/data.js +21 -23
- package/src/doctor.js +4 -4
- package/src/history.js +3 -9
- package/src/init.js +2 -1
- package/src/locales.js +17 -11
- package/src/metrics.js +45 -63
- package/src/minify.js +2 -2
- package/src/modes.js +8 -8
- package/src/optional.js +2 -2
- package/src/page/app.css +33 -8
- package/src/page/app.js +139 -71
- package/src/page/build.js +166 -19
- package/src/page/panel.js +135 -30
- package/src/page/payload.js +168 -0
- package/src/page/state.js +88 -63
- package/src/page/table.js +280 -73
- package/src/page/work.js +55 -0
- package/src/parse.js +1 -1
- package/src/project.js +2 -2
- package/src/strip/guard.js +4 -3
- package/src/strip.js +4 -5
- package/src/table.css +44 -8
- package/src/tokens.js +5 -6
- package/templates/README.md +3 -3
package/src/doctor.js
CHANGED
|
@@ -48,12 +48,12 @@ function environment(root) {
|
|
|
48
48
|
|
|
49
49
|
/* Dependencies: what counts the metrics here and now. The very loaders the sensors use are asked
|
|
50
50
|
* (`minifier`, `tokenizer`), so the answer cannot drift from the number: without the minifier `min` counts
|
|
51
|
-
* by
|
|
51
|
+
* by simplification, without the dictionary `tok` by estimate.
|
|
52
52
|
*
|
|
53
53
|
* Only what the project actually asked for is loaded: the dictionary weighs megabytes, and touching it for
|
|
54
54
|
* the sake of an "installed" line would mean paying for an answer the numbers never needed (the same rule as
|
|
55
55
|
* in the report: `test/tokens.test.js`). An unwanted sensor is named unneeded rather than unknown — it does
|
|
56
|
-
* not affect
|
|
56
|
+
* not affect the count, and that is the answer; "unknown" stays for the case where the settings are unreadable
|
|
57
57
|
* and there is nobody to ask. */
|
|
58
58
|
const UNREADABLE = 'unknown: the settings cannot be read';
|
|
59
59
|
|
|
@@ -132,8 +132,8 @@ function hookFindings(hooks) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/* Coverage is the same answer `size check` gives, plus the kind of trouble if there is one: an incomplete path
|
|
135
|
-
* or
|
|
136
|
-
* without it there are no numbers at all.
|
|
135
|
+
* or a sensor counting another way (the kinds weigh differently — `WEIGHT`), with incompleteness outranking,
|
|
136
|
+
* because without it there are no numbers at all.
|
|
137
137
|
* What becomes a finding and what does not: the report holds the whole coverage block (the same text as
|
|
138
138
|
* `size check`), so incompleteness is not retold a second time — it weighs. Sensors do get a finding:
|
|
139
139
|
* `size check` prints them as a `!` line, while here they are part of the answer. */
|
package/src/history.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { assertFullHistory, diskForm, diskHashes, git, headTree, readBlobs, readHistory } from './git.js';
|
|
4
|
-
import { METRICS, measureBlob
|
|
4
|
+
import { METRICS, measureBlob } from './metrics.js';
|
|
5
5
|
import { touchedSection } from './journal.js';
|
|
6
6
|
import { EXIT, refuse } from './refusal.js';
|
|
7
7
|
|
|
@@ -82,15 +82,10 @@ function applyPicks(pass, c, picks) {
|
|
|
82
82
|
}
|
|
83
83
|
const blob = pass.blobs.get(pick.spec);
|
|
84
84
|
const cells = {};
|
|
85
|
-
/* Approximation is a property of the path rather than of the blob: whether the format goes to
|
|
86
|
-
* the minifier depends on its extension. It is computed here along with the measurement, and
|
|
87
|
-
* so it does not enter the content cache. */
|
|
88
|
-
const approx = {};
|
|
89
85
|
pass.metrics.forEach((m) => {
|
|
90
86
|
cells[m] = pass.measure(m, blob, pick.path, c.sha);
|
|
91
|
-
approx[m] = !pointExact(m, pick.path, pass.cfg);
|
|
92
87
|
});
|
|
93
|
-
pass.state[i] = { path: pick.path, sha: blob.sha, cells: cells
|
|
88
|
+
pass.state[i] = { path: pick.path, sha: blob.sha, cells: cells };
|
|
94
89
|
});
|
|
95
90
|
}
|
|
96
91
|
|
|
@@ -127,8 +122,7 @@ function stepCommit(pass, c, ci) {
|
|
|
127
122
|
when: c.when,
|
|
128
123
|
subject: c.subject,
|
|
129
124
|
section: section,
|
|
130
|
-
cells: pass.state.map((s) => (s === null ? null : s.cells))
|
|
131
|
-
approx: pass.state.map((s) => (s === null ? null : s.approx))
|
|
125
|
+
cells: pass.state.map((s) => (s === null ? null : s.cells))
|
|
132
126
|
});
|
|
133
127
|
}
|
|
134
128
|
|
package/src/init.js
CHANGED
|
@@ -14,7 +14,8 @@ import { packageManager } from './project.js';
|
|
|
14
14
|
* pass the very check the first run will apply**, or the advice leads a person into a new dead end.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/* What to say after writing: what was written, what
|
|
17
|
+
/* What to say after writing: what was written, what counts the same numbers when the optional
|
|
18
|
+
* dependency is missing, and what to do
|
|
18
19
|
* next. The lines are assembled into a list rather than printed as they come, so that "what was said"
|
|
19
20
|
* can be read as a whole. */
|
|
20
21
|
function draftLines(root, target, cfg) {
|
package/src/locales.js
CHANGED
|
@@ -9,7 +9,7 @@ export const LOCALES = {
|
|
|
9
9
|
commit: 'Коммит',
|
|
10
10
|
total: 'Общий объём',
|
|
11
11
|
now: 'сейчас',
|
|
12
|
-
categories: { code: 'Код', docs: 'Документация', chore: 'Служебные', assets: 'Ресурсы' },
|
|
12
|
+
categories: { code: 'Код', tests: 'Тесты', docs: 'Документация', chore: 'Служебные', assets: 'Ресурсы' },
|
|
13
13
|
/* The page texts live inside the report file itself (a dictionary of their own, next to
|
|
14
14
|
* the data), so changing a word costs a rebuild of the report — otherwise the file
|
|
15
15
|
* drifts from the history. */
|
|
@@ -37,12 +37,6 @@ export const LOCALES = {
|
|
|
37
37
|
categoryByExtension: 'по расширению',
|
|
38
38
|
all: 'все',
|
|
39
39
|
sub: '{tool} {version} · {artifact}',
|
|
40
|
-
/* Words of precision: a metric label speaks about the worst in its column, a cell
|
|
41
|
-
* about its own number. One word serves both the label and the cell hint, so the two
|
|
42
|
-
* cannot drift or sound differently. */
|
|
43
|
-
exact: 'точное число',
|
|
44
|
-
approximate: 'приближение',
|
|
45
|
-
approximateCell: 'приближённое число: ',
|
|
46
40
|
panelMethod: 'способ:',
|
|
47
41
|
emptyMetrics: 'Ни одна метрика не выбрана — таблицу не из чего собрать. '
|
|
48
42
|
+ 'Включите хотя бы одну в панели выше: без метрик нет ни одной колонки с числом.',
|
|
@@ -54,6 +48,14 @@ export const LOCALES = {
|
|
|
54
48
|
+ 'что выбрано в этом браузере.',
|
|
55
49
|
linkExtra: 'Часть ссылки пропущена: в этом отчёте нет {n} названных имён. '
|
|
56
50
|
+ 'Остальное применено.',
|
|
51
|
+
/* Данные отчёта лежат в файле сжатыми, и распаковывает их браузер. Текст называет причину
|
|
52
|
+
* (браузера нет в списке тех, что умеют `DecompressionStream`) и что с этим делать — пустая
|
|
53
|
+
* таблица не сказала бы читателю ничего. */
|
|
54
|
+
unpack: 'Данные отчёта сжаты, а браузер не умеет их распаковать: в нём нет DecompressionStream. '
|
|
55
|
+
+ 'Обновите браузер — таблицу показывать не из чего.',
|
|
56
|
+
/* Полоска вверху страницы: имя для тех, кто читает страницу не глазами, — она показывает,
|
|
57
|
+
* что идёт отрисовка колонок, а не что отчёт завис. */
|
|
58
|
+
working: 'отрисовка колонок',
|
|
57
59
|
note: 'Абсолютные размеры стоят один раз — в строке «{now}». Строк: {rows}: коммиты без '
|
|
58
60
|
+ 'изменения объёма и коммиты, тронувшие только выключенные файлы, строк не получают. '
|
|
59
61
|
+ 'Собрано из истории git: {command}.'
|
|
@@ -81,7 +83,7 @@ export const LOCALES = {
|
|
|
81
83
|
commit: 'Commit',
|
|
82
84
|
total: 'Total',
|
|
83
85
|
now: 'now',
|
|
84
|
-
categories: { code: 'Code', docs: '
|
|
86
|
+
categories: { code: 'Code', tests: 'Tests', docs: 'Docs', chore: 'Chores', assets: 'Assets' },
|
|
85
87
|
page: {
|
|
86
88
|
metrics: 'Metrics',
|
|
87
89
|
files: 'Files',
|
|
@@ -99,9 +101,6 @@ export const LOCALES = {
|
|
|
99
101
|
categoryByExtension: 'by extension',
|
|
100
102
|
all: 'all',
|
|
101
103
|
sub: '{tool} {version} · {artifact}',
|
|
102
|
-
exact: 'an exact number',
|
|
103
|
-
approximate: 'an approximation',
|
|
104
|
-
approximateCell: 'an approximate number: ',
|
|
105
104
|
panelMethod: 'method:',
|
|
106
105
|
emptyMetrics: 'No metric is selected — there is nothing to build the table from. '
|
|
107
106
|
+ 'Turn on at least one above: without metrics there is no column holding a number.',
|
|
@@ -113,6 +112,13 @@ export const LOCALES = {
|
|
|
113
112
|
+ 'has.',
|
|
114
113
|
linkExtra: 'Part of the link is skipped: this report has none of the {n} names '
|
|
115
114
|
+ 'it mentions. The rest is applied.',
|
|
115
|
+
/* The report's data lies in the file compressed and the browser unpacks it; this text is what a
|
|
116
|
+
* browser without `DecompressionStream` gets instead of an empty table. */
|
|
117
|
+
unpack: 'The report\u2019s data is compressed and this browser cannot unpack it: there is no '
|
|
118
|
+
+ 'DecompressionStream. Update the browser — there is nothing to build the table from.',
|
|
119
|
+
/* The bar at the top of the page: a name for whoever reads the page without eyes — it shows
|
|
120
|
+
* that the columns are being drawn rather than that the report has hung. */
|
|
121
|
+
working: 'drawing the columns',
|
|
116
122
|
note: 'Absolute sizes appear once — in the “{now}” row. Rows: {rows}: commits that change '
|
|
117
123
|
+ 'no number, and commits touching only the disabled files, get no row. '
|
|
118
124
|
+ 'Collected from git history: {command}.'
|
package/src/metrics.js
CHANGED
|
@@ -4,9 +4,9 @@ import { EXACT_STRATEGIES, assertCompilable, byteLen, minifyForm, strategyFor }
|
|
|
4
4
|
import { MINIFY_LOADERS, minifier, minifyWithEsbuild } from './minify.js';
|
|
5
5
|
import { CHARS_PER_TOKEN, isBinary, tokenCount, tokenizer } from './tokens.js';
|
|
6
6
|
|
|
7
|
-
/* The registry of metrics: what is measured, whether a metric needs the text, and how
|
|
8
|
-
*
|
|
9
|
-
* number rather than a way to obtain one.
|
|
7
|
+
/* The registry of metrics: what is measured, whether a metric needs the text, and how its
|
|
8
|
+
* number is obtained. Separate from the ways of stripping ballast: a metric is a promise about
|
|
9
|
+
* a number rather than a way to obtain one.
|
|
10
10
|
*
|
|
11
11
|
* The description a reader sees comes from `metricView` rather than from the registry fields:
|
|
12
12
|
* for one and the same metric it depends on the settings (`min` is either real compression or
|
|
@@ -14,8 +14,8 @@ import { CHARS_PER_TOKEN, isBinary, tokenCount, tokenizer } from './tokens.js';
|
|
|
14
14
|
* question have nowhere to drift apart. */
|
|
15
15
|
|
|
16
16
|
/* The ways to obtain the `min` metric: stripping ballast and real compression. Their mechanism
|
|
17
|
-
* differs, and so does
|
|
18
|
-
*
|
|
17
|
+
* differs, and so does the method a reader is told, so each has its own text — and neither is
|
|
18
|
+
* passed off as the other. */
|
|
19
19
|
export const MINIFY_ENGINES = ['strip', 'esbuild'];
|
|
20
20
|
|
|
21
21
|
const STYLES = {
|
|
@@ -33,12 +33,12 @@ const STYLES = {
|
|
|
33
33
|
},
|
|
34
34
|
method: { ru: 'esbuild {version} (minify, rename)', en: 'esbuild {version} (minify, rename)' },
|
|
35
35
|
fallback: {
|
|
36
|
-
ru: '; остальные форматы ({exts}) — упрощение без комментариев и
|
|
37
|
-
en: '; other formats ({exts}) lose comments and indentation
|
|
36
|
+
ru: '; остальные форматы ({exts}) — упрощение без комментариев и отступов',
|
|
37
|
+
en: '; other formats ({exts}) lose comments and indentation'
|
|
38
38
|
},
|
|
39
39
|
unavailable: {
|
|
40
|
-
ru: ' (минификатор esbuild недоступен — счёт
|
|
41
|
-
en: ' (the esbuild minifier is unavailable — comments and indentation are stripped
|
|
40
|
+
ru: ' (минификатор esbuild недоступен — счёт идёт упрощением)',
|
|
41
|
+
en: ' (the esbuild minifier is unavailable — comments and indentation are stripped)'
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
tok: {
|
|
@@ -48,12 +48,12 @@ const STYLES = {
|
|
|
48
48
|
},
|
|
49
49
|
method: { ru: '{tool} {version}, {encoding} (BPE)', en: '{tool} {version}, {encoding} (BPE)' },
|
|
50
50
|
binary: {
|
|
51
|
-
ru: '; для бинарных форматов ({exts}) это счёт байтов, а не текста
|
|
52
|
-
en: '; binary formats ({exts}) are counted by bytes rather than text
|
|
51
|
+
ru: '; для бинарных форматов ({exts}) это счёт байтов, а не текста',
|
|
52
|
+
en: '; binary formats ({exts}) are counted by bytes rather than text'
|
|
53
53
|
},
|
|
54
54
|
estimate: {
|
|
55
|
-
ru: 'оценка по длине: 1 токен ≈ {chars} знака ({encoding} недоступен)
|
|
56
|
-
en: 'length-based estimate: 1 token ≈ {chars} characters ({encoding} is unavailable)
|
|
55
|
+
ru: 'оценка по длине: 1 токен ≈ {chars} знака ({encoding} недоступен)',
|
|
56
|
+
en: 'length-based estimate: 1 token ≈ {chars} characters ({encoding} is unavailable)'
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
};
|
|
@@ -71,7 +71,6 @@ export const METRICS = {
|
|
|
71
71
|
ru: 'размер объекта git',
|
|
72
72
|
en: 'the size of the git object'
|
|
73
73
|
},
|
|
74
|
-
accuracy: 'exact',
|
|
75
74
|
measure: (text) => byteLen(text)
|
|
76
75
|
},
|
|
77
76
|
min: {
|
|
@@ -103,57 +102,50 @@ export const METRICS = {
|
|
|
103
102
|
needsText: true,
|
|
104
103
|
note: { ru: 'сжатый поток (zlib, уровень 9)', en: 'compressed stream (zlib, level 9)' },
|
|
105
104
|
method: { ru: 'zlib, уровень 9', en: 'zlib, level 9' },
|
|
106
|
-
accuracy: 'exact',
|
|
107
105
|
measure: (text) => zlib.gzipSync(Buffer.from(text, 'utf8'), { level: 9 }).length
|
|
108
106
|
}
|
|
109
107
|
};
|
|
110
108
|
|
|
111
109
|
/* The description of a metric for a reader: `note` is what the number means, `method` how it
|
|
112
|
-
* was obtained
|
|
110
|
+
* was obtained. */
|
|
113
111
|
export function metricView(name, cfg) {
|
|
114
112
|
const metric = METRICS[name];
|
|
115
113
|
if (metric.view !== undefined) return metric.view(cfg);
|
|
116
114
|
return {
|
|
117
115
|
label: metric.label,
|
|
118
116
|
note: metric.note[cfg.locale],
|
|
119
|
-
method: metric.method[cfg.locale]
|
|
120
|
-
accuracy: metric.accuracy
|
|
117
|
+
method: metric.method[cfg.locale]
|
|
121
118
|
};
|
|
122
119
|
}
|
|
123
120
|
|
|
124
|
-
/* The label of the `min` metric. The
|
|
125
|
-
* the
|
|
126
|
-
* a
|
|
127
|
-
*
|
|
128
|
-
* rather than by the name of the method: a report with no approximate format at all is exact
|
|
129
|
-
* even with the ballast stripped. */
|
|
121
|
+
/* The label of the `min` metric. The method names the formats this metric counts another way: a
|
|
122
|
+
* format the minifier does not take is given in words rather than hidden behind the neighbouring
|
|
123
|
+
* one, so a reader sees what the column is made of. The list comes from the same rule the counting
|
|
124
|
+
* uses (`otherCountFormats`) rather than from the name of the method. */
|
|
130
125
|
function minView(cfg) {
|
|
131
126
|
const loc = cfg.locale;
|
|
132
|
-
const
|
|
127
|
+
const other = otherCountFormats('min', cfg);
|
|
133
128
|
if (minEngine(cfg) === 'esbuild') {
|
|
134
129
|
let method = STYLES.esbuild.method[loc].replace('{version}', minifier().version);
|
|
135
|
-
if (
|
|
130
|
+
if (other.length > 0) method += STYLES.esbuild.fallback[loc].replace('{exts}', other.join(' '));
|
|
136
131
|
return {
|
|
137
132
|
label: METRICS.min.label,
|
|
138
133
|
note: STYLES.esbuild.note[loc],
|
|
139
|
-
method: method
|
|
140
|
-
accuracy: rough.length === 0 ? 'exact' : 'approximate'
|
|
134
|
+
method: method
|
|
141
135
|
};
|
|
142
136
|
}
|
|
143
137
|
const degraded = cfg.minify.engine === 'esbuild';
|
|
144
138
|
return {
|
|
145
139
|
label: METRICS.min.label,
|
|
146
140
|
note: STYLES.strip.note[loc],
|
|
147
|
-
method: STYLES.strip.method[loc] + (degraded ? STYLES.esbuild.unavailable[loc] : '')
|
|
148
|
-
accuracy: rough.length === 0 ? 'exact' : 'approximate'
|
|
141
|
+
method: STYLES.strip.method[loc] + (degraded ? STYLES.esbuild.unavailable[loc] : '')
|
|
149
142
|
};
|
|
150
143
|
}
|
|
151
144
|
|
|
152
|
-
/* The label of the `tok` metric. The
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* by bytes) and a missing dictionary (then the count is an estimate by length). */
|
|
145
|
+
/* The label of the `tok` metric. The method says which dictionary produced the number (family and
|
|
146
|
+
* encoding are part of the count rather than a detail) and names the two cases where the count is
|
|
147
|
+
* another one, both spelled out: formats for which tokens are not counted (their number runs by
|
|
148
|
+
* bytes) and a missing dictionary (then the count is an estimate by length). */
|
|
157
149
|
function tokView(cfg) {
|
|
158
150
|
const loc = cfg.locale;
|
|
159
151
|
const settings = cfg.tokens;
|
|
@@ -163,44 +155,38 @@ function tokView(cfg) {
|
|
|
163
155
|
label: METRICS.tok.label,
|
|
164
156
|
note: STYLES.tok.note[loc],
|
|
165
157
|
method: STYLES.tok.estimate[loc]
|
|
166
|
-
.replace('{chars}', CHARS_PER_TOKEN).replace('{encoding}', settings.encoding)
|
|
167
|
-
accuracy: 'approximate'
|
|
158
|
+
.replace('{chars}', CHARS_PER_TOKEN).replace('{encoding}', settings.encoding)
|
|
168
159
|
};
|
|
169
160
|
}
|
|
170
|
-
const binary =
|
|
161
|
+
const binary = otherCountFormats('tok', cfg);
|
|
171
162
|
let method = STYLES.tok.method[loc]
|
|
172
163
|
.replace('{tool}', 'gpt-tokenizer').replace('{version}', version).replace('{encoding}', settings.encoding);
|
|
173
164
|
if (binary.length > 0) method += STYLES.tok.binary[loc].replace('{exts}', binary.join(' '));
|
|
174
165
|
return {
|
|
175
166
|
label: METRICS.tok.label,
|
|
176
167
|
note: STYLES.tok.note[loc],
|
|
177
|
-
method: method
|
|
178
|
-
accuracy: binary.length === 0 ? 'exact' : 'approximate'
|
|
168
|
+
method: method
|
|
179
169
|
};
|
|
180
170
|
}
|
|
181
171
|
|
|
182
|
-
/* Whether a
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* that cannot get any shorter (JSON loses only insignificant whitespace — the list of exact
|
|
188
|
-
* strategies is owned by `strip.js`, where the strategies live). `tok` is exact where a
|
|
189
|
-
* dictionary exists and the format is text: the "tokens" of a picture or a font are its bytes.
|
|
190
|
-
* `raw` and `gzip` are always exact: they are unambiguous quantities. */
|
|
191
|
-
export function pointExact(name, file, cfg) {
|
|
192
|
-
if (name === 'min') return minifiedForm(file, cfg);
|
|
193
|
-
if (name === 'tok') return tokenizer(cfg.tokens).tool !== null && !isBinary(file);
|
|
194
|
-
return true;
|
|
172
|
+
/* Whether a file of this report is really minified: by the minifier, or by parsing a format that
|
|
173
|
+
* cannot get any shorter (JSON loses only insignificant whitespace — the list of exact strategies
|
|
174
|
+
* is owned by `strip.js`, where the strategies live). */
|
|
175
|
+
function minifiedForm(file, cfg) {
|
|
176
|
+
return esbuildLoader(file, cfg) !== null || EXACT_STRATEGIES.indexOf(strategyFor(file, cfg)) >= 0;
|
|
195
177
|
}
|
|
196
178
|
|
|
197
|
-
/* The formats of this report that
|
|
198
|
-
*
|
|
199
|
-
|
|
179
|
+
/* The formats of this report that the metric counts another way — the minifier does not take
|
|
180
|
+
* them, or tokens are meaningless for them (the "tokens" of a picture or a font are its bytes).
|
|
181
|
+
* The list is derived from the settings and the counting rule rather than written by hand, and
|
|
182
|
+
* it is what the method names in words. */
|
|
183
|
+
function otherCountFormats(name, cfg) {
|
|
200
184
|
const exts = [];
|
|
201
185
|
cfg.columns.forEach((col) => {
|
|
202
186
|
col.paths.forEach((p) => {
|
|
203
|
-
|
|
187
|
+
const counted = name === 'min' ? minifiedForm(p, cfg)
|
|
188
|
+
: tokenizer(cfg.tokens).tool !== null && !isBinary(p);
|
|
189
|
+
if (counted) return;
|
|
204
190
|
const ext = path.extname(p).toLowerCase();
|
|
205
191
|
if (exts.indexOf(ext) < 0) exts.push(ext);
|
|
206
192
|
});
|
|
@@ -208,10 +194,6 @@ function approximateFormats(name, cfg) {
|
|
|
208
194
|
return exts.sort();
|
|
209
195
|
}
|
|
210
196
|
|
|
211
|
-
function minifiedForm(file, cfg) {
|
|
212
|
-
return esbuildLoader(file, cfg) !== null || EXACT_STRATEGIES.indexOf(strategyFor(file, cfg)) >= 0;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
197
|
/* Whether the file goes to the minifier: compression is requested, available and not overridden
|
|
216
198
|
* by an explicit choice of the project — `minify.ext` outranks the engine and serves as the way
|
|
217
199
|
* out when an extension lied about its content. One answer serves two questions: how to count
|
|
@@ -224,8 +206,8 @@ function esbuildLoader(file, cfg) {
|
|
|
224
206
|
}
|
|
225
207
|
|
|
226
208
|
/* The engine actually in force: the requested one may be unavailable, in which case the metric
|
|
227
|
-
*
|
|
228
|
-
*
|
|
209
|
+
* counts another way — and that is announced (`sensorGaps`), or a different count would travel
|
|
210
|
+
* into CI as the requested one. */
|
|
229
211
|
export function minEngine(cfg) {
|
|
230
212
|
if (cfg.minify.engine !== 'esbuild') return 'strip';
|
|
231
213
|
return minifier().tool === null ? 'strip' : 'esbuild';
|
package/src/minify.js
CHANGED
|
@@ -10,8 +10,8 @@ import { refuseCause } from './refusal.js';
|
|
|
10
10
|
* number. */
|
|
11
11
|
|
|
12
12
|
/* The extensions the minifier answers for. This table is the single source of truth both for
|
|
13
|
-
* the measurement and for the metric label ("the other formats
|
|
14
|
-
* two cannot drift apart. JSX and TSX are not here: the output depends on the project's `jsx`
|
|
13
|
+
* the measurement and for the metric label ("the other formats lose comments and
|
|
14
|
+
* indentation"), so the two cannot drift apart. JSX and TSX are not here: the output depends on the project's `jsx`
|
|
15
15
|
* setting (`React.createElement` versus `react/jsx-runtime`), and measuring someone else's
|
|
16
16
|
* decision about a runtime is not this tool's business — such files are honestly counted as a
|
|
17
17
|
* simplification. */
|
package/src/modes.js
CHANGED
|
@@ -26,9 +26,9 @@ function kmb(bytes) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/* Degradation is a fact of the report, not an error: the numbers came from a different
|
|
29
|
-
* method (stripping instead of minification, an estimate instead of
|
|
29
|
+
* method (stripping instead of minification, an estimate instead of a counted one) because
|
|
30
30
|
* an optional dependency is missing. The fact is printed once per sensor and becomes code
|
|
31
|
-
* 4 — otherwise
|
|
31
|
+
* 4 — otherwise a different count would travel into CI as the requested one. */
|
|
32
32
|
function note(gaps) {
|
|
33
33
|
gaps.forEach((gap) => console.error('! ' + gap.why + '\n fix: ' + gap.fix));
|
|
34
34
|
return gaps.length === 0 ? EXIT.OK : EXIT.SENSOR;
|
|
@@ -39,11 +39,11 @@ function sensorNote(cfg) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/* The mode's verdict together with the sensor notes: the note is printed always — silence
|
|
42
|
-
* about a different count reads as
|
|
43
|
-
* a cause — while the code stays the more important one. A violation outranks
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
42
|
+
* about a different count reads as the requested one, and a disagreement would be left without
|
|
43
|
+
* a cause — while the code stays the more important one. A violation outranks a sensor note
|
|
44
|
+
* (the same order as in `check` and `doctor`): code 4 claims the difference is explained by the
|
|
45
|
+
* missing sensor, and when the table disagrees nobody checked that — the disagreement may be a
|
|
46
|
+
* real edit that went past the report. */
|
|
47
47
|
function verdict(code, gaps) {
|
|
48
48
|
const sensors = note(gaps);
|
|
49
49
|
return code === EXIT.OK ? sensors : code;
|
|
@@ -127,7 +127,7 @@ export function coverageMode(cfg, root, configFile, asJson) {
|
|
|
127
127
|
/* Diagnostics in one answer (`size doctor`): environment, dependencies, settings and
|
|
128
128
|
* coverage, assembled from the same pieces as the other modes. The exit code is not
|
|
129
129
|
* "something is wrong" but the first by importance (settings → history → coverage →
|
|
130
|
-
*
|
|
130
|
+
* sensor): an agent branches on it, a human reads the text. */
|
|
131
131
|
export function doctorMode(root, configFile, asJson) {
|
|
132
132
|
return answer(doctor(root, configFile), asJson, doctorText).exit;
|
|
133
133
|
}
|
package/src/optional.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
2
|
|
|
3
3
|
/* Optional dependencies: the minifier and the tokenizer. Missing ones are not a refusal but a
|
|
4
|
-
* different count (a simplification instead of compression, an estimate instead of
|
|
5
|
-
*
|
|
4
|
+
* different count (a simplification instead of compression, an estimate instead of a counted
|
|
5
|
+
* one), so their loading is shared and shaped the same way: lazy, synchronous
|
|
6
6
|
* (`createRequire` — measuring is one synchronous pass, and `import()` would make the whole
|
|
7
7
|
* chain asynchronous for the sake of a single sensor) and without an exception escaping —
|
|
8
8
|
* unavailability comes back as an answer.
|
package/src/page/app.css
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* The shared part is frozen by the artifact's bytes (`src/css.js`), which is why everything where the page departs from
|
|
8
8
|
* its geometry is gathered in the "adaptations" section together with its reason: the shared part cannot grow, while on a
|
|
9
|
-
* narrow screen the
|
|
9
|
+
* narrow screen the commit column would eat the whole screen. */
|
|
10
10
|
|
|
11
11
|
:root {
|
|
12
12
|
color-scheme: light dark;
|
|
@@ -111,12 +111,8 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
111
111
|
.box.plain input { cursor: default; }
|
|
112
112
|
|
|
113
113
|
/* There is no legend under the file tree, and on purpose: below the list it pushed the numbers away, while its content
|
|
114
|
-
* already stands next to what it explains — the colour of a delta is named by the sign of the number itself,
|
|
115
|
-
* stands under the metric switches, and the mark of a
|
|
116
|
-
*
|
|
117
|
-
* An approximate number is marked by a dashed line rather than a colour: colour in the table is taken by the delta (growth
|
|
118
|
-
* and fall), and a second meaning on the same sign would read as the first. */
|
|
119
|
-
#grid td.approx { text-decoration: underline dotted; text-underline-offset: 2.5px; }
|
|
114
|
+
* already stands next to what it explains — the colour of a delta is named by the sign of the number itself, the way each
|
|
115
|
+
* number was counted stands under the metric switches, and the mark of a gap lives in the cell's text. */
|
|
120
116
|
|
|
121
117
|
/* The table has a frame and a scroll of its own: the header and the commit column stick to it (the rules of stickiness
|
|
122
118
|
* live in the shared part) rather than to the page, so scrolling sideways shows whose row it is while scrolling down shows
|
|
@@ -131,6 +127,33 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
131
127
|
/* Numbers are denser than the page's text: there are more of them, they are shorter, and they are read by their digits. */
|
|
132
128
|
#grid { font-size: 12.5px; }
|
|
133
129
|
|
|
130
|
+
/* The stripe of a long drawing: over the window's own top edge for as long as a switch is being drawn (`src/page/work.js`).
|
|
131
|
+
* It is fixed rather than laid out — the page's shape is a grid of five rows, and a stripe appearing inside it would move the
|
|
132
|
+
* numbers every time a switch is drawn — and it is taken away as soon as the drawing is over, so the reader never sees it at
|
|
133
|
+
* rest. It **does not fill**: the drawing is one task of the browser's own layout, inside which nothing can repaint, so a
|
|
134
|
+
* share would be a share nobody has (`src/page/work.js` says what was measured). What moves instead is an indeterminate
|
|
135
|
+
* stripe — a travelling highlight, which is what this state is in the language of progress — and it moves by a transform,
|
|
136
|
+
* so the stripe itself costs the compositor rather than the page. Its colour is the system accent, the one colour the page
|
|
137
|
+
* uses for anything but the numbers, and it carries a role and a name, because a reader without eyes has to be told that
|
|
138
|
+
* work is going on rather than that the report is broken. */
|
|
139
|
+
.bar { position: fixed; top: 0; left: 0; right: 0; z-index: 5; height: 3px; overflow: hidden; }
|
|
140
|
+
.bar[hidden] { display: none; }
|
|
141
|
+
.bar > i {
|
|
142
|
+
display: block;
|
|
143
|
+
width: 30%;
|
|
144
|
+
height: 100%;
|
|
145
|
+
background: AccentColor;
|
|
146
|
+
animation: bar-run 1.1s linear infinite;
|
|
147
|
+
}
|
|
148
|
+
@keyframes bar-run {
|
|
149
|
+
from { transform: translateX(-100%); }
|
|
150
|
+
to { transform: translateX(333%); }
|
|
151
|
+
}
|
|
152
|
+
/* Whoever asked the system for less motion gets a still stripe: the state is what matters, not its travelling. */
|
|
153
|
+
@media (prefers-reduced-motion: reduce) {
|
|
154
|
+
.bar > i { animation: none; width: 100%; }
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
/* The message about a link that came in stands above the table, so that it cannot be missed, while it does not push the
|
|
135
158
|
* table away: one line in the place of the page. Its colour is the system one (the accent), for the page keeps no colours
|
|
136
159
|
* of its own. */
|
|
@@ -230,5 +253,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
230
253
|
h1 { font-size: 18px; }
|
|
231
254
|
#grid { font-size: 12px; }
|
|
232
255
|
.shell { max-height: calc(100vh - 260px); }
|
|
233
|
-
|
|
256
|
+
/* The commit column needs no rule of its own here any more: its width is the measure `--clip` of the shared part
|
|
257
|
+
* (`src/table.css`), and on a narrow screen that measure stands as it is. A shorter one would be `--clip: 150px`
|
|
258
|
+
* here — the adaptation the shared part cannot make for itself. */
|
|
234
259
|
}
|