@vernikr/size-report 1.1.1
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/CHANGELOG.md +241 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/bin/size.js +6 -0
- package/package.json +61 -0
- package/src/artifact.css +8 -0
- package/src/artifact.js +21 -0
- package/src/check.js +142 -0
- package/src/cli.js +476 -0
- package/src/config.js +158 -0
- package/src/css.js +37 -0
- package/src/data.js +105 -0
- package/src/derived.js +115 -0
- package/src/doctor.js +225 -0
- package/src/explain.js +113 -0
- package/src/git.js +221 -0
- package/src/history.js +206 -0
- package/src/hook.js +404 -0
- package/src/journal.js +66 -0
- package/src/locales.js +124 -0
- package/src/metrics.js +268 -0
- package/src/minify.js +67 -0
- package/src/optional.js +31 -0
- package/src/page/app.css +165 -0
- package/src/page/app.js +560 -0
- package/src/page/build.js +102 -0
- package/src/parse-worker.js +34 -0
- package/src/parse.js +136 -0
- package/src/refusal.js +133 -0
- package/src/render.js +131 -0
- package/src/size-table.js +86 -0
- package/src/strip.js +231 -0
- package/src/table.css +35 -0
- package/src/tokens.js +76 -0
- package/src/tool.js +24 -0
- package/templates/README.md +79 -0
- package/templates/ci.yml +67 -0
- package/templates/size-report.config.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
# @vernikr/size-report
|
|
2
|
+
|
|
3
|
+
Инструмент учёта роста объёма кода и документов: показывает, насколько вырос или
|
|
4
|
+
уменьшился проект в каждом изменении, в трёх разрезах — «как написано» (raw),
|
|
5
|
+
«в минифицированном виде» (min) и «в токенах для языковой модели» (tok).
|
|
6
|
+
|
|
7
|
+
Отвечает на два вопроса: человеку — «где проект распухает», ИИ-агенту — «сколько
|
|
8
|
+
весит моё изменение в его собственном контексте». Ничего не запрещает и не
|
|
9
|
+
блокирует: только показывает.
|
|
10
|
+
|
|
11
|
+
## Статус
|
|
12
|
+
|
|
13
|
+
**Выпуск 1.1.1 (2026-09-15).** Инструмент живёт отдельным пакетом: имя в
|
|
14
|
+
реестре — `@vernikr/size-report` (выпуск переименования, числа не изменились).
|
|
15
|
+
Версия — в манифесте, а у выпуска есть `CHANGELOG.md` с разделом «Что изменится
|
|
16
|
+
в числах»:
|
|
17
|
+
таблица чисел в нём не пересказ, а замер на фикстуре, который сверяется с живым
|
|
18
|
+
прогоном (`test/changelog.test.js`). Числа этим выпуском не меняются — правки
|
|
19
|
+
лежат в ответах инструмента человеку; `schema: 1` данных, замороженная 1.0.0,
|
|
20
|
+
остаётся той же.
|
|
21
|
+
|
|
22
|
+
**Шаг 1 плана пройден — перенос без изменения поведения.** Команда —
|
|
23
|
+
`bin/size.js`, точка входа пакета — `src/size-table.js` (только реэкспорт),
|
|
24
|
+
механика разложена по модулям `src/`; паритет доказан автоматически:
|
|
25
|
+
`pnpm test` сверяет пакет с эталоном побайтово на фикстуре и в четырёх заведомо
|
|
26
|
+
чужих окружениях (настройки git машины, локаль), `pnpm run parity:live` — на живой
|
|
27
|
+
истории `safe-resets` в двух средах (95 строк × 27 колонок, артефакт байт в байт).
|
|
28
|
+
Вывод не зависит от настроек машины — настройки git, влияющие на разбор, закреплены
|
|
29
|
+
в самом движке (`BLOCKERS.md` §B1). Сверка с рабочим деревом сравнивает содержимое,
|
|
30
|
+
а не размеры, поэтому выкладка с переводами строк в CRLF (`.gitattributes`,
|
|
31
|
+
`core.autocrlf` — значение по умолчанию в установке Git для Windows) работе не
|
|
32
|
+
мешает (`BLOCKERS.md` §B2).
|
|
33
|
+
|
|
34
|
+
**История с удалениями больше не тупик** (`BLOCKERS.md` §B3). Колонка, чей файл жил
|
|
35
|
+
в истории и был удалён до HEAD, роняла прогон **кодом 1** с текстом «файла нет
|
|
36
|
+
вместо файла нет» — то есть на проекте с удалёнными файлами отчёта не было вовсе.
|
|
37
|
+
Теперь отказом считается **расхождение** сторон сверки, а не пустота с обеих:
|
|
38
|
+
потерянное создание, потерянное изменение и потерянное удаление по-прежнему роняют
|
|
39
|
+
прогон — но с настоящей причиной и готовой командой, а файл, удалённый до HEAD,
|
|
40
|
+
просто пуст в таблице. Доказано числами, а не словом: размер колонки на каждом
|
|
41
|
+
коммите сверяется с размером блоба из git (возврат файла даёт то же число, что его
|
|
42
|
+
первое появление), а второй свидетель — файл, который появляется только в слиянии,
|
|
43
|
+
— роняет прогон и называет обе стороны: «в дереве `src/only-in-merge.js` 77d3e2f, в
|
|
44
|
+
состоянии файла нет». Заодно закрыта граница того же разбора (`BLOCKERS.md` §N8):
|
|
45
|
+
путь для состояния выбирался по порядку настроек, а не по тому, что в коммите есть,
|
|
46
|
+
поэтому при `diff.renames=false` — когда git отдаёт в одном коммите и старое имя
|
|
47
|
+
переименованного файла, и новое — движок брал исчезнувшее и сверка отказывала на
|
|
48
|
+
законном случае. Теперь берётся тот псевдоним колонки, для которого git отдал блоб.
|
|
49
|
+
Прежние числа поехать не могли: обе логики совпадают всюду, где первый по порядку
|
|
50
|
+
псевдоним в коммите существует, то есть в любом прогоне, который до сих пор
|
|
51
|
+
заканчивался отчётом. Это проверено, а не заявлено: вывод движка до и после правки
|
|
52
|
+
совпал побайтово на фикстуре при `diff.renames` в обоих значениях, оба эталона
|
|
53
|
+
воспроизводятся байт в байт, живой отчёт — те же 95 × 27 и 225 673 Б, а прогон на
|
|
54
|
+
живой истории остался 1,56–1,58 с.
|
|
55
|
+
|
|
56
|
+
**Подключение к проекту-потребителю сделано (2026-09-14, шаг 5 плана).**
|
|
57
|
+
`safe-resets` ставит пакет из git по тегу выпуска и больше не держит своей копии
|
|
58
|
+
инструмента: ни `tools/size-table.js`, ни его теста — таблицу собирает и
|
|
59
|
+
проверяет команда `size` (`pnpm run test:sizes`), а её шаги в проекте — одна
|
|
60
|
+
строка в раннере (`WORKLOG.md` §18). Инструкция подключения оказалась верна, а
|
|
61
|
+
двух вещей в ней не было: шага доступа к пакету в CI и порядка переезда с уже
|
|
62
|
+
лежащей копии — оба дописаны в её же раздел («Как подключить»); шаг с ключом
|
|
63
|
+
оттуда потом ушёл вместе с приватностью (§1).
|
|
64
|
+
|
|
65
|
+
**Шаг 2 начат первым срезом — контрактом данных.** Движок отдаёт абсолютные
|
|
66
|
+
значения и устройство таблицы (`--data`), а дельты, суммы, «сейчас» и фильтры
|
|
67
|
+
считает страница (`--page`, `size-report.html` рядом с таблицей): без этого
|
|
68
|
+
фильтры и «итого по выбору» невозможны в принципе. В контракте едет и точность
|
|
69
|
+
числа — рядом пометок `approx` по клеткам, — потому что это факт замера, а не
|
|
70
|
+
вывод: страница показывает то, что сказал движок, и своего правила точности не
|
|
71
|
+
заводит. Панель страницы — дерево файлов по папкам, с переключателем у каждой
|
|
72
|
+
папки на всё поддерево; выбор читателя переживает перезаход и передаётся
|
|
73
|
+
ссылкой — адрес страницы и есть ссылка.
|
|
74
|
+
Минификация и токены сделаны первыми срезами шагов 3 и 4 (ниже).
|
|
75
|
+
Разбиение движка по файлам сделано (пункт D1 плана, R-1.3 `REFACTOR.md`).
|
|
76
|
+
Источник инструмента — скрипт `size-table.js` (1145 строк) в проекте
|
|
77
|
+
[`safe-resets`](../figma/safe-resets) (метрики `raw` и «упрощение вместо
|
|
78
|
+
минификации», статичный отчёт в git); в этом репозитории такого пути нет, и
|
|
79
|
+
дальше он не упоминается без имени проекта.
|
|
80
|
+
**Пункт R-1.2 волны 1** (`REFACTOR.md`): в пакете есть линтер — правила те же, что у
|
|
81
|
+
проекта-потребителя, плюс запрет склейки операторов в одну строку; всё настоящее
|
|
82
|
+
дерево (37 файлов) даёт ноль замечаний, `fixtures/` не линтуются — там данные.
|
|
83
|
+
|
|
84
|
+
**Пункты R-1.1 и R-2.1 волн 1–2** (`REFACTOR.md`): вычислительная часть отчёта одна
|
|
85
|
+
(`src/derived.js`) — страница исполняет тот же код, что считает статическую таблицу,
|
|
86
|
+
и разметку для неё строит обычный исходник `src/page/app.js`, а не строка внутри
|
|
87
|
+
движка; артефакт и разметка страницы при этом совпали со старым выводом побайтово.
|
|
88
|
+
|
|
89
|
+
**Отчёт собирается и в проекте с модулями в `.js`** (`REFACTOR.md` R-4.6): гард
|
|
90
|
+
стриппера понимает обе формы — скрипт и модуль, — поэтому подключение не требует
|
|
91
|
+
ни одной правки настроек руками, а когда в графе и правда не JavaScript, отказ
|
|
92
|
+
называет причину и команду. Подсказки, справка, умолчание команды починки и
|
|
93
|
+
шаблоны называют **путь внутри проекта** (`node node_modules/@vernikr/size-report/bin/size.js`),
|
|
94
|
+
а не имя пакета: `npx <имя>` запускает установленный пакет, только пока тот на
|
|
95
|
+
месте, а в проекте без него это имя уходит в реестр и тянет пакет по сети
|
|
96
|
+
(`R-4.7` — прежнее решение, `R-4.21` — почему оно отменено).
|
|
97
|
+
|
|
98
|
+
**У обещаний документации есть сторож** (`REFACTOR.md` R-4.1): он разложен по
|
|
99
|
+
обещаниям, поэтому у каждого свой дом — `test/docs-paths.test.js` (пути из текста
|
|
100
|
+
есть в дереве, таблица файлов сходится с ним в обе стороны),
|
|
101
|
+
`test/docs-commands.test.js` (команды и ключи есть в справке, причины отказа
|
|
102
|
+
совпадают с реестром движка, ссылки на разделы ведут в существующие),
|
|
103
|
+
`test/docs-numbers.test.js` (числа проверок и целей — факт) и
|
|
104
|
+
`test/docs-pin.test.js` (пример установки ведёт на ревизию, чья справка знает
|
|
105
|
+
названные команды), а с выпуском добавился пятый — `test/changelog.test.js`
|
|
106
|
+
(версия выпуска — версия манифеста, а таблица «что изменится в числах» — не
|
|
107
|
+
пересказ, а замер на фикстуре, сверенный с живым прогоном); читатель фактов один —
|
|
108
|
+
`tools/docs-facts.js`. Заведённый сторож сразу
|
|
109
|
+
нашёл четыре расхождения, и все починены: таблица файлов не называла
|
|
110
|
+
`fixtures/live/README.md`, `test/runner.test.js` и сам файл сторожа, `README.md`
|
|
111
|
+
обещал 92 проверки при 97, а две ссылки `PLAN.md` вели в разделы, которых в названных
|
|
112
|
+
документах нет. Чего машиной не проверить — формулировок, обещаний о будущем и
|
|
113
|
+
верности описания роли файла — сторож за собой не берёт и говорит об этом в шапке.
|
|
114
|
+
|
|
115
|
+
**git читается через одну границу — и в проверках тоже** (`REFACTOR.md` R-1.4).
|
|
116
|
+
Список закреплений (`core.quotePath`, раскраска, подпись, кодировка) один на движок и
|
|
117
|
+
обвязку: проверки и инструменты зовут git через общее место, а незакреплённое
|
|
118
|
+
место стережёт `test/git-pins.test.js` — и он же свидетелем показывает, что
|
|
119
|
+
закрепление работает: то же чтение без него отдаёт не-английский путь кавычками, с
|
|
120
|
+
ним — как есть. Это тот же дефект, что B1, только найденный в обвязке: без
|
|
121
|
+
закрепления проверка зелена на машине с нашими настройками и красна на машине с
|
|
122
|
+
настройками по умолчанию. У проверки, которая измеряет само окружение,
|
|
123
|
+
незакреплённое чтение осталось намеренно — оно названо и стоит в отдельном списке.
|
|
124
|
+
|
|
125
|
+
**Каждый отказ инструмента говорит правду — и это сторожится, а не подразумевается**
|
|
126
|
+
(`REFACTOR.md` R-4.18). Ложную причину в тексте отказа находил живой прогон, и находил
|
|
127
|
+
четыре раза подряд (B1, B3, разбор аргументов, `explain HEAD`) — каждый раз случайно.
|
|
128
|
+
Класс закрыт не пятым исправлением: в `tools/refusals.js` лежит по строке на каждый
|
|
129
|
+
отказ — что он обязан донести, каким кодом ответить и какие фразы в выводе обязаны
|
|
130
|
+
остаться, — а две проверки делят обе половины обещания. `test/refusals.test.js`
|
|
131
|
+
**вызывает** тридцать два отказа (тридцать три запуска инструмента, включая чужие
|
|
132
|
+
клоны для хука, обрезанной истории и ветки мимо отчёта) и сверяет код выхода и
|
|
133
|
+
фразы; `test/refusals-catalog.test.js` читает исходники и требует, чтобы у каждого
|
|
134
|
+
места отказа был свой пункт (карты `SITES` и `PRINTED` держат числа мест), а у
|
|
135
|
+
каждого пункта — случай в каталоге, — то есть новый отказ не может появиться без
|
|
136
|
+
проверки. Отказы, которых прогоном не поймать,
|
|
137
|
+
названы явно: четыре стережёт своя проверка (в каталоге записаны её файл и фразы),
|
|
138
|
+
а один не поймать вовсе — «внутренняя ошибка» — и там же сказано, почему. Чего
|
|
139
|
+
каталог не берёт, сказано словами: формулировки вне перечисленных фраз, смысл, и
|
|
140
|
+
знак «!» — это примечание (приближение, смешанный коммит, выключенная автоматика),
|
|
141
|
+
а не отказ, и код выхода у него нулевой.
|
|
142
|
+
|
|
143
|
+
**И совет в отказе исполним — это тоже проверяется.** Правда о причине — половина
|
|
144
|
+
обещания: вторая — что предложенную команду можно выполнить. Поводом стал живой
|
|
145
|
+
случай (R-4.21): подсказка звала по имени из реестра, где пакета с таким именем нет,
|
|
146
|
+
и в проекте без установленного пакета запускала чужой код. Теперь у каждого случая
|
|
147
|
+
каталога сказано, что отказ советует, и совет выполняется в том состоянии, которое
|
|
148
|
+
его напечатало: сверяется код (включая «отказ ушёл» — тот же зов после совета должен
|
|
149
|
+
ответить другим), а совет-форма без значений проверяется по справке (те ли команды и
|
|
150
|
+
ключи). Совет, который выполнить нечем, назван с причиной: правка настроек, коммит,
|
|
151
|
+
установка зависимости — за человеком, и это сказано там же, в каталоге. Новый совет
|
|
152
|
+
в уже существующем отказе молча не пройдёт: совет вынимается из вывода по маркерам
|
|
153
|
+
(«починка:», «создайте его:», «соберите её:»), и у него обязано быть объявление.
|
|
154
|
+
|
|
155
|
+
**Волна 3 чистки пройдена** (`REFACTOR.md`): обвязка проверок одна на пакет
|
|
156
|
+
(`tools/harness.js`), прогоны на чтение не повторяются, клон фикстуры держится на
|
|
157
|
+
набор, а тяжёлый паритетный файл распался по предметам — и он, и команды CLI идут
|
|
158
|
+
волной по ядрам. Проверок было 39 и все 39 сохранены (имена сверены), а новые
|
|
159
|
+
добавлены только вместе с новым поведением страницы, подключения и разбора
|
|
160
|
+
модулей, а последние — о том, что записанное в манифестах сходится с файлами
|
|
161
|
+
эталонов, и о склейке кусков вывода процесса, и о дереве файлов, и о памяти
|
|
162
|
+
выбора и ссылке на странице, семь — о настоящем сжатии и семь — о токенах (§«Метрика
|
|
163
|
+
`min` умеет считать по-настоящему», §«Метрика `tok` считает токены настоящим
|
|
164
|
+
словарём», четыре — о сверке с деревом и выборе пути для состояния (§«История с
|
|
165
|
+
удалениями больше не тупик»), три — о точности по клетке (§«Точность числа»);
|
|
166
|
+
всего 80.
|
|
167
|
+
|
|
168
|
+
**Разбор модуля перестал стоить запуска Node на клетку** (`REFACTOR.md` R-5.4).
|
|
169
|
+
Гард понимает модуль через `vm.SourceTextModule`, а он живёт только под
|
|
170
|
+
`--experimental-vm-modules`: раньше отсюда и брался отдельный процесс на каждую
|
|
171
|
+
клетку. Теперь модуль разбирает один рабочий поток на прогон: на синтетической
|
|
172
|
+
истории с модулями (31 коммит, 3 файла, файл меняется каждым коммитом) `--write`
|
|
173
|
+
**5,59 → 0,47 с**, на той же истории со скриптами — те же 0,39 с (скриптовый
|
|
174
|
+
проект за поток не платит вовсе), а на проекте с одной изменённой клеткой —
|
|
175
|
+
0,44 → 0,40 с. Цена не исчезла, а стала разовой: старт потока ≈ 54 мс вместо
|
|
176
|
+
86 мс на каждую клетку, плюс разбор опирается на экспериментальный API (без него
|
|
177
|
+
гард отступает к прежнему `node --check` — медленнее, но не мягче).
|
|
178
|
+
Прогон подешевел втрое: `pnpm test` 15,7 → **5,6 с** (тогда в наборе было 39
|
|
179
|
+
проверок), `pnpm run parity:live` 23,8 → **8,3 с**. Бюджет времени переснят с
|
|
180
|
+
запасом на следующую проверку, а не под сегодняшнее число: набор стоит
|
|
181
|
+
**23,4–29,9 с** при 124 проверках — в зависимости от загрузки машины: окна с
|
|
182
|
+
загрузкой 18–70 несравнимы (в спокойном — 23,4–24,2 с, в занятых — 26,6–29,9 с;
|
|
183
|
+
в среде без настроек git — 27,4 с, с `CI=1` — 29,9 с), и это свойство окна, а не
|
|
184
|
+
набора: под той же загрузкой та же ревизия без нового сторожа идёт 24,4–26,3 с
|
|
185
|
+
при 122 проверках. Вклад сторожа выпуска измерен **парным прогоном** с
|
|
186
|
+
чередованием (124 → 122 проверки и обратно, два круга): **+2,0 и +2,2 с**, при этом
|
|
187
|
+
сам он стоит 1,4 с собственным прогоном (`node --test test/changelog.test.js`: два
|
|
188
|
+
запуска инструмента на фикстуре) и идёт параллельно прочим файлам.
|
|
189
|
+
Числа разных окон несравнимы вовсе: тот же набор при 119 проверках шёл 25,5–25,7 с,
|
|
190
|
+
хотя теперь проверок больше. Дороже всего в наборе — запуски инструмента: перебор
|
|
191
|
+
режимов и правило `--json` в `test/cli.test.js` стоят по нескольку десятых секунды
|
|
192
|
+
каждая, а разложение сторожа документации на четыре файла времени **не
|
|
193
|
+
прибавило** — 23,2 с и до него, и после: обе новые проверки измерены отдельным
|
|
194
|
+
прогоном, а не выведены из разброса. Из общего времени **+8,5 с** — десять проверок хука
|
|
195
|
+
(`test/hook.test.js`: сам он идёт 17,9–18,5 с и становится самым долгим файлом
|
|
196
|
+
набора, а та же ревизия без него — 15,8–16,6 с при 107 проверках). Цель не
|
|
197
|
+
двигалась, и это решение, а не пропуск: цель тогда была одна — полный набор
|
|
198
|
+
**≤ 28 с** (запас 2,3 с) — измеренное в неё укладывается, а поднимают цель по делу и с измерением,
|
|
199
|
+
а не под занятую машину: интеграционные
|
|
200
|
+
прогоны (клон, коммиты, слияние, отказы) дешевле не сделать, не ослабив проверку.
|
|
201
|
+
`pnpm run parity:live` **≤ 15 с** (9,3 с в обеих средах). Замеры, машина и разброс —
|
|
202
|
+
`REFACTOR.md` §5.
|
|
203
|
+
|
|
204
|
+
**Прогонов два, и у каждого своя цель** (`REFACTOR.md` R-5.5). Цена проверки в этом
|
|
205
|
+
наборе — не объём файла, а сколько раз файл запускает инструмент и git: запуск — это
|
|
206
|
+
процесс Node, а клон фикстуры и сборка артефакта — сотни миллисекунд. Поэтому
|
|
207
|
+
быстрый прогон собирает то, что доказывает по прочитанному (исходники, дерево,
|
|
208
|
+
справка, эталонные числа на общей фикстуре), а полный добавляет то, что гоняет
|
|
209
|
+
инструмент по многу раз на своих клонах, коммитит и ставит хуки; причина для каждого
|
|
210
|
+
дорогого файла названа построчно в `tools/suites.js`, там же снимок стоимостей
|
|
211
|
+
(перемерить — `pnpm run suites:measure`).
|
|
212
|
+
|
|
213
|
+
| Прогон | Команда | Проверок | Цель |
|
|
214
|
+
|---|---|---|---|
|
|
215
|
+
| Быстрый — каждая правка | `pnpm test` | **57 из 131** | **≤ 10 с** |
|
|
216
|
+
| Полный — выкладка и CI | `pnpm test:all` | **131** | **≤ 32 с** |
|
|
217
|
+
|
|
218
|
+
Ни одна проверка не потеряна и не ослаблена: полный прогон запускает все 132 теми же
|
|
219
|
+
файлами, а быстрый берёт их часть. Умолчание — полный: файл становится быстрым только
|
|
220
|
+
явно и с причиной, поэтому новое дорогое не может тихо уехать в быстрый. Стерегут это
|
|
221
|
+
двое, и по-разному: `test/suites.test.js` — объявление (полнота классификации,
|
|
222
|
+
причины, потолок стоимости быстрого файла), а сам прогон (`tools/run-tests.js`) —
|
|
223
|
+
замер: он печатает стоимость каждого файла своим запуском, складывает числа проверок
|
|
224
|
+
и валится, если вышел за цель вдвое. Снимок стоимости проверяется только замером,
|
|
225
|
+
и это его законное место: подделанная стоимость у быстрого файла объявление проходит,
|
|
226
|
+
а прогон её ловит. Цель при этом не порог: занятое окно растягивает прогон, и
|
|
227
|
+
прогон, не уложившийся в цель, говорит это словами («ЦЕЛЬ НЕ ДОСТИГНУТА»), а не
|
|
228
|
+
показывает зелёную галку при 29 с. CI зовёт полный прогон дважды — обычной средой и
|
|
229
|
+
без настроек машины. Числа и цели в этой таблице сверяются сторожем документации
|
|
230
|
+
(`test/docs-numbers.test.js`), а не живут второй копией без присмотра.
|
|
231
|
+
|
|
232
|
+
**Обещанное пакетом сведено к факту.** Список поставки называл четыре пути,
|
|
233
|
+
которых в репозитории нет (`dist/`, `templates/`, `CHANGELOG.md`, `LICENSE`):
|
|
234
|
+
теперь он обещает только существующее (шаблоны и `CHANGELOG.md` вернулись в список
|
|
235
|
+
вместе с файлами, а не раньше их), а `pnpm run pack:check` проверяет это с двух
|
|
236
|
+
сторон — в списке нет того, чего нет, и в тарболл не попадает то, чего список не
|
|
237
|
+
обещает. Снятие обоих эталонов снова работает (`pnpm run parity`, `pnpm run
|
|
238
|
+
fixture`) и больше не зависит ни от того, держит ли проект-потребитель свою копию
|
|
239
|
+
инструмента, ни от настроек git на машине. Заодно поправлены два текста, которые
|
|
240
|
+
это же обещали: подсказка `--init` (говорила «проверки едут вместе с пакетом», а
|
|
241
|
+
сьют в пакет не входит) и умолчание `fixCommand` (называло несуществующее имя
|
|
242
|
+
пакета `npx size-table --write`).
|
|
243
|
+
|
|
244
|
+
**Проверки идут сами (шаг 6 плана, `.github/workflows/ci.yml`).** На каждый пуш и
|
|
245
|
+
на каждый запрос правки один job проходит семь шагов теми же командами, что и у
|
|
246
|
+
себя локально: строгий линтер, набор проверок, тот же набор в среде, где настроек
|
|
247
|
+
машины нет вовсе (`GIT_CONFIG_GLOBAL=/dev/null`), работу из собранного тарболла,
|
|
248
|
+
сверку с историей проекта-потребителя и воспроизводимость обоих эталонов.
|
|
249
|
+
Секретов job не требует: история потребителя лежит в репозитории бандлом на той
|
|
250
|
+
же ревизии, что записана в эталоне (`fixtures/live/`), а пересъём идёт во временный
|
|
251
|
+
каталог и сверяется с закоммиченным — рабочее дерево остаётся чистым. Матрицы по
|
|
252
|
+
версиям Node и публикаций нет намеренно: этот проход про контроль.
|
|
253
|
+
|
|
254
|
+
Первым же прогоном CI окупился: шаг живого паритета упал не на расхождении чисел,
|
|
255
|
+
а на самой проверке — вывод процессов собирался как строка, и многобайтовый символ,
|
|
256
|
+
разорванный между кусками чтения, превращался в два символа-заменителя (местные
|
|
257
|
+
прогоны этого не показывали: границы кусков зависят от того, как ядро вернуло
|
|
258
|
+
чтение). Дефект починен, сторож — `test/runner.test.js` (`WORKLOG.md` §21).
|
|
259
|
+
|
|
260
|
+
Второй прогон нашёл ещё два дефекта, и оба — про git по обе стороны границы
|
|
261
|
+
вызова. Шаг воспроизводимости эталонов сверял бандл истории **побайтово**, а
|
|
262
|
+
упаковку пишет git: её байты зависят от версии, и проверка была зелёной на одной
|
|
263
|
+
машине и красной на другой (фикстура это и в README утверждает). Теперь у бандла
|
|
264
|
+
сверяется содержимое — ветки, верхушка и число коммитов, — а побайтово только то,
|
|
265
|
+
что пишем мы сами. Второй: бандл живой истории лежал **без `HEAD`**, и клон сам
|
|
266
|
+
решает, какую ветку выложить, — разные версии git решают по-разному (`hint: Using
|
|
267
|
+
'master' …`). Бандл пересобран с `HEAD`, `check:standards` это требует, а отказ
|
|
268
|
+
инструмента печатается целиком, а не первой строкой (`WORKLOG.md` §22).
|
|
269
|
+
|
|
270
|
+
**Страница отчёта выглядит и ведёт себя как инструмент** (`REFACTOR.md` R-2.2):
|
|
271
|
+
один набор стилей таблицы на оба вывода (`src/table.css`) — странице достались
|
|
272
|
+
липкие шапка и колонка коммита, которые раньше были только у статического
|
|
273
|
+
артефакта, и она больше не уезжает вбок в узком окне (до правки — 1518px при
|
|
274
|
+
окне 620). Добавились легенда с цветами дельт, состояния «нечего показать»
|
|
275
|
+
(сняты все метрики или все файлы) и переключатели, доступные с клавиатуры. Цвет
|
|
276
|
+
deльт задан один раз и по артефакту: рост зелёный, спад красный — смена это две
|
|
277
|
+
строки в `src/table.css` плюс пересборка эталона артефакта, больше цвета нигде нет.
|
|
278
|
+
|
|
279
|
+
**Левая панель страницы — дерево файлов** (`REFACTOR.md` R-2.4). Папки берутся из
|
|
280
|
+
тех же путей, что показаны в подписи файла (ни одного перечисления руками), у
|
|
281
|
+
папки три состояния — все её файлы включены, часть, ни одного, — и переключатель
|
|
282
|
+
папки ведёт за собой всё поддерево; рядом стоит число файлов. Своего состояния у
|
|
283
|
+
папки и у быстрой кнопки категории нет: обе переставляют галочки файлов, поэтому
|
|
284
|
+
дерево, кнопки и таблица не могут разойтись. Список файлов длиной больше трети
|
|
285
|
+
экрана прокручивается внутри панели, а не выталкивает таблицу: на живой истории
|
|
286
|
+
(91 строка × 25 файлов) панель занимает 429 px, таблица начинается на 544-м.
|
|
287
|
+
Стрежет это проверка на числах таблицы, а не на разметке: выключение папки убирает
|
|
288
|
+
ровно её колонки и ровно её объём из итога (`test/contract.test.js`). Проверено в
|
|
289
|
+
настоящем Chrome: 9 папок до трёх уровней вложенности (`.github/workflows`,
|
|
290
|
+
`tests/golden`), 25 файлов, ни одного внешнего запроса (сеть — только сам файл),
|
|
291
|
+
ни одной ошибки в консоли, `docs/6` после выключения одного своего файла показала
|
|
292
|
+
третье состояние, а после выключения целиком — 52 колонки → 40.
|
|
293
|
+
|
|
294
|
+
**Панель помнит выбор читателя** (`REFACTOR.md` R-2.5). Запись хранится в памяти
|
|
295
|
+
браузера, и она привязана к «паспорту отчёта» — имя инструмента, схема данных,
|
|
296
|
+
путь артефакта, заголовок и метки колонок; в ключ входит отпечаток паспорта,
|
|
297
|
+
поэтому чужие отчёты живут порознь и не видят выбора друг друга (в браузере все
|
|
298
|
+
страницы `file://` делят одну память, так что это не мелочь). Внутри записи выбор
|
|
299
|
+
лежит **по именам** — файл путём, метрика ключом, — и хранится только выключенное:
|
|
300
|
+
колонка, перенаправленная на другое, или метрика, убранная из настроек, просто
|
|
301
|
+
ничего не значит, появившееся остаётся включённым, а «включил всё обратно»
|
|
302
|
+
возвращает страницу к умолчанию и стирает запись. Первому читателю (и тому, чья
|
|
303
|
+
запись испорчена или устарела) достаётся именно умолчание — состояние на числа и
|
|
304
|
+
разметку не влияет. Проверено перезаходом в настоящем Chrome с диска, без сети:
|
|
305
|
+
после выключения метрики и одного файла следующий заход даёт те же **25 колонок
|
|
306
|
+
вместо 52** и тот же итог **994 335 вместо 1 133 362**; два отчёта в одном браузере
|
|
307
|
+
держат по своей записи (`size-report:4684b2b2` и `size-report:5dcd0db1`), и выбор
|
|
308
|
+
одного не трогает другой.
|
|
309
|
+
|
|
310
|
+
**Ту же выборку отдают ссылкой** (`REFACTOR.md` R-2.6). Адрес страницы — это и есть
|
|
311
|
+
ссылка: та же запись, что ложится в память браузера, ложится и в якорь
|
|
312
|
+
(`#size-report=…`), поэтому отправитель просто копирует адрес, а получатель видит
|
|
313
|
+
его выбор без единого действия. Ссылка старше памяти: она — явный выбор
|
|
314
|
+
отправителя, а память читателя она не подменяет, пока тот сам чего-нибудь не
|
|
315
|
+
поменяет. Чужой или испорченный адрес не применяется — и не молчит: над таблицей
|
|
316
|
+
появляется строка с причиной («ссылка собрана в другом отчёте» / «выбор в адресе
|
|
317
|
+
нечитаем»), вид остаётся читательским, а присланный адрес не переписывается; о
|
|
318
|
+
именах, которых в отчёте нет, сообщается числом, они пропускаются, остальное
|
|
319
|
+
применяется. Ссылка работает и когда отчёт уже открыт: браузер на смену якоря
|
|
320
|
+
документ не перезагружает, поэтому страница слушает адрес сама (без этого ссылка
|
|
321
|
+
срабатывала бы только в новой вкладке — этот разрыв нашёлся в браузерной
|
|
322
|
+
проверке, а не в тестах). Проверено на живом отчёте в Chrome с диска: получатель с
|
|
323
|
+
пустой памятью по ссылке видит те же **25 колонок и тот же итог 994 335**, что и
|
|
324
|
+
отправитель; чужой адрес оставляет 52 колонки и 1 133 362 и объясняет отказ; на
|
|
325
|
+
уже открытой странице ссылка меняет вид с 52 колонок на 25, а консоль остаётся
|
|
326
|
+
пустой. Ни одного обращения в сеть в странице нет — это отдельное утверждение
|
|
327
|
+
проверки, а не обещание.
|
|
328
|
+
|
|
329
|
+
**Метрика `min` умеет считать по-настоящему** (шаг 3 плана, срез 1). Способ
|
|
330
|
+
выбирается в настройках: `"minify": {"engine": "esbuild"}` — настоящее сжатие
|
|
331
|
+
(JS/TS/CSS) необязательной зависимостью, `"engine": "strip"` — прежнее снятие
|
|
332
|
+
комментариев и отступов; умолчание не менялось, потому что под ним сняты оба
|
|
333
|
+
замороженных эталона. На фикстуре сжатие меньше упрощения в **44 клетках и ни разу
|
|
334
|
+
не больше**: `src/code.js` **276 → 185 Б**, `src/style.css` **55 → 43 Б**, по фикстуре
|
|
335
|
+
**−1 372 Б**. Цена сжатия названа, а не спрятана: на живой истории (95 строк ×
|
|
336
|
+
27 колонок) прогон стал **1,48 → 1,71 с** — это запуск минификатора и разбор тех
|
|
337
|
+
файлов, которые он берёт. JSON минифицируется разбором и потому
|
|
338
|
+
остаётся точным, а форматы, которых минификатор не берёт, честно названы в подписи
|
|
339
|
+
метрики вместе с теми, которые он берёт. Точность объявлена дважды, и это не два
|
|
340
|
+
ответа на один вопрос: подпись метрики говорит про **худшее в колонке** (один
|
|
341
|
+
формат без минификатора делает метрику приближённой целиком, а не прячется за
|
|
342
|
+
«точное» соседа), а каждая клетка — про своё число, и приближённая помечена
|
|
343
|
+
пунктиром с подписью способа. Худшее берётся у клеток, а не у названия способа:
|
|
344
|
+
отчёт из одного JSON точен и под снятием балласта — разбор теряет только
|
|
345
|
+
незначащие пробелы, короче его не сделает никто, — и подпись так и говорит.
|
|
346
|
+
Оба ответа считаются одним правилом (`pointExact` в
|
|
347
|
+
`src/metrics.js`), поэтому разойтись не могут. Минификатора нет (установка без необязательных
|
|
348
|
+
зависимостей, платформа без него) — метрика отступает к упрощению, способ говорит
|
|
349
|
+
об этом словами, а прогон отдаёт **код 4**, а не молчание: числа при этом те же, что
|
|
350
|
+
у прежнего способа, — побайтово со эталоном. Черновик `--init` ведёт новые проекты
|
|
351
|
+
сразу на сжатие; цена названа прямо в его подсказке. Файл, который минификатор не
|
|
352
|
+
разобрал (разметка в `.js`, чужой синтаксис), — отказ кодом 2 с причиной от него
|
|
353
|
+
самого и двумя готовыми выходами.
|
|
354
|
+
|
|
355
|
+
**Метрика `tok` считает токены настоящим словарём** (шаг 4 плана, срез 1). Токены —
|
|
356
|
+
третье измерение отчёта: вес файла для языковой модели. Словарь выбирается в
|
|
357
|
+
настройках (`"tokens": {"family": "openai", "encoding": "o200k_base"}`), и
|
|
358
|
+
кодировка — часть числа, а не подробность: на фикстуре `src/code.js` это **168
|
|
359
|
+
токенов** в `o200k_base` и **196** в `cl100k_base`, поэтому кодировка называется
|
|
360
|
+
рядом с семейством, а способ метрики цитирует ровно ту, что посчитана. Токены —
|
|
361
|
+
не байты и не сжатие, и расхождение видно, а не заглажено: та же клетка — **735 Б**
|
|
362
|
+
`raw`, **276 Б** упрощением, **185 Б** настоящим сжатием и **168** токенов; байт на
|
|
363
|
+
токен отличается по файлам в **2,5 раза** (от 2,56 у `package.json` до 6,30 у
|
|
364
|
+
`crlf.txt`), то есть считается текст, а не отношение. Семейство в этой версии одно —
|
|
365
|
+
`openai`: у остальных нет словаря, который можно было бы назвать их собственным, а
|
|
366
|
+
считать чужим и называть это семейством значило бы обещать то, чего нет.
|
|
367
|
+
Переключателя словаря на странице нет намеренно: страница получает готовые числа и
|
|
368
|
+
сама не считает ничего, а посчитать токены другим словарём ей нечем. Сосчитать все
|
|
369
|
+
семейства на каждый прогон — это платить временем за числа, о которых читатель,
|
|
370
|
+
может быть, и не спросит, поэтому выбор семейства и кодировки живёт там, где стоит
|
|
371
|
+
времени (в настройках запуска), а страница его **называет**: способ каждой метрики
|
|
372
|
+
виден под переключателями текстом, а не только во всплывающей строке (решение
|
|
373
|
+
плана §4.8.4 отменено осознанно — `PLAN.md`, шаг 4).
|
|
374
|
+
Форматы без текста (картинка, шрифт, архив) названы в подписи метрики вместе с
|
|
375
|
+
причиной: у них число идёт по байтам, и по тому же правилу помечена клетка такого
|
|
376
|
+
файла, а подпись метрики берёт худшее в колонке — двум ответам разойтись нечем.
|
|
377
|
+
Словаря нет (установка без необязательных зависимостей, платформа без него) — счёт
|
|
378
|
+
идёт оценкой по длине с названным коэффициентом, а прогон отдаёт **код 4**; числа
|
|
379
|
+
при этом те же, что у прежнего отчёта без токенов, а сам шов проверяется
|
|
380
|
+
окружением `SIZE_REPORT_NO_OPTIONAL`. Прогон этим платит временем, и это честная
|
|
381
|
+
цена словаря, а не разбор: таблицы словаря читаются **0,3 с на процесс**, а на
|
|
382
|
+
живой истории (95 строк × 27 колонок, 1,23 МБ текста) тот же отчёт идёт
|
|
383
|
+
**1,55 → 6,35 с** — умножается именно сбор истории, а не таблица: токенов в
|
|
384
|
+
«сейчас» — **303 705**, то есть 4,05 Б на токен. Отсюда и цена набора проверок:
|
|
385
|
+
**7,3–7,9 → 10,4 с** при 66 → 73 проверках (запас и новый бюджет — ниже). Черновик
|
|
386
|
+
`--init` ведёт новые проекты сразу на токены.
|
|
387
|
+
|
|
388
|
+
**Волна 0 чистки пройдена** (`REFACTOR.md`): у отказов командной строки появились
|
|
389
|
+
коды выхода и справка вместо стека, `--help` отвечает, `--page` и `--write`
|
|
390
|
+
создают недостающий каталог, подсказка в отказе ведёт к работающей команде, а
|
|
391
|
+
черновик `--init` больше не предлагает колонкой саму таблицу — иначе первая же
|
|
392
|
+
проверка настроек его отвергала.
|
|
393
|
+
|
|
394
|
+
**Появились две команды: полнота и объяснение** (шаг 5 плана). `size check`
|
|
395
|
+
отвечает, всё ли в истории попало в отчёт: каждый путь, тронутый коммитами,
|
|
396
|
+
обязан быть колонкой или объявленным исключением, а непонятый путь — это код 1,
|
|
397
|
+
путь, коммит, который его завёл, и готовая починка. Тем же ответом идут сводка по
|
|
398
|
+
выпавшим коммитам (сколько и почему) и списки их sha — то есть «какая часть
|
|
399
|
+
истории покрыта». `size explain <коммит>` отвечает про один коммит — назвать его
|
|
400
|
+
можно и именем ревизии (`HEAD`, ветка, тег, `HEAD~1`), и sha, и началом sha:
|
|
401
|
+
строка есть (и которая) либо причина, почему её нет, — тронут только отчёт, числа не сдвинулись
|
|
402
|
+
при тронутых файлах колонок, коммит мимо колонок, слияние скрыто `rows.merges`.
|
|
403
|
+
Обе берут причину у того же прохода, что и отчёты, а улики — из списка изменённых
|
|
404
|
+
путей коммита: чего в истории нет, о том молчание вместо догадки. Полнота — из требований (§4.2: «ни одно изменение не
|
|
405
|
+
просочилось мимо отчёта»), и она же заменяет контроль
|
|
406
|
+
«артефакт ↔ история»: отчёт можно не хранить в git. Смысл `skip` в настройках от
|
|
407
|
+
этого не изменился, но **значение расширилось**: это не только «пути, которые
|
|
408
|
+
колонками быть не могут», но и объявленные исключения полноты — тот же список, и
|
|
409
|
+
чеканить второй инструмент не стал. Цена названа: `check` — это проход по истории,
|
|
410
|
+
как и любой отчёт (**1,5 с** на живой истории), а набор проверок подорожал на
|
|
411
|
+
тринадцать запусков инструмента (бюджет — ниже). К ним добавился `size doctor` —
|
|
412
|
+
диагностика одним ответом (ниже, в разделе про проверки).
|
|
413
|
+
|
|
414
|
+
**Отчёт обновляется сам** (последний пункт шага 5 плана). `size install-hook`
|
|
415
|
+
ставит два хука — `post-commit` и `post-merge` (`post-commit` при `git merge` не
|
|
416
|
+
выполняется вовсе, поэтому одного файла мало), — и после каждого коммита и слияния
|
|
417
|
+
отчёт пересобирается, а лежащий в git — ложится **отдельным коммитом**: ручного шага
|
|
418
|
+
«код, потом таблица» больше нет. Коммит отчёта собирается плумбингом git
|
|
419
|
+
(`commit-tree`): в него физически не могут попасть ни индекс, ни чужая
|
|
420
|
+
незакоммиченная работа, и зацикливание невозможно по устройству, а не по флагу в
|
|
421
|
+
окружении. Отказ инструмента коммит не роняет — причина печатается строкой и
|
|
422
|
+
видна в `size doctor`.
|
|
423
|
+
|
|
424
|
+
Перенос, доработка и оформление в пакет расписаны в `PLAN.md` по шагам, с
|
|
425
|
+
приёмкой каждого.
|
|
426
|
+
|
|
427
|
+
## Что в репозитории
|
|
428
|
+
|
|
429
|
+
| Файл | Роль |
|
|
430
|
+
|---|---|
|
|
431
|
+
| `PLAN.md` | **Главный документ:** инвентаризация, границы, инварианты, архитектура, семь шагов переноса, приёмка, риски, открытые вопросы |
|
|
432
|
+
| `docs/requirements.md` | Требования заказчика: что и зачем |
|
|
433
|
+
| `docs/module-design.md` | Архитектурный проект выноса: как устроен модуль |
|
|
434
|
+
| `WORKLOG.md` | Журнал запросов и сделанного |
|
|
435
|
+
| `BLOCKERS.md` | Открытые блокеры и известные пробелы (обход обязан держаться проверкой) |
|
|
436
|
+
| `REFACTOR.md` | Поканальный план чистки: объём кода, потом скорость; границы и чем доказывается, что поведение не изменилось |
|
|
437
|
+
| `CHANGELOG.md` | История выпусков и, у каждого выпуска, раздел «Что изменится в числах»: у кого числа поедут и почему |
|
|
438
|
+
| `tools/parity-freeze.js` | Снимает эталон паритета (`pnpm run parity`): замороженной копией, на ревизии проекта из манифеста — `--json`, конфиг, хеш артефакта, хеш инструмента |
|
|
439
|
+
| `tools/make-fixture.js` | Собирает синтетическую фикстуру (`pnpm run fixture`): детерминированную историю с ловушками плюс эталонные числа |
|
|
440
|
+
| `tools/parity-live.js` | Сверяет движок с живым проектом на клоне: числа и артефакт (`pnpm run parity:live`) |
|
|
441
|
+
| `tools/pack-check.js` | Собирает тарболл и проверяет, что из него всё работает: все исходники доехали, числа, артефакт и страница — как из репозитория (`pnpm run pack:check`) |
|
|
442
|
+
| `tools/check-standards.js` | Проверяет, что оба эталона воспроизводятся: пересъём идёт в никуда и сверяется с закоммиченным (наши файлы — побайтово, бандл — по содержимому) и что бандл живой истории несёт `HEAD` (`pnpm run check:standards`) |
|
|
443
|
+
| `.github/workflows/ci.yml` | CI: семь шагов на каждый пуш и запрос правки — те же команды, что локально, без секретов и матриц |
|
|
444
|
+
| `templates/` | То, что проект берёт как есть: `size-report.config.json` (черновик настроек), `ci.yml` (описание проверки) и `README.md` (куда что кладётся и что в них менять); едут в поставке и стерегутся `pack:check` и `test/templates.test.js` |
|
|
445
|
+
| `fixtures/parity/` | Эталон с `safe-resets` на коммите `bd6ef9d`: 95 строк × 27 колонок. Копия реализации, которой он снят, в дереве не лежит — её байты живут в истории и берутся оттуда по требованию (`REFACTOR.md` R-1.5) |
|
|
446
|
+
| `fixtures/synthetic/` | Бандл фикстуры на 16 коммитов, её конфиг, эталонные числа и хеш артефакта |
|
|
447
|
+
| `fixtures/live/history.bundle`, `fixtures/live/README.md` | История проекта-потребителя на ревизии эталона `bd6ef9d` и записка о том, какую ревизию бандл несёт и почему он лежит в репозитории: живая сверка работает без доступа к приватному проекту |
|
|
448
|
+
| `bin/size.js` | Команда `size`: то, что ставит пакет (`package.json` → `bin`); сама ничего не считает, только зовёт точку входа |
|
|
449
|
+
| `LICENSE` | MIT: условия лицензии едут в поставке вместе с пакетом |
|
|
450
|
+
| `.gitignore`, `pnpm-lock.yaml` | Что в репозиторий не идёт; lock-файл pnpm, а версия менеджера — в поле `packageManager` (оттуда её берёт CI) |
|
|
451
|
+
| `src/size-table.js` | Точка входа пакета: только реэкспорт публичного API (58 имён), ни одного расчёта |
|
|
452
|
+
| `src/derived.js` | Общий расчёт отчёта: итоги, дельты, клетка, подпись коммита — одно место на артефакт и страницу |
|
|
453
|
+
| `src/css.js` | Чтение оформления с диска: какие наборы стилей есть и какая у них роль |
|
|
454
|
+
| `src/table.css` | Общая часть таблицы: геометрия клеток, липкие шапка и колонка, цвет дельт — одна на артефакт и страницу |
|
|
455
|
+
| `src/artifact.css` | Оформление статического артефакта сверх общей части |
|
|
456
|
+
| `src/page/app.css` | Оформление страницы сверх общей части: панель с деревом файлов, состояния пустоты, узкое окно |
|
|
457
|
+
| `src/page/app.js` | Программа страницы: дерево файлов, разметка, состояние галочек, память выбора и ссылка; вклеивается в собранную страницу |
|
|
458
|
+
| `src/page/build.js` | Сборка страницы: данные, оформление и программа в одном файле без внешних ссылок |
|
|
459
|
+
| `src/git.js` | Единственная граница вызова git: закрепления настроек, блобы пачкой, история, сверка с диском |
|
|
460
|
+
| `src/strip.js` | Снятие балласта: стрипперы комментариев и отступов и правила, какая форма к какому файлу, гард компиляции |
|
|
461
|
+
| `src/parse.js` | Разбор модуля: рабочий поток на прогон и отступление к `node --check`, способ разбора последнего модуля |
|
|
462
|
+
| `src/parse-worker.js` | Сам разбор внутри потока: разбирает текст без исполнения, сообщает, что модулей vm в Node нет |
|
|
463
|
+
| `src/metrics.js` | Реестр метрик: что измеряется, нужен ли текст и насколько честна цифра; описание метрики для читателя — в одном месте |
|
|
464
|
+
| `src/minify.js` | Настоящий минификатор: необязательная зависимость, загружается один раз и не роняет прогон, если её нет |
|
|
465
|
+
| `src/tokens.js` | Токены: словарь по семейству и кодировке, оценка по длине как запасной счёт, форматы без текста |
|
|
466
|
+
| `src/optional.js` | Общее устройство необязательных зависимостей (минификатор и словарь): ленивая загрузка, версия пакета, шов отсутствия |
|
|
467
|
+
| `src/history.js` | Обход истории: измерение по коммитам, сдвиг чисел, сборка, сверка с деревом и причина пропуска у каждого выпавшего коммита |
|
|
468
|
+
| `src/check.js` | Полнота покрытия (`size check`): настройки, история, пути, датчики — что прошло мимо колонок и чем это чинится |
|
|
469
|
+
| `src/explain.js` | Объяснение пропущенной строки (`size explain <коммит>`): причина, улики и готовая починка |
|
|
470
|
+
| `src/doctor.js` | Диагностика одним ответом (`size doctor`): окружение, зависимости, настройки, покрытие, состояние хука — сборкой из существующих кусков |
|
|
471
|
+
| `src/hook.js` | Хуки автообновления (`install-hook` / `uninstall-hook` / `hook-run`): установка и снятие, коммит только отчёта, замок и запись о запуске |
|
|
472
|
+
| `src/artifact.js` | Артефакт на диске: единственное место, где отчёт превращается в файл (им пользуются и `--write`, и хук) |
|
|
473
|
+
| `src/journal.js` | Журнал и ссылки: к какому разделу относится коммит и куда ведёт описание |
|
|
474
|
+
| `src/data.js` | Категории файлов и контракт со страницей (`--data`) |
|
|
475
|
+
| `src/render.js` | Статический артефакт: клетки, таблица, примечание (стили — в `src/css.js`) |
|
|
476
|
+
| `src/config.js` | Настройки проекта-потребителя: умолчания, чтение, проверка |
|
|
477
|
+
| `src/locales.js`, `src/refusal.js`, `src/tool.js` | Тексты отчёта; коды выхода и справка; имя и версия пакета |
|
|
478
|
+
| `src/cli.js` | Режимы командной строки и разбор ключей; главный файл пакета |
|
|
479
|
+
| `test/api.test.js` | Публичный API пакета: список имён заморожен, разбиение не имеет права его менять |
|
|
480
|
+
| `eslint.config.js` | Правила оформления: те же, что у проекта-потребителя, плюс запрет склейки операторов в строке (`pnpm run lint`, `pnpm run lint:strict`) |
|
|
481
|
+
| `tools/harness.js` | Обвязка проверок: пути, клоны фикстуры (в том числе общий на набор и с CRLF), запуск инструмента, разбор отказов, хеши |
|
|
482
|
+
| `tools/suites.js` | Разделение набора: какие файлы идут в быстрый прогон (с причиной и снимком стоимости), почему каждый дорогой — в полном, и цели обоих прогонов |
|
|
483
|
+
| `tools/run-tests.js` | Прогон набора (`pnpm test`, `pnpm test:all`, `pnpm run suites:measure`): стоимость каждого файла своим замером, сверка числа проверок, граница бюджета |
|
|
484
|
+
| `tools/docs-facts.js` | Чтение фактов из документации — один слой на четыре проверки сторожа: что документ называет (пути, зовы, адреса разделов) против того, что есть в репозитории |
|
|
485
|
+
| `tools/refusals.js` | Каталог отказов: по строке на каждый — причина, код выхода, обязательные фразы вывода, **что отказ советует** (`advice`: `run` — команда, `template` — форма с подстановкой, `manual` — действие человека с причиной, `coveredBy` — отдан другой проверке), а для непроверяемого — почему; карты мест отказа (`SITES`, `PRINTED`) держат числа, чтобы новый отказ не появился молча, а маркеры совета — чтобы не появился молча новый совет |
|
|
486
|
+
| `test/parity.test.js` | Паритет движка с эталоном: числа, артефакт, локаль |
|
|
487
|
+
| `test/frozen.test.js` | Замороженная копия: та ли это ревизия, с которой снят эталон, и воспроизводит ли она его |
|
|
488
|
+
| `test/environment.test.js` | Герметичность: вывод не зависит от настроек git машины и локали |
|
|
489
|
+
| `test/crlf.test.js` | Выкладка с CRLF (`core.autocrlf`) не мешает сверке |
|
|
490
|
+
| `test/disk.test.js` | Сверка с рабочим деревом: правка только на диске, три вида потери (правка, создание, удаление — все мутацией), файл, удалённый до HEAD, и переименование внутри псевдонимов — не потеря (`BLOCKERS.md` §B3, §N8) |
|
|
491
|
+
| `test/cli.test.js`, `test/cli-paths.test.js` | Отказы командной строки: справка, настройки, коды выхода — и куда инструмент пишет |
|
|
492
|
+
| `test/refusals.test.js` | Отказы исполняются: каждый вызван прогоном, сверены код выхода и обещанные фразы (свои клоны — для чужого хука, обрезанной истории и ветки мимо отчёта), и **совет выполняется** — команда даёт обещанный код, не падает стеком, а где объявлено «отказ ушёл», тот же зов после неё отвечает другим |
|
|
493
|
+
| `test/refusals-catalog.test.js` | Сторож каталога отказов: у каждого места отказа в исходниках есть пункт, у каждого пункта — объявленный совет, а отказы, отданные другой проверке, ею в самом деле утверждаются (названные файл и строка проверяются) |
|
|
494
|
+
| `test/contract.test.js` | Контракт данных и страница: числа против эталона, производные против чисел артефакта, дерево файлов против путей, память выбора и ссылка против перезахода, чужого отчёта и чужого адреса, пометки приближения против подписи метрики |
|
|
495
|
+
| `test/module.test.js` | Модуль в расширении `.js`: измеряется без правок настроек; гард стриппера жив (доказано мутацией) и не обвиняет невиновного |
|
|
496
|
+
| `test/guard.test.js` | Разбор модуля: идёт потоком, оба пути дают один вердикт, отступление работает без файла потока, сотни разборов дешевле запуска |
|
|
497
|
+
| `test/runner.test.js` | Чтение вывода процесса: куски склеиваются буферами, а не приклеиваются к строке — многобайтовый символ на границе кусков не превращается в два символа-заменителя |
|
|
498
|
+
| `test/git-pins.test.js` | Сторож границы git: прямых вызовов git без общего списка закреплений нет, и незакреплённое чтение показывается свидетелем (путь кавычками) |
|
|
499
|
+
| `test/docs-paths.test.js`, `test/docs-commands.test.js`, `test/docs-numbers.test.js`, `test/docs-pin.test.js` | Сторож документации, по файлу на обещание: пути и таблица файлов; зовы, причины отказа и адреса разделов; числа проверок и цели по времени; пин в примере установки |
|
|
500
|
+
| `test/changelog.test.js` | Сторож выпуска: версия в `CHANGELOG.md` — версия манифеста, а таблица «что изменится в числах» — это замер на фикстуре, сверенный с живым прогоном |
|
|
501
|
+
| `test/suites.test.js` | Сторож разделения набора: полнота классификации (быстрый — явно, полный — с причиной), потолок стоимости быстрого файла, что быстрый прогон остаётся частью набора |
|
|
502
|
+
| `test/check.test.js` | Полнота и объяснение на настоящих коммитах фикстуры: непокрытый путь, «только отчёт», «число не сдвинулось», «мимо колонок», слияние — и что починка настроек не двигает числа |
|
|
503
|
+
| `test/doctor.test.js` | Диагностика на пяти состояниях проекта: без настроек (2), полное покрытие (0), неполное (1), обрезанная история (3), нет датчика (4) — и блок покрытия равен ответу `size check`, а не считается вторым разом |
|
|
504
|
+
| `test/hook.test.js` | Хуки на свежем клоне: ставятся только командой, дают отдельный коммит отчёта (в том числе после слияния), повторный запуск молчит, чужая работа и индекс не тронуты, в CI и при отказе инструмента ничего не делают, снятие возвращает проект к прежнему |
|
|
505
|
+
| `test/templates.test.js` | Шаблоны: черновик настроек проходит проверку инструмента и собирает настоящий отчёт; описание проверки разбирается и зовёт только существующие команды и ключи |
|
|
506
|
+
| `test/minify.test.js`, `test/tokens.test.js` | Настоящее сжатие и токены: числа против упрощения, кодировка как часть числа, честность подписи, работа без необязательной зависимости (код 4) и шов `SIZE_REPORT_NO_OPTIONAL` |
|
|
507
|
+
| `package.json` | Манифест пакета: имя `@vernikr/size-report`, версия `1.1.1`, список поставки — только существующее |
|
|
508
|
+
|
|
509
|
+
Оба каталога эталонов снимаются заново теми же инструментами: `pnpm run parity` и
|
|
510
|
+
`pnpm run fixture` дают те же файлы. Побайтово сверяется наше — конфиг, эталонные
|
|
511
|
+
числа, хеш артефакта, описание; у бандла истории сверяется содержимое (ветки,
|
|
512
|
+
верхушка, число коммитов), потому что упаковку пишет git и её байты зависят от его
|
|
513
|
+
версии. Обе стороны пары закреплены — инструмент это замороженная копия реализации,
|
|
514
|
+
чьи байты лежат в истории (`fixtures/legacy/size-table.cjs`, `REFACTOR.md` R-1.5) и
|
|
515
|
+
сверяются с записью о происхождении эталона, а ревизия проекта берётся из манифеста
|
|
516
|
+
(`--at` сдвигает её осознанно), окружение снятия задано (`core.quotePath=false`).
|
|
517
|
+
Без закрепления окружения эталон снимается другими числами: на машине с настройками
|
|
518
|
+
git по умолчанию фикстура с не-английским именем файла теряет строку. Проверено
|
|
519
|
+
тремя прогонами: повтор даёт те же байты и прогон без настроек машины
|
|
520
|
+
(`GIT_CONFIG_GLOBAL=/dev/null`) — тоже. Сходимость записанного в манифестах с
|
|
521
|
+
файлами стережёт `test/frozen.test.js`.
|
|
522
|
+
|
|
523
|
+
## Чего ещё нет
|
|
524
|
+
|
|
525
|
+
```text
|
|
526
|
+
dist/app.js пре-собранная программа отчёта для публикации
|
|
527
|
+
size init/measure командами вместо флагов: сейчас командами стали только check, explain, doctor и хук
|
|
528
|
+
блок для агентов инструкция агенту проекта: требования её не просят, поэтому в шаблонах её нет
|
|
529
|
+
минификация HTML минификатор разметки: пока HTML считается упрощением (шаг 3)
|
|
530
|
+
JSX и TSX выход зависит от настройки jsx самого проекта — упрощение (шаг 3)
|
|
531
|
+
семейства токенов кроме openai: у остальных нет своего словаря — считали бы чужим (шаг 4)
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Швы между модулями проходят по границам данных: сверху — то, что читает git и
|
|
535
|
+
файловую систему (`git`, `strip`, `metrics`, `history`), ниже — то, что работает на
|
|
536
|
+
уже собранных значениях (`data`, `render`, `page`), а настройки, тексты и отказ —
|
|
537
|
+
по краям, потому что их знает любой и они не знают никого. Оба отчёта считаются на
|
|
538
|
+
сборке: страница получает исходники общего расчёта и своей программы вклеенными
|
|
539
|
+
(`src/derived.js`, `src/page/app.js`), потому что открывается она с диска, без
|
|
540
|
+
сервера и без сети. Остальное — по шагам 2–6 (`PLAN.md` §5).
|
|
541
|
+
|
|
542
|
+
## Как подключить к своему проекту
|
|
543
|
+
|
|
544
|
+
Инструкция проверена покомандно на свежем проекте (три коммита, ESM в `src/`,
|
|
545
|
+
`pnpm`): ниже — ровно те команды, которые работают сегодня. Всё, что сегодня
|
|
546
|
+
**не** работает, названо здесь же и с причиной, чтобы это не искали опытом;
|
|
547
|
+
каждый такой случай — отдельный пункт `REFACTOR.md`.
|
|
548
|
+
|
|
549
|
+
Эта же инструкция — рецепт для шага 5 (`PLAN.md`): интеграция в проект-потребитель
|
|
550
|
+
идёт по ней, а не по памяти.
|
|
551
|
+
|
|
552
|
+
Нужны: **git-репозиторий с историей** (хотя бы один коммит — таблица строится по
|
|
553
|
+
коммитам) и **Node ≥ 20.19** (`engines` пакета).
|
|
554
|
+
|
|
555
|
+
### 1. Установка
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
pnpm add -D github:vernikr/size-report#v1.1.1
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
`npm i -D` и `yarn add -D` принимают ту же ссылку. **В npm пакет не опубликован**,
|
|
562
|
+
поэтому `pnpm add -D @vernikr/size-report` не сработает. Репозиторий публичный, и
|
|
563
|
+
учётных данных установка не требует: `github:` pnpm разрешает в архив
|
|
564
|
+
`codeload.github.com` и тянет его по HTTPS.
|
|
565
|
+
|
|
566
|
+
Без сети (или если тянуть из codeload нечем) — тарболл: `pnpm pack` в клоне
|
|
567
|
+
пакета, затем `pnpm add -D ./vernikr-size-report-1.1.1.tgz`.
|
|
568
|
+
|
|
569
|
+
**Почему тег, а не sha.** Короткий sha pnpm разрешает только через видимые рефы, а
|
|
570
|
+
`git ls-remote` отдаёт одни верхушки веток: пока ревизия — верхушка, короткий sha
|
|
571
|
+
работает, а как только ветка ушла вперёд, установка падает с `Could not resolve
|
|
572
|
+
<sha> to a commit`. Это не рассуждение, а проба: короткий пин `6530237` ставился,
|
|
573
|
+
пока `main` стоял на нём, и перестал — на следующем же коммите, а тот же sha
|
|
574
|
+
целиком поставился. Имя ветки (`#main`) или тег принимаются оба, но ветка —
|
|
575
|
+
движущаяся цель, а тег постоянен: этот выпуск стоит на теге `v1.1.1`, он же и в
|
|
576
|
+
примере (сорок знаков тоже годятся, но их придётся брать глазами из истории).
|
|
577
|
+
|
|
578
|
+
Ревизия в примере — не украшение, а часть утверждения: она закреплена за тем, что
|
|
579
|
+
описано ниже. Пин старше подкоманд (`check`, `doctor`, `explain`, `install-hook`)
|
|
580
|
+
означал бы, что текст учит командам, которых в установленной ревизии нет, а
|
|
581
|
+
лишнее слово там не отвергается, а молча пропускается — то есть вместо отказа
|
|
582
|
+
человек получил бы ноль и решил, что всё в порядке. Поэтому пин берётся не «какой был под рукой», а
|
|
583
|
+
ревизией, в которой есть всё названное ниже — включая отказы на незнакомое слово. За этим следит сторож документации (`test/docs-pin.test.js`): пин обязан вести
|
|
584
|
+
на ревизию этого репозитория, и все названные в тексте команды обязаны быть в её
|
|
585
|
+
справке.
|
|
586
|
+
|
|
587
|
+
Репозиторий пакета **публичный** (приватным был до 2026-09-14), и это ровно то,
|
|
588
|
+
что упрощает установку: ни ключа разработчика, ни шага в CI с доступом. Проверено
|
|
589
|
+
прогоном в пустом проекте, где у git не было ни глобальных настроек, ни
|
|
590
|
+
помощника учётных данных (`GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
|
|
591
|
+
GIT_SSH_COMMAND=false`): установка 3,4 с, дальше `size --write` и `size` работают
|
|
592
|
+
(`WORKLOG.md` §44). Прежнее требование было ценой приватности: локально — ключ, а
|
|
593
|
+
в CI — read-only deploy key перед `pnpm install` (то самое первое подключение,
|
|
594
|
+
`WORKLOG.md` §18); шаг с ключом из шаблона ушёл вместе с приватностью. Что
|
|
595
|
+
остаётся на потом — публикация в npm (`PLAN.md` §8.4), и там есть цена: имя
|
|
596
|
+
`size-report` в npm занято чужим пакетом (2017 год, три версии), поэтому
|
|
597
|
+
публикация — это имя в области владельца (`@vernikr/size-report`), а не выкладка
|
|
598
|
+
под прежним именем; что затронуло переименование — `PLAN.md` §10.
|
|
599
|
+
|
|
600
|
+
### 2. Черновик настроек
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
pnpm exec size --init # создаёт size-table.config.json
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
Черновик собирается по самому проекту: колонки — крупнейшие файлы каждого
|
|
607
|
+
расширения, журнал — по знакомым именам (`WORKLOG.md`, `CHANGELOG.md`, …), вывод —
|
|
608
|
+
в `docs/`, если он есть, `fixCommand` — под ваш менеджер пакетов. Он же печатает
|
|
609
|
+
следующие три шага. Черновик ведёт метрику `min` на настоящее сжатие
|
|
610
|
+
(`"minify": {"engine": "esbuild"}`) и сразу включает метрику `tok`
|
|
611
|
+
(`"tokens": {"family": "openai", "encoding": "o200k_base"}`): и минификатор, и
|
|
612
|
+
словарь едут необязательными зависимостями и ставятся обычной установкой, а без
|
|
613
|
+
них инструмент работает и сам скажет об этом (код 4) — правки настроек не
|
|
614
|
+
требуются.
|
|
615
|
+
|
|
616
|
+
> Subкоманды `size init` пока нет — CLI знает только флаги (`--init`, `--write`,
|
|
617
|
+
> `--page`, `--data`, `--json`, без флага — проверка); полный список даёт `size --help`.
|
|
618
|
+
> Subкоманды — шаг 5 плана (`REFACTOR.md` R-4.5).
|
|
619
|
+
|
|
620
|
+
### 3. Что правится в конфиге
|
|
621
|
+
|
|
622
|
+
Черновик знает про проект только размеры файлов — какие колонки важны, знает
|
|
623
|
+
человек. Чаще всего правят:
|
|
624
|
+
|
|
625
|
+
| Ключ | Что это |
|
|
626
|
+
|---|---|
|
|
627
|
+
| `columns` | колонки таблицы: `{label, paths: [...]}`; пути в одной колонке складываются (например, `src` целиком), `label` — то, что увидит человек |
|
|
628
|
+
| `metrics` | из чего состоит число: `raw` (размер объекта git), `min` (минифицированная форма — какая именно, решает `minify.engine`), `tok` (токены), `gzip` |
|
|
629
|
+
| `tokens.family`, `tokens.encoding` | словарь для `tok`: семейство (`openai`) и кодировка (`o200k_base` или `cl100k_base`) — кодировка меняет число, поэтому она и в настройках, и в подписи метрики |
|
|
630
|
+
| `minify.engine` | чем считается `min`: `strip` (комментарии и отступы, точность не обещается) или `esbuild` (настоящее сжатие; форматы без минификатора — упрощение, и это видно в подписи метрики) |
|
|
631
|
+
| `output` | файл таблицы (по черновику — `docs/size-table.html`) |
|
|
632
|
+
| `journal` | где искать разделы журнала, на которые ссылаются строки |
|
|
633
|
+
| `links.commitUrl` | шаблон ссылки на коммит, например `https://github.com/org/repo/commit/{sha}` |
|
|
634
|
+
| `skip` | пути, которые колонками быть не могут |
|
|
635
|
+
| `fixCommand` | команда, которую цитирует подпись отчёта и подсказывает отказ; в черновике уже ваша |
|
|
636
|
+
| `minify.guard` | расширения, где результат стриппера проверяется разбором; модуль в `.js` гард понимает сам, трогать его не нужно |
|
|
637
|
+
| `hooks.enabled` | выключатель хука автообновления (`false` — хук остаётся на месте, но молчит; убирается он только `size uninstall-hook`) |
|
|
638
|
+
|
|
639
|
+
Остальные ключи и умолчания — `src/config.js` (`DEFAULT_CONFIG`).
|
|
640
|
+
|
|
641
|
+
### 4. Скрипты и первый отчёт
|
|
642
|
+
|
|
643
|
+
```jsonc
|
|
644
|
+
// package.json
|
|
645
|
+
"scripts": { "sizes": "size --write", "test:sizes": "size" }
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
pnpm run sizes # → docs/size-table.html — таблица коммитов и чисел
|
|
650
|
+
pnpm exec size --page # → docs/size-report.html — интерактивная страница
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
Оба отчёта — самодостаточные файлы: открываются двойным щелчком, без сервера и
|
|
654
|
+
без сети (внешних ссылок в них нет вовсе, данные и программа вклеены). Числа
|
|
655
|
+
между ними одни и те же — страница считает производные (дельты, итоги, фильтры)
|
|
656
|
+
из тех же абсолютных значений тем же кодом, что и таблица.
|
|
657
|
+
|
|
658
|
+
**Порядок правок:** код → `pnpm run sizes` → коммит с одной таблицей. Таблица
|
|
659
|
+
обновляется **отдельным коммитом**, потому что строка коммита не может попасть в
|
|
660
|
+
саму таблицу: обновили её вместе с кодом — инструмент предупредит
|
|
661
|
+
(`! таблицу обновляли вместе с кодом: <sha>`) и назовёт коммит, который выпал.
|
|
662
|
+
Проверка `size` собирает таблицу заново и сверяет с файлом на диске, поэтому она
|
|
663
|
+
же ловит и забытую пересборку. Убрать отчёт из git совсем — шаг 5 плана
|
|
664
|
+
(`PLAN.md` §5).
|
|
665
|
+
|
|
666
|
+
### 5. Проверка в CI и перед коммитом
|
|
667
|
+
|
|
668
|
+
```bash
|
|
669
|
+
pnpm run test:sizes # 0 — таблица сходится с историей
|
|
670
|
+
pnpm exec size check # 0 — ни одно изменение не прошло мимо колонок
|
|
671
|
+
pnpm exec size doctor # 0 — делать нечего; иначе первый по важности код
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
`size check` отвечает на другой вопрос, чем сама команда `size`: та говорит
|
|
675
|
+
«таблица совпадает с историей», а эта — «история вся посчитана»: каждый путь,
|
|
676
|
+
который трогали коммиты, должен быть либо колонкой, либо объявленным исключением
|
|
677
|
+
(`skip` и сам файл отчёта), иначе это **код 1** со списком путей, коммитом,
|
|
678
|
+
который путь завёл, и командой починки. Отчёт при этом не обязан лежать в git —
|
|
679
|
+
полнота и есть та проверка, которой заменяют «артефакт ↔ история».
|
|
680
|
+
Если сомнение вызывает один коммит, `pnpm exec size explain <коммит>` объяснит,
|
|
681
|
+
почему строки нет: тронут только отчёт, числа не сдвинулись, коммит мимо колонок
|
|
682
|
+
или слияние скрыто настройкой — с уликами и починкой, где она есть. Коммит можно
|
|
683
|
+
назвать так, как его зовёт git: `HEAD`, `HEAD~1`, имя ветки или тега, полный sha
|
|
684
|
+
или его начало. Если имя ведёт на коммит вне истории отчёта (другая ветка),
|
|
685
|
+
инструмент скажет именно это и назовёт его sha — а не «нет такого коммита».
|
|
686
|
+
|
|
687
|
+
`size doctor` собирает всю диагностику в один ответ: окружение и его влияние на
|
|
688
|
+
числа (настройки машины на числа не влияют — движок закрепляет их на границе
|
|
689
|
+
вызова), состояние необязательных зависимостей и что оно значит для точности,
|
|
690
|
+
годность настроек и полноту покрытия. Отвечает он теми же кусками, что и
|
|
691
|
+
остальные команды: блок покрытия — это ровно ответ `size check`, а не второй
|
|
692
|
+
расчёт. Код выхода — первый по важности, а не «что-то нашлось»: `2` настройки
|
|
693
|
+
нечитаемы (читать больше нечего), `3` история обрезана, `1` покрытие неполно,
|
|
694
|
+
`4` число приближённо, `0` делать нечего. Датчик, о котором настройки молчат,
|
|
695
|
+
назван ненужным, а не отсутствующим, и не загружается: словарь весит мегабайты,
|
|
696
|
+
а платить за строку ответа, которой у чисел не было, нечем.
|
|
697
|
+
|
|
698
|
+
В шаблонный CI (`templates/ci.yml`) полнота намеренно **не** входит: колонки в
|
|
699
|
+
шаблоне — пример, и на проекте, где колонки ещё не подобраны, такая проверка была
|
|
700
|
+
бы красной не по делу. Когда колонки обрисуют проект, её добавляют одной строкой
|
|
701
|
+
(`pnpm exec size check`).
|
|
702
|
+
|
|
703
|
+
Готовая строка для CI: `pnpm run test:sizes` — больше ничего не нужно: проверка —
|
|
704
|
+
это и есть команда `size`, своего набора тестов потребителю ставить не надо.
|
|
705
|
+
Подсказка `--init` говорит то же самое: проверка — команда пакета, своих файлов в
|
|
706
|
+
проект она не приносит.
|
|
707
|
+
|
|
708
|
+
В поставке лежит и готовое описание этой проверки: `templates/ci.yml` из пакета
|
|
709
|
+
(`node_modules/@vernikr/size-report/templates/ci.yml`) кладётся в
|
|
710
|
+
`.github/workflows/size-report.yml` без правок — сборка таблицы, сверка с файлом
|
|
711
|
+
на диске, два снимка чисел (обычный и в среде без настроек git) и их сравнение.
|
|
712
|
+
Секретов оно не требует. Для `npm`/`yarn` в самом файле сказано, какие две строки
|
|
713
|
+
заменить. Рядом — `templates/size-report.config.json`, черновик настроек в дополнение
|
|
714
|
+
к `pnpm exec size --init`: колонки в нём примерные (`README.md`, `package.json`),
|
|
715
|
+
они есть почти в любом проекте, поэтому первый отчёт собирается сразу.
|
|
716
|
+
|
|
717
|
+
Свой CI у пакета — `.github/workflows/ci.yml`: он гоняет у себя тот же список
|
|
718
|
+
команд, что описан ниже, и его можно взять за образец для шага потребителя.
|
|
719
|
+
|
|
720
|
+
| Код | Что случилось | Что делать |
|
|
721
|
+
|---|---|---|
|
|
722
|
+
| 0 | всё сходится | ничего |
|
|
723
|
+
| 1 | таблица разошлась с историей (или правка на диске не закоммичена); у `size check` — путь истории не отслеживается и не исключён | `pnpm run sizes` и закоммитить таблицу; для `check` — дописать путь колонкой или в `skip` |
|
|
724
|
+
| 2 | что-то в вызове или в проекте — **командная строка** (незнакомый ключ, ключ без значения, повтор ключа, два режима сразу, лишнее слово, команда и режим, неизвестная команда, несовместимый ключ, нет ответа в JSON, два ответа сразу, нет коммита); **настройки и проект** (нет файла настроек, настройки не разобраны, настройки неверны, нет git, не git-репозиторий, конфиг уже есть); **история** (нет такого коммита, коммит назван неточно, коммит вне истории); **хук** (чужой хук, чужой core.hooksPath, нечем звать инструмент); **измерение** (файл не JavaScript, минификатор не разобрал) | текст отказа называет причину и готовую команду — и она выполнима: это сторожит `test/refusals.test.js` |
|
|
725
|
+
| 3 | неполная история (clone с `--depth`) | полный клон: `git fetch --unshallow` |
|
|
726
|
+
| 4 | нет датчика | `minify.engine: "esbuild"`, а минификатора нет: числа получены упрощением. Отчёт собран, причина и починка — в тексте |
|
|
727
|
+
| 5 | внутренняя ошибка | это дефект инструмента: текст нужен нам, см. «Ловушки» ниже |
|
|
728
|
+
|
|
729
|
+
### 6. Отчёт обновляется сам после коммита (по желанию)
|
|
730
|
+
|
|
731
|
+
```bash
|
|
732
|
+
pnpm exec size install-hook # поставить post-commit и post-merge
|
|
733
|
+
pnpm exec size uninstall-hook # снять и вернуть проект к прежнему поведению
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
Хуки ставятся **только этой командой**: ни один обычный запуск их не создаёт и
|
|
737
|
+
проект до неё не меняется (файлы живут в `.git`, `git status` их не видит). После
|
|
738
|
+
каждого коммита и слияния отчёт пересобирается, а если он лежит в git — ложится
|
|
739
|
+
отдельным коммитом с подписью `chore(report): отчёт пересобран после <sha>`.
|
|
740
|
+
Коммитится только путь отчёта: чужой индекс и незакоммиченная работа не тронуты.
|
|
741
|
+
Слияние обрабатывается тем же входом, что обычный коммит, но другим файлом —
|
|
742
|
+
`post-merge`: git создаёт коммит слияния сам и `post-commit` при этом не зовёт.
|
|
743
|
+
|
|
744
|
+
Зацикливания нет по устройству, а не по флагу: коммит отчёта собирается
|
|
745
|
+
плумбингом (`commit-tree` — хуков не зовёт), и сам отчёт строки не получает.
|
|
746
|
+
Выключается автоматика двумя способами — `"hooks": {"enabled": false}` в
|
|
747
|
+
настройках (хук остаётся, но молчит) или `size uninstall-hook`; в окружениях, где
|
|
748
|
+
обновлять отчёт не нужно (CI, чужая машина, зависимости не поставлены), хук молчит
|
|
749
|
+
сам и ничего не пишет в вывод коммита. Что он делает и чем кончился последний
|
|
750
|
+
запуск, видно в `pnpm exec size doctor`; отказ инструмента коммит не роняет —
|
|
751
|
+
причина едет одной строкой и остаётся в записи о запуске.
|
|
752
|
+
|
|
753
|
+
### 7. Ловушки, найденные этой же инструкцией
|
|
754
|
+
|
|
755
|
+
Две из них найдены прогоном и уже закрыты — они оставлены здесь как объяснение
|
|
756
|
+
поведения, а не как обходные пути:
|
|
757
|
+
|
|
758
|
+
- **Модуль в расширении `.js`** (`import`/`export` в `.js` — обычное дело в
|
|
759
|
+
проектах с бандлером) измеряется как любой другой файл, с `type: module` в
|
|
760
|
+
манифесте или без него: гард разбирает результат и как скрипт, и как модуль.
|
|
761
|
+
Раньше он пробовал только скрипт и падал кодом 5 на самом `export`, обвиняя
|
|
762
|
+
стриппер; сегодня это невозможно, и правки в настройках не требуются
|
|
763
|
+
(`REFACTOR.md` R-4.6);
|
|
764
|
+
- **Не JavaScript в графе** (разметка или типы прямо в `.js`) — это код 2 и
|
|
765
|
+
отказ, который называет причину и что править. Причина берётся с того способа,
|
|
766
|
+
которым файл считали: при `minify.engine: "esbuild"` отказ называет минификатор
|
|
767
|
+
и даёт два выхода (расширению — упрощение в `minify.ext` или способ `strip`), а
|
|
768
|
+
при упрощении — `minify.guard`. Стеком такой случай не выглядит ни там, ни там;
|
|
769
|
+
- **Минификатора нет** (установка без необязательных зависимостей, платформа без
|
|
770
|
+
`esbuild`) — метрика честно отступает к упрощению: числа те же, что у `strip`,
|
|
771
|
+
способ говорит об этом словами, а прогон отдаёт **код 4** с готовой починкой.
|
|
772
|
+
Проверить это без переустановки можно окружением `SIZE_REPORT_NO_OPTIONAL=1` —
|
|
773
|
+
тем же приёмом это делает `test/minify.test.js`;
|
|
774
|
+
- **Разбор модуля — рабочий поток, поднятый один раз на прогон** (`REFACTOR.md`
|
|
775
|
+
R-5.4): сам разбор стоит ~0,1 мс, а платится за него стартовой ценой потока
|
|
776
|
+
(≈ 54 мс) — и только если в измеряемых файлах вообще есть модули. Отступление
|
|
777
|
+
к `node --check` (≈ 86 мс на клетку) осталось на случай, когда файла потока нет
|
|
778
|
+
в упаковке, поток не ответил или в Node нет модулей vm;
|
|
779
|
+
- **Новый файл-колонка должен быть закоммичен** до запуска: иначе проверка
|
|
780
|
+
состояния скажет «не совпало с деревом коммита» (сначала `git add` + коммит,
|
|
781
|
+
потом `pnpm run sizes`);
|
|
782
|
+
- **Доковая правка — тоже правка.** Коммит, тронувший `WORKLOG.md` или любой
|
|
783
|
+
файл-колонку, получает в таблице строку, поэтому после него таблицу собирают
|
|
784
|
+
заново — иначе проверка говорит «расходится с историей git» и называет строку.
|
|
785
|
+
Незакоммиченная правка таблицу не двигает («сейчас» берётся из коммита), поэтому
|
|
786
|
+
сборка не ломается от того, что рядом с ней правят доки.
|
|
787
|
+
- **`--init` не правит `.gitignore`** (`REFACTOR.md` R-4.8) — добавьте отчёты
|
|
788
|
+
руками, если им не место в истории.
|
|
789
|
+
|
|
790
|
+
Проверено не на словах: раздел пройден покомандно на свежем репозитории (три
|
|
791
|
+
коммита, ESM в `src/`) — протокол и найденные расхождения в `WORKLOG.md` §16, а
|
|
792
|
+
пути, которые README называет своими, сверены с деревом. За этим следит сторож
|
|
793
|
+
документации, и он падает вместе с документом, а не по желанию (`REFACTOR.md`
|
|
794
|
+
R-4.1): пути, таблица файлов, зовы и ключи инструкций, числа проверок и цели по
|
|
795
|
+
времени, ссылки на разделы и пин установки проверяются машинно. Формулировки,
|
|
796
|
+
смысл и обещания о будущем машиной не проверяются — их держит человек.
|
|
797
|
+
|
|
798
|
+
### 8. Если в проекте уже лежит копия инструмента
|
|
799
|
+
|
|
800
|
+
Порядок выше — для проекта, который подключает инструмент впервые. Когда копия
|
|
801
|
+
уже лежит (свои `size-table.js` и его тесты), шаги идут в другом порядке; ниже —
|
|
802
|
+
тот, которым переезжал `safe-resets` (`WORKLOG.md` §18):
|
|
803
|
+
|
|
804
|
+
1. **Установить, не удаляя копию** — две реализации какое-то время сосуществуют,
|
|
805
|
+
и это даёт бесплатную сверку на одном дереве: команда пакета с конфигом проекта
|
|
806
|
+
обязана собрать тот же артефакт байт в байт (у `safe-resets` — 225 673 Б,
|
|
807
|
+
sha256 `1bdb27e1…`). Не совпало — дальше не идём.
|
|
808
|
+
2. **Перевести команды проекта на пакет:** `"test:sizes": "size"`,
|
|
809
|
+
`"sizes": "size --write"`.
|
|
810
|
+
3. **Удалить копию** — и инструмент, и его тест: те же утверждения проверяет
|
|
811
|
+
набор пакета, а в проекте остаётся одна команда. Если тест звался из общего
|
|
812
|
+
раннера, шаг раннера становится одним и зовёт команду пакета, а не файл
|
|
813
|
+
проекта (в `safe-resets` путь берётся из манифеста установленного пакета,
|
|
814
|
+
чтобы шаг не знал внутренних имён файлов).
|
|
815
|
+
4. **Убрать колонки удалённых файлов из настроек** и пересобрать артефакт
|
|
816
|
+
**отдельным коммитом**: коммиты, трогавшие только эти файлы, без них не
|
|
817
|
+
двигают ни одного числа, а такие коммиты строк не получают (у `safe-resets`
|
|
818
|
+
95 × 27 → 91 × 25).
|
|
819
|
+
5. **Почистить документацию проекта:** ссылки на файлы инструмента заменяются
|
|
820
|
+
именем пакета и его командами, а описание внутренностей (стриппер, чтение
|
|
821
|
+
истории пачкой, вёрстка) из доков проекта уходит в доки пакета — иначе их две
|
|
822
|
+
копии и они разойдутся.
|
|
823
|
+
|
|
824
|
+
Доступа к пакету не требуется ни локально, ни в CI — репозиторий публичный (§1),
|
|
825
|
+
поэтому шага с ключом в этом порядке нет.
|
|
826
|
+
|
|
827
|
+
Что при этом теряется: проверки, которые сверяли настройки проекта с ожиданиями
|
|
828
|
+
инструмента, отдельным набором больше не идут. Большую часть закрывает сама
|
|
829
|
+
команда (чужой ключ или незнакомая метрика в конфиге — отказ с объяснением, файл
|
|
830
|
+
таблицы не может быть колонкой), но _содержимое_ подписи (заголовок и команда
|
|
831
|
+
починки взяты из конфига) не проверяет никто: если это важно, это одна проверка
|
|
832
|
+
поверх `--data` в проекте.
|
|
833
|
+
|
|
834
|
+
## Для ИИ-агента
|
|
835
|
+
|
|
836
|
+
- `size check --json` — готово ли всё: какая часть истории покрыта, какие пути
|
|
837
|
+
мимо колонок (с коммитом-первопричиной) и какие коммиты выпали без строки.
|
|
838
|
+
- `size explain <коммит> --json` — почему у конкретного коммита нет строки: причина,
|
|
839
|
+
тронутые файлы (колонки, исключённые, непокрытые) и готовая починка. Коммит —
|
|
840
|
+
именем ревизии (`HEAD`, ветка, тег), полным sha или его началом.
|
|
841
|
+
- `size measure --json` — данные без вёрстки: строки, числа, суммы. Сегодня это
|
|
842
|
+
`--json` (прежняя форма, заморожена эталоном) и `--data` (контракт страницы).
|
|
843
|
+
- `--json` — форма ответа, а не отдельный режим, и правило у него одно: ответ
|
|
844
|
+
бывает ровно у четырёх вызовов. Без команды это прежняя форма данных
|
|
845
|
+
(заморожена эталоном паритета), у `check`, `explain` и `doctor` — их ответ.
|
|
846
|
+
У команды без ответа и рядом с режимом (`--write`, `--data`, `--page`, `--init`)
|
|
847
|
+
он отказ, а не тишина: просить JSON там, где его не бывает, — ошибка вызова.
|
|
848
|
+
- `size doctor --json` — вся диагностика одним ответом: окружение, зависимости,
|
|
849
|
+
настройки, покрытие и находки с уровнем (`action` — делать, `note` — знать).
|
|
850
|
+
- Коды выхода: `0` всё хорошо · `1` расхождение с историей или неполнота ·
|
|
851
|
+
`2` настройки, окружение, неизвестное или лишнее слово, два режима сразу ·
|
|
852
|
+
`3` неполная история · `4` нет датчика · `5` внутренняя ошибка (таблица —
|
|
853
|
+
`PLAN.md` §4.1). Действуют уже сейчас: отказ — это код и одна строка с готовой
|
|
854
|
+
командой починки, без стека. `--help` печатает и то, и другое.
|
|
855
|
+
- Прогонов два, и оба названы: `pnpm test` — быстрый (каждая правка), `pnpm test:all` —
|
|
856
|
+
полный (выкладка и CI); что в каком и почему — `tools/suites.js`, печатает числа и
|
|
857
|
+
стоимости сам прогон.
|
|
858
|
+
- Разбор аргументов один на входе и до чтения проекта: режим либо один, либо
|
|
859
|
+
отказ с обоими названными; команда и режим вместе не работают; ключ без
|
|
860
|
+
значения и ключ, названный дважды, — такой же отказ. Поэтому зов, который
|
|
861
|
+
инструмент не понял, нельзя спутать с исправным прогоном: вместо нуля придёт
|
|
862
|
+
код 2 и готовая команда.
|
|
863
|
+
|
|
864
|
+
## Ловушки, на которых стоит проверять движок
|
|
865
|
+
|
|
866
|
+
Фикстура (`fixtures/synthetic/history.bundle`) — это история, в которой
|
|
867
|
+
собрано то, на чём ломаются такие инструменты: `//` внутри строки, регексп с
|
|
868
|
+
экранированным слэшем, шаблон с выражением, `.mjs` с `export`, не-английское имя
|
|
869
|
+
файла, CRLF, переименование файла, коммит «только отчёт», смешанный коммит,
|
|
870
|
+
слияние с правкой разрешения конфликта, замена символа без изменения объёма,
|
|
871
|
+
удаление и возврат файла, пустой файл, незнакомое расширение. Полный список — в
|
|
872
|
+
`fixtures/synthetic/README.md`.
|
|
873
|
+
|
|
874
|
+
```bash
|
|
875
|
+
pnpm test # быстрый прогон (каждая правка): паритет на фикстуре,
|
|
876
|
+
# контракт данных и страница, сторож документации и выпуска
|
|
877
|
+
pnpm test:all # полный прогон (выкладка и CI): то же плюс интеграционные —
|
|
878
|
+
# сборка на дисках, сверка с деревом, хуки, метрики
|
|
879
|
+
pnpm run suites:measure # перемерить стоимости файлов набора
|
|
880
|
+
pnpm run parity:live # паритет с живым проектом на клоне, две среды
|
|
881
|
+
node bin/size.js --data # контракт данных: страница и агент
|
|
882
|
+
node bin/size.js --page # минимальная страница отчёта
|
|
883
|
+
node bin/size.js --help # справка и коды выхода
|
|
884
|
+
pnpm run parity # переснять эталон паритета: проект и ревизия — из манифеста
|
|
885
|
+
pnpm run fixture # пересобрать фикстуру и её эталон
|
|
886
|
+
pnpm run pack:check # работает ли движок из собранного тарболла
|
|
887
|
+
pnpm run check:standards # эталоны воспроизводятся, а дерево остаётся чистым
|
|
888
|
+
git clone fixtures/synthetic/history.bundle /tmp/size-report-fixture
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
Открытые блокеры и известные пробелы — в `BLOCKERS.md`; там же таблица настроек,
|
|
892
|
+
которые проверены и оказались инертными (чтобы не проверять их заново).
|