@vernikr/size-report 2.2.0 → 2.4.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 +104 -0
- package/README.md +68 -29
- package/package.json +2 -2
- package/src/data.js +5 -1
- package/src/history.js +16 -1
- package/src/locales.js +6 -0
- package/src/page/app.css +25 -3
- package/src/page/app.js +11 -1
- package/src/page/build.js +2 -0
- package/src/page/dom.js +14 -0
- package/src/page/panel.js +70 -28
- package/src/page/state.js +42 -1
- package/src/project.js +27 -20
- package/templates/README.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,110 @@
|
|
|
18
18
|
где за основу взяты `fixtures/synthetic/config.json`, метрики — `raw`, `min`, `tok`, а
|
|
19
19
|
способ минификации — `strip` или `esbuild`.
|
|
20
20
|
|
|
21
|
+
## 2.4.0 — 2026-09-15
|
|
22
|
+
|
|
23
|
+
Вид дерева и порядок колонок: чего в отчёте нет — после остальных и со снятой
|
|
24
|
+
галочкой, колонки последнего коммита — впереди, складка папки не считает числа.
|
|
25
|
+
|
|
26
|
+
- **Файл вне отчёта был подписью без галочки, а папка, где измерять нечего, —
|
|
27
|
+
без неё вовсе.** Один и тот же вопрос — «почему у этого нет числа» — читался
|
|
28
|
+
по-разному в двух строках подряд, а рядом с колонками такие строки выбивались из
|
|
29
|
+
ряда. Теперь у всего, что вне отчёта, галочка **стоит на месте, снята и
|
|
30
|
+
недоступна**: ряд строк ровный (глаз сравнивает одно с одним), а недоступность
|
|
31
|
+
говорит, что это не выбор читателя, — причина во всплывающей строке, как и
|
|
32
|
+
раньше. Заодно всё, чего в отчёте нет (и папки, и листья), идёт **после** того,
|
|
33
|
+
что в нём есть: в списке, где половина строк не переключается, отчёт виден сразу,
|
|
34
|
+
а не среди чужого.
|
|
35
|
+
- **Складывание папки считало числа, которых не меняет.** Клик по знаку пересобирал
|
|
36
|
+
панель и таблицу целиком — то есть платил за всю таблицу (в этом репозитории 97
|
|
37
|
+
строк × 136 колонок, 39 576 клеток) ради того, чтобы спрятать строки. Замер в
|
|
38
|
+
настоящем Chrome на этой же странице: **107 + 380 мс** перерисовки — столько же,
|
|
39
|
+
сколько стоит переключение одного файла. Стало **0,6 мс** в обработчике (и 6 мс на
|
|
40
|
+
перекладку страницы, которая прячет 42 строки): поддерево лежит в разметке и
|
|
41
|
+
прячется классом на строке, а клик меняет только то, что читатель и видит, —
|
|
42
|
+
класс, знак и запись в памяти.
|
|
43
|
+
- **Колонки, которых коснулся последний коммит, идут впереди остальных.** Отчёт
|
|
44
|
+
пересобирается после каждого коммита, и первый вопрос читателя — что принесла эта
|
|
45
|
+
правка. Знак приходит из истории (`last` в контракте `--data`), а не из чисел:
|
|
46
|
+
правка без изменения размера — тоже правка. Берётся последний коммит, задевший
|
|
47
|
+
хотя бы одну колонку: коммиты мимо колонок (и прежде всего сам отчёт, который
|
|
48
|
+
коммитит хук) пропускаются — иначе знак зависел бы от собственного коммита
|
|
49
|
+
отчёта, тот же прогон давал бы другие байты, и хук коммитил бы отчёт второй раз
|
|
50
|
+
на пустом месте. Внутри каждой части порядок прежний, из настроек.
|
|
51
|
+
|
|
52
|
+
### Что изменится в числах
|
|
53
|
+
|
|
54
|
+
**Ничего.** Числа считает тот же движок теми же датчиками: выпуск про то, что и в
|
|
55
|
+
каком порядке видно на странице, а не про измерение. Таблица та же, что у 2.3.0, до
|
|
56
|
+
последней клетки.
|
|
57
|
+
|
|
58
|
+
| Файл | raw | min со strip | min с esbuild | tok |
|
|
59
|
+
|---|---|---|---|---|
|
|
60
|
+
| code.js | 735 | 276 | 185 | 168 |
|
|
61
|
+
| modern.js | 246 | 51 | 45 | 44 |
|
|
62
|
+
| config.mjs | 172 | 45 | 40 | 40 |
|
|
63
|
+
| заметки.md | 306 | 303 | 303 | 53 |
|
|
64
|
+
| crlf.txt | 63 | 60 | 60 | 10 |
|
|
65
|
+
| package.json | 87 | 69 | 69 | 34 |
|
|
66
|
+
| style.css | 156 | 55 | 43 | 38 |
|
|
67
|
+
| table.toml | 300 | 299 | 299 | 52 |
|
|
68
|
+
| empty.js | 0 | 0 | 0 | 0 |
|
|
69
|
+
| WORKLOG.md | 446 | 439 | 439 | 101 |
|
|
70
|
+
| **ИТОГО** | **2511** | **1597** | **1483** | **540** |
|
|
71
|
+
|
|
72
|
+
## 2.3.0 — 2026-09-15
|
|
73
|
+
|
|
74
|
+
Отчёт описывает проект целиком: колонкой идёт каждый отслеживаемый файл, а дерево
|
|
75
|
+
складывается.
|
|
76
|
+
|
|
77
|
+
- **Колонками была выборка из проекта.** Без файла настроек инструмент брал
|
|
78
|
+
двенадцать крупнейших файлов — по одному от каждого расширения — и называл
|
|
79
|
+
прочие исключениями. Отчёт при этом считал «общий объём» по горсти файлов, и
|
|
80
|
+
читатель принимал это за объём проекта (в этом репозитории — 12 файлов из 148).
|
|
81
|
+
Теперь колонка — **каждый отслеживаемый git файл, который можно измерить**, и
|
|
82
|
+
`skip` называет только то, что колонкой быть не может: сам отчёт, замки
|
|
83
|
+
зависимостей, карты, собранное, незнакомый формат, файл сверх 512 КБ — и путь,
|
|
84
|
+
которого на HEAD нет (он живёт только в истории: измерять в нём нечего).
|
|
85
|
+
- **Цена названа замером** (этот же репозиторий, 149 коммитов, 135 колонок против
|
|
86
|
+
12): сборка отчёта 2,4 с → **6,3 с**, файл отчёта 122 КБ → **417 КБ**, `check` —
|
|
87
|
+
6,1 с. Отчёт пересобирает хук после каждого коммита, поэтому столько теперь стоит
|
|
88
|
+
и каждый коммит; кому это дорого — колонки задают файлом настроек (`size --init`
|
|
89
|
+
даёт черновик, дальше его правят: прежний выбор воспроизводится списком из
|
|
90
|
+
двенадцати файлов).
|
|
91
|
+
- **Дерево складывается.** У каждой папки свой знак (▾/▸): он отвечает за то,
|
|
92
|
+
сколько дерева видно, а галочка — за то, что считается, и потому это две разные
|
|
93
|
+
цели нажатия, а не одна. Сложенное помнится между заходами и **не уезжает в
|
|
94
|
+
ссылку**: ссылку отправляют ради чисел, а не ради того, как у кого разложено
|
|
95
|
+
дерево. Запись у складывания своя (тот же паспорт отчёта), разворот всех папок её
|
|
96
|
+
убирает — как и возврат галочек. На 148 путях проекта это единственный способ
|
|
97
|
+
добраться до его середины.
|
|
98
|
+
- **Вклейка программы страницы получила сторож.** Модульный синтаксис снимается
|
|
99
|
+
построчно, поэтому многострочный `import` оставлял в странице хвост и ломал её
|
|
100
|
+
целиком — теперь проверка ищет в вклеенной программе и имя модуля, а не только
|
|
101
|
+
начало строки.
|
|
102
|
+
|
|
103
|
+
### Что изменится в числах
|
|
104
|
+
|
|
105
|
+
**Колонка — каждый отслеживаемый файл, поэтому там, где настройки не заданы, колонок
|
|
106
|
+
станет больше** (в этом репозитории 12 → 135), а вместе с ними вырастут «общий объём»
|
|
107
|
+
(он и раньше считался по колонкам) и время прогона — оно названо выше. Числа ниже
|
|
108
|
+
замерены на фикстуре, где колонки заданы файлом настроек: такой проект замер видит
|
|
109
|
+
точно так же, как 2.2.0, — до последней клетки.
|
|
110
|
+
|
|
111
|
+
| Файл | raw | min со strip | min с esbuild | tok |
|
|
112
|
+
|---|---|---|---|---|
|
|
113
|
+
| code.js | 735 | 276 | 185 | 168 |
|
|
114
|
+
| modern.js | 246 | 51 | 45 | 44 |
|
|
115
|
+
| config.mjs | 172 | 45 | 40 | 40 |
|
|
116
|
+
| заметки.md | 306 | 303 | 303 | 53 |
|
|
117
|
+
| crlf.txt | 63 | 60 | 60 | 10 |
|
|
118
|
+
| package.json | 87 | 69 | 69 | 34 |
|
|
119
|
+
| style.css | 156 | 55 | 43 | 38 |
|
|
120
|
+
| table.toml | 300 | 299 | 299 | 52 |
|
|
121
|
+
| empty.js | 0 | 0 | 0 | 0 |
|
|
122
|
+
| WORKLOG.md | 446 | 439 | 439 | 101 |
|
|
123
|
+
| **ИТОГО** | **2511** | **1597** | **1483** | **540** |
|
|
124
|
+
|
|
21
125
|
## 2.2.0 — 2026-09-15
|
|
22
126
|
|
|
23
127
|
Дерево страницы — дерево проекта: видно всё, числа есть у измеряемых.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
## Статус
|
|
12
12
|
|
|
13
|
-
**Выпуск 2.
|
|
13
|
+
**Выпуск 2.4.0 (2026-09-15).** Инструмент живёт отдельным пакетом: имя в
|
|
14
14
|
реестре — `@vernikr/size-report` (публикуется тегом из CI, без секрета). Настроек
|
|
15
15
|
проект может не заводить вовсе: без файла инструмент выводит их из самого проекта и
|
|
16
16
|
говорит об этом строкой, а `--init` закрепляет выведенное файлом (чем этот шаг
|
|
@@ -21,10 +21,13 @@
|
|
|
21
21
|
Версия — в манифесте, а у выпуска есть `CHANGELOG.md` с разделом «Что изменится
|
|
22
22
|
в числах»:
|
|
23
23
|
таблица чисел в нём не пересказ, а замер на фикстуре, который сверяется с живым
|
|
24
|
-
прогоном (`test/changelog.test.js`). Числа этим выпуском не меняются —
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
прогоном (`test/changelog.test.js`). Числа на фикстуре этим выпуском не меняются —
|
|
25
|
+
правки в том, **что и в каком порядке видно**: без файла настроек колонкой идёт
|
|
26
|
+
каждый отслеживаемый файл (а не выборка из двенадцати), в дереве панели
|
|
27
|
+
складываются папки, а всё, чего в отчёте нет, стоит после остальных и со снятой
|
|
28
|
+
галочкой; в таблице колонки, которых коснулся последний коммит, идут впереди.
|
|
29
|
+
`schema: 1` данных, замороженная 1.0.0, остаётся той же (выпуск добавил поле, а не
|
|
30
|
+
поменял смысл прежних).
|
|
28
31
|
|
|
29
32
|
**Шаг 1 плана пройден — перенос без изменения поведения.** Команда —
|
|
30
33
|
`bin/size.js`, точка входа пакета — `src/size-table.js` (только реэкспорт),
|
|
@@ -223,10 +226,10 @@
|
|
|
223
226
|
|
|
224
227
|
| Прогон | Команда | Проверок |
|
|
225
228
|
|---|---|---|
|
|
226
|
-
| Быстрый — каждая правка | `pnpm test` | **
|
|
227
|
-
| Полный — выкладка и CI | `pnpm test:all` | **
|
|
229
|
+
| Быстрый — каждая правка | `pnpm test` | **72 из 177** |
|
|
230
|
+
| Полный — выкладка и CI | `pnpm test:all` | **177** |
|
|
228
231
|
|
|
229
|
-
Ни одна проверка не потеряна и не ослаблена: полный прогон запускает все
|
|
232
|
+
Ни одна проверка не потеряна и не ослаблена: полный прогон запускает все 177 теми же
|
|
230
233
|
файлами, а быстрый берёт их часть. Умолчание — полный: файл становится быстрым только
|
|
231
234
|
явно и с причиной, поэтому новое дорогое не может тихо уехать в быстрый. Стерегут это
|
|
232
235
|
объявление `test/suites.test.js` (полнота классификации и причина у каждого файла) и
|
|
@@ -319,21 +322,54 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
319
322
|
строки в `src/table.css` плюс пересборка эталона артефакта, больше цвета нигде нет.
|
|
320
323
|
|
|
321
324
|
**Левая панель страницы — дерево файлов проекта** (`REFACTOR.md` R-2.4, каталог
|
|
322
|
-
путей — `CHANGELOG.md` 2.2.0
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
325
|
+
путей — `CHANGELOG.md` 2.2.0, складывание папок — 2.3.0, вид того, чего в отчёте
|
|
326
|
+
нет, — 2.4.0). Дерево строится по путям проекта, а не по одним
|
|
327
|
+
колонкам, поэтому в нём видно и то, что в отчёт не попало: у такого листа (и у
|
|
328
|
+
папки, где измерять нечего) галочка стоит на месте, но **снята и недоступна** —
|
|
329
|
+
включать нечего, — а причина в всплывающей строке («колонкой быть не может» —
|
|
330
|
+
правило пакета — или «в набор колонок не попал» — выбор проекта). Снятая, а не
|
|
331
|
+
убранная: ряд строк остаётся ровным (глаз сравнивает одно с одним), а недоступность
|
|
332
|
+
говорит, что это не выбор читателя. Заодно счётчик папки со смешанным
|
|
326
333
|
составом написан долей («2/5»: два файла в отчёте из пяти в папке). Сам отчёт в
|
|
327
334
|
дереве назван всегда: его отслеживаемость — свойство момента, и от неё содержимое
|
|
328
|
-
страницы не зависит.
|
|
335
|
+
страницы не зависит. **Всё, чего в отчёте нет, стоит после того, что в нём есть** —
|
|
336
|
+
и папки, и листья: в списке, где половина строк не переключается, отчёт должен
|
|
337
|
+
быть виден сразу, а не среди чужого (`test/page-tree.test.js`). У
|
|
329
338
|
папки три состояния — все её файлы включены, часть, ни одного, — и переключатель
|
|
330
339
|
папки ведёт за собой всё поддерево; рядом стоит число файлов. Своего состояния у
|
|
331
340
|
папки и у быстрой кнопки категории нет: обе переставляют галочки файлов, поэтому
|
|
332
|
-
дерево, кнопки и таблица не могут разойтись.
|
|
341
|
+
дерево, кнопки и таблица не могут разойтись. У каждой папки есть ещё свой знак
|
|
342
|
+
(▾/▸): он отвечает за то, сколько дерева видно, — это дело смотрящего, а не выбор
|
|
343
|
+
читателя, поэтому знак помнится между заходами и в ссылку не идёт (у записи свой
|
|
344
|
+
ключ и тот же паспорт отчёта; разворот всех папок её убирает, как возврат галочек —
|
|
345
|
+
запись выбора). Список файлов длиннее панели
|
|
333
346
|
прокручивается, а не выталкивает таблицу, — и прокрутка эта одна: на широком
|
|
334
|
-
экране листается панель целиком (
|
|
335
|
-
|
|
336
|
-
страницей.
|
|
347
|
+
экране листается панель целиком (при окне 1440×900 страница укладывается в окно, а
|
|
348
|
+
таблица берёт всю оставшуюся высоту), а на узком — сам список, потому что там
|
|
349
|
+
панель растёт вместе со страницей. Дерево стало длинным (в этом репозитории 148
|
|
350
|
+
подписей), поэтому папки и складываются: иначе до его середины не добраться.
|
|
351
|
+
|
|
352
|
+
**Складывание папки — чистый вид, и оно не считает числа** (`CHANGELOG.md` 2.4.0).
|
|
353
|
+
Поддерево лежит в разметке и прячется классом на строке: клик по знаку меняет три
|
|
354
|
+
вещи, которые читатель и видит, — класс, знак и запись в памяти. Пересборка здесь
|
|
355
|
+
была бы честной работой впустую: она строит таблицу целиком (в этом репозитории —
|
|
356
|
+
97 строк × 136 колонок, 39 576 клеток), то есть платит за числа, которых складывание
|
|
357
|
+
не меняет, — и это было видно глазом как задержка. Замер в настоящем Chrome на этой
|
|
358
|
+
же странице: клик по знаку папки `src/` (42 строки поддерева) — **0,6 мс** в
|
|
359
|
+
обработчике и 6 мс на перекладку против **107 + 380 мс** перерисовки, которую он
|
|
360
|
+
вызывал раньше (столько же стоит переключение одного файла у той же страницы).
|
|
361
|
+
Стережёт это проверка о том, что после складывания таблица осталась той же самой
|
|
362
|
+
разметкой (`test/page-tree.test.js`), а не собранной заново.
|
|
363
|
+
|
|
364
|
+
**Колонки, которых коснулся последний коммит, идут впереди** (`CHANGELOG.md` 2.4.0).
|
|
365
|
+
Отчёт пересобирается после каждого коммита, и первый вопрос читателя — что принесла
|
|
366
|
+
эта правка. Знак приходит из истории, а не из чисел: правка без изменения размера —
|
|
367
|
+
тоже правка. Берётся последний коммит, задевший хотя бы одну колонку, — считая от
|
|
368
|
+
верхушки назад: коммиты мимо колонок (и, прежде всего, сам отчёт, который коммитит
|
|
369
|
+
хук) пропускаются, — иначе знак зависел бы от собственного коммита отчёта, а тот же
|
|
370
|
+
прогон давал бы другие байты. Внутри каждой части порядок прежний, из настроек
|
|
371
|
+
(`sort` устойчив): порядок колонок — то, к чему читатель привык, и своим выбором
|
|
372
|
+
файлов он его не переставляет (`test/page-view.test.js`, поле `last` контракта).
|
|
337
373
|
|
|
338
374
|
**На широком экране панель стоит слева от таблицы и не вытесняет числа**
|
|
339
375
|
(от 900 px, `src/page/app.css`). Это не украшение: на десктопе бокового места
|
|
@@ -561,9 +597,9 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
561
597
|
| `src/css.js` | Чтение оформления с диска: какие наборы стилей есть и какая у них роль |
|
|
562
598
|
| `src/table.css` | Таблица отчёта: геометрия клеток, липкие шапка и колонка, цвет дельт |
|
|
563
599
|
| `src/page/app.css` | Оформление страницы сверх общей части: панель с деревом файлов и липкой строкой категорий (на широком экране — колонка слева, страница в окно), состояния пустоты, узкое окно |
|
|
564
|
-
| `src/page/state.js` | Состояние страницы: данные отчёта, вид галочек, указатель «какой путь — какая колонка», паспорт записи, память браузера и обмен ссылкой — глава программы страницы |
|
|
600
|
+
| `src/page/state.js` | Состояние страницы: данные отчёта, вид галочек, указатель «какой путь — какая колонка», сложенные папки, паспорт записи, память браузера и обмен ссылкой — глава программы страницы |
|
|
565
601
|
| `src/page/dom.js` | Узлы страницы: мелкие помощники разметки (`appEl`, `appBox`) — одни на панель и таблицу |
|
|
566
|
-
| `src/page/panel.js` | Панель выбора: галочки метрик и файлов, категории, дерево путей проекта (файлы вне отчёта —
|
|
602
|
+
| `src/page/panel.js` | Панель выбора: галочки метрик и файлов, категории, дерево путей проекта (файлы вне отчёта — снятой галочкой с причиной, после остальных; папки — со знаком складывания, который прячет поддерево классом, а не пересборкой); перерисовку просит у главы сборки |
|
|
567
603
|
| `src/page/table.js` | Таблица страницы: клетка, подпись коммита, шапка и состояния пустоты — разметка поверх общего расчёта |
|
|
568
604
|
| `src/page/app.js` | Сборка и запуск страницы: таблица целиком, перерисовка по выбору читателя (с возвратом фокуса и прокрутки), первая отрисовка и смена якоря; вклеивается в собранную страницу
|
|
569
605
|
| `src/page/build.js` | Сборка страницы: данные, оформление и программа в одном файле без внешних ссылок |
|
|
@@ -578,7 +614,7 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
578
614
|
| `src/minify.js` | Настоящий минификатор: необязательная зависимость, загружается один раз и не роняет прогон, если её нет |
|
|
579
615
|
| `src/tokens.js` | Токены: словарь по семейству и кодировке, оценка по длине как запасной счёт, форматы без текста |
|
|
580
616
|
| `src/optional.js` | Общее устройство необязательных зависимостей (минификатор и словарь): ленивая загрузка, версия пакета, шов отсутствия |
|
|
581
|
-
| `src/history.js` | Обход истории: измерение по коммитам, сдвиг чисел, сборка, сверка с
|
|
617
|
+
| `src/history.js` | Обход истории: измерение по коммитам, сдвиг чисел, сборка, сверка с деревом, знак «какой колонки коснулся последний коммит» и причина пропуска у каждого выпавшего коммита |
|
|
582
618
|
| `src/check.js` | Полнота покрытия (`size check`): настройки, история, пути, датчики — что прошло мимо колонок и чем это чинится |
|
|
583
619
|
| `src/explain.js` | Объяснение пропущенной строки (`size explain <коммит>`): причина, улики и готовая починка |
|
|
584
620
|
| `src/doctor.js` | Диагностика одним ответом (`size doctor`): окружение, зависимости, настройки, покрытие, состояние хука — сборкой из существующих кусков |
|
|
@@ -614,7 +650,7 @@ deльт задан один раз и по артефакту: рост зел
|
|
|
614
650
|
| `test/contract-data.test.js` | Контракт данных: числа против эталона, состав полей против производных, пометки приближения против подписи метрики |
|
|
615
651
|
| `test/contract-derived.test.js` | Производные против чисел артефакта: итоги строки, дельты клетки и дельта итога — на коде, который лежит в дереве |
|
|
616
652
|
| `test/page-view.test.js` | Собранная страница: вклейка без копий расчёта, самодостаточность, состояния пустоты, оформление и переключатели |
|
|
617
|
-
| `test/page-tree.test.js` | Дерево файлов панели: папки по путям проекта, три состояния, поддерево, файлы вне отчёта и прокрутка при пересборке |
|
|
653
|
+
| `test/page-tree.test.js` | Дерево файлов панели: папки по путям проекта, три состояния, поддерево, файлы и папки вне отчёта (снятая галочка и место после остальных), складывание без пересборки и прокрутка при пересборке |
|
|
618
654
|
| `test/page-choice.test.js` | Память выбора и обмен ссылкой: перезаход, чужой отчёт, чужая и битая запись, смена адреса на открытой странице |
|
|
619
655
|
| `test/module.test.js` | Модуль в расширении `.js`: измеряется без правок настроек; гард стриппера жив (доказано мутацией) и не обвиняет невиновного |
|
|
620
656
|
| `test/guard.test.js` | Разбор модуля: идёт потоком, оба пути дают один вердикт, отступление работает без файла потока, сотни разборов дешевле запуска |
|
|
@@ -686,7 +722,7 @@ pnpm add -D @vernikr/size-report
|
|
|
686
722
|
```
|
|
687
723
|
|
|
688
724
|
Пакет **опубликован в реестре**, и публично: `npm view @vernikr/size-report
|
|
689
|
-
version` отвечает `2.
|
|
725
|
+
version` отвечает `2.4.0`, `npm access get status @vernikr/size-report` — `public`,
|
|
690
726
|
а анонимный запрос тарболла — код 200. `npm i -D` и `yarn add -D` принимают то же
|
|
691
727
|
имя; ни ключа, ни ссылки на репозиторий не нужно.
|
|
692
728
|
|
|
@@ -694,11 +730,11 @@ version` отвечает `2.2.0`, `npm access get status @vernikr/size-report`
|
|
|
694
730
|
реестра, но остаётся привязанной к ревизии:
|
|
695
731
|
|
|
696
732
|
```bash
|
|
697
|
-
pnpm add -D github:vernikr/size-report#v2.
|
|
733
|
+
pnpm add -D github:vernikr/size-report#v2.4.0
|
|
698
734
|
```
|
|
699
735
|
|
|
700
736
|
Без сети (или если тянуть из codeload нечем) — тарболл: `pnpm pack` в клоне
|
|
701
|
-
пакета, затем `pnpm add -D ./vernikr-size-report-2.
|
|
737
|
+
пакета, затем `pnpm add -D ./vernikr-size-report-2.4.0.tgz`.
|
|
702
738
|
|
|
703
739
|
**Почему тег, а не sha.** Короткий sha pnpm разрешает только через видимые рефы, а
|
|
704
740
|
`git ls-remote` отдаёт одни верхушки веток: пока ревизия — верхушка, короткий sha
|
|
@@ -706,7 +742,7 @@ pnpm add -D github:vernikr/size-report#v2.2.0
|
|
|
706
742
|
<sha> to a commit`. Это не рассуждение, а проба: короткий пин `6530237` ставился,
|
|
707
743
|
пока `main` стоял на нём, и перестал — на следующем же коммите, а тот же sha
|
|
708
744
|
целиком поставился. Имя ветки (`#main`) или тег принимаются оба, но ветка —
|
|
709
|
-
движущаяся цель, а тег постоянен: этот выпуск стоит на теге `v2.
|
|
745
|
+
движущаяся цель, а тег постоянен: этот выпуск стоит на теге `v2.4.0`, он же и в
|
|
710
746
|
примере (сорок знаков тоже годятся, но их придётся брать глазами из истории).
|
|
711
747
|
|
|
712
748
|
Ревизия в примере — не украшение, а часть утверждения: она закреплена за тем, что
|
|
@@ -739,8 +775,10 @@ pnpm exec size --init # закрепить выведенное в size-tab
|
|
|
739
775
|
```
|
|
740
776
|
|
|
741
777
|
Начинать с настроек не нужно: без файла инструмент выводит их из проекта — колонками
|
|
742
|
-
берёт
|
|
743
|
-
|
|
778
|
+
берёт **каждый отслеживаемый git файл, который можно измерить** (отчёт называет
|
|
779
|
+
объём проекта, а не выборки из него; границы остались только у того, что колонкой
|
|
780
|
+
быть не может: сам отчёт, замки зависимостей, собранное, незнакомый формат и файл
|
|
781
|
+
сверх 512 КБ), а прочие называет в `skip`, журналом —
|
|
744
782
|
первый знакомый (`WORKLOG.md`, `CHANGELOG.md`, …), файлом отчёта — `docs/`, если
|
|
745
783
|
каталог есть, командой починки — объявленный скрипт `sizes`, а без него — путь
|
|
746
784
|
к установленному пакету (его цитируют подпись отчёта и отказы, поэтому он обязан
|
|
@@ -750,8 +788,9 @@ pnpm exec size --init # закрепить выведенное в size-tab
|
|
|
750
788
|
этих необязательных зависимостей метрика честно отступает к другому счёту и прогон
|
|
751
789
|
отдаёт код 4 — правки настроек и тут не требуются.
|
|
752
790
|
|
|
753
|
-
Всё, что колонкой быть не может
|
|
754
|
-
|
|
791
|
+
Всё, что колонкой быть не может (сам отчёт, замки зависимостей, карты, собранное,
|
|
792
|
+
незнакомый формат, слишком крупный файл) и чего git не отслеживает, называется
|
|
793
|
+
в `skip` — поэтому первый же `size check`
|
|
755
794
|
полон, а не красен: «пути мимо колонок» появляются от новых правок, а не от того, что
|
|
756
795
|
проект ещё не описан. О том, что настройки выведены, инструмент говорит строкой в
|
|
757
796
|
stderr и называет команду, которая их закрепляет, — `--init`; закреплённое проходит
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vernikr/size-report",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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.
|
|
69
|
+
"@vernikr/size-report": "2.3.0",
|
|
70
70
|
"c8": "10",
|
|
71
71
|
"dependency-cruiser": "17",
|
|
72
72
|
"eslint": "^9.18.0",
|
package/src/data.js
CHANGED
|
@@ -39,7 +39,7 @@ export function categoryOf(col) {
|
|
|
39
39
|
* вопроса, на который пригодилось бы поле («почему у коммита нет строки» задают
|
|
40
40
|
* командой `explain`, и там причина уже разложена). */
|
|
41
41
|
export function reportData(cfg, root) {
|
|
42
|
-
const { rows, state, dropped } = build(cfg, root);
|
|
42
|
+
const { rows, state, dropped, last } = build(cfg, root);
|
|
43
43
|
const loc = LOCALES[cfg.locale];
|
|
44
44
|
const files = cfg.columns.map((col, i) => {
|
|
45
45
|
const cat = categoryOf(col);
|
|
@@ -74,6 +74,10 @@ export function reportData(cfg, root) {
|
|
|
74
74
|
rows: rows.map((r) => Object.assign(rowShape(r),
|
|
75
75
|
{ href: rowHref(r.section, r.sha, cfg), values: r.cells })),
|
|
76
76
|
now: state.map((s) => (s === null ? null : s.cells)),
|
|
77
|
+
/* Какие колонки тронул последний коммит: страница по этому знаку ставит их
|
|
78
|
+
* впереди — читателю нужнее то, что изменилось только что. Это факт из истории,
|
|
79
|
+
* а не производная величина: она не считает, а говорит, чего коснулась правка. */
|
|
80
|
+
last: last,
|
|
77
81
|
approx: approxMarks(rows, state, cfg),
|
|
78
82
|
skipped: dropped.map(skipLine)
|
|
79
83
|
};
|
package/src/history.js
CHANGED
|
@@ -158,7 +158,22 @@ export function measureHistory(cfg, root, known) {
|
|
|
158
158
|
journalPrev: ''
|
|
159
159
|
};
|
|
160
160
|
commits.forEach((c, ci) => stepCommit(pass, c, ci));
|
|
161
|
-
|
|
161
|
+
/* Какие колонки тронул последний коммит — по тому же плану чтения, по которому
|
|
162
|
+
* идёт перенос состояния: путь колонки есть в списке изменённых путей коммита.
|
|
163
|
+
* Страница ставит эти колонки впереди остальных: отчёт пересобирается после
|
|
164
|
+
* каждого коммита, и первый вопрос читателя — что принесла эта правка.
|
|
165
|
+
*
|
|
166
|
+
* Берётся последний коммит, задевший хотя бы одну колонку, — считая от верхушки
|
|
167
|
+
* назад. Коммиты мимо колонок (и, прежде всего, сам отчёт, который коммитит хук)
|
|
168
|
+
* пропускаются: правка отчёта — не правка проекта. Иначе знак зависел бы от
|
|
169
|
+
* собственного коммита отчёта: тот же прогон давал бы другие байты, отчёт
|
|
170
|
+
* перестал бы быть неподвижной точкой, а хук коммитил бы его по второму разу. */
|
|
171
|
+
let last = cfg.columns.map(() => false);
|
|
172
|
+
for (let i = commits.length - 1; i >= 0 && !last.some(Boolean); i--) {
|
|
173
|
+
const picks = reads.plan[i].picks.map((paths) => paths.length > 0);
|
|
174
|
+
if (picks.some(Boolean)) last = picks;
|
|
175
|
+
}
|
|
176
|
+
return { rows: pass.rows, dropped: pass.dropped, mixed: pass.mixed, state: pass.state, last: last };
|
|
162
177
|
}
|
|
163
178
|
|
|
164
179
|
/* Сверка с рабочим деревом отвечает на два вопроса, и оба обязательны: состояние
|
package/src/locales.js
CHANGED
|
@@ -20,6 +20,10 @@ export const LOCALES = {
|
|
|
20
20
|
files: 'Файлы',
|
|
21
21
|
dir: 'все файлы папки {name} ({n})',
|
|
22
22
|
dirNone: 'ни один файл папки {name} в отчёт не попал (в папке {n})',
|
|
23
|
+
/* Знак сложенной папки — своя цель нажатия: галочка отвечает за числа,
|
|
24
|
+
* знак — за то, сколько дерева видно. */
|
|
25
|
+
foldClose: 'свернуть папку {name}',
|
|
26
|
+
foldOpen: 'развернуть папку {name}',
|
|
23
27
|
/* Причина — у листа: в дереве он подписью, а не галочкой, и без этих слов
|
|
24
28
|
* читатель решил бы, что файл потерялся. */
|
|
25
29
|
notMeasuredRule: 'не измеряется: такой файл колонкой быть не может'
|
|
@@ -78,6 +82,8 @@ export const LOCALES = {
|
|
|
78
82
|
files: 'Files',
|
|
79
83
|
dir: 'all files in “{name}” ({n})',
|
|
80
84
|
dirNone: 'no file of “{name}” made it into the report ({n} in it)',
|
|
85
|
+
foldClose: 'collapse “{name}”',
|
|
86
|
+
foldOpen: 'expand “{name}”',
|
|
81
87
|
notMeasuredRule: 'not measured: such a file cannot be a column'
|
|
82
88
|
+ ' (built, a dependency lock, the report itself, or too large)',
|
|
83
89
|
notMeasuredChoice: 'not measured: it is not in the report’s set of columns'
|
package/src/page/app.css
CHANGED
|
@@ -71,12 +71,32 @@ 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
|
+
*
|
|
84
|
+
* Знак складки: место под него есть у каждой строки (отступ строки), а сам он
|
|
85
|
+
* только у папок — так листья и папки выстроены в один столбец. Он не часть
|
|
86
|
+
* галочки: галочка отвечает за числа, знак — за то, сколько дерева видно. */
|
|
87
|
+
.panel .tree li.folded > .tree { display: none; }
|
|
88
|
+
.panel .tree li { position: relative; }
|
|
89
|
+
.panel .tree li > .fold {
|
|
90
|
+
position: absolute;
|
|
91
|
+
left: -13px;
|
|
92
|
+
width: 14px;
|
|
93
|
+
text-align: center;
|
|
94
|
+
color: var(--muted);
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
user-select: none;
|
|
97
|
+
}
|
|
98
|
+
.panel .tree li > .fold:hover { color: var(--ink); }
|
|
99
|
+
|
|
80
100
|
/* Переключатель — метка вокруг поля ввода: и подпись, и цель нажатия одна, поэтому
|
|
81
101
|
* по нему попадает и мышь, и клавиатура (Space на поле ввода), и вспомогательные
|
|
82
102
|
* технологии. */
|
|
@@ -91,10 +111,12 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
91
111
|
.box:hover { background: var(--tint); }
|
|
92
112
|
.box input { margin: 0; accent-color: AccentColor; }
|
|
93
113
|
.box.all { font-weight: 600; }
|
|
94
|
-
/* Файл вне отчёта:
|
|
95
|
-
*
|
|
114
|
+
/* Файл или папка вне отчёта: галочка на месте, но снята и недоступна — чисел для
|
|
115
|
+
* него не измеряли, и переключать нечего. Строка приглушена и на мышь не
|
|
116
|
+
* отзывается: обещать нажатие нечем. */
|
|
96
117
|
.box.plain { cursor: default; opacity: .55; }
|
|
97
118
|
.box.plain:hover { background: none; }
|
|
119
|
+
.box.plain input { cursor: default; }
|
|
98
120
|
|
|
99
121
|
/* Расшифровки под деревом файлов нет намеренно: под списком она отодвигала
|
|
100
122
|
* числа, а её содержимое и так стоит рядом с тем, что объясняет, — цвет дельт
|
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
|
|
|
@@ -11,6 +11,12 @@ function appTable() {
|
|
|
11
11
|
const on = appView.files;
|
|
12
12
|
const files = [];
|
|
13
13
|
appData.files.forEach((f, i) => { if (on[i]) files.push(i); });
|
|
14
|
+
/* Колонки, которых коснулся последний коммит, идут впереди: отчёт пересобирается
|
|
15
|
+
* после каждого коммита, и первый вопрос читателя — что принесла эта правка. Внутри
|
|
16
|
+
* каждой части порядок прежний, из настроек: `sort` устойчив, а порядок колонок — это
|
|
17
|
+
* то, к чему читатель привык. Знак берётся из истории (её знает движок), а не из
|
|
18
|
+
* чисел: правка без изменения размера тоже правка. */
|
|
19
|
+
files.sort((a, b) => (appData.last[a] === true ? 0 : 1) - (appData.last[b] === true ? 0 : 1));
|
|
14
20
|
|
|
15
21
|
const table = document.getElementById('grid');
|
|
16
22
|
table.textContent = '';
|
|
@@ -76,6 +82,10 @@ if (appStart !== 'ours') {
|
|
|
76
82
|
const appSaved = appRead();
|
|
77
83
|
if (appSaved !== null) appApply(appSaved);
|
|
78
84
|
}
|
|
85
|
+
/* Сложенное дерево — память смотрящего, а не выбор читателя: она возвращается и
|
|
86
|
+
* тогда, когда открыта чужая ссылка (иначе присланная ссылка разложила бы дерево
|
|
87
|
+
* заново на каждом заходе). */
|
|
88
|
+
appFoldRead();
|
|
79
89
|
appRender(true);
|
|
80
90
|
appStartup = false;
|
|
81
91
|
appForeign = false;
|
package/src/page/build.js
CHANGED
package/src/page/dom.js
CHANGED
|
@@ -20,3 +20,17 @@ export function appBox(label, title, checked, onChange, cls) {
|
|
|
20
20
|
box.appendChild(appEl('span', null, label));
|
|
21
21
|
return box;
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
/* Галочка, которую нечем переключить: место в дереве есть, а включать нечего —
|
|
25
|
+
* файла нет в колонках, отчёт его не измеряет. Она снята и недоступна: так строка
|
|
26
|
+
* выглядит как все прочие (глаз сравнивает одно с одним), но видно, что это не
|
|
27
|
+
* «выключено читателем», а «не измеряется». Причина — во всплывающей строке. */
|
|
28
|
+
export function appOffBox(label, title, cls) {
|
|
29
|
+
const box = appBox(label, title, false, null, cls);
|
|
30
|
+
box.classList.add('plain');
|
|
31
|
+
box.querySelector('input').disabled = true;
|
|
32
|
+
/* Подсказка — на всей строке, а не только на поле: у недоступного поля браузер её
|
|
33
|
+
* не показывает, а причина читателю нужна именно здесь. */
|
|
34
|
+
box.title = title;
|
|
35
|
+
return box;
|
|
36
|
+
}
|
package/src/page/panel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { appEl, appBox } from './dom.js';
|
|
2
|
-
import { appData, appUi, appView, appFileAt, appMeasured } from './state.js';
|
|
1
|
+
import { appEl, appBox, appOffBox } from './dom.js';
|
|
2
|
+
import { appData, appUi, appView, appFileAt, appFoldSet, appMeasured } from './state.js';
|
|
3
3
|
|
|
4
4
|
/* Галочку файла ставит только файл: и категория, и папка в дереве — способы
|
|
5
5
|
* переставить те же галочки сразу группой, а своего состояния у них нет. Иначе
|
|
@@ -14,14 +14,12 @@ function appFileBox(i) {
|
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/* Файл, которого в отчёте нет: он стоит в дереве на своём месте, но
|
|
18
|
-
* него
|
|
19
|
-
* видит во всплывающей строке, а не догадывается по виду. */
|
|
17
|
+
/* Файл, которого в отчёте нет: он стоит в дереве на своём месте, но галочка у
|
|
18
|
+
* него снята и недоступна — чисел для него не измеряли, и переключать нечего.
|
|
19
|
+
* Причину читатель видит во всплывающей строке, а не догадывается по виду. */
|
|
20
20
|
function appUnmeasuredBox(entry) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
+ (entry.why === 'rule' ? appUi.notMeasuredRule : appUi.notMeasuredChoice);
|
|
24
|
-
return box;
|
|
21
|
+
return appOffBox(entry.path.split('/').pop(), entry.path + ' · '
|
|
22
|
+
+ (entry.why === 'rule' ? appUi.notMeasuredRule : appUi.notMeasuredChoice));
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/* Все измеряемые файлы поддерева — то, чем управляет переключатель папки: файл
|
|
@@ -48,15 +46,14 @@ function appNode() {
|
|
|
48
46
|
* состояния — все файлы включены, часть, ни одного. Рядом число файлов; если в
|
|
49
47
|
* папке есть и те, что вне отчёта, оно написано долей («2/5»): читателю важно, что
|
|
50
48
|
* в папке пять файлов, а измеряются два. Папка без единого измеряемого файла
|
|
51
|
-
*
|
|
49
|
+
* остаётся на месте, но её галочка снята и недоступна: включать в ней нечего. */
|
|
52
50
|
function appDirHead(name, sub) {
|
|
53
51
|
const idx = appIndexes(sub);
|
|
54
52
|
const total = appCount(sub);
|
|
55
53
|
const label = name + '/';
|
|
56
54
|
let head;
|
|
57
55
|
if (idx.length === 0) {
|
|
58
|
-
head =
|
|
59
|
-
head.title = appUi.dirNone.replace('{name}', name).replace('{n}', total);
|
|
56
|
+
head = appOffBox(label, appUi.dirNone.replace('{name}', name).replace('{n}', total), 'dir');
|
|
60
57
|
} else {
|
|
61
58
|
const on = idx.map((i) => appView.files[i]);
|
|
62
59
|
const every = on.every((v) => v);
|
|
@@ -71,6 +68,33 @@ function appDirHead(name, sub) {
|
|
|
71
68
|
return head;
|
|
72
69
|
}
|
|
73
70
|
|
|
71
|
+
/* Знак папки — своя цель нажатия, отдельная от галочки: галочка отвечает за числа
|
|
72
|
+
* (включает файлы поддерева), а знак — за то, сколько дерева видно. Одна цель на
|
|
73
|
+
* два разных действия означала бы, что сложить папку можно только вместе с
|
|
74
|
+
* включением её файлов. Знак нарисован спаном, а не кнопкой, потому что в строке
|
|
75
|
+
* папки рядом уже стоит метка-галочка, а вложенная в метку кнопка поднимала бы её
|
|
76
|
+
* же нажатие.
|
|
77
|
+
*
|
|
78
|
+
* Клик по знаку ничего не пересобирает: поддерево лежит в разметке, а прячет его
|
|
79
|
+
* класс на строке. Пересборка здесь была бы честной работой впустую — она считает
|
|
80
|
+
* таблицу целиком (в этом проекте — сотни строк на полторы сотни колонок), то есть
|
|
81
|
+
* платит за числа, которых складывание не меняет. Поэтому меняются только три
|
|
82
|
+
* вещи, которые читатель и видит: класс, знак и запись в памяти. */
|
|
83
|
+
function appFoldBox(name, path) {
|
|
84
|
+
const folded = appView.folded[path] === true;
|
|
85
|
+
const box = appEl('span', 'fold', folded ? '▸' : '▾');
|
|
86
|
+
box.title = (folded ? appUi.foldOpen : appUi.foldClose).replace('{name}', name);
|
|
87
|
+
box.addEventListener('click', () => {
|
|
88
|
+
const now = !(appView.folded[path] === true);
|
|
89
|
+
appFoldSet(path, now);
|
|
90
|
+
const li = box.closest('li');
|
|
91
|
+
if (li !== null) li.classList.toggle('folded', now);
|
|
92
|
+
box.textContent = now ? '▸' : '▾';
|
|
93
|
+
box.title = (now ? appUi.foldOpen : appUi.foldClose).replace('{name}', name);
|
|
94
|
+
});
|
|
95
|
+
return box;
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
/* Листья уровня: измеряемые файлы и файлы вне отчёта — вперемешку и по алфавиту
|
|
75
99
|
* имени, как в дереве файлов, а не отдельными списками. */
|
|
76
100
|
function appLeaves(node) {
|
|
@@ -81,22 +105,40 @@ function appLeaves(node) {
|
|
|
81
105
|
return items.sort((a, b) => (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)));
|
|
82
106
|
}
|
|
83
107
|
|
|
84
|
-
/*
|
|
85
|
-
*
|
|
86
|
-
function
|
|
108
|
+
/* Строка папки: знак складывания, галочка с числом файлов и поддерево. Сложенная
|
|
109
|
+
* папка отличается только классом — разметка одна и та же. */
|
|
110
|
+
function appDir(name, sub, prefix) {
|
|
111
|
+
const here = prefix === '' ? name : prefix + '/' + name;
|
|
112
|
+
const folded = appView.folded[here] === true;
|
|
113
|
+
const li = appEl('li', folded ? 'folded' : null);
|
|
114
|
+
li.appendChild(appFoldBox(name, here));
|
|
115
|
+
li.appendChild(appDirHead(name, sub));
|
|
116
|
+
li.appendChild(appTreeList(sub, here));
|
|
117
|
+
return li;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Строка листа: измеряемый файл — с галочкой, файл вне отчёта — со снятой.
|
|
121
|
+
function appLeaf(leaf) {
|
|
122
|
+
const li = appEl('li');
|
|
123
|
+
li.appendChild(leaf.entry === null ? appFileBox(leaf.i) : appUnmeasuredBox(leaf.entry));
|
|
124
|
+
return li;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Узлы одного уровня: сперва всё, что в отчёте (папки по алфавиту, затем листья —
|
|
128
|
+
* их порядок из `appLeaves`), потом то, чего в отчёте нет: папки без измеряемых
|
|
129
|
+
* файлов и файлы вне колонок. Так решено не из вкуса: у всего вне отчёта галочка
|
|
130
|
+
* снята и недоступна, и в конце списка оно не отвлекает от того, что в таблице,
|
|
131
|
+
* а найти его по-прежнему можно — оно там же, где было. */
|
|
132
|
+
function appTreeList(node, prefix) {
|
|
87
133
|
const list = appEl('ul', 'tree');
|
|
88
|
-
[...node.dirs.keys()].sort()
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const li = appEl('li');
|
|
97
|
-
li.appendChild(leaf.entry === null ? appFileBox(leaf.i) : appUnmeasuredBox(leaf.entry));
|
|
98
|
-
list.appendChild(li);
|
|
99
|
-
});
|
|
134
|
+
const dirs = [...node.dirs.keys()].sort()
|
|
135
|
+
.map((name) => ({ name: name, sub: node.dirs.get(name), inReport: appIndexes(node.dirs.get(name)).length > 0 }));
|
|
136
|
+
const leaves = appLeaves(node);
|
|
137
|
+
const inside = leaves.filter((leaf) => leaf.entry === null);
|
|
138
|
+
dirs.filter((d) => d.inReport).forEach((d) => list.appendChild(appDir(d.name, d.sub, prefix)));
|
|
139
|
+
inside.forEach((leaf) => list.appendChild(appLeaf(leaf)));
|
|
140
|
+
dirs.filter((d) => !d.inReport).forEach((d) => list.appendChild(appDir(d.name, d.sub, prefix)));
|
|
141
|
+
leaves.filter((leaf) => leaf.entry !== null).forEach((leaf) => list.appendChild(appLeaf(leaf)));
|
|
100
142
|
return list;
|
|
101
143
|
}
|
|
102
144
|
|
|
@@ -123,7 +165,7 @@ function appTree() {
|
|
|
123
165
|
appData.catalog.forEach((entry) => {
|
|
124
166
|
if (appMeasured[entry.path] === undefined) appLeafAt(root, entry.path, null, entry);
|
|
125
167
|
});
|
|
126
|
-
return appTreeList(root);
|
|
168
|
+
return appTreeList(root, '');
|
|
127
169
|
}
|
|
128
170
|
|
|
129
171
|
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
|
|
|
@@ -231,3 +231,44 @@ export function appApply(rec) {
|
|
|
231
231
|
appData.metrics.forEach((m) => { if (metrics[m.key] === false) appView.metrics[m.key] = false; });
|
|
232
232
|
appData.files.forEach((_f, i) => { if (files[appFileAt(i)] === false) appView.files[i] = false; });
|
|
233
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,8 +238,9 @@ 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
|
};
|
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`: дальше
|