@vernikr/size-report 2.0.2 → 2.2.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/CHANGELOG.md +98 -0
- package/README.md +79 -36
- package/package.json +2 -2
- package/src/css.js +1 -1
- package/src/data.js +5 -0
- package/src/locales.js +12 -10
- package/src/page/app.css +71 -38
- package/src/page/app.js +30 -5
- package/src/page/build.js +5 -7
- package/src/page/panel.js +91 -46
- package/src/page/state.js +7 -0
- package/src/project.js +26 -0
- package/src/size-table.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,104 @@
|
|
|
18
18
|
где за основу взяты `fixtures/synthetic/config.json`, метрики — `raw`, `min`, `tok`, а
|
|
19
19
|
способ минификации — `strip` или `esbuild`.
|
|
20
20
|
|
|
21
|
+
## 2.2.0 — 2026-09-15
|
|
22
|
+
|
|
23
|
+
Дерево страницы — дерево проекта: видно всё, числа есть у измеряемых.
|
|
24
|
+
|
|
25
|
+
- **В дереве было видно только то, что стало колонками.** В проекте со 147 файлами
|
|
26
|
+
и выведёнными настройками колонок двенадцать — дерево и показывало двенадцать
|
|
27
|
+
листьев, а про остальные молчало: читатель видел не дерево проекта, а список
|
|
28
|
+
измеренного, и решить, что файлы потерялись, было проще всего. Теперь в контракте
|
|
29
|
+
(`--data`) едет **каталог** — все пути, которые видит git (`ls-files`), — и дерево
|
|
30
|
+
строится по нему.
|
|
31
|
+
- **Числа — только у колонок, и это видно, а не подразумевается.** Лист вне отчёта
|
|
32
|
+
стоит на своём месте, но подписью без галочки, а причина — во всплывающей строке,
|
|
33
|
+
и причин две: `rule` — такой файл колонкой быть не может (сам отчёт, замок
|
|
34
|
+
зависимостей, собранное, слишком крупный или формат без текста), `choice` — мог бы,
|
|
35
|
+
но в набор колонок не выбран (его задают настройки). Знак причины движок ставит
|
|
36
|
+
**теми же** правилами, по которым выбирает колонки, — иначе подсказка говорила бы
|
|
37
|
+
одно, а выбор делал другое. Сам отчёт в дереве назван **всегда** — и пока не
|
|
38
|
+
собран, и когда он вне git: его отслеживаемость это свойство момента, и зависеть
|
|
39
|
+
от неё отчёт не должен (иначе первая же пересборка в свежем клоне даёт другие
|
|
40
|
+
байты, и хук коммитит отчёт второй раз на пустом месте).
|
|
41
|
+
- **Счётчик папки со смешанным составом — доля** («2/5»: два файла в отчёте из
|
|
42
|
+
пяти в папке), а целиком измеряемая папка считает как раньше («5»). Папка, где
|
|
43
|
+
измеряемых нет вовсе, галочки не получает: включать в ней нечего, но на месте она
|
|
44
|
+
остаётся — дерево не должно врать о том, что в проекте есть.
|
|
45
|
+
- **Цена названа:** набор колонок этим не меняется, поэтому прогон не подорожал.
|
|
46
|
+
Замер на этом же проекте (162 колонки × 79 строк против 12): с токенами 16,1 с
|
|
47
|
+
вместо 2,4 с, а на живой истории полный набор ещё и отказывает (перенос состояния
|
|
48
|
+
теряет правку пути, которого нет на HEAD) — поэтому «мерить всё» решением не стало.
|
|
49
|
+
|
|
50
|
+
### Что изменится в числах
|
|
51
|
+
|
|
52
|
+
**Ничего.** Числа считает тот же движок теми же датчиками: выпуск про то, что видно в
|
|
53
|
+
дереве панели, а не про измерение. Таблица та же, что у 2.1.0, до последней клетки.
|
|
54
|
+
|
|
55
|
+
| Файл | raw | min со strip | min с esbuild | tok |
|
|
56
|
+
|---|---|---|---|---|
|
|
57
|
+
| code.js | 735 | 276 | 185 | 168 |
|
|
58
|
+
| modern.js | 246 | 51 | 45 | 44 |
|
|
59
|
+
| config.mjs | 172 | 45 | 40 | 40 |
|
|
60
|
+
| заметки.md | 306 | 303 | 303 | 53 |
|
|
61
|
+
| crlf.txt | 63 | 60 | 60 | 10 |
|
|
62
|
+
| package.json | 87 | 69 | 69 | 34 |
|
|
63
|
+
| style.css | 156 | 55 | 43 | 38 |
|
|
64
|
+
| table.toml | 300 | 299 | 299 | 52 |
|
|
65
|
+
| empty.js | 0 | 0 | 0 | 0 |
|
|
66
|
+
| WORKLOG.md | 446 | 439 | 439 | 101 |
|
|
67
|
+
| **ИТОГО** | **2511** | **1597** | **1483** | **540** |
|
|
68
|
+
|
|
69
|
+
## 2.1.0 — 2026-09-15
|
|
70
|
+
|
|
71
|
+
Вид страницы на десктопе: числам — вся оставшаяся высота, панели — своя прокрутка.
|
|
72
|
+
|
|
73
|
+
- **Под таблицей была пустота, а список файлов задавал высоту страницы.** На широком
|
|
74
|
+
экране оба столбца шли каждый со своей высотой (`align-items: start`), поэтому
|
|
75
|
+
высота страницы равнялась тому, что вышло у панели: замер до правки при окне
|
|
76
|
+
1440×900 — панель 883 px, страница 1097 при окне 900, под таблицей 195 px пустоты,
|
|
77
|
+
а нижние файлы дерева — за нижним краем окна (список при этом ещё и резался своим
|
|
78
|
+
потолком в 62vh). Теперь страница целиком укладывается в окно: строки сетки
|
|
79
|
+
названы по предмету, тянется одна рабочая — таблица берёт всю оставшуюся высоту
|
|
80
|
+
(1440×900: 1060 × 735 вместо 602), панель — не больше неё и прокручивается сама
|
|
81
|
+
(содержимое 804 при высоте 785). Список файлов на широком экране больше не режется:
|
|
82
|
+
все 23 подписи дерева видны сразу.
|
|
83
|
+
- **Клик по галочке возвращал список к началу.** Панель рисуется заново, и вместе с
|
|
84
|
+
ней терялось место, до которого докурили: до нижних файлов дерева было не
|
|
85
|
+
добраться. Прокрутка панели и списка теперь часть вида — запоминается перед
|
|
86
|
+
пересборкой и ставится обратно (подтверждено в Chrome: при окне 1440×500
|
|
87
|
+
`panel.scrollTop` = 421 до клика и 421 после).
|
|
88
|
+
- **Строка категорий липнет** к верху панели, пока листается дерево. Чтобы под
|
|
89
|
+
липкой строкой не читались проезжающие файлы, верхний отступ панели переехал в
|
|
90
|
+
первое поле: прокручиваемое видно и в отступе прокрутки (в полосе 13 px читались
|
|
91
|
+
`parity/` и `data.json` — теперь там сама строка).
|
|
92
|
+
- **Расшифровка под деревом убрана**, а шрифт подписей файлов стал как у чисел
|
|
93
|
+
таблицы (12,5 px): расшифровка отодвигала числа, а её смысл стоит у того, что
|
|
94
|
+
объясняет — цвет дельты называет знак числа, способ и точность стоят под
|
|
95
|
+
переключателями метрик, знак пропуска — в подсказке клетки.
|
|
96
|
+
- **Граница широкой раскладки — 899 px.** При ровно 900 px обе половины оформления
|
|
97
|
+
применялись к одной странице, и от «узкой» в «широкой» оставался потолок высоты
|
|
98
|
+
таблицы: те же пустые 179 px под ней на одном размере окна.
|
|
99
|
+
|
|
100
|
+
### Что изменится в числах
|
|
101
|
+
|
|
102
|
+
**Ничего.** Правка — про вид страницы и прокрутку; движок тот же, что у 2.0.2, а
|
|
103
|
+
страница получает от него готовые числа.
|
|
104
|
+
|
|
105
|
+
| Файл | raw | min со strip | min с esbuild | tok |
|
|
106
|
+
|---|---|---|---|---|
|
|
107
|
+
| code.js | 735 | 276 | 185 | 168 |
|
|
108
|
+
| modern.js | 246 | 51 | 45 | 44 |
|
|
109
|
+
| config.mjs | 172 | 45 | 40 | 40 |
|
|
110
|
+
| заметки.md | 306 | 303 | 303 | 53 |
|
|
111
|
+
| crlf.txt | 63 | 60 | 60 | 10 |
|
|
112
|
+
| package.json | 87 | 69 | 69 | 34 |
|
|
113
|
+
| style.css | 156 | 55 | 43 | 38 |
|
|
114
|
+
| table.toml | 300 | 299 | 299 | 52 |
|
|
115
|
+
| empty.js | 0 | 0 | 0 | 0 |
|
|
116
|
+
| WORKLOG.md | 446 | 439 | 439 | 101 |
|
|
117
|
+
| **ИТОГО** | **2511** | **1597** | **1483** | **540** |
|
|
118
|
+
|
|
21
119
|
## 2.0.2 — 2026-09-15
|
|
22
120
|
|
|
23
121
|
Инструкция перестала обещать лишний ручной шаг и назвала платформенную разницу.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
## Статус
|
|
12
12
|
|
|
13
|
-
**Выпуск 2.0
|
|
13
|
+
**Выпуск 2.2.0 (2026-09-15).** Инструмент живёт отдельным пакетом: имя в
|
|
14
14
|
реестре — `@vernikr/size-report` (публикуется тегом из CI, без секрета). Настроек
|
|
15
15
|
проект может не заводить вовсе: без файла инструмент выводит их из самого проекта и
|
|
16
16
|
говорит об этом строкой, а `--init` закрепляет выведенное файлом (чем этот шаг
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
в числах»:
|
|
23
23
|
таблица чисел в нём не пересказ, а замер на фикстуре, который сверяется с живым
|
|
24
24
|
прогоном (`test/changelog.test.js`). Числа этим выпуском не меняются — правки
|
|
25
|
-
лежат в
|
|
26
|
-
остаётся той
|
|
25
|
+
лежат в виде страницы отчёта на десктопе и в том, что видно в её дереве; `schema: 1`
|
|
26
|
+
данных, замороженная 1.0.0, остаётся той же (выпуск добавил поле, а не поменял
|
|
27
|
+
смысл прежних).
|
|
27
28
|
|
|
28
29
|
**Шаг 1 плана пройден — перенос без изменения поведения.** Команда —
|
|
29
30
|
`bin/size.js`, точка входа пакета — `src/size-table.js` (только реэкспорт),
|
|
@@ -76,7 +77,10 @@
|
|
|
76
77
|
вывод: страница показывает то, что сказал движок, и своего правила точности не
|
|
77
78
|
заводит. Панель страницы — дерево файлов по папкам, с переключателем у каждой
|
|
78
79
|
папки на всё поддерево; выбор читателя переживает перезаход и передаётся
|
|
79
|
-
ссылкой — адрес страницы и есть ссылка.
|
|
80
|
+
ссылкой — адрес страницы и есть ссылка. В контракте же едет **каталог проекта** —
|
|
81
|
+
все пути, которые видит git: дерево страницы — дерево проекта, а числа есть только
|
|
82
|
+
у тех файлов, что стали колонками (чем это отличается от прежнего дерева, где
|
|
83
|
+
были одни колонки, — `CHANGELOG.md` 2.2.0).
|
|
80
84
|
Минификация и токены сделаны первыми срезами шагов 3 и 4 (ниже).
|
|
81
85
|
Разбиение движка по файлам сделано (пункт D1 плана, R-1.3 `REFACTOR.md`).
|
|
82
86
|
Источник инструмента — скрипт `size-table.js` (1145 строк) в проекте
|
|
@@ -219,10 +223,10 @@
|
|
|
219
223
|
|
|
220
224
|
| Прогон | Команда | Проверок |
|
|
221
225
|
|---|---|---|
|
|
222
|
-
| Быстрый — каждая правка | `pnpm test` | **
|
|
223
|
-
| Полный — выкладка и CI | `pnpm test:all` | **
|
|
226
|
+
| Быстрый — каждая правка | `pnpm test` | **68 из 172** |
|
|
227
|
+
| Полный — выкладка и CI | `pnpm test:all` | **172** |
|
|
224
228
|
|
|
225
|
-
Ни одна проверка не потеряна и не ослаблена: полный прогон запускает все
|
|
229
|
+
Ни одна проверка не потеряна и не ослаблена: полный прогон запускает все 172 теми же
|
|
226
230
|
файлами, а быстрый берёт их часть. Умолчание — полный: файл становится быстрым только
|
|
227
231
|
явно и с причиной, поэтому новое дорогое не может тихо уехать в быстрый. Стерегут это
|
|
228
232
|
объявление `test/suites.test.js` (полнота классификации и причина у каждого файла) и
|
|
@@ -309,30 +313,45 @@ Trusted Publisher в настройках пакета), права **publish**
|
|
|
309
313
|
один набор стилей таблицы на оба вывода (`src/table.css`) — странице достались
|
|
310
314
|
липкие шапка и колонка коммита, которые раньше были только у статического
|
|
311
315
|
артефакта, и она больше не уезжает вбок в узком окне (до правки — 1518px при
|
|
312
|
-
окне 620). Добавились
|
|
313
|
-
|
|
316
|
+
окне 620). Добавились состояния «нечего показать» (сняты все метрики или все
|
|
317
|
+
файлы) и переключатели, доступные с клавиатуры. Цвет
|
|
314
318
|
deльт задан один раз и по артефакту: рост зелёный, спад красный — смена это две
|
|
315
319
|
строки в `src/table.css` плюс пересборка эталона артефакта, больше цвета нигде нет.
|
|
316
320
|
|
|
317
|
-
**Левая панель страницы — дерево
|
|
318
|
-
|
|
321
|
+
**Левая панель страницы — дерево файлов проекта** (`REFACTOR.md` R-2.4, каталог
|
|
322
|
+
путей — `CHANGELOG.md` 2.2.0). Дерево строится по путям проекта, а не по одним
|
|
323
|
+
колонкам, поэтому в нём видно и то, что в отчёт не попало: такой лист — подпись без
|
|
324
|
+
галочки с причиной во всплывающей строке («колонкой быть не может» — правило пакета
|
|
325
|
+
— или «в набор колонок не попал» — выбор проекта), а счётчик папки со смешанным
|
|
326
|
+
составом написан долей («2/5»: два файла в отчёте из пяти в папке). Сам отчёт в
|
|
327
|
+
дереве назван всегда: его отслеживаемость — свойство момента, и от неё содержимое
|
|
328
|
+
страницы не зависит. У
|
|
319
329
|
папки три состояния — все её файлы включены, часть, ни одного, — и переключатель
|
|
320
330
|
папки ведёт за собой всё поддерево; рядом стоит число файлов. Своего состояния у
|
|
321
331
|
папки и у быстрой кнопки категории нет: обе переставляют галочки файлов, поэтому
|
|
322
|
-
дерево, кнопки и таблица не могут разойтись. Список файлов
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
дерево, кнопки и таблица не могут разойтись. Список файлов длиннее панели
|
|
333
|
+
прокручивается, а не выталкивает таблицу, — и прокрутка эта одна: на широком
|
|
334
|
+
экране листается панель целиком (дерево в ней видно целиком: 23 подписи из 23 при
|
|
335
|
+
окне 1440×900), а на узком — сам список, потому что там панель растёт вместе со
|
|
336
|
+
страницей.
|
|
337
|
+
|
|
338
|
+
**На широком экране панель стоит слева от таблицы и не вытесняет числа**
|
|
339
|
+
(от 900 px, `src/page/app.css`). Это не украшение: на десктопе бокового места
|
|
340
|
+
много, а вертикального мало — переключатели, дерево и числа видны одновременно, и
|
|
341
|
+
ни прокрутка чисел, ни прокрутка дерева не уводит управление за верх экрана.
|
|
342
|
+
Раскладка сделана сеткой на `body`: обёртки в разметке нет, потому что страница
|
|
343
|
+
собирается вклейкой глав и форма страницы должна жить в одном месте. Строк у сетки
|
|
344
|
+
пять и они названы по предмету (заголовок, сообщение о ссылке, работа, сообщение
|
|
345
|
+
пустоты, подпись), тянется только рабочая: таблица берёт всю оставшуюся высоту, а
|
|
346
|
+
панель — не больше неё. Цена прежнего поведения была видна глазом: высоту страницы
|
|
347
|
+
задавал список файлов, и под таблицей оставалась пустота (замер до правки при окне
|
|
348
|
+
1440×900: панель 883 px, страница 1097 при окне 900, под таблицей 195 px).
|
|
349
|
+
Измерено в настоящем Chrome после правки: при 1440×900 страница ровно в окно
|
|
350
|
+
(900), панель — колонка 300 px слева (103…888, содержимое 804 — дальше она
|
|
351
|
+
прокручивается), таблица 1060 × 735 на том же верху 103 (до правки 1060 × 602), а
|
|
352
|
+
под ней остаётся только подпись под таблицей (62 px); при 1024×800 — те же 300 и
|
|
353
|
+
таблица 651 × 620; при 899 раскладка снимается и столбцы снова идут друг под
|
|
354
|
+
другом (панель 871, таблица 871 × 442, страница прокручивается — 972).
|
|
336
355
|
Стрежет это проверка на числах таблицы, а не на разметке: выключение папки убирает
|
|
337
356
|
ровно её колонки и ровно её объём из итога (`test/contract.test.js`). Проверено в
|
|
338
357
|
настоящем Chrome: 9 папок до трёх уровней вложенности (`.github/workflows`,
|
|
@@ -340,6 +359,29 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
340
359
|
ни одной ошибки в консоли, `docs/6` после выключения одного своего файла показала
|
|
341
360
|
третье состояние, а после выключения целиком — 52 колонки → 40.
|
|
342
361
|
|
|
362
|
+
**Галочка не отбирает ни прокрутку, ни место у чисел** (правка вида страницы
|
|
363
|
+
2026-09-15, `src/page/app.js`, `src/page/app.css`). Панель рисуется заново после
|
|
364
|
+
каждого переключения, и вместе с ней терялось место, до которого читатель
|
|
365
|
+
долистал: клик по галочке возвращал список к началу, а до нижних файлов дерева так
|
|
366
|
+
и не добирались. Теперь прокрутка панели и списка — часть вида, как галочки: она
|
|
367
|
+
запоминается перед пересборкой и ставится обратно после (фокус возвращается без
|
|
368
|
+
прокрутки — `preventScroll`), а поле «Файлы» больше не режет дерево своим
|
|
369
|
+
потолком в 62vh. Строка категорий («Код», «Документация», «Служебные») в широкой
|
|
370
|
+
раскладке липнет к верху панели — фон у неё тот же, что у панели, поэтому под ней
|
|
371
|
+
не читаются проезжающие файлы; верхний отступ панели для этого переехал в первое
|
|
372
|
+
поле (прокручиваемое видно и в отступе прокрутки — замер: до него в полосе 13 px
|
|
373
|
+
читались «parity/» и «data.json», после — сама строка). Шрифт подписей файлов
|
|
374
|
+
стал как у чисел таблицы (12,5 px), а расшифровка под деревом убрана: она
|
|
375
|
+
отодвигала числа, а её смысл и так стоит у того, что объясняет (знак числа называет
|
|
376
|
+
цвет дельты, способ и точность — под переключателями метрик, знак пропуска —
|
|
377
|
+
в подсказке клетки). Заодно граница широкой раскладки стала 899 px: при ровно
|
|
378
|
+
900 px обе половины оформления применялись к одной странице, и от «узкой» в
|
|
379
|
+
«широкой» оставался потолок высоты таблицы — те же пустые 179 px под ней на одном
|
|
380
|
+
единственном размере окна. Проверено в настоящем Chrome на этой странице: при
|
|
381
|
+
окне 1440×500 (панель прокручивается) и прокрутке до последнего файла
|
|
382
|
+
`panel.scrollTop` = 421 до клика и 421 после, строка категорий стоит на 1 px от
|
|
383
|
+
верхнего края панели, а под ней на всех полосах прокрутки — только её же подписи.
|
|
384
|
+
|
|
343
385
|
**Панель помнит выбор читателя** (`REFACTOR.md` R-2.5). Запись хранится в памяти
|
|
344
386
|
браузера, и она привязана к «паспорту отчёта» — имя инструмента, схема данных,
|
|
345
387
|
путь артефакта, заголовок и метки колонок; в ключ входит отпечаток паспорта,
|
|
@@ -518,12 +560,12 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
518
560
|
| `src/derived.js` | Общий расчёт отчёта: итоги, дельты, клетка, подпись коммита — один на движок и программу страницы |
|
|
519
561
|
| `src/css.js` | Чтение оформления с диска: какие наборы стилей есть и какая у них роль |
|
|
520
562
|
| `src/table.css` | Таблица отчёта: геометрия клеток, липкие шапка и колонка, цвет дельт |
|
|
521
|
-
| `src/page/app.css` | Оформление страницы сверх общей части: панель с деревом файлов (на широком экране — колонка
|
|
522
|
-
| `src/page/state.js` | Состояние страницы: данные отчёта, вид галочек, паспорт записи, память браузера и обмен ссылкой — глава программы страницы |
|
|
563
|
+
| `src/page/app.css` | Оформление страницы сверх общей части: панель с деревом файлов и липкой строкой категорий (на широком экране — колонка слева, страница в окно), состояния пустоты, узкое окно |
|
|
564
|
+
| `src/page/state.js` | Состояние страницы: данные отчёта, вид галочек, указатель «какой путь — какая колонка», паспорт записи, память браузера и обмен ссылкой — глава программы страницы |
|
|
523
565
|
| `src/page/dom.js` | Узлы страницы: мелкие помощники разметки (`appEl`, `appBox`) — одни на панель и таблицу |
|
|
524
|
-
| `src/page/panel.js` | Панель выбора: галочки метрик и файлов, категории, дерево
|
|
566
|
+
| `src/page/panel.js` | Панель выбора: галочки метрик и файлов, категории, дерево путей проекта (файлы вне отчёта — подписью с причиной); перерисовку просит у главы сборки |
|
|
525
567
|
| `src/page/table.js` | Таблица страницы: клетка, подпись коммита, шапка и состояния пустоты — разметка поверх общего расчёта |
|
|
526
|
-
| `src/page/app.js` | Сборка и запуск страницы: таблица целиком, перерисовка по выбору
|
|
568
|
+
| `src/page/app.js` | Сборка и запуск страницы: таблица целиком, перерисовка по выбору читателя (с возвратом фокуса и прокрутки), первая отрисовка и смена якоря; вклеивается в собранную страницу
|
|
527
569
|
| `src/page/build.js` | Сборка страницы: данные, оформление и программа в одном файле без внешних ссылок |
|
|
528
570
|
| `src/git.js` | Единственная граница вызова git: закрепления настроек, блобы пачкой, история, сверка с диском |
|
|
529
571
|
| `src/strip.js` | Снятие балласта: какая форма к какому файлу (расширение, стратегия) и что считать точным числом — вход разбора форм |
|
|
@@ -544,9 +586,9 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
544
586
|
| `bin/postinstall.js` | Установка хука после постановки пакета: ищет проект-потребитель и молчит, если поставить негде |
|
|
545
587
|
| `src/artifact.js` | Отчёт на диске: единственное место, где он превращается в файл (им пользуются и `--write`, и хук); отчёт — самодостаточная страница |
|
|
546
588
|
| `src/journal.js` | Журнал и ссылки: к какому разделу относится коммит и куда ведёт описание |
|
|
547
|
-
| `src/data.js` | Категории файлов и контракт со страницей (`--data`) |
|
|
589
|
+
| `src/data.js` | Категории файлов и контракт со страницей (`--data`): числа, устройство таблицы и каталог путей проекта |
|
|
548
590
|
| `src/config.js` | Настройки проекта-потребителя: умолчания, чтение, проверка |
|
|
549
|
-
| `src/project.js` | Настройки, выведенные из самого проекта (дерево и история): колонки, журнал,
|
|
591
|
+
| `src/project.js` | Настройки, выведенные из самого проекта (дерево и история): колонки, журнал, исключения, каталог путей для дерева страницы. Без файла настроек он и есть настройки; `--init` закрепляет его файлом |
|
|
550
592
|
| `src/locales.js`, `src/refusal.js`, `src/tool.js` | Тексты отчёта; коды выхода и справка; имя и версия пакета |
|
|
551
593
|
| `src/cli.js` | Вход инструмента: разбор строки, чтение проекта и доставка запроса режиму; главный файл пакета |
|
|
552
594
|
| `src/args.js` | Грамматика командной строки: режимы, ключи и команды плюс проверки их сочетаний — отказ называет виновника и готовую команду |
|
|
@@ -571,7 +613,8 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
571
613
|
| `test/refusals-catalog.test.js` | Сторож каталога отказов: у каждого места отказа в исходниках есть пункт, у каждого пункта — объявленный совет, а отказы, отданные другой проверке, ею в самом деле утверждаются (названные файл и строка проверяются) |
|
|
572
614
|
| `test/contract-data.test.js` | Контракт данных: числа против эталона, состав полей против производных, пометки приближения против подписи метрики |
|
|
573
615
|
| `test/contract-derived.test.js` | Производные против чисел артефакта: итоги строки, дельты клетки и дельта итога — на коде, который лежит в дереве |
|
|
574
|
-
| `test/page-view.test.js` | Собранная страница: вклейка без копий расчёта, самодостаточность,
|
|
616
|
+
| `test/page-view.test.js` | Собранная страница: вклейка без копий расчёта, самодостаточность, состояния пустоты, оформление и переключатели |
|
|
617
|
+
| `test/page-tree.test.js` | Дерево файлов панели: папки по путям проекта, три состояния, поддерево, файлы вне отчёта и прокрутка при пересборке |
|
|
575
618
|
| `test/page-choice.test.js` | Память выбора и обмен ссылкой: перезаход, чужой отчёт, чужая и битая запись, смена адреса на открытой странице |
|
|
576
619
|
| `test/module.test.js` | Модуль в расширении `.js`: измеряется без правок настроек; гард стриппера жив (доказано мутацией) и не обвиняет невиновного |
|
|
577
620
|
| `test/guard.test.js` | Разбор модуля: идёт потоком, оба пути дают один вердикт, отступление работает без файла потока, сотни разборов дешевле запуска |
|
|
@@ -643,7 +686,7 @@ pnpm add -D @vernikr/size-report
|
|
|
643
686
|
```
|
|
644
687
|
|
|
645
688
|
Пакет **опубликован в реестре**, и публично: `npm view @vernikr/size-report
|
|
646
|
-
version` отвечает `2.0
|
|
689
|
+
version` отвечает `2.2.0`, `npm access get status @vernikr/size-report` — `public`,
|
|
647
690
|
а анонимный запрос тарболла — код 200. `npm i -D` и `yarn add -D` принимают то же
|
|
648
691
|
имя; ни ключа, ни ссылки на репозиторий не нужно.
|
|
649
692
|
|
|
@@ -651,11 +694,11 @@ version` отвечает `2.0.2`, `npm access get status @vernikr/size-report`
|
|
|
651
694
|
реестра, но остаётся привязанной к ревизии:
|
|
652
695
|
|
|
653
696
|
```bash
|
|
654
|
-
pnpm add -D github:vernikr/size-report#v2.0
|
|
697
|
+
pnpm add -D github:vernikr/size-report#v2.2.0
|
|
655
698
|
```
|
|
656
699
|
|
|
657
700
|
Без сети (или если тянуть из codeload нечем) — тарболл: `pnpm pack` в клоне
|
|
658
|
-
пакета, затем `pnpm add -D ./vernikr-size-report-2.0.
|
|
701
|
+
пакета, затем `pnpm add -D ./vernikr-size-report-2.2.0.tgz`.
|
|
659
702
|
|
|
660
703
|
**Почему тег, а не sha.** Короткий sha pnpm разрешает только через видимые рефы, а
|
|
661
704
|
`git ls-remote` отдаёт одни верхушки веток: пока ревизия — верхушка, короткий sha
|
|
@@ -663,7 +706,7 @@ pnpm add -D github:vernikr/size-report#v2.0.2
|
|
|
663
706
|
<sha> to a commit`. Это не рассуждение, а проба: короткий пин `6530237` ставился,
|
|
664
707
|
пока `main` стоял на нём, и перестал — на следующем же коммите, а тот же sha
|
|
665
708
|
целиком поставился. Имя ветки (`#main`) или тег принимаются оба, но ветка —
|
|
666
|
-
движущаяся цель, а тег постоянен: этот выпуск стоит на теге `v2.0
|
|
709
|
+
движущаяся цель, а тег постоянен: этот выпуск стоит на теге `v2.2.0`, он же и в
|
|
667
710
|
примере (сорок знаков тоже годятся, но их придётся брать глазами из истории).
|
|
668
711
|
|
|
669
712
|
Ревизия в примере — не украшение, а часть утверждения: она закреплена за тем, что
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vernikr/size-report",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"author": "vernikr",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"report"
|
|
67
67
|
],
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@vernikr/size-report": "2.0
|
|
69
|
+
"@vernikr/size-report": "2.1.0",
|
|
70
70
|
"c8": "10",
|
|
71
71
|
"dependency-cruiser": "17",
|
|
72
72
|
"eslint": "^9.18.0",
|
package/src/css.js
CHANGED
|
@@ -10,7 +10,7 @@ import fs from 'node:fs';
|
|
|
10
10
|
* 1. `table.css` — **таблица**: геометрия клеток, липкие шапка и колонка коммита,
|
|
11
11
|
* подпись коммита, цвета дельт.
|
|
12
12
|
* 2. `page/app.css` — оформление страницы **сверх таблицы**: холст, панель
|
|
13
|
-
* выбора,
|
|
13
|
+
* выбора, состояния пустоты и адаптации под узкое окно.
|
|
14
14
|
*
|
|
15
15
|
* Соглашение о цвете дельт задано один раз — в `table.css`: `.up` зелёный, `.down`
|
|
16
16
|
* красный (рост — «больше логики», а не тревога). Второго места у него нет
|
package/src/data.js
CHANGED
|
@@ -3,6 +3,7 @@ import { LOCALES } from './locales.js';
|
|
|
3
3
|
import { metricView } from './metrics.js';
|
|
4
4
|
import { rowHref } from './journal.js';
|
|
5
5
|
import { build, skipLine } from './history.js';
|
|
6
|
+
import { projectTree } from './project.js';
|
|
6
7
|
import { TOOL_PKG } from './tool.js';
|
|
7
8
|
|
|
8
9
|
/* Категории файлов и контракт со страницей: абсолютные значения и устройство
|
|
@@ -66,6 +67,10 @@ export function reportData(cfg, root) {
|
|
|
66
67
|
categories: CATEGORY_ORDER.filter((key) => files.some((f) => f.category === key))
|
|
67
68
|
.map((key) => ({ key: key, label: loc.categories[key] })),
|
|
68
69
|
files: files,
|
|
70
|
+
/* Дерево страницы — дерево проекта: все пути, а не только колонки. Числа есть
|
|
71
|
+
* лишь у колонок, поэтому у каждой записи каталога сказано, чего ей не
|
|
72
|
+
* досталось: `why` пусто — файл измеряется, иначе названа причина. */
|
|
73
|
+
catalog: projectTree(root, cfg.output, files.map((f) => (f.path === null ? f.paths[0] : f.path))),
|
|
69
74
|
rows: rows.map((r) => Object.assign(rowShape(r),
|
|
70
75
|
{ href: rowHref(r.section, r.sha, cfg), values: r.cells })),
|
|
71
76
|
now: state.map((s) => (s === null ? null : s.cells)),
|
package/src/locales.js
CHANGED
|
@@ -19,13 +19,15 @@ export const LOCALES = {
|
|
|
19
19
|
metrics: 'Метрики',
|
|
20
20
|
files: 'Файлы',
|
|
21
21
|
dir: 'все файлы папки {name} ({n})',
|
|
22
|
+
dirNone: 'ни один файл папки {name} в отчёт не попал (в папке {n})',
|
|
23
|
+
/* Причина — у листа: в дереве он подписью, а не галочкой, и без этих слов
|
|
24
|
+
* читатель решил бы, что файл потерялся. */
|
|
25
|
+
notMeasuredRule: 'не измеряется: такой файл колонкой быть не может'
|
|
26
|
+
+ ' (собранный, замок зависимостей, сам отчёт или слишком крупный)',
|
|
27
|
+
notMeasuredChoice: 'не измеряется: в набор колонок отчёта не попал'
|
|
28
|
+
+ ' — набор задают настройки (columns)',
|
|
22
29
|
all: 'все',
|
|
23
30
|
sub: '{tool} {version} · {artifact}',
|
|
24
|
-
legendUp: 'рост',
|
|
25
|
-
legendDown: 'спад',
|
|
26
|
-
legendSame: 'пустая клетка — не менялось',
|
|
27
|
-
legendMiss: '«—» — файла в этой ревизии ещё нет',
|
|
28
|
-
legendApprox: 'подчёркнутое число — приближённое: способ назван под метриками',
|
|
29
31
|
/* Слова точности: подпись метрики говорит про худшее в колонке, а клетки —
|
|
30
32
|
* про своё число. Одно слово на подпись и на подсказку клетки, чтобы не
|
|
31
33
|
* разошлось и не звучало по-разному. */
|
|
@@ -75,13 +77,13 @@ export const LOCALES = {
|
|
|
75
77
|
metrics: 'Metrics',
|
|
76
78
|
files: 'Files',
|
|
77
79
|
dir: 'all files in “{name}” ({n})',
|
|
80
|
+
dirNone: 'no file of “{name}” made it into the report ({n} in it)',
|
|
81
|
+
notMeasuredRule: 'not measured: such a file cannot be a column'
|
|
82
|
+
+ ' (built, a dependency lock, the report itself, or too large)',
|
|
83
|
+
notMeasuredChoice: 'not measured: it is not in the report’s set of columns'
|
|
84
|
+
+ ' — that set comes from the settings (columns)',
|
|
78
85
|
all: 'all',
|
|
79
86
|
sub: '{tool} {version} · {artifact}',
|
|
80
|
-
legendUp: 'growth',
|
|
81
|
-
legendDown: 'fall',
|
|
82
|
-
legendSame: 'an empty cell — no change',
|
|
83
|
-
legendMiss: '“—” — the file does not exist in that revision yet',
|
|
84
|
-
legendApprox: 'an underlined number is approximate: its way of measuring is named under the metrics',
|
|
85
87
|
exact: 'an exact number',
|
|
86
88
|
approximate: 'an approximation',
|
|
87
89
|
approximateCell: 'an approximate number: ',
|
package/src/page/app.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Оформление страницы отчёта — то, что стоит сверх общей таблицы (`src/table.css`):
|
|
2
|
-
* холст и типографика, панель
|
|
2
|
+
* холст и типографика, панель выбора, состояния пустоты и адаптации под
|
|
3
3
|
* узкое окно. Чисел и цвета дельт здесь нет намеренно: их задаёт общая часть, и она
|
|
4
4
|
* же попадает в статический артефакт, поэтому двух наборов одной таблицы не
|
|
5
5
|
* бывает. Страница открывается с диска, без сервера и без сети, поэтому ни одной
|
|
@@ -64,8 +64,13 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
64
64
|
/* Дерево файлов: вложенность показана отступом и линией уровня, папка — такой же
|
|
65
65
|
* переключатель, как файл, только его галочка отвечает за всё поддерево, а число
|
|
66
66
|
* рядом говорит, за сколько файлов. Список файлов длиннее окна, поэтому панель
|
|
67
|
-
* прокручивается сама: иначе управление вытолкнуло бы таблицу за экран.
|
|
68
|
-
|
|
67
|
+
* прокручивается сама: иначе управление вытолкнуло бы таблицу за экран.
|
|
68
|
+
*
|
|
69
|
+
* На узком экране прокручивается сам список (панель там растёт вместе со
|
|
70
|
+
* страницей), на широком — панель целиком (ниже): прокрутка одна, и она у того,
|
|
71
|
+
* кто и вправду ограничен окном. Шрифт файлов — тот же, что у чисел таблицы:
|
|
72
|
+
* подписей в списке много и они короткие, а рядом с ними стоит таблица. */
|
|
73
|
+
.panel .files { max-height: min(30vh, 320px); overflow: auto; font-size: 12.5px; }
|
|
69
74
|
.panel .tree { margin: 0; padding: 0; list-style: none; }
|
|
70
75
|
.panel .tree .tree { margin-left: 14px; padding-left: 9px; border-left: 1px solid var(--line); }
|
|
71
76
|
.panel .tree .box { padding: 1px 7px 1px 5px; }
|
|
@@ -86,26 +91,17 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
86
91
|
.box:hover { background: var(--tint); }
|
|
87
92
|
.box input { margin: 0; accent-color: AccentColor; }
|
|
88
93
|
.box.all { font-weight: 600; }
|
|
94
|
+
/* Файл вне отчёта: подпись на месте галочки — чисел для него не измеряли. Он
|
|
95
|
+
* приглушён и не отзывается на мышь: обещать нажатие нечем. */
|
|
96
|
+
.box.plain { cursor: default; opacity: .55; }
|
|
97
|
+
.box.plain:hover { background: none; }
|
|
89
98
|
|
|
90
|
-
/*
|
|
91
|
-
*
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
margin: 10px 0 0;
|
|
97
|
-
padding: 0;
|
|
98
|
-
list-style: none;
|
|
99
|
-
color: var(--muted);
|
|
100
|
-
font-size: 12px;
|
|
101
|
-
}
|
|
102
|
-
.legend li { display: inline-flex; gap: 6px; align-items: center; }
|
|
103
|
-
.swatch { width: 10px; height: 10px; border-radius: 3px; background: currentColor; }
|
|
104
|
-
.swatch.same { background: none; border: 1px dashed currentColor; }
|
|
105
|
-
.swatch.miss { background: none; border: 1px solid currentColor; border-radius: 0; }
|
|
106
|
-
.swatch.approx { background: none; border-bottom: 2px dotted currentColor; }
|
|
107
|
-
|
|
108
|
-
/* Приближённое число помечено пунктиром, а не цветом: цвет в таблице занят дельтой
|
|
99
|
+
/* Расшифровки под деревом файлов нет намеренно: под списком она отодвигала
|
|
100
|
+
* числа, а её содержимое и так стоит рядом с тем, что объясняет, — цвет дельт
|
|
101
|
+
* называет сам знак числа, точность стоит под переключателями метрик, а знак
|
|
102
|
+
* «файла ещё нет» — в подсказке клетки.
|
|
103
|
+
*
|
|
104
|
+
* Приближённое число помечено пунктиром, а не цветом: цвет в таблице занят дельтой
|
|
109
105
|
* (рост и спад), и второй смысл на том же признаке читался бы как первый. */
|
|
110
106
|
#grid td.approx { text-decoration: underline dotted; text-underline-offset: 2.5px; }
|
|
111
107
|
|
|
@@ -154,39 +150,76 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
154
150
|
* сдвигает разметку. Ссылки журнала и все переключатели доступны с Tab. */
|
|
155
151
|
:focus-visible { outline: 2px solid AccentColor; outline-offset: 2px; border-radius: 3px; }
|
|
156
152
|
|
|
157
|
-
/* Широкая страница: панель выбора (метрики, дерево
|
|
158
|
-
*
|
|
159
|
-
* мало — переключатели и числа видны
|
|
153
|
+
/* Широкая страница: панель выбора (метрики, дерево файлов) стоит **слева** от
|
|
154
|
+
* таблицы, а страница целиком укладывается в окно. Это не украшение: на десктопе
|
|
155
|
+
* бокового места много, а вертикального мало — переключатели и числа видны
|
|
156
|
+
* одновременно, и ни прокрутка чисел, ни прокрутка списка файлов не уводит
|
|
160
157
|
* управление за экран. Узкое окно эту же раскладку снимает (ниже): там столбцы
|
|
161
158
|
* снова идут друг под другом, потому что рядом им не хватает ширины.
|
|
162
159
|
*
|
|
163
160
|
* Раскладка — сетка на `body`, а не обёртка в разметке: страница собирается
|
|
164
161
|
* вклейкой глав (`src/page/build.js`), и добавлять ей узлы ради оформления значило
|
|
165
|
-
* бы менять форму страницы в двух местах вместо одного.
|
|
162
|
+
* бы менять форму страницы в двух местах вместо одного. Строк пять, и они названы
|
|
163
|
+
* по предмету: заголовок, сообщение о ссылке, **рабочая строка**, сообщение
|
|
164
|
+
* пустоты и подпись. Тянется только рабочая — таблица получает всю оставшуюся
|
|
165
|
+
* высоту, а панель не больше неё; при этом своя высота у панели не «сколько
|
|
166
|
+
* получилось» (тогда она вытолкнула бы таблицу за экран), а та же рабочая строка,
|
|
167
|
+
* внутри которой она прокручивается: список файлов длиннее окна — обычное дело.
|
|
166
168
|
*
|
|
167
169
|
* Порог 900px — тот же, что у адаптаций ниже: одна граница на «широко» и «узко»,
|
|
168
170
|
* иначе между двумя порогами страница осталась бы без ни одного правила. */
|
|
169
171
|
@media (min-width: 900px) {
|
|
170
172
|
body {
|
|
173
|
+
box-sizing: border-box;
|
|
174
|
+
height: 100vh;
|
|
175
|
+
height: 100dvh;
|
|
176
|
+
padding-bottom: var(--gap);
|
|
177
|
+
overflow: auto;
|
|
171
178
|
display: grid;
|
|
172
179
|
grid-template-columns: minmax(230px, 300px) minmax(0, 1fr);
|
|
180
|
+
grid-template-rows: auto auto minmax(0, 1fr) auto auto;
|
|
173
181
|
column-gap: var(--gap);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
align-items: stretch;
|
|
183
|
+
}
|
|
184
|
+
header { grid-area: 1 / 1 / 2 / -1; }
|
|
185
|
+
#notice { grid-area: 2 / 2 / 3 / 3; }
|
|
186
|
+
#shell { grid-area: 3 / 2 / 4 / 3; }
|
|
187
|
+
#state { grid-area: 4 / 2 / 5 / 3; align-self: start; }
|
|
188
|
+
#note { grid-area: 5 / 2 / 6 / 3; }
|
|
189
|
+
/* Работа — на всю высоту, как и таблица: панель кончается там же, где она.
|
|
190
|
+
* Прокручивается панель, а не страница. Верхнего отступа у неё здесь нет:
|
|
191
|
+
* список проезжает под ним, и липкая строка категорий стояла бы не вплотную к
|
|
192
|
+
* краю, а под полосой проезжающих файлов. Отступ никуда не делся — он у первого
|
|
193
|
+
* поля, и уезжает вместе с ним. */
|
|
194
|
+
#panel { grid-area: 2 / 1 / -1 / 2; min-height: 0; margin-bottom: 0; padding-top: 0; overflow: auto; }
|
|
195
|
+
.panel > fieldset:first-child { padding-top: 12px; }
|
|
196
|
+
/* Высоту таблицы здесь задаёт строка, а не окно: своё правило высоты ей нужно
|
|
197
|
+
* только в узком окне, где страница прокручивается целиком. */
|
|
198
|
+
.shell { max-height: none; }
|
|
199
|
+
/* Прокрутка у панели одна: список файлов больше её не заводит, и поле «Файлы»
|
|
200
|
+
* не режет дерево своим потолком. */
|
|
201
|
+
.panel .files { max-height: none; overflow: visible; }
|
|
202
|
+
/* Переключатели категорий остаются на виду, пока листаешь дерево. Фон — тот же,
|
|
203
|
+
* что у панели (поверхность плюс её подсветка), иначе под ними читались бы
|
|
204
|
+
* проезжающие строки списка. */
|
|
205
|
+
.panel .cats {
|
|
206
|
+
position: sticky;
|
|
207
|
+
top: 0;
|
|
208
|
+
z-index: 2;
|
|
209
|
+
background-color: Canvas;
|
|
210
|
+
background-image: linear-gradient(var(--tint), var(--tint));
|
|
178
211
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
#
|
|
182
|
-
/* В столбце дерево прокручивается выше, чем над таблицей: место занимает
|
|
183
|
-
* боковая колонка, а числа не отодвигает. */
|
|
184
|
-
.panel .files { max-height: min(62vh, 620px); }
|
|
212
|
+
/* Без метрик таблицы нет, и растянутая пустая строка ей не место: свободное
|
|
213
|
+
* место этой строки забирает сообщение о пустоте, а не полоса над ним. */
|
|
214
|
+
body:has(#shell[hidden]) #state { grid-area: 3 / 2 / 4 / 3; }
|
|
185
215
|
}
|
|
186
216
|
|
|
187
217
|
/* Адаптации: единственное место, где страница правит общую геометрию, — потому что
|
|
188
|
-
* общая часть заморожена байтами артефакта, а не потому что так удобнее.
|
|
189
|
-
|
|
218
|
+
* общая часть заморожена байтами артефакта, а не потому что так удобнее. Порог —
|
|
219
|
+
* 899px, а не 900: при ровно 900px обе половины применились бы к одной странице, и
|
|
220
|
+
* от «узкой» в «широкой» остался бы потолок высоты таблицы — то есть пустое место
|
|
221
|
+
* под ней на одном единственном размере окна. */
|
|
222
|
+
@media (max-width: 899px) {
|
|
190
223
|
body { padding: 14px 14px 32px; }
|
|
191
224
|
h1 { font-size: 18px; }
|
|
192
225
|
#grid { font-size: 12px; }
|
package/src/page/app.js
CHANGED
|
@@ -25,14 +25,39 @@ function appTable() {
|
|
|
25
25
|
appWrite();
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
/* Прокрутка панели — свойство панели, а не разметки, поэтому она переживает
|
|
29
|
+
* пересборку: иначе каждый клик по галочке возвращал бы список к началу, и до
|
|
30
|
+
* нижних файлов дерева было бы не добраться. Запоминается прокрутка панели и
|
|
31
|
+
* списка файлов — у каждого она своя, а в узком окне прокручивается список.
|
|
32
|
+
* Элементы берутся те, что есть в разметке страницы (`src/page/build.js`):
|
|
33
|
+
* второго перечисления мест прокрутки в пакете нет. */
|
|
34
|
+
const appScrolled = ['#panel', '#panel .files'];
|
|
35
|
+
function appScrollTop() {
|
|
36
|
+
return appScrolled.map((sel) => {
|
|
37
|
+
const el = document.querySelector(sel);
|
|
38
|
+
return el === null ? 0 : el.scrollTop;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function appScrollBack(saved) {
|
|
43
|
+
appScrolled.forEach((sel, i) => {
|
|
44
|
+
const el = document.querySelector(sel);
|
|
45
|
+
if (el !== null) el.scrollTop = saved[i];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Панель перерисовывается целиком, поэтому поле, стоящее под клавиатурой, и
|
|
50
|
+
* прокрутка после каждой пересборки возвращаются на своё место: иначе
|
|
51
|
+
* переключение с Tab и Space требовало бы начинать обход панели заново, а
|
|
52
|
+
* прокрутка — искать своё место заново. Место поля опознаётся порядковым номером —
|
|
53
|
+
* порядок полей панели от данных не зависит. Фокус ставится без прокрутки (`preventScroll`):
|
|
54
|
+
* он возвращает клавиатуру, а не двигает список. */
|
|
32
55
|
function appRender(keepNotice) {
|
|
33
56
|
const at = Array.from(document.querySelectorAll('#panel input')).indexOf(document.activeElement);
|
|
57
|
+
const saved = appScrollTop();
|
|
34
58
|
appPanel();
|
|
35
|
-
|
|
59
|
+
appScrollBack(saved);
|
|
60
|
+
if (at >= 0) document.querySelectorAll('#panel input')[at].focus({ preventScroll: true });
|
|
36
61
|
appTable();
|
|
37
62
|
/* Сообщение о ссылке переживает отрисовку, которая сама же им и вызвана, и
|
|
38
63
|
* гаснет от действия читателя: он его уже прочитал. */
|
package/src/page/build.js
CHANGED
|
@@ -62,6 +62,7 @@ function uiText(page, loc) {
|
|
|
62
62
|
metrics: page.metrics,
|
|
63
63
|
files: page.files,
|
|
64
64
|
dir: page.dir,
|
|
65
|
+
dirNone: page.dirNone,
|
|
65
66
|
all: page.all,
|
|
66
67
|
linkForeign: page.linkForeign,
|
|
67
68
|
linkBroken: page.linkBroken,
|
|
@@ -71,14 +72,11 @@ function uiText(page, loc) {
|
|
|
71
72
|
exact: page.exact,
|
|
72
73
|
approximate: page.approximate,
|
|
73
74
|
approxCell: page.approximateCell,
|
|
75
|
+
/* Почему файла нет в отчёте — словами: причину называет движок знаком (`why`),
|
|
76
|
+
* а страница одевает знак в текст, как и всё остальное в панели. */
|
|
77
|
+
notMeasuredRule: page.notMeasuredRule,
|
|
78
|
+
notMeasuredChoice: page.notMeasuredChoice,
|
|
74
79
|
methodLabel: page.panelMethod,
|
|
75
|
-
legend: [
|
|
76
|
-
{ cls: 'up', text: page.legendUp },
|
|
77
|
-
{ cls: 'down', text: page.legendDown },
|
|
78
|
-
{ cls: 'approx', text: page.legendApprox },
|
|
79
|
-
{ cls: 'same', text: page.legendSame },
|
|
80
|
-
{ cls: 'miss', text: page.legendMiss }
|
|
81
|
-
],
|
|
82
80
|
empty: page.emptyMetrics,
|
|
83
81
|
noFiles: page.noFiles,
|
|
84
82
|
/* {command} подставляет страница: у неё есть данные, а {now} — уже здесь. */
|
package/src/page/panel.js
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
import { appEl, appBox } from './dom.js';
|
|
2
|
-
import { appData, appUi, appView, appFileAt } from './state.js';
|
|
3
|
-
|
|
4
|
-
/* Легенда: образцы — теми же классами, что и числа в клетках (`up`/`down` из
|
|
5
|
-
* общей части оформления), поэтому образец не может разойтись с цветом числа. */
|
|
6
|
-
function appLegend() {
|
|
7
|
-
const list = appEl('ul', 'legend');
|
|
8
|
-
appUi.legend.forEach((item) => {
|
|
9
|
-
const li = appEl('li');
|
|
10
|
-
li.appendChild(appEl('span', 'swatch ' + item.cls));
|
|
11
|
-
li.appendChild(appEl('span', null, item.text));
|
|
12
|
-
list.appendChild(li);
|
|
13
|
-
});
|
|
14
|
-
return list;
|
|
15
|
-
}
|
|
2
|
+
import { appData, appUi, appView, appFileAt, appMeasured } from './state.js';
|
|
16
3
|
|
|
17
4
|
/* Галочку файла ставит только файл: и категория, и папка в дереве — способы
|
|
18
5
|
* переставить те же галочки сразу группой, а своего состояния у них нет. Иначе
|
|
@@ -27,56 +14,114 @@ function appFileBox(i) {
|
|
|
27
14
|
});
|
|
28
15
|
}
|
|
29
16
|
|
|
30
|
-
/*
|
|
17
|
+
/* Файл, которого в отчёте нет: он стоит в дереве на своём месте, но галочки у
|
|
18
|
+
* него нет — чисел для него не измеряли, и переключать нечего. Причину читатель
|
|
19
|
+
* видит во всплывающей строке, а не догадывается по виду. */
|
|
20
|
+
function appUnmeasuredBox(entry) {
|
|
21
|
+
const box = appEl('span', 'box plain', entry.path.split('/').pop());
|
|
22
|
+
box.title = entry.path + ' · '
|
|
23
|
+
+ (entry.why === 'rule' ? appUi.notMeasuredRule : appUi.notMeasuredChoice);
|
|
24
|
+
return box;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Все измеряемые файлы поддерева — то, чем управляет переключатель папки: файл
|
|
28
|
+
* вне отчёта переключать нечего. */
|
|
31
29
|
function appIndexes(node) {
|
|
32
30
|
const out = node.files.slice();
|
|
33
31
|
node.dirs.forEach((sub) => { out.push(...appIndexes(sub)); });
|
|
34
32
|
return out;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
/*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
/* Сколько файлов в поддереве — вместе с теми, что в отчёт не попали. */
|
|
36
|
+
function appCount(node) {
|
|
37
|
+
let n = node.files.length + node.others.length;
|
|
38
|
+
node.dirs.forEach((sub) => { n += appCount(sub); });
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Узел дерева: измеряемые файлы (колонки), прочие файлы проекта и подпапки. */
|
|
43
|
+
function appNode() {
|
|
44
|
+
return { files: [], others: [], dirs: new Map() };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Переключатель папки: его галочка ведёт за собой всё поддерево и показывает три
|
|
48
|
+
* состояния — все файлы включены, часть, ни одного. Рядом число файлов; если в
|
|
49
|
+
* папке есть и те, что вне отчёта, оно написано долей («2/5»): читателю важно, что
|
|
50
|
+
* в папке пять файлов, а измеряются два. Папка без единого измеряемого файла
|
|
51
|
+
* галочки не получает — включать в ней нечего, — но на месте остаётся. */
|
|
52
|
+
function appDirHead(name, sub) {
|
|
53
|
+
const idx = appIndexes(sub);
|
|
54
|
+
const total = appCount(sub);
|
|
55
|
+
const label = name + '/';
|
|
56
|
+
let head;
|
|
57
|
+
if (idx.length === 0) {
|
|
58
|
+
head = appEl('span', 'box dir plain', label);
|
|
59
|
+
head.title = appUi.dirNone.replace('{name}', name).replace('{n}', total);
|
|
60
|
+
} else {
|
|
45
61
|
const on = idx.map((i) => appView.files[i]);
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
const every = on.every((v) => v);
|
|
63
|
+
head = appBox(label, appUi.dir.replace('{name}', name).replace('{n}', idx.length),
|
|
64
|
+
every, (e) => {
|
|
49
65
|
idx.forEach((i) => { appView.files[i] = e.target.checked; });
|
|
50
66
|
appRender();
|
|
51
67
|
}, 'dir');
|
|
52
|
-
head.querySelector('input').indeterminate =
|
|
53
|
-
|
|
68
|
+
head.querySelector('input').indeterminate = !every && on.some((v) => v);
|
|
69
|
+
}
|
|
70
|
+
head.appendChild(appEl('span', 'n', idx.length === total ? String(total) : idx.length + '/' + total));
|
|
71
|
+
return head;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Листья уровня: измеряемые файлы и файлы вне отчёта — вперемешку и по алфавиту
|
|
75
|
+
* имени, как в дереве файлов, а не отдельными списками. */
|
|
76
|
+
function appLeaves(node) {
|
|
77
|
+
const items = node.files.map((i) => ({ name: appData.files[i].label, i: i, entry: null }));
|
|
78
|
+
node.others.forEach((entry) => {
|
|
79
|
+
items.push({ name: entry.path.split('/').pop(), i: null, entry: entry });
|
|
80
|
+
});
|
|
81
|
+
return items.sort((a, b) => (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Узлы одного уровня: сперва папки по алфавиту, затем листья (их порядок — из
|
|
85
|
+
* `appLeaves`). */
|
|
86
|
+
function appTreeList(node) {
|
|
87
|
+
const list = appEl('ul', 'tree');
|
|
88
|
+
[...node.dirs.keys()].sort().forEach((name) => {
|
|
89
|
+
const sub = node.dirs.get(name);
|
|
54
90
|
const li = appEl('li');
|
|
55
|
-
li.appendChild(
|
|
91
|
+
li.appendChild(appDirHead(name, sub));
|
|
56
92
|
li.appendChild(appTreeList(sub));
|
|
57
93
|
list.appendChild(li);
|
|
58
94
|
});
|
|
59
|
-
node.
|
|
95
|
+
appLeaves(node).forEach((leaf) => {
|
|
60
96
|
const li = appEl('li');
|
|
61
|
-
li.appendChild(appFileBox(i));
|
|
97
|
+
li.appendChild(leaf.entry === null ? appFileBox(leaf.i) : appUnmeasuredBox(leaf.entry));
|
|
62
98
|
list.appendChild(li);
|
|
63
99
|
});
|
|
64
100
|
return list;
|
|
65
101
|
}
|
|
66
102
|
|
|
67
|
-
/*
|
|
68
|
-
*
|
|
69
|
-
|
|
103
|
+
/* Место листа в дереве: путь делится по «/», промежуточные папки заводятся по
|
|
104
|
+
* дороге. Одно место на измеряемые и прочие — иначе они разошлись бы папками. */
|
|
105
|
+
function appLeafAt(node, p, i, entry) {
|
|
106
|
+
const parts = p.split('/');
|
|
107
|
+
for (let d = 0; d < parts.length - 1; d++) {
|
|
108
|
+
if (!node.dirs.has(parts[d])) node.dirs.set(parts[d], appNode());
|
|
109
|
+
node = node.dirs.get(parts[d]);
|
|
110
|
+
}
|
|
111
|
+
if (entry === null) node.files.push(i);
|
|
112
|
+
else node.others.push(entry);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Дерево страницы — дерево проекта: узлы берутся из каталога (все пути, которые
|
|
116
|
+
* видит git), поэтому в нём есть и файлы вне отчёта. Измеряемый лист — колонка, и
|
|
117
|
+
* путь у него тот же, что в подписи файла; колонка, чьего файла на HEAD уже нет, в
|
|
118
|
+
* каталог не попала (в индексе её нет) и стоит на месте по последнему известному
|
|
119
|
+
* пути. */
|
|
70
120
|
function appTree() {
|
|
71
|
-
const root =
|
|
72
|
-
appData.files.forEach((
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
for (let d = 0; d < parts.length - 1; d++) {
|
|
76
|
-
if (!node.dirs.has(parts[d])) node.dirs.set(parts[d], { files: [], dirs: new Map() });
|
|
77
|
-
node = node.dirs.get(parts[d]);
|
|
78
|
-
}
|
|
79
|
-
node.files.push(i);
|
|
121
|
+
const root = appNode();
|
|
122
|
+
appData.files.forEach((_f, i) => appLeafAt(root, appFileAt(i), i, null));
|
|
123
|
+
appData.catalog.forEach((entry) => {
|
|
124
|
+
if (appMeasured[entry.path] === undefined) appLeafAt(root, entry.path, null, entry);
|
|
80
125
|
});
|
|
81
126
|
return appTreeList(root);
|
|
82
127
|
}
|
|
@@ -106,7 +151,9 @@ export function appPanel() {
|
|
|
106
151
|
|
|
107
152
|
const files = appEl('fieldset', 'files');
|
|
108
153
|
files.appendChild(appEl('legend', null, appUi.files));
|
|
109
|
-
|
|
154
|
+
/* Строка категорий помечена классом: список файлов листается, и она остаётся на
|
|
155
|
+
* виду (липкость — в широкой раскладке, там панель и прокручивается). */
|
|
156
|
+
const cats = appEl('div', 'row cats');
|
|
110
157
|
appData.categories.forEach((cat) => {
|
|
111
158
|
const idx = [];
|
|
112
159
|
appData.files.forEach((f, i) => { if (f.category === cat.key) idx.push(i); });
|
|
@@ -119,6 +166,4 @@ export function appPanel() {
|
|
|
119
166
|
files.appendChild(cats);
|
|
120
167
|
files.appendChild(appTree());
|
|
121
168
|
panel.appendChild(files);
|
|
122
|
-
|
|
123
|
-
panel.appendChild(appLegend());
|
|
124
169
|
}
|
package/src/page/state.js
CHANGED
|
@@ -35,6 +35,13 @@ appData.files.forEach(() => { appView.files.push(true); });
|
|
|
35
35
|
export const appMetric = {};
|
|
36
36
|
appData.metrics.forEach((m) => { appMetric[m.key] = m; });
|
|
37
37
|
|
|
38
|
+
/* Колонка по пути файла: дерево страницы — дерево проекта (все пути каталога), а
|
|
39
|
+
* числа есть только у колонок, поэтому лист дерева по этому указателю и решает,
|
|
40
|
+
* галочка он или подпись. Имя берётся тем же правилом, что у записи выбора
|
|
41
|
+
* (`appFileAt`), — дерево и память читателя разойтись не могут. */
|
|
42
|
+
export const appMeasured = {};
|
|
43
|
+
appData.files.forEach((_f, i) => { appMeasured[appFileAt(i)] = i; });
|
|
44
|
+
|
|
38
45
|
/* Ссылка — это тот же выбор в адресе, под своим именем: чужой якорь страницы
|
|
39
46
|
* ссылкой не считается, и спорить с ним нечем. */
|
|
40
47
|
const APP_LINK = '#size-report=';
|
package/src/project.js
CHANGED
|
@@ -239,6 +239,32 @@ export function projectConfig(root) {
|
|
|
239
239
|
};
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
/* Путь самого отчёта — в проекте ли он назван (не пустой, не абсолютный и не с
|
|
243
|
+
* выходом наверх): только такой путь дереву можно назвать листом. */
|
|
244
|
+
const ownPath = (output) => output !== '' && !path.isAbsolute(output) && output.indexOf('..') !== 0;
|
|
245
|
+
|
|
246
|
+
/* Каталог проекта для страницы: все пути, которые видит git, и причина у тех из
|
|
247
|
+
* них, что колонкой не стали (`null` — стал: страница по этому знаку решает, лист
|
|
248
|
+
* — галочка или подпись). Дерево страницы — дерево проекта, поэтому пути берутся
|
|
249
|
+
* из индекса, а не из колонок; причины — **теми же** правилами, по которым профиль
|
|
250
|
+
* выбирает колонки (`generated` и предел размера), иначе подсказка говорила бы
|
|
251
|
+
* одно, а выбор колонок делал другое. Колонка, чьего файла на HEAD уже нет, в
|
|
252
|
+
* каталог не попадает: в индексе её нет, а на месте в дереве её держит страница.
|
|
253
|
+
*
|
|
254
|
+
* Сам отчёт в каталоге есть всегда — и пока он ещё не собран, и когда он вне git:
|
|
255
|
+
* его отслеживаемость — свойство момента, а не проекта. Зависеть от неё отчёт не
|
|
256
|
+
* должен: иначе первая же пересборка в свежем клоне даёт другие байты (отчёт в
|
|
257
|
+
* каталоге появился), и хук коммитит его второй раз на пустом месте. */
|
|
258
|
+
export function projectTree(root, output, measured) {
|
|
259
|
+
const files = indexFiles(root);
|
|
260
|
+
if (ownPath(output) && files.every((f) => f.p !== output)) files.push({ p: output, size: 0 });
|
|
261
|
+
return files.sort((a, b) => (a.p < b.p ? -1 : 1)).map((f) => ({
|
|
262
|
+
path: f.p,
|
|
263
|
+
why: measured.indexOf(f.p) >= 0 ? null
|
|
264
|
+
: (generated(f.p, output) || f.size > MAX_BYTES ? 'rule' : 'choice')
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
|
|
242
268
|
/* Имя черновика (`sniffColumns`) осталось публичным: на него опираются те, кто звал
|
|
243
269
|
* вывод колонок (`test/api.test.js` держит список имён), и форма ответа та же —
|
|
244
270
|
* колонки, знакомые расширения проекта и сколько путей всего. */
|
package/src/size-table.js
CHANGED
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
* strip → refusal, parse — снятие балласта и гард;
|
|
64
64
|
* metrics → strip — реестр метрик;
|
|
65
65
|
* git → refusal — всё, что читается у git;
|
|
66
|
-
* project → git, refusal
|
|
66
|
+
* project → git, refusal — что проект говорит о себе сам;
|
|
67
67
|
* config → project, git, refusal, locales, metrics, data — настройки проекта;
|
|
68
68
|
* history → git, metrics, journal, refusal — сборка по истории;
|
|
69
|
-
* data → locales, metrics, journal, history, tool
|
|
69
|
+
* data → locales, metrics, journal, history, project, tool — контракт со страницей;
|
|
70
70
|
* page/build → locales, css — отчёт одним файлом;
|
|
71
71
|
* artifact → data, page/build — запись отчёта;
|
|
72
72
|
* modes → почти все — что делать по запросу;
|