@vernikr/size-report 2.1.0 → 2.3.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 +101 -0
- package/README.md +47 -27
- package/package.json +2 -2
- package/src/data.js +5 -0
- package/src/locales.js +18 -0
- package/src/page/app.css +20 -1
- package/src/page/app.js +5 -1
- package/src/page/build.js +7 -0
- package/src/page/panel.js +108 -32
- package/src/page/state.js +49 -1
- package/src/project.js +53 -20
- package/src/size-table.js +2 -2
- package/templates/README.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,107 @@
|
|
|
18
18
|
где за основу взяты `fixtures/synthetic/config.json`, метрики — `raw`, `min`, `tok`, а
|
|
19
19
|
способ минификации — `strip` или `esbuild`.
|
|
20
20
|
|
|
21
|
+
## 2.3.0 — 2026-09-15
|
|
22
|
+
|
|
23
|
+
Отчёт описывает проект целиком: колонкой идёт каждый отслеживаемый файл, а дерево
|
|
24
|
+
складывается.
|
|
25
|
+
|
|
26
|
+
- **Колонками была выборка из проекта.** Без файла настроек инструмент брал
|
|
27
|
+
двенадцать крупнейших файлов — по одному от каждого расширения — и называл
|
|
28
|
+
прочие исключениями. Отчёт при этом считал «общий объём» по горсти файлов, и
|
|
29
|
+
читатель принимал это за объём проекта (в этом репозитории — 12 файлов из 148).
|
|
30
|
+
Теперь колонка — **каждый отслеживаемый git файл, который можно измерить**, и
|
|
31
|
+
`skip` называет только то, что колонкой быть не может: сам отчёт, замки
|
|
32
|
+
зависимостей, карты, собранное, незнакомый формат, файл сверх 512 КБ — и путь,
|
|
33
|
+
которого на HEAD нет (он живёт только в истории: измерять в нём нечего).
|
|
34
|
+
- **Цена названа замером** (этот же репозиторий, 149 коммитов, 135 колонок против
|
|
35
|
+
12): сборка отчёта 2,4 с → **6,3 с**, файл отчёта 122 КБ → **417 КБ**, `check` —
|
|
36
|
+
6,1 с. Отчёт пересобирает хук после каждого коммита, поэтому столько теперь стоит
|
|
37
|
+
и каждый коммит; кому это дорого — колонки задают файлом настроек (`size --init`
|
|
38
|
+
даёт черновик, дальше его правят: прежний выбор воспроизводится списком из
|
|
39
|
+
двенадцати файлов).
|
|
40
|
+
- **Дерево складывается.** У каждой папки свой знак (▾/▸): он отвечает за то,
|
|
41
|
+
сколько дерева видно, а галочка — за то, что считается, и потому это две разные
|
|
42
|
+
цели нажатия, а не одна. Сложенное помнится между заходами и **не уезжает в
|
|
43
|
+
ссылку**: ссылку отправляют ради чисел, а не ради того, как у кого разложено
|
|
44
|
+
дерево. Запись у складывания своя (тот же паспорт отчёта), разворот всех папок её
|
|
45
|
+
убирает — как и возврат галочек. На 148 путях проекта это единственный способ
|
|
46
|
+
добраться до его середины.
|
|
47
|
+
- **Вклейка программы страницы получила сторож.** Модульный синтаксис снимается
|
|
48
|
+
построчно, поэтому многострочный `import` оставлял в странице хвост и ломал её
|
|
49
|
+
целиком — теперь проверка ищет в вклеенной программе и имя модуля, а не только
|
|
50
|
+
начало строки.
|
|
51
|
+
|
|
52
|
+
### Что изменится в числах
|
|
53
|
+
|
|
54
|
+
**Колонка — каждый отслеживаемый файл, поэтому там, где настройки не заданы, колонок
|
|
55
|
+
станет больше** (в этом репозитории 12 → 135), а вместе с ними вырастут «общий объём»
|
|
56
|
+
(он и раньше считался по колонкам) и время прогона — оно названо выше. Числа ниже
|
|
57
|
+
замерены на фикстуре, где колонки заданы файлом настроек: такой проект замер видит
|
|
58
|
+
точно так же, как 2.2.0, — до последней клетки.
|
|
59
|
+
|
|
60
|
+
| Файл | raw | min со strip | min с esbuild | tok |
|
|
61
|
+
|---|---|---|---|---|
|
|
62
|
+
| code.js | 735 | 276 | 185 | 168 |
|
|
63
|
+
| modern.js | 246 | 51 | 45 | 44 |
|
|
64
|
+
| config.mjs | 172 | 45 | 40 | 40 |
|
|
65
|
+
| заметки.md | 306 | 303 | 303 | 53 |
|
|
66
|
+
| crlf.txt | 63 | 60 | 60 | 10 |
|
|
67
|
+
| package.json | 87 | 69 | 69 | 34 |
|
|
68
|
+
| style.css | 156 | 55 | 43 | 38 |
|
|
69
|
+
| table.toml | 300 | 299 | 299 | 52 |
|
|
70
|
+
| empty.js | 0 | 0 | 0 | 0 |
|
|
71
|
+
| WORKLOG.md | 446 | 439 | 439 | 101 |
|
|
72
|
+
| **ИТОГО** | **2511** | **1597** | **1483** | **540** |
|
|
73
|
+
|
|
74
|
+
## 2.2.0 — 2026-09-15
|
|
75
|
+
|
|
76
|
+
Дерево страницы — дерево проекта: видно всё, числа есть у измеряемых.
|
|
77
|
+
|
|
78
|
+
- **В дереве было видно только то, что стало колонками.** В проекте со 147 файлами
|
|
79
|
+
и выведёнными настройками колонок двенадцать — дерево и показывало двенадцать
|
|
80
|
+
листьев, а про остальные молчало: читатель видел не дерево проекта, а список
|
|
81
|
+
измеренного, и решить, что файлы потерялись, было проще всего. Теперь в контракте
|
|
82
|
+
(`--data`) едет **каталог** — все пути, которые видит git (`ls-files`), — и дерево
|
|
83
|
+
строится по нему.
|
|
84
|
+
- **Числа — только у колонок, и это видно, а не подразумевается.** Лист вне отчёта
|
|
85
|
+
стоит на своём месте, но подписью без галочки, а причина — во всплывающей строке,
|
|
86
|
+
и причин две: `rule` — такой файл колонкой быть не может (сам отчёт, замок
|
|
87
|
+
зависимостей, собранное, слишком крупный или формат без текста), `choice` — мог бы,
|
|
88
|
+
но в набор колонок не выбран (его задают настройки). Знак причины движок ставит
|
|
89
|
+
**теми же** правилами, по которым выбирает колонки, — иначе подсказка говорила бы
|
|
90
|
+
одно, а выбор делал другое. Сам отчёт в дереве назван **всегда** — и пока не
|
|
91
|
+
собран, и когда он вне git: его отслеживаемость это свойство момента, и зависеть
|
|
92
|
+
от неё отчёт не должен (иначе первая же пересборка в свежем клоне даёт другие
|
|
93
|
+
байты, и хук коммитит отчёт второй раз на пустом месте).
|
|
94
|
+
- **Счётчик папки со смешанным составом — доля** («2/5»: два файла в отчёте из
|
|
95
|
+
пяти в папке), а целиком измеряемая папка считает как раньше («5»). Папка, где
|
|
96
|
+
измеряемых нет вовсе, галочки не получает: включать в ней нечего, но на месте она
|
|
97
|
+
остаётся — дерево не должно врать о том, что в проекте есть.
|
|
98
|
+
- **Цена названа:** набор колонок этим не меняется, поэтому прогон не подорожал.
|
|
99
|
+
Замер на этом же проекте (162 колонки × 79 строк против 12): с токенами 16,1 с
|
|
100
|
+
вместо 2,4 с, а на живой истории полный набор ещё и отказывает (перенос состояния
|
|
101
|
+
теряет правку пути, которого нет на HEAD) — поэтому «мерить всё» решением не стало.
|
|
102
|
+
|
|
103
|
+
### Что изменится в числах
|
|
104
|
+
|
|
105
|
+
**Ничего.** Числа считает тот же движок теми же датчиками: выпуск про то, что видно в
|
|
106
|
+
дереве панели, а не про измерение. Таблица та же, что у 2.1.0, до последней клетки.
|
|
107
|
+
|
|
108
|
+
| Файл | raw | min со strip | min с esbuild | tok |
|
|
109
|
+
|---|---|---|---|---|
|
|
110
|
+
| code.js | 735 | 276 | 185 | 168 |
|
|
111
|
+
| modern.js | 246 | 51 | 45 | 44 |
|
|
112
|
+
| config.mjs | 172 | 45 | 40 | 40 |
|
|
113
|
+
| заметки.md | 306 | 303 | 303 | 53 |
|
|
114
|
+
| crlf.txt | 63 | 60 | 60 | 10 |
|
|
115
|
+
| package.json | 87 | 69 | 69 | 34 |
|
|
116
|
+
| style.css | 156 | 55 | 43 | 38 |
|
|
117
|
+
| table.toml | 300 | 299 | 299 | 52 |
|
|
118
|
+
| empty.js | 0 | 0 | 0 | 0 |
|
|
119
|
+
| WORKLOG.md | 446 | 439 | 439 | 101 |
|
|
120
|
+
| **ИТОГО** | **2511** | **1597** | **1483** | **540** |
|
|
121
|
+
|
|
21
122
|
## 2.1.0 — 2026-09-15
|
|
22
123
|
|
|
23
124
|
Вид страницы на десктопе: числам — вся оставшаяся высота, панели — своя прокрутка.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
## Статус
|
|
12
12
|
|
|
13
|
-
**Выпуск 2.
|
|
13
|
+
**Выпуск 2.3.0 (2026-09-15).** Инструмент живёт отдельным пакетом: имя в
|
|
14
14
|
реестре — `@vernikr/size-report` (публикуется тегом из CI, без секрета). Настроек
|
|
15
15
|
проект может не заводить вовсе: без файла инструмент выводит их из самого проекта и
|
|
16
16
|
говорит об этом строкой, а `--init` закрепляет выведенное файлом (чем этот шаг
|
|
@@ -21,9 +21,11 @@
|
|
|
21
21
|
Версия — в манифесте, а у выпуска есть `CHANGELOG.md` с разделом «Что изменится
|
|
22
22
|
в числах»:
|
|
23
23
|
таблица чисел в нём не пересказ, а замер на фикстуре, который сверяется с живым
|
|
24
|
-
прогоном (`test/changelog.test.js`). Числа этим выпуском не меняются —
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
прогоном (`test/changelog.test.js`). Числа на фикстуре этим выпуском не меняются —
|
|
25
|
+
правки в том, **что именно считается**: без файла настроек колонкой идёт каждый
|
|
26
|
+
отслеживаемый файл (а не выборка из двенадцати), и в дереве панели теперь
|
|
27
|
+
складываются папки; `schema: 1` данных, замороженная 1.0.0, остаётся той же (выпуск
|
|
28
|
+
добавил поле, а не поменял смысл прежних).
|
|
27
29
|
|
|
28
30
|
**Шаг 1 плана пройден — перенос без изменения поведения.** Команда —
|
|
29
31
|
`bin/size.js`, точка входа пакета — `src/size-table.js` (только реэкспорт),
|
|
@@ -76,7 +78,10 @@
|
|
|
76
78
|
вывод: страница показывает то, что сказал движок, и своего правила точности не
|
|
77
79
|
заводит. Панель страницы — дерево файлов по папкам, с переключателем у каждой
|
|
78
80
|
папки на всё поддерево; выбор читателя переживает перезаход и передаётся
|
|
79
|
-
ссылкой — адрес страницы и есть ссылка.
|
|
81
|
+
ссылкой — адрес страницы и есть ссылка. В контракте же едет **каталог проекта** —
|
|
82
|
+
все пути, которые видит git: дерево страницы — дерево проекта, а числа есть только
|
|
83
|
+
у тех файлов, что стали колонками (чем это отличается от прежнего дерева, где
|
|
84
|
+
были одни колонки, — `CHANGELOG.md` 2.2.0).
|
|
80
85
|
Минификация и токены сделаны первыми срезами шагов 3 и 4 (ниже).
|
|
81
86
|
Разбиение движка по файлам сделано (пункт D1 плана, R-1.3 `REFACTOR.md`).
|
|
82
87
|
Источник инструмента — скрипт `size-table.js` (1145 строк) в проекте
|
|
@@ -219,10 +224,10 @@
|
|
|
219
224
|
|
|
220
225
|
| Прогон | Команда | Проверок |
|
|
221
226
|
|---|---|---|
|
|
222
|
-
| Быстрый — каждая правка | `pnpm test` | **
|
|
223
|
-
| Полный — выкладка и CI | `pnpm test:all` | **
|
|
227
|
+
| Быстрый — каждая правка | `pnpm test` | **69 из 174** |
|
|
228
|
+
| Полный — выкладка и CI | `pnpm test:all` | **174** |
|
|
224
229
|
|
|
225
|
-
Ни одна проверка не потеряна и не ослаблена: полный прогон запускает все
|
|
230
|
+
Ни одна проверка не потеряна и не ослаблена: полный прогон запускает все 174 теми же
|
|
226
231
|
файлами, а быстрый берёт их часть. Умолчание — полный: файл становится быстрым только
|
|
227
232
|
явно и с причиной, поэтому новое дорогое не может тихо уехать в быстрый. Стерегут это
|
|
228
233
|
объявление `test/suites.test.js` (полнота классификации и причина у каждого файла) и
|
|
@@ -314,16 +319,27 @@ Trusted Publisher в настройках пакета), права **publish**
|
|
|
314
319
|
deльт задан один раз и по артефакту: рост зелёный, спад красный — смена это две
|
|
315
320
|
строки в `src/table.css` плюс пересборка эталона артефакта, больше цвета нигде нет.
|
|
316
321
|
|
|
317
|
-
**Левая панель страницы — дерево
|
|
318
|
-
|
|
322
|
+
**Левая панель страницы — дерево файлов проекта** (`REFACTOR.md` R-2.4, каталог
|
|
323
|
+
путей — `CHANGELOG.md` 2.2.0, складывание папок — 2.3.0). Дерево строится по путям проекта, а не по одним
|
|
324
|
+
колонкам, поэтому в нём видно и то, что в отчёт не попало: такой лист — подпись без
|
|
325
|
+
галочки с причиной во всплывающей строке («колонкой быть не может» — правило пакета
|
|
326
|
+
— или «в набор колонок не попал» — выбор проекта), а счётчик папки со смешанным
|
|
327
|
+
составом написан долей («2/5»: два файла в отчёте из пяти в папке). Сам отчёт в
|
|
328
|
+
дереве назван всегда: его отслеживаемость — свойство момента, и от неё содержимое
|
|
329
|
+
страницы не зависит. У
|
|
319
330
|
папки три состояния — все её файлы включены, часть, ни одного, — и переключатель
|
|
320
331
|
папки ведёт за собой всё поддерево; рядом стоит число файлов. Своего состояния у
|
|
321
332
|
папки и у быстрой кнопки категории нет: обе переставляют галочки файлов, поэтому
|
|
322
|
-
дерево, кнопки и таблица не могут разойтись.
|
|
333
|
+
дерево, кнопки и таблица не могут разойтись. У каждой папки есть ещё свой знак
|
|
334
|
+
(▾/▸): он отвечает за то, сколько дерева видно, — это дело смотрящего, а не выбор
|
|
335
|
+
читателя, поэтому знак помнится между заходами и в ссылку не идёт (у записи свой
|
|
336
|
+
ключ и тот же паспорт отчёта; разворот всех папок её убирает, как возврат галочек —
|
|
337
|
+
запись выбора). Список файлов длиннее панели
|
|
323
338
|
прокручивается, а не выталкивает таблицу, — и прокрутка эта одна: на широком
|
|
324
|
-
экране листается панель целиком (
|
|
325
|
-
|
|
326
|
-
страницей.
|
|
339
|
+
экране листается панель целиком (при окне 1440×900 страница укладывается в окно, а
|
|
340
|
+
таблица берёт всю оставшуюся высоту), а на узком — сам список, потому что там
|
|
341
|
+
панель растёт вместе со страницей. Дерево стало длинным (в этом репозитории 148
|
|
342
|
+
подписей), поэтому папки и складываются: иначе до его середины не добраться.
|
|
327
343
|
|
|
328
344
|
**На широком экране панель стоит слева от таблицы и не вытесняет числа**
|
|
329
345
|
(от 900 px, `src/page/app.css`). Это не украшение: на десктопе бокового места
|
|
@@ -551,9 +567,9 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
551
567
|
| `src/css.js` | Чтение оформления с диска: какие наборы стилей есть и какая у них роль |
|
|
552
568
|
| `src/table.css` | Таблица отчёта: геометрия клеток, липкие шапка и колонка, цвет дельт |
|
|
553
569
|
| `src/page/app.css` | Оформление страницы сверх общей части: панель с деревом файлов и липкой строкой категорий (на широком экране — колонка слева, страница в окно), состояния пустоты, узкое окно |
|
|
554
|
-
| `src/page/state.js` | Состояние страницы: данные отчёта, вид галочек, паспорт записи, память браузера и обмен ссылкой — глава программы страницы |
|
|
570
|
+
| `src/page/state.js` | Состояние страницы: данные отчёта, вид галочек, указатель «какой путь — какая колонка», сложенные папки, паспорт записи, память браузера и обмен ссылкой — глава программы страницы |
|
|
555
571
|
| `src/page/dom.js` | Узлы страницы: мелкие помощники разметки (`appEl`, `appBox`) — одни на панель и таблицу |
|
|
556
|
-
| `src/page/panel.js` | Панель выбора: галочки метрик и файлов, категории, дерево
|
|
572
|
+
| `src/page/panel.js` | Панель выбора: галочки метрик и файлов, категории, дерево путей проекта (файлы вне отчёта — подписью с причиной, папки — со знаком складывания); перерисовку просит у главы сборки |
|
|
557
573
|
| `src/page/table.js` | Таблица страницы: клетка, подпись коммита, шапка и состояния пустоты — разметка поверх общего расчёта |
|
|
558
574
|
| `src/page/app.js` | Сборка и запуск страницы: таблица целиком, перерисовка по выбору читателя (с возвратом фокуса и прокрутки), первая отрисовка и смена якоря; вклеивается в собранную страницу
|
|
559
575
|
| `src/page/build.js` | Сборка страницы: данные, оформление и программа в одном файле без внешних ссылок |
|
|
@@ -576,9 +592,9 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
576
592
|
| `bin/postinstall.js` | Установка хука после постановки пакета: ищет проект-потребитель и молчит, если поставить негде |
|
|
577
593
|
| `src/artifact.js` | Отчёт на диске: единственное место, где он превращается в файл (им пользуются и `--write`, и хук); отчёт — самодостаточная страница |
|
|
578
594
|
| `src/journal.js` | Журнал и ссылки: к какому разделу относится коммит и куда ведёт описание |
|
|
579
|
-
| `src/data.js` | Категории файлов и контракт со страницей (`--data`) |
|
|
595
|
+
| `src/data.js` | Категории файлов и контракт со страницей (`--data`): числа, устройство таблицы и каталог путей проекта |
|
|
580
596
|
| `src/config.js` | Настройки проекта-потребителя: умолчания, чтение, проверка |
|
|
581
|
-
| `src/project.js` | Настройки, выведенные из самого проекта (дерево и история): колонки, журнал,
|
|
597
|
+
| `src/project.js` | Настройки, выведенные из самого проекта (дерево и история): колонки, журнал, исключения, каталог путей для дерева страницы. Без файла настроек он и есть настройки; `--init` закрепляет его файлом |
|
|
582
598
|
| `src/locales.js`, `src/refusal.js`, `src/tool.js` | Тексты отчёта; коды выхода и справка; имя и версия пакета |
|
|
583
599
|
| `src/cli.js` | Вход инструмента: разбор строки, чтение проекта и доставка запроса режиму; главный файл пакета |
|
|
584
600
|
| `src/args.js` | Грамматика командной строки: режимы, ключи и команды плюс проверки их сочетаний — отказ называет виновника и готовую команду |
|
|
@@ -603,7 +619,8 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
603
619
|
| `test/refusals-catalog.test.js` | Сторож каталога отказов: у каждого места отказа в исходниках есть пункт, у каждого пункта — объявленный совет, а отказы, отданные другой проверке, ею в самом деле утверждаются (названные файл и строка проверяются) |
|
|
604
620
|
| `test/contract-data.test.js` | Контракт данных: числа против эталона, состав полей против производных, пометки приближения против подписи метрики |
|
|
605
621
|
| `test/contract-derived.test.js` | Производные против чисел артефакта: итоги строки, дельты клетки и дельта итога — на коде, который лежит в дереве |
|
|
606
|
-
| `test/page-view.test.js` | Собранная страница: вклейка без копий расчёта, самодостаточность,
|
|
622
|
+
| `test/page-view.test.js` | Собранная страница: вклейка без копий расчёта, самодостаточность, состояния пустоты, оформление и переключатели |
|
|
623
|
+
| `test/page-tree.test.js` | Дерево файлов панели: папки по путям проекта, три состояния, поддерево, файлы вне отчёта и прокрутка при пересборке |
|
|
607
624
|
| `test/page-choice.test.js` | Память выбора и обмен ссылкой: перезаход, чужой отчёт, чужая и битая запись, смена адреса на открытой странице |
|
|
608
625
|
| `test/module.test.js` | Модуль в расширении `.js`: измеряется без правок настроек; гард стриппера жив (доказано мутацией) и не обвиняет невиновного |
|
|
609
626
|
| `test/guard.test.js` | Разбор модуля: идёт потоком, оба пути дают один вердикт, отступление работает без файла потока, сотни разборов дешевле запуска |
|
|
@@ -675,7 +692,7 @@ pnpm add -D @vernikr/size-report
|
|
|
675
692
|
```
|
|
676
693
|
|
|
677
694
|
Пакет **опубликован в реестре**, и публично: `npm view @vernikr/size-report
|
|
678
|
-
version` отвечает `2.
|
|
695
|
+
version` отвечает `2.3.0`, `npm access get status @vernikr/size-report` — `public`,
|
|
679
696
|
а анонимный запрос тарболла — код 200. `npm i -D` и `yarn add -D` принимают то же
|
|
680
697
|
имя; ни ключа, ни ссылки на репозиторий не нужно.
|
|
681
698
|
|
|
@@ -683,11 +700,11 @@ version` отвечает `2.1.0`, `npm access get status @vernikr/size-report`
|
|
|
683
700
|
реестра, но остаётся привязанной к ревизии:
|
|
684
701
|
|
|
685
702
|
```bash
|
|
686
|
-
pnpm add -D github:vernikr/size-report#v2.
|
|
703
|
+
pnpm add -D github:vernikr/size-report#v2.3.0
|
|
687
704
|
```
|
|
688
705
|
|
|
689
706
|
Без сети (или если тянуть из codeload нечем) — тарболл: `pnpm pack` в клоне
|
|
690
|
-
пакета, затем `pnpm add -D ./vernikr-size-report-2.
|
|
707
|
+
пакета, затем `pnpm add -D ./vernikr-size-report-2.3.0.tgz`.
|
|
691
708
|
|
|
692
709
|
**Почему тег, а не sha.** Короткий sha pnpm разрешает только через видимые рефы, а
|
|
693
710
|
`git ls-remote` отдаёт одни верхушки веток: пока ревизия — верхушка, короткий sha
|
|
@@ -695,7 +712,7 @@ pnpm add -D github:vernikr/size-report#v2.1.0
|
|
|
695
712
|
<sha> to a commit`. Это не рассуждение, а проба: короткий пин `6530237` ставился,
|
|
696
713
|
пока `main` стоял на нём, и перестал — на следующем же коммите, а тот же sha
|
|
697
714
|
целиком поставился. Имя ветки (`#main`) или тег принимаются оба, но ветка —
|
|
698
|
-
движущаяся цель, а тег постоянен: этот выпуск стоит на теге `v2.
|
|
715
|
+
движущаяся цель, а тег постоянен: этот выпуск стоит на теге `v2.3.0`, он же и в
|
|
699
716
|
примере (сорок знаков тоже годятся, но их придётся брать глазами из истории).
|
|
700
717
|
|
|
701
718
|
Ревизия в примере — не украшение, а часть утверждения: она закреплена за тем, что
|
|
@@ -728,8 +745,10 @@ pnpm exec size --init # закрепить выведенное в size-tab
|
|
|
728
745
|
```
|
|
729
746
|
|
|
730
747
|
Начинать с настроек не нужно: без файла инструмент выводит их из проекта — колонками
|
|
731
|
-
берёт
|
|
732
|
-
|
|
748
|
+
берёт **каждый отслеживаемый git файл, который можно измерить** (отчёт называет
|
|
749
|
+
объём проекта, а не выборки из него; границы остались только у того, что колонкой
|
|
750
|
+
быть не может: сам отчёт, замки зависимостей, собранное, незнакомый формат и файл
|
|
751
|
+
сверх 512 КБ), а прочие называет в `skip`, журналом —
|
|
733
752
|
первый знакомый (`WORKLOG.md`, `CHANGELOG.md`, …), файлом отчёта — `docs/`, если
|
|
734
753
|
каталог есть, командой починки — объявленный скрипт `sizes`, а без него — путь
|
|
735
754
|
к установленному пакету (его цитируют подпись отчёта и отказы, поэтому он обязан
|
|
@@ -739,8 +758,9 @@ pnpm exec size --init # закрепить выведенное в size-tab
|
|
|
739
758
|
этих необязательных зависимостей метрика честно отступает к другому счёту и прогон
|
|
740
759
|
отдаёт код 4 — правки настроек и тут не требуются.
|
|
741
760
|
|
|
742
|
-
Всё, что колонкой быть не может
|
|
743
|
-
|
|
761
|
+
Всё, что колонкой быть не может (сам отчёт, замки зависимостей, карты, собранное,
|
|
762
|
+
незнакомый формат, слишком крупный файл) и чего git не отслеживает, называется
|
|
763
|
+
в `skip` — поэтому первый же `size check`
|
|
744
764
|
полон, а не красен: «пути мимо колонок» появляются от новых правок, а не от того, что
|
|
745
765
|
проект ещё не описан. О том, что настройки выведены, инструмент говорит строкой в
|
|
746
766
|
stderr и называет команду, которая их закрепляет, — `--init`; закреплённое проходит
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vernikr/size-report",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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.2.0",
|
|
70
70
|
"c8": "10",
|
|
71
71
|
"dependency-cruiser": "17",
|
|
72
72
|
"eslint": "^9.18.0",
|
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,6 +19,17 @@ export const LOCALES = {
|
|
|
19
19
|
metrics: 'Метрики',
|
|
20
20
|
files: 'Файлы',
|
|
21
21
|
dir: 'все файлы папки {name} ({n})',
|
|
22
|
+
dirNone: 'ни один файл папки {name} в отчёт не попал (в папке {n})',
|
|
23
|
+
/* Знак сложенной папки — своя цель нажатия: галочка отвечает за числа,
|
|
24
|
+
* знак — за то, сколько дерева видно. */
|
|
25
|
+
foldClose: 'свернуть папку {name}',
|
|
26
|
+
foldOpen: 'развернуть папку {name}',
|
|
27
|
+
/* Причина — у листа: в дереве он подписью, а не галочкой, и без этих слов
|
|
28
|
+
* читатель решил бы, что файл потерялся. */
|
|
29
|
+
notMeasuredRule: 'не измеряется: такой файл колонкой быть не может'
|
|
30
|
+
+ ' (собранный, замок зависимостей, сам отчёт или слишком крупный)',
|
|
31
|
+
notMeasuredChoice: 'не измеряется: в набор колонок отчёта не попал'
|
|
32
|
+
+ ' — набор задают настройки (columns)',
|
|
22
33
|
all: 'все',
|
|
23
34
|
sub: '{tool} {version} · {artifact}',
|
|
24
35
|
/* Слова точности: подпись метрики говорит про худшее в колонке, а клетки —
|
|
@@ -70,6 +81,13 @@ export const LOCALES = {
|
|
|
70
81
|
metrics: 'Metrics',
|
|
71
82
|
files: 'Files',
|
|
72
83
|
dir: 'all files in “{name}” ({n})',
|
|
84
|
+
dirNone: 'no file of “{name}” made it into the report ({n} in it)',
|
|
85
|
+
foldClose: 'collapse “{name}”',
|
|
86
|
+
foldOpen: 'expand “{name}”',
|
|
87
|
+
notMeasuredRule: 'not measured: such a file cannot be a column'
|
|
88
|
+
+ ' (built, a dependency lock, the report itself, or too large)',
|
|
89
|
+
notMeasuredChoice: 'not measured: it is not in the report’s set of columns'
|
|
90
|
+
+ ' — that set comes from the settings (columns)',
|
|
73
91
|
all: 'all',
|
|
74
92
|
sub: '{tool} {version} · {artifact}',
|
|
75
93
|
exact: 'an exact number',
|
package/src/page/app.css
CHANGED
|
@@ -71,12 +71,27 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
71
71
|
* кто и вправду ограничен окном. Шрифт файлов — тот же, что у чисел таблицы:
|
|
72
72
|
* подписей в списке много и они короткие, а рядом с ними стоит таблица. */
|
|
73
73
|
.panel .files { max-height: min(30vh, 320px); overflow: auto; font-size: 12.5px; }
|
|
74
|
-
.panel .tree { margin: 0; padding: 0; list-style: none; }
|
|
74
|
+
.panel .tree { margin: 0; padding: 0 0 0 14px; list-style: none; }
|
|
75
75
|
.panel .tree .tree { margin-left: 14px; padding-left: 9px; border-left: 1px solid var(--line); }
|
|
76
76
|
.panel .tree .box { padding: 1px 7px 1px 5px; }
|
|
77
77
|
.box.dir { font-weight: 600; }
|
|
78
78
|
.box .n { margin-left: 1px; color: var(--muted); font-size: 11px; }
|
|
79
79
|
|
|
80
|
+
/* Знак сложенной папки: место под него есть у каждой строки (отступ строки), а сам
|
|
81
|
+
* он только у папок — так листья и папки выстроены в один столбец. Он не часть
|
|
82
|
+
* галочки: галочка отвечает за числа, знак — за то, сколько дерева видно. */
|
|
83
|
+
.panel .tree li { position: relative; }
|
|
84
|
+
.panel .tree li > .fold {
|
|
85
|
+
position: absolute;
|
|
86
|
+
left: -13px;
|
|
87
|
+
width: 14px;
|
|
88
|
+
text-align: center;
|
|
89
|
+
color: var(--muted);
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
user-select: none;
|
|
92
|
+
}
|
|
93
|
+
.panel .tree li > .fold:hover { color: var(--ink); }
|
|
94
|
+
|
|
80
95
|
/* Переключатель — метка вокруг поля ввода: и подпись, и цель нажатия одна, поэтому
|
|
81
96
|
* по нему попадает и мышь, и клавиатура (Space на поле ввода), и вспомогательные
|
|
82
97
|
* технологии. */
|
|
@@ -91,6 +106,10 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
91
106
|
.box:hover { background: var(--tint); }
|
|
92
107
|
.box input { margin: 0; accent-color: AccentColor; }
|
|
93
108
|
.box.all { font-weight: 600; }
|
|
109
|
+
/* Файл вне отчёта: подпись на месте галочки — чисел для него не измеряли. Он
|
|
110
|
+
* приглушён и не отзывается на мышь: обещать нажатие нечем. */
|
|
111
|
+
.box.plain { cursor: default; opacity: .55; }
|
|
112
|
+
.box.plain:hover { background: none; }
|
|
94
113
|
|
|
95
114
|
/* Расшифровки под деревом файлов нет намеренно: под списком она отодвигала
|
|
96
115
|
* числа, а её содержимое и так стоит рядом с тем, что объясняет, — цвет дельт
|
package/src/page/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appData, appUi, appView, appWrite, appNotice, appLinkUse, appRead, appApply } from './state.js';
|
|
1
|
+
import { appData, appUi, appView, appWrite, appNotice, appLinkUse, appRead, appApply, appFoldRead } from './state.js';
|
|
2
2
|
import { appBody, appHead, appState } from './table.js';
|
|
3
3
|
import { appPanel } from './panel.js';
|
|
4
4
|
|
|
@@ -76,6 +76,10 @@ if (appStart !== 'ours') {
|
|
|
76
76
|
const appSaved = appRead();
|
|
77
77
|
if (appSaved !== null) appApply(appSaved);
|
|
78
78
|
}
|
|
79
|
+
/* Сложенное дерево — память смотрящего, а не выбор читателя: она возвращается и
|
|
80
|
+
* тогда, когда открыта чужая ссылка (иначе присланная ссылка разложила бы дерево
|
|
81
|
+
* заново на каждом заходе). */
|
|
82
|
+
appFoldRead();
|
|
79
83
|
appRender(true);
|
|
80
84
|
appStartup = false;
|
|
81
85
|
appForeign = false;
|
package/src/page/build.js
CHANGED
|
@@ -62,6 +62,9 @@ function uiText(page, loc) {
|
|
|
62
62
|
metrics: page.metrics,
|
|
63
63
|
files: page.files,
|
|
64
64
|
dir: page.dir,
|
|
65
|
+
dirNone: page.dirNone,
|
|
66
|
+
foldClose: page.foldClose,
|
|
67
|
+
foldOpen: page.foldOpen,
|
|
65
68
|
all: page.all,
|
|
66
69
|
linkForeign: page.linkForeign,
|
|
67
70
|
linkBroken: page.linkBroken,
|
|
@@ -71,6 +74,10 @@ function uiText(page, loc) {
|
|
|
71
74
|
exact: page.exact,
|
|
72
75
|
approximate: page.approximate,
|
|
73
76
|
approxCell: page.approximateCell,
|
|
77
|
+
/* Почему файла нет в отчёте — словами: причину называет движок знаком (`why`),
|
|
78
|
+
* а страница одевает знак в текст, как и всё остальное в панели. */
|
|
79
|
+
notMeasuredRule: page.notMeasuredRule,
|
|
80
|
+
notMeasuredChoice: page.notMeasuredChoice,
|
|
74
81
|
methodLabel: page.panelMethod,
|
|
75
82
|
empty: page.emptyMetrics,
|
|
76
83
|
noFiles: page.noFiles,
|
package/src/page/panel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { appEl, appBox } from './dom.js';
|
|
2
|
-
import { appData, appUi, appView, appFileAt } from './state.js';
|
|
2
|
+
import { appData, appUi, appView, appFileAt, appFoldSet, appMeasured } from './state.js';
|
|
3
3
|
|
|
4
4
|
/* Галочку файла ставит только файл: и категория, и папка в дереве — способы
|
|
5
5
|
* переставить те же галочки сразу группой, а своего состояния у них нет. Иначе
|
|
@@ -14,58 +14,134 @@ function appFileBox(i) {
|
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/*
|
|
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
|
+
* вне отчёта переключать нечего. */
|
|
18
29
|
function appIndexes(node) {
|
|
19
30
|
const out = node.files.slice();
|
|
20
31
|
node.dirs.forEach((sub) => { out.push(...appIndexes(sub)); });
|
|
21
32
|
return out;
|
|
22
33
|
}
|
|
23
34
|
|
|
24
|
-
/*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 {
|
|
32
61
|
const on = idx.map((i) => appView.files[i]);
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
62
|
+
const every = on.every((v) => v);
|
|
63
|
+
head = appBox(label, appUi.dir.replace('{name}', name).replace('{n}', idx.length),
|
|
64
|
+
every, (e) => {
|
|
36
65
|
idx.forEach((i) => { appView.files[i] = e.target.checked; });
|
|
37
66
|
appRender();
|
|
38
67
|
}, 'dir');
|
|
39
|
-
head.querySelector('input').indeterminate =
|
|
40
|
-
|
|
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
|
+
* два разных действия означала бы, что сложить папку можно только вместе с
|
|
77
|
+
* включением её файлов. Знак нарисован спаном, а не кнопкой, потому что в строке
|
|
78
|
+
* папки рядом уже стоит метка-галочка, а вложенная в метку кнопка поднимала бы её
|
|
79
|
+
* же нажатие. */
|
|
80
|
+
function appFoldBox(name, path) {
|
|
81
|
+
const folded = appView.folded[path] === true;
|
|
82
|
+
const box = appEl('span', 'fold', folded ? '▸' : '▾');
|
|
83
|
+
box.title = (folded ? appUi.foldOpen : appUi.foldClose).replace('{name}', name);
|
|
84
|
+
box.addEventListener('click', () => { appFoldSet(path, !folded); appRender(); });
|
|
85
|
+
return box;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Листья уровня: измеряемые файлы и файлы вне отчёта — вперемешку и по алфавиту
|
|
89
|
+
* имени, как в дереве файлов, а не отдельными списками. */
|
|
90
|
+
function appLeaves(node) {
|
|
91
|
+
const items = node.files.map((i) => ({ name: appData.files[i].label, i: i, entry: null }));
|
|
92
|
+
node.others.forEach((entry) => {
|
|
93
|
+
items.push({ name: entry.path.split('/').pop(), i: null, entry: entry });
|
|
94
|
+
});
|
|
95
|
+
return items.sort((a, b) => (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Узлы одного уровня: сперва папки по алфавиту, затем листья (их порядок — из
|
|
99
|
+
* `appLeaves`). Сложенная папка — это та, у которой нет самого списка: прятать
|
|
100
|
+
* поддерево оформлением значило бы держать в разметке то, чего не видно, и
|
|
101
|
+
* пересобирать её на каждый клик по знаку. */
|
|
102
|
+
function appTreeList(node, prefix) {
|
|
103
|
+
const list = appEl('ul', 'tree');
|
|
104
|
+
[...node.dirs.keys()].sort().forEach((name) => {
|
|
105
|
+
const sub = node.dirs.get(name);
|
|
106
|
+
const here = prefix === '' ? name : prefix + '/' + name;
|
|
41
107
|
const li = appEl('li');
|
|
42
|
-
li.appendChild(
|
|
43
|
-
li.appendChild(
|
|
108
|
+
li.appendChild(appFoldBox(name, here));
|
|
109
|
+
li.appendChild(appDirHead(name, sub));
|
|
110
|
+
if (appView.folded[here] !== true) li.appendChild(appTreeList(sub, here));
|
|
44
111
|
list.appendChild(li);
|
|
45
112
|
});
|
|
46
|
-
node.
|
|
113
|
+
appLeaves(node).forEach((leaf) => {
|
|
47
114
|
const li = appEl('li');
|
|
48
|
-
li.appendChild(appFileBox(i));
|
|
115
|
+
li.appendChild(leaf.entry === null ? appFileBox(leaf.i) : appUnmeasuredBox(leaf.entry));
|
|
49
116
|
list.appendChild(li);
|
|
50
117
|
});
|
|
51
118
|
return list;
|
|
52
119
|
}
|
|
53
120
|
|
|
54
|
-
/*
|
|
55
|
-
*
|
|
56
|
-
|
|
121
|
+
/* Место листа в дереве: путь делится по «/», промежуточные папки заводятся по
|
|
122
|
+
* дороге. Одно место на измеряемые и прочие — иначе они разошлись бы папками. */
|
|
123
|
+
function appLeafAt(node, p, i, entry) {
|
|
124
|
+
const parts = p.split('/');
|
|
125
|
+
for (let d = 0; d < parts.length - 1; d++) {
|
|
126
|
+
if (!node.dirs.has(parts[d])) node.dirs.set(parts[d], appNode());
|
|
127
|
+
node = node.dirs.get(parts[d]);
|
|
128
|
+
}
|
|
129
|
+
if (entry === null) node.files.push(i);
|
|
130
|
+
else node.others.push(entry);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Дерево страницы — дерево проекта: узлы берутся из каталога (все пути, которые
|
|
134
|
+
* видит git), поэтому в нём есть и файлы вне отчёта. Измеряемый лист — колонка, и
|
|
135
|
+
* путь у него тот же, что в подписи файла; колонка, чьего файла на HEAD уже нет, в
|
|
136
|
+
* каталог не попала (в индексе её нет) и стоит на месте по последнему известному
|
|
137
|
+
* пути. */
|
|
57
138
|
function appTree() {
|
|
58
|
-
const root =
|
|
59
|
-
appData.files.forEach((
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for (let d = 0; d < parts.length - 1; d++) {
|
|
63
|
-
if (!node.dirs.has(parts[d])) node.dirs.set(parts[d], { files: [], dirs: new Map() });
|
|
64
|
-
node = node.dirs.get(parts[d]);
|
|
65
|
-
}
|
|
66
|
-
node.files.push(i);
|
|
139
|
+
const root = appNode();
|
|
140
|
+
appData.files.forEach((_f, i) => appLeafAt(root, appFileAt(i), i, null));
|
|
141
|
+
appData.catalog.forEach((entry) => {
|
|
142
|
+
if (appMeasured[entry.path] === undefined) appLeafAt(root, entry.path, null, entry);
|
|
67
143
|
});
|
|
68
|
-
return appTreeList(root);
|
|
144
|
+
return appTreeList(root, '');
|
|
69
145
|
}
|
|
70
146
|
|
|
71
147
|
export function appPanel() {
|
package/src/page/state.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
export const appData = JSON.parse(document.getElementById('data').textContent);
|
|
27
27
|
export const appUi = JSON.parse(document.getElementById('ui').textContent);
|
|
28
|
-
export const appView = { metrics: {}, files: [] };
|
|
28
|
+
export const appView = { metrics: {}, files: [], folded: {} };
|
|
29
29
|
appData.metrics.forEach((m) => { appView.metrics[m.key] = true; });
|
|
30
30
|
appData.files.forEach(() => { appView.files.push(true); });
|
|
31
31
|
|
|
@@ -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=';
|
|
@@ -224,3 +231,44 @@ export function appApply(rec) {
|
|
|
224
231
|
appData.metrics.forEach((m) => { if (metrics[m.key] === false) appView.metrics[m.key] = false; });
|
|
225
232
|
appData.files.forEach((_f, i) => { if (files[appFileAt(i)] === false) appView.files[i] = false; });
|
|
226
233
|
}
|
|
234
|
+
|
|
235
|
+
/* -------- сложенное дерево -------- */
|
|
236
|
+
|
|
237
|
+
/* Сложенные папки — память того же рода, что выбор, но своей записи: она про то,
|
|
238
|
+
* сколько дерева видно, а не про то, какие числа читают. Поэтому в адрес она не
|
|
239
|
+
* идёт: ссылку отправляют ради чисел, а разложенное дерево — дело смотрящего. Как и
|
|
240
|
+
* у выбора, здесь помнится только сложенное (`true`), а имя папки — это путь
|
|
241
|
+
* («src/page»), поэтому исчезнувшее имя просто ничего не значит. */
|
|
242
|
+
const appFoldKey = appKey + ':tree';
|
|
243
|
+
|
|
244
|
+
export function appFoldRead() {
|
|
245
|
+
let text = null;
|
|
246
|
+
try {
|
|
247
|
+
text = window.localStorage.getItem(appFoldKey);
|
|
248
|
+
} catch (_e) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (text === null) return;
|
|
252
|
+
let rec = null;
|
|
253
|
+
try {
|
|
254
|
+
rec = JSON.parse(text);
|
|
255
|
+
} catch (_e) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!appRecordOk(rec)) return;
|
|
259
|
+
const folded = rec.folded || {};
|
|
260
|
+
Object.keys(folded).forEach((p) => { if (folded[p] === true) appView.folded[p] = true; });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function appFoldSet(path, folded) {
|
|
264
|
+
if (folded) appView.folded[path] = true;
|
|
265
|
+
else delete appView.folded[path];
|
|
266
|
+
const rec = { v: 1, passport: appPassport(), folded: Object.assign({}, appView.folded) };
|
|
267
|
+
try {
|
|
268
|
+
if (Object.keys(rec.folded).length === 0) window.localStorage.removeItem(appFoldKey);
|
|
269
|
+
else window.localStorage.setItem(appFoldKey, JSON.stringify(rec));
|
|
270
|
+
} catch (_e) {
|
|
271
|
+
/* Памяти нет: сложенное не переживёт закрытия страницы, а вид от этого не
|
|
272
|
+
* зависит — дерево сложено ровно так, как его сложил читатель сейчас. */
|
|
273
|
+
}
|
|
274
|
+
}
|
package/src/project.js
CHANGED
|
@@ -18,15 +18,16 @@ import { cliCommand, invocation } from './refusal.js';
|
|
|
18
18
|
*
|
|
19
19
|
* **Колонка — это файл.** Состояние движка хранит на колонку один путь, а список
|
|
20
20
|
* путей колонки — её переименования (в ревизии берётся тот, который в ней есть).
|
|
21
|
-
* Поэтому «папка целиком» колонкой быть не может, и профиль
|
|
22
|
-
* группы путей.
|
|
21
|
+
* Поэтому «папка целиком» колонкой быть не может, и профиль называет файлы, а не
|
|
22
|
+
* группы путей. Колонкой идёт **каждый** отслеживаемый файл, который можно
|
|
23
|
+
* измерить: выборка из проекта выдавала объём выборки за объём проекта.
|
|
23
24
|
*
|
|
24
25
|
* **Профиль обязан проходить ту же проверку, которой его встретит первый запуск**
|
|
25
26
|
* (BLOCKERS §N2, REFACTOR R-0.4): путь, не ставший колонкой и не объявленный
|
|
26
27
|
* исключением, — это код 1 на первом же прогоне. Поэтому `skip` называет и то,
|
|
27
|
-
* что колонкой быть не может (сам отчёт, замки зависимостей,
|
|
28
|
-
* то,
|
|
29
|
-
*
|
|
28
|
+
* что колонкой быть не может (сам отчёт, замки зависимостей, собранное, незнакомый
|
|
29
|
+
* формат, слишком крупный файл), и то, чего git не отслеживает: так первый `check`
|
|
30
|
+
* зелёный, а про каждый файл вне отчёта сказано, почему его там нет.
|
|
30
31
|
*/
|
|
31
32
|
|
|
32
33
|
/* Что вообще берётся колонкой: текстовые формы, с которыми движок умеет работать.
|
|
@@ -41,11 +42,6 @@ const JOURNALS = ['WORKLOG.md', 'CHANGELOG.md', 'CHANGES.md', 'HISTORY.md'];
|
|
|
41
42
|
* сгенерирован, и в отчёте он перевесил бы весь проект. */
|
|
42
43
|
const MAX_BYTES = 512 * 1024;
|
|
43
44
|
|
|
44
|
-
/* Сколько файлов идут колонками: отчёт читают глазами, а сотня колонок читается
|
|
45
|
-
* хуже, чем скачок. Остальное остаётся на виду — в `skip`, откуда его берут
|
|
46
|
-
* колонкой, когда оно важно. */
|
|
47
|
-
const COLUMN_LIMIT = 12;
|
|
48
|
-
|
|
49
45
|
/* Индекс — путь и размер: `ls-files -s` даёт объекты, размер спрашивается у них
|
|
50
46
|
* одной пачкой (`cat-file --batch-check`), а не чтением содержимого. */
|
|
51
47
|
function indexFiles(root) {
|
|
@@ -173,11 +169,17 @@ function labelFor(used, p) {
|
|
|
173
169
|
return p + ' (' + n + ')';
|
|
174
170
|
}
|
|
175
171
|
|
|
176
|
-
/* Выбор колонок:
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
172
|
+
/* Выбор колонок: колонкой идёт всё, что можно измерить, — то есть каждый файл
|
|
173
|
+
* проекта, а не выборка из него. Выборка (прежде — крупнейшие по одному от каждого
|
|
174
|
+
* расширения, всего не больше двенадцати) врала дважды: отчёт называл объём файлов,
|
|
175
|
+
* которых в нём не было, а читатель принимал это за объём проекта. Границы остались
|
|
176
|
+
* только у того, что колонкой **не может** быть: сам отчёт, замки, собранное,
|
|
177
|
+
* незнакомый формат и слишком крупный файл (`generated` и `MAX_BYTES`) — они уходят
|
|
178
|
+
* в `skip` и называются там.
|
|
179
|
+
*
|
|
180
|
+
* Порядок колонок — по кругу от каждого расширения, от крупнейшего к мелкому:
|
|
181
|
+
* сперва то, чего в проекте больше всего, и в каждом расширении крупное раньше
|
|
182
|
+
* мелкого. Это порядок чтения отчёта, а не вес: числа от него не зависят. */
|
|
181
183
|
function columnsOf(files, journal) {
|
|
182
184
|
const byExt = new Map();
|
|
183
185
|
files.forEach((f) => {
|
|
@@ -194,11 +196,11 @@ function columnsOf(files, journal) {
|
|
|
194
196
|
used.push(columns[columns.length - 1].label);
|
|
195
197
|
};
|
|
196
198
|
if (journal !== '' && files.some((f) => f.p === journal)) take(journal);
|
|
197
|
-
for (let round = 0; columns.length <
|
|
199
|
+
for (let round = 0; columns.length < files.length; round++) {
|
|
198
200
|
const before = columns.length;
|
|
199
201
|
rings.forEach((ring) => {
|
|
200
202
|
const f = ring[round];
|
|
201
|
-
if (f !== undefined &&
|
|
203
|
+
if (f !== undefined && f.p !== journal) take(f.p);
|
|
202
204
|
});
|
|
203
205
|
if (columns.length === before) break;
|
|
204
206
|
}
|
|
@@ -209,7 +211,11 @@ export function projectConfig(root) {
|
|
|
209
211
|
const output = outputOf(root);
|
|
210
212
|
const journal = journalOf(root);
|
|
211
213
|
const files = allPaths(root);
|
|
212
|
-
|
|
214
|
+
/* Колонками идёт отслеживаемое git: файл, которого на HEAD нет, измерять нечего
|
|
215
|
+
* (в отчёте он был бы пуст во всех строках). Путь, живущий только в истории,
|
|
216
|
+
* поэтому — исключение, а не колонка; сам список путей — всё равно полный. */
|
|
217
|
+
const tracked = new Set(indexFiles(root).map((f) => f.p));
|
|
218
|
+
const readable = files.filter((f) => tracked.has(f.p) && !generated(f.p, output) && f.size <= MAX_BYTES);
|
|
213
219
|
const columns = columnsOf(readable, journal);
|
|
214
220
|
const taken = new Set(columns.reduce((all, c) => all.concat(c.paths), []));
|
|
215
221
|
// Язык, заголовок, порядок строк и выключатель хука не выводятся ни из чего:
|
|
@@ -232,13 +238,40 @@ export function projectConfig(root) {
|
|
|
232
238
|
anchor: 'heading'
|
|
233
239
|
},
|
|
234
240
|
links: { commitUrl: commitUrlOf(root) },
|
|
235
|
-
// Исключения — всё, что колонкой не стало:
|
|
236
|
-
//
|
|
241
|
+
// Исключения — всё, что колонкой не стало: то, что ею быть не может, и то, чего
|
|
242
|
+
// git не отслеживает (путь из одной истории). Так первый запуск полон, а
|
|
243
|
+
// названное в `skip` — это ответ на вопрос «почему его нет в отчёте».
|
|
237
244
|
skip: [output].concat(files.filter((f) => !taken.has(f.p)).map((f) => f.p))
|
|
238
245
|
.filter((p, i, all) => all.indexOf(p) === i)
|
|
239
246
|
};
|
|
240
247
|
}
|
|
241
248
|
|
|
249
|
+
/* Путь самого отчёта — в проекте ли он назван (не пустой, не абсолютный и не с
|
|
250
|
+
* выходом наверх): только такой путь дереву можно назвать листом. */
|
|
251
|
+
const ownPath = (output) => output !== '' && !path.isAbsolute(output) && output.indexOf('..') !== 0;
|
|
252
|
+
|
|
253
|
+
/* Каталог проекта для страницы: все пути, которые видит git, и причина у тех из
|
|
254
|
+
* них, что колонкой не стали (`null` — стал: страница по этому знаку решает, лист
|
|
255
|
+
* — галочка или подпись). Дерево страницы — дерево проекта, поэтому пути берутся
|
|
256
|
+
* из индекса, а не из колонок; причины — **теми же** правилами, по которым профиль
|
|
257
|
+
* выбирает колонки (`generated` и предел размера), иначе подсказка говорила бы
|
|
258
|
+
* одно, а выбор колонок делал другое. Колонка, чьего файла на HEAD уже нет, в
|
|
259
|
+
* каталог не попадает: в индексе её нет, а на месте в дереве её держит страница.
|
|
260
|
+
*
|
|
261
|
+
* Сам отчёт в каталоге есть всегда — и пока он ещё не собран, и когда он вне git:
|
|
262
|
+
* его отслеживаемость — свойство момента, а не проекта. Зависеть от неё отчёт не
|
|
263
|
+
* должен: иначе первая же пересборка в свежем клоне даёт другие байты (отчёт в
|
|
264
|
+
* каталоге появился), и хук коммитит его второй раз на пустом месте. */
|
|
265
|
+
export function projectTree(root, output, measured) {
|
|
266
|
+
const files = indexFiles(root);
|
|
267
|
+
if (ownPath(output) && files.every((f) => f.p !== output)) files.push({ p: output, size: 0 });
|
|
268
|
+
return files.sort((a, b) => (a.p < b.p ? -1 : 1)).map((f) => ({
|
|
269
|
+
path: f.p,
|
|
270
|
+
why: measured.indexOf(f.p) >= 0 ? null
|
|
271
|
+
: (generated(f.p, output) || f.size > MAX_BYTES ? 'rule' : 'choice')
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
|
|
242
275
|
/* Имя черновика (`sniffColumns`) осталось публичным: на него опираются те, кто звал
|
|
243
276
|
* вывод колонок (`test/api.test.js` держит список имён), и форма ответа та же —
|
|
244
277
|
* колонки, знакомые расширения проекта и сколько путей всего. */
|
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 → почти все — что делать по запросу;
|
package/templates/README.md
CHANGED
|
@@ -19,8 +19,9 @@ uninstall-hook`, выключить, не снимая, — `"hooks": {"enabled"
|
|
|
19
19
|
## Настройки
|
|
20
20
|
|
|
21
21
|
**Файл настроек заводить не нужно.** Без него инструмент выводит профиль из самого
|
|
22
|
-
проекта: колонками —
|
|
23
|
-
|
|
22
|
+
проекта: колонками — каждый отслеживаемый файл, который можно измерить (отчёт тогда
|
|
23
|
+
описывает проект целиком, а не несколько самых крупных его файлов), журналом —
|
|
24
|
+
первый знакомый (`WORKLOG.md`,
|
|
24
25
|
`CHANGELOG.md`, …), файлом отчёта — `docs/`, если каталог есть, командой починки —
|
|
25
26
|
объявленный скрипт `sizes`, ссылкой на коммит — адрес `origin`. О том, что настройки
|
|
26
27
|
выведены, инструмент говорит строкой, а закрепляет их файлом `size --init`: дальше
|