mdt-module-builder 1.0.3 → 2.0.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 ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ Все заметные изменения пакета документируются в этом файле.
4
+
5
+ Формат — [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/), версии — [Semantic Versioning](https://semver.org/lang/ru/).
6
+ Правила major/minor/patch для этого пакета — см. README, раздел «Версионирование»: изменения
7
+ контракта (имя глобала, `SHARED_EXACT`, мажор React, источник Tailwind-классов модуля) — всегда
8
+ major, т.к. потребители фиксируют зависимость через `^1.x` и автоматически подхватят любой minor/patch.
9
+
10
+ ## [Unreleased]
11
+
12
+ ### Removed
13
+
14
+ - Билдер больше не собирает Tailwind CSS модуля (`generateModuleTailwindCss`, `loadIcsPreset` —
15
+ удалены из пакета). Модуль перестаёт вести собственную, независимую Tailwind-сборку — источник
16
+ Tailwind-классов теперь один: курируемый safelist core-сборки MDT (`ui/tailwind-safelist.mjs`).
17
+ Причина — MP-16147: две независимые Tailwind-сборки ломали гарантию порядка CSS-правил
18
+ (`base` перед `variant`), из-за чего голая утилита модуля могла перебить адаптивный вариант
19
+ ui-kit (`.h-10` модуля поверх `.lg:h-9` из `ics-ui-kit`).
20
+ - Зависимость `tailwindcss` убрана из `package.json` — больше не используется пакетом.
21
+
22
+ ### Changed
23
+
24
+ - **Ломающее изменение контракта.** Модуль, собранный этой версией, требует MDT с уже подключённым
25
+ курируемым Tailwind-safelist (`safelist` в core-сборке). Классы, которых нет в safelist,
26
+ физически не применятся в рантайме модуля — добавляются в MDT по мере реальной потребности
27
+ (см. `ui/build_tools/tailwind-inventory.md`).
28
+ - Модуль-специфичная стилизация — только обычный CSS (`import "./x.css"`, без изменений в этой
29
+ части — `postcss-nested` + `autoprefixer` как и раньше), не Tailwind-утилиты.
30
+
31
+ ## Версии до 2.0.0
32
+
33
+ Пакет публиковался (`npm run release`, без git-тега) без чейнджлога — история 1.0.0…1.0.4 не
34
+ восстановлена задним числом (нет надёжного источника: релизы не коммитятся в git). Ведение
35
+ чейнджлога начинается с этой версии.
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
  модуля, выдавая **тот же по контракту артефакт**: один `dist/index.js` (IIFE), который при загрузке
5
5
  через `<script>` самовыполняется и зовёт `window.MDT.registerModule`.
6
6
 
7
+ История изменений — [CHANGELOG.md](./CHANGELOG.md).
8
+
7
9
  ## Использование
8
10
 
9
11
  В `package.json` модуля:
@@ -37,13 +39,40 @@ devtool/builder вызывают `npm run build` в репе модуля — м
37
39
  компоненты модуля читали бы чужой `ThemeContext`. CSS-сабпасы (`styles.css`) не externalize-ятся.
38
40
  - **lucide-react, mdt-client** — бандлятся внутрь `index.js` (должны быть установлены).
39
41
  Их внутренний `import "react"` тоже перехватывается шимом.
42
+ Почему lucide бандлится, а ui-kit шарится (решение зафиксировано, не пересматривать без новых данных):
43
+ ui-kit **обязан** быть общим по корректности — у него React-контекст (тема, провайдеры), две копии =
44
+ сломанная тема. Иконки lucide — stateless SVG без контекста, дубли безвредны. При этом lucide импортят
45
+ именованно из корня (`import { X } from "lucide-react"`), так что шаринг = отдать весь namespace →
46
+ MDT забандлил бы **все ~1900 иконок без tree-shaking**. Per-module бандл, наоборот, tree-shaking-ом
47
+ тянет только used-иконки (единицы, ~КБ). Пара дублей по модулям несравнимо дешевле, чем вся lucide в MDT.
40
48
  - **CSS** инлайнится в `index.js` (рантайм-инъекция `<style>`), как старый style-loader —
41
49
  оверлей копирует только `index.js`, отдельного `.css` нет.
42
- - **Tailwind (вариант A)** билдер сам генерит утилиты Tailwind, найденные **только в `src`
43
- модуля**, и подмешивает их в ту же инъекцию CSS. Модулю не нужен ни `tailwindcss`, ни конфиг:
44
- пресет берётся из `ics-ui-kit` модуля, `preflight` выключен, базовый reset и токены
45
- (`--primary` и пр.) даёт MDT глобально. ui-kit из `node_modules` повторно не сканируется —
46
- его классы уже эмитит основная сборка MDT. Возможны дубли утилит между модулями — это принято.
50
+ - **Tailwind модуль больше не собирает** версии 2.0.0, MP-16147). Раньше билдер сам генерил
51
+ утилиты Tailwind из `src` модуля второй, независимой сборкой это ломало гарантию порядка
52
+ правил (`base` перед `variant`), на которой держится mobile-first: голая утилита модуля могла
53
+ перебить вариант ui-kit (`.h-10` модуля поверх `.lg:h-9` из `ics-ui-kit`). Теперь Tailwind-классы
54
+ модулю даёт единственная, core-сборка MDT по курируемому `tailwind-safelist.mjs`
55
+ (собран из реально используемых модулями классов, см. `tailwind-inventory.mjs`). Класс, которого
56
+ нет в safelist, физически не применится в рантайме — не задача сборки, а несовпадение safelist;
57
+ добавляется в MDT по мере реальной потребности. Модуль-специфичная стилизация — обычный CSS
58
+ (`import "./x.css"`, см. ниже), не Tailwind.
59
+
60
+ ## Совместимость исходников модуля (webpack → Vite)
61
+
62
+ Ядро воспроизводит удобства старого (webpack) билдера, чтобы исходники модуля не переписывать:
63
+
64
+ - **tsconfig `paths` → `resolve.alias`.** Билдер читает `compilerOptions.paths`/`baseUrl` из
65
+ `tsconfig.json` модуля и разворачивает в alias'ы Vite (аналог `tsconfig-paths-webpack-plugin`).
66
+ Импорты вида `import x from "_core/scripts/..."` резолвятся без правок. Берётся первый target
67
+ маппинга; `prefix/*` → wildcard с захватом хвоста, точный ключ → строковый алиас.
68
+ - **CSS-вложенность.** Компонентный CSS модуля (`import "./x.css"`) прогоняется через
69
+ `postcss-nested` (Sass-подобный `&`) + `autoprefixer` — та же семантика, что у старого
70
+ `postcss-loader`. Native CSS nesting не задействуется.
71
+ - **`.svg` как строка.** SVG импортится встроенным Vite-суффиксом `?raw`
72
+ (`import logo from "./logo.svg?raw"`) → строка с разметкой, инлайнится, без отдельного ассет-файла.
73
+ Байт-в-байт как старый `svg-inline-loader`. Билдер SVG специально не обрабатывает: **bare**-импорт
74
+ `.svg` без `?raw` уедет в отдельный файл, который контракт модуля (копируется только `index.js`)
75
+ потеряет — всегда с `?raw`.
47
76
 
48
77
  ## Доступ к функционалу MDT — `@mdt/*`
49
78
 
@@ -93,18 +122,25 @@ MDT-сторона контракта — `ui/vite-app/src/legacy/config-global.
93
122
  SemVer пакета относительно платформы:
94
123
 
95
124
  - **major** — смена контракта: имя глобала, набор `SHARED_EXACT`, **мажор React** (обновляются
96
- `SHARED_NAMES`). Модули осознанно поднимают `mdt-module-builder` и проверяют совместимость.
125
+ `SHARED_NAMES`), или изменение источника Tailwind-классов модуля. Модули осознанно поднимают
126
+ `mdt-module-builder` и проверяют совместимость.
97
127
  - **minor/patch** — внутренние правки сборки; выходной артефакт остаётся совместимым.
98
128
 
99
129
  | mdt-module-builder | React | Платформа MDT |
100
130
  |---|---|---|
101
131
  | 1.x | 19 | (заполнить при первом релизе) |
132
+ | 2.x | 19 | требует MDT с курируемым Tailwind-safelist (`ui/tailwind-safelist.mjs` в `safelist` core-сборки, MP-16147) — модуль больше не собирает свой Tailwind |
102
133
 
103
134
  ## Публикация
104
135
 
105
136
  Публикуется **вручную** в публичный npm (как `mdt-client`), автопубликации в CI нет.
106
- Версию руками в `package.json` не правим — скрипт `release` сам поднимает patch (без git-тега)
107
- и публикует:
137
+
138
+ Перед любым релизом — перенеси `## [Unreleased]` в [CHANGELOG.md](./CHANGELOG.md) в датированную
139
+ секцию `## [x.y.z] - YYYY-MM-DD` (формат [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/)),
140
+ оставив пустой `## [Unreleased]` сверху для следующих изменений.
141
+
142
+ Версию руками в `package.json` не правим для patch — скрипт `release` сам поднимает patch (без
143
+ git-тега) и публикует:
108
144
 
109
145
  ```
110
146
  cd ui/module-builder
@@ -1,119 +1,119 @@
1
- # Общие зависимости модулей: вынос в MDT
2
-
3
- Когда внешнему модулю нужна библиотека, которую логично держать **одним инстансом на всё
4
- приложение** (или не таскать её копию в каждый модуль), она «выносится» в MDT: MDT ставит её,
5
- экспонирует через `globalThis.__mdtShared`, а сборка модуля её **не бандлит**, а читает оттуда.
6
- Ровно так уже сделано для react-семьи; этот документ — про добавление новой такой зависимости.
7
-
8
- ---
9
-
10
- ## Шаг 0. Решить: выносить или бандлить
11
-
12
- Выносим, если выполняется хотя бы одно:
13
-
14
- - **Нужен единый инстанс/контекст.** Несколько копий ломаются: react (hooks), `@tanstack/react-query`
15
- (общий `QueryClient`/контекст). → выносить **обязательно**.
16
- - **Тяжёлая.** `echarts` ~1 МБ — копия в каждом модуле недопустима. → выносим ради веса.
17
- - **Нужна ≥ 2 модулям.** Дедуп.
18
-
19
- Если ничего из этого — **не выносим**. Просто импортируй в модуле, она забандлится внутрь. Ничего
20
- делать не надо.
21
-
22
- ---
23
-
24
- ## План выноса (по шагам)
25
-
26
- Каждый шаг — действие и явное «Готово, когда». Не переходи к следующему, пока текущее не закрыто.
27
-
28
- ### Шаг 1. Поставить зависимость в MDT
29
-
30
- ```
31
- cd ui && npm i <dep>@<версия>
32
- ```
33
-
34
- Версия, которую ты зафиксировал в MDT, **становится контрактом** — модули будут пользоваться
35
- этим мажором.
36
-
37
- > **Готово, когда:** `<dep>` стоит в `ui/package.json → dependencies`.
38
-
39
- ### Шаг 2. Экспонировать в `__mdtShared`
40
-
41
- В `ui/vite-app/src/legacy/config-global.ts`:
42
-
43
- ```ts
44
- import * as _Dep from "<dep>";
45
- // ...
46
- (globalThis as any).__mdtShared = Object.assign((globalThis as any).__mdtShared ?? {}, {
47
- // ...уже существующие...
48
- "<dep>": _Dep
49
- });
50
- ```
51
-
52
- Ключ — **точный спецификатор импорта**. Для каждого подпути, который импортируют модули
53
- (`<dep>/sub`, например `zustand/middleware`), — отдельный `import * as` и отдельный ключ.
54
-
55
- > **Готово, когда:** в рантайме `globalThis.__mdtShared["<dep>"]` возвращает библиотеку.
56
-
57
- ### Шаг 3. Externalize в билдере
58
-
59
- В `ui/module-builder/src/index.mjs` добавь спецификатор (и каждый подпуть) в массив `SHARED_GLOBALS`.
60
-
61
- > **Готово, когда:** сборка модуля ссылается на `globalThis.__mdtShared["<dep>"]`, а кода библиотеки
62
- > в `dist/index.js` нет. Имена экспортов перечислять **не нужно** — Rollup обращается к членам глобала сам.
63
-
64
- ### Шаг 4. Доставить билдер в CI модуля
65
-
66
- Dev-стенд MDT собирает модуль **локальным** исходником билдера → новый `SHARED_GLOBALS` работает
67
- сразу. Для `npm run build` в репозитории модуля нужен **опубликованный** `mdt-module-builder` с этим
68
- изменением:
69
-
70
- ```
71
- cd ui/module-builder && npm publish # вручную, как и весь mdt-module-builder
72
- ```
73
-
74
- > **Готово, когда:** в репо модуля стоит версия `mdt-module-builder`, где зависимость уже в `SHARED_GLOBALS`.
75
-
76
- ### Шаг 5. Подключить в модуле (только типы)
77
-
78
- ```
79
- npm i -D <dep>@<версия> # в репозитории модуля; тот же мажор, что в MDT
80
- ```
81
-
82
- Импортируй и пользуйся как обычно — в бандл библиотека не попадёт, тип берётся из devDependency.
83
-
84
- > **Готово, когда:** типы резолвятся, импорт компилируется.
85
-
86
- ### Шаг 6. Проверить
87
-
88
- Собери модуль и убедись:
89
-
90
- - вес `dist/index.js` **не вырос** на размер библиотеки;
91
- - в нём есть `globalThis.__mdtShared["<dep>"]` и **нет** кода библиотеки.
92
-
93
- > **Готово, когда:** оба пункта выполнены.
94
-
95
- ---
96
-
97
- ## Подводные камни
98
-
99
- - **Версия — это контракт.** Мажор в MDT и в модуле должен совпадать (как с react). Рассинхрон
100
- даёт трудноуловимые поломки (аналог «Invalid hook call»).
101
- - **Подпуть — отдельный спецификатор.** `zustand/middleware` ≠ `zustand`. Каждый используемый
102
- подпуть нужно добавить и в `__mdtShared` (Шаг 2), и в `SHARED_GLOBALS` (Шаг 3).
103
- - **`@tanstack/react-query`: единого инстанса мало.** Модуль должен обернуть своё дерево в
104
- `<QueryClientProvider client={...}>`. Контекст работает именно потому, что инстанс один; сам
105
- провайдер — ответственность модуля (каждый React-root монтируется отдельно через
106
- `wrapComponent`/`createPageControl`).
107
- - **Default-импорт** (`import X from "echarts-for-react"`) работает через стандартный interop
108
- Rollup; после выноса проверь в рантайме, что `X` — это сам компонент, а не неймспейс.
109
- - **react-семья — особый, исторический случай** (alias-шимы `SHARED_EXACT`/`SHARED_NAMES` с
110
- перечислением имён). **Новые** зависимости добавляй в `SHARED_GLOBALS` — там имена перечислять не надо.
111
-
112
- ---
113
-
114
- ## Текущий список общих зависимостей
115
-
116
- - **react-семья** (`SHARED_EXACT`, alias-шимы): `react`, `react-dom`, `react-dom/client`,
117
- `react/jsx-runtime`, `react/jsx-dev-runtime`.
118
- - **Externalize** (`SHARED_GLOBALS`): `@tanstack/react-query`, `zustand`, `zustand/middleware`,
119
- `zustand/react/shallow`, `echarts`, `echarts-for-react`.
1
+ # Общие зависимости модулей: вынос в MDT
2
+
3
+ Когда внешнему модулю нужна библиотека, которую логично держать **одним инстансом на всё
4
+ приложение** (или не таскать её копию в каждый модуль), она «выносится» в MDT: MDT ставит её,
5
+ экспонирует через `globalThis.__mdtShared`, а сборка модуля её **не бандлит**, а читает оттуда.
6
+ Ровно так уже сделано для react-семьи; этот документ — про добавление новой такой зависимости.
7
+
8
+ ---
9
+
10
+ ## Шаг 0. Решить: выносить или бандлить
11
+
12
+ Выносим, если выполняется хотя бы одно:
13
+
14
+ - **Нужен единый инстанс/контекст.** Несколько копий ломаются: react (hooks), `@tanstack/react-query`
15
+ (общий `QueryClient`/контекст). → выносить **обязательно**.
16
+ - **Тяжёлая.** `echarts` ~1 МБ — копия в каждом модуле недопустима. → выносим ради веса.
17
+ - **Нужна ≥ 2 модулям.** Дедуп.
18
+
19
+ Если ничего из этого — **не выносим**. Просто импортируй в модуле, она забандлится внутрь. Ничего
20
+ делать не надо.
21
+
22
+ ---
23
+
24
+ ## План выноса (по шагам)
25
+
26
+ Каждый шаг — действие и явное «Готово, когда». Не переходи к следующему, пока текущее не закрыто.
27
+
28
+ ### Шаг 1. Поставить зависимость в MDT
29
+
30
+ ```
31
+ cd ui && npm i <dep>@<версия>
32
+ ```
33
+
34
+ Версия, которую ты зафиксировал в MDT, **становится контрактом** — модули будут пользоваться
35
+ этим мажором.
36
+
37
+ > **Готово, когда:** `<dep>` стоит в `ui/package.json → dependencies`.
38
+
39
+ ### Шаг 2. Экспонировать в `__mdtShared`
40
+
41
+ В `ui/vite-app/src/legacy/config-global.ts`:
42
+
43
+ ```ts
44
+ import * as _Dep from "<dep>";
45
+ // ...
46
+ (globalThis as any).__mdtShared = Object.assign((globalThis as any).__mdtShared ?? {}, {
47
+ // ...уже существующие...
48
+ "<dep>": _Dep
49
+ });
50
+ ```
51
+
52
+ Ключ — **точный спецификатор импорта**. Для каждого подпути, который импортируют модули
53
+ (`<dep>/sub`, например `zustand/middleware`), — отдельный `import * as` и отдельный ключ.
54
+
55
+ > **Готово, когда:** в рантайме `globalThis.__mdtShared["<dep>"]` возвращает библиотеку.
56
+
57
+ ### Шаг 3. Externalize в билдере
58
+
59
+ В `ui/module-builder/src/index.mjs` добавь спецификатор (и каждый подпуть) в массив `SHARED_GLOBALS`.
60
+
61
+ > **Готово, когда:** сборка модуля ссылается на `globalThis.__mdtShared["<dep>"]`, а кода библиотеки
62
+ > в `dist/index.js` нет. Имена экспортов перечислять **не нужно** — Rollup обращается к членам глобала сам.
63
+
64
+ ### Шаг 4. Доставить билдер в CI модуля
65
+
66
+ Dev-стенд MDT собирает модуль **локальным** исходником билдера → новый `SHARED_GLOBALS` работает
67
+ сразу. Для `npm run build` в репозитории модуля нужен **опубликованный** `mdt-module-builder` с этим
68
+ изменением:
69
+
70
+ ```
71
+ cd ui/module-builder && npm publish # вручную, как и весь mdt-module-builder
72
+ ```
73
+
74
+ > **Готово, когда:** в репо модуля стоит версия `mdt-module-builder`, где зависимость уже в `SHARED_GLOBALS`.
75
+
76
+ ### Шаг 5. Подключить в модуле (только типы)
77
+
78
+ ```
79
+ npm i -D <dep>@<версия> # в репозитории модуля; тот же мажор, что в MDT
80
+ ```
81
+
82
+ Импортируй и пользуйся как обычно — в бандл библиотека не попадёт, тип берётся из devDependency.
83
+
84
+ > **Готово, когда:** типы резолвятся, импорт компилируется.
85
+
86
+ ### Шаг 6. Проверить
87
+
88
+ Собери модуль и убедись:
89
+
90
+ - вес `dist/index.js` **не вырос** на размер библиотеки;
91
+ - в нём есть `globalThis.__mdtShared["<dep>"]` и **нет** кода библиотеки.
92
+
93
+ > **Готово, когда:** оба пункта выполнены.
94
+
95
+ ---
96
+
97
+ ## Подводные камни
98
+
99
+ - **Версия — это контракт.** Мажор в MDT и в модуле должен совпадать (как с react). Рассинхрон
100
+ даёт трудноуловимые поломки (аналог «Invalid hook call»).
101
+ - **Подпуть — отдельный спецификатор.** `zustand/middleware` ≠ `zustand`. Каждый используемый
102
+ подпуть нужно добавить и в `__mdtShared` (Шаг 2), и в `SHARED_GLOBALS` (Шаг 3).
103
+ - **`@tanstack/react-query`: единого инстанса мало.** Модуль должен обернуть своё дерево в
104
+ `<QueryClientProvider client={...}>`. Контекст работает именно потому, что инстанс один; сам
105
+ провайдер — ответственность модуля (каждый React-root монтируется отдельно через
106
+ `wrapComponent`/`createPageControl`).
107
+ - **Default-импорт** (`import X from "echarts-for-react"`) работает через стандартный interop
108
+ Rollup; после выноса проверь в рантайме, что `X` — это сам компонент, а не неймспейс.
109
+ - **react-семья — особый, исторический случай** (alias-шимы `SHARED_EXACT`/`SHARED_NAMES` с
110
+ перечислением имён). **Новые** зависимости добавляй в `SHARED_GLOBALS` — там имена перечислять не надо.
111
+
112
+ ---
113
+
114
+ ## Текущий список общих зависимостей
115
+
116
+ - **react-семья** (`SHARED_EXACT`, alias-шимы): `react`, `react-dom`, `react-dom/client`,
117
+ `react/jsx-runtime`, `react/jsx-dev-runtime`.
118
+ - **Externalize** (`SHARED_GLOBALS`): `@tanstack/react-query`, `zustand`, `zustand/middleware`,
119
+ `zustand/react/shallow`, `echarts`, `echarts-for-react`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdt-module-builder",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
4
  "description": "Сборщик внешних UI-модулей MDT (Vite).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,7 @@
17
17
  "src",
18
18
  "bin",
19
19
  "README.md",
20
+ "CHANGELOG.md",
20
21
  "SHARED-DEPENDENCIES.md"
21
22
  ],
22
23
  "engines": {
@@ -25,7 +26,7 @@
25
26
  "dependencies": {
26
27
  "autoprefixer": "^10.4.0",
27
28
  "postcss": "^8.4.0",
28
- "tailwindcss": "^3.4.0",
29
+ "postcss-nested": "^6.2.0",
29
30
  "vite": "^6.0.0"
30
31
  },
31
32
  "keywords": [
package/src/index.mjs CHANGED
@@ -12,8 +12,10 @@
12
12
  import * as fs from "node:fs";
13
13
  import * as os from "node:os";
14
14
  import * as path from "node:path";
15
- import { createRequire } from "node:module";
16
- import { pathToFileURL } from "node:url";
15
+ // postcss-плагины для компонентного CSS модуля (см. css.postcss в moduleViteConfig). Оба —
16
+ // прямые зависимости пакета, поэтому импортим статически (нужны только при реальной сборке).
17
+ import autoprefixer from "autoprefixer";
18
+ import postcssNested from "postcss-nested";
17
19
 
18
20
  // Спецификаторы, которые резолвятся на единственную копию MDT через window.__mdtShared.
19
21
  export const SHARED_EXACT = ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "react/jsx-dev-runtime"];
@@ -53,10 +55,9 @@ export const SHARED_NAMES = {
53
55
  react: "Activity,Children,Component,Fragment,Profiler,PureComponent,StrictMode,Suspense,act,cache,cacheSignal,captureOwnerStack,cloneElement,createContext,createElement,createRef,forwardRef,isValidElement,lazy,memo,startTransition,use,useActionState,useCallback,useContext,useDebugValue,useDeferredValue,useEffect,useEffectEvent,useId,useImperativeHandle,useInsertionEffect,useLayoutEffect,useMemo,useOptimistic,useReducer,useRef,useState,useSyncExternalStore,useTransition,version".split(
54
56
  ","
55
57
  ),
56
- "react-dom":
57
- "createPortal,flushSync,preconnect,prefetchDNS,preinit,preinitModule,preload,preloadModule,requestFormReset,unstable_batchedUpdates,useFormState,useFormStatus,version".split(
58
- ","
59
- ),
58
+ "react-dom": "createPortal,flushSync,preconnect,prefetchDNS,preinit,preinitModule,preload,preloadModule,requestFormReset,unstable_batchedUpdates,useFormState,useFormStatus,version".split(
59
+ ","
60
+ ),
60
61
  "react-dom/client": ["createRoot", "hydrateRoot", "version"],
61
62
  "react/jsx-runtime": ["Fragment", "jsx", "jsxs"],
62
63
  "react/jsx-dev-runtime": ["Fragment", "jsxDEV"]
@@ -89,6 +90,50 @@ function sharedAliases(shimDir) {
89
90
  }));
90
91
  }
91
92
 
93
+ // Толерантный парсер tsconfig (JSONC): вырезаем // и /* */ комментарии (не трогая их внутри строк)
94
+ // и висячие запятые, затем JSON.parse.
95
+ function parseJsonc(text) {
96
+ const noComments = text.replace(/("(?:\\.|[^"\\])*")|\/\/[^\n\r]*|\/\*[\s\S]*?\*\//g, (_m, str) =>
97
+ str ? str : ""
98
+ );
99
+ return JSON.parse(noComments.replace(/,(\s*[}\]])/g, "$1"));
100
+ }
101
+
102
+ function escapeRegExp(s) {
103
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
104
+ }
105
+
106
+ // Разворачивает tsconfig `compilerOptions.paths` модуля в resolve.alias — аналог
107
+ // tsconfig-paths-webpack-plugin старого (webpack) билдера. Без этого Vite не резолвит алиасы вида
108
+ // "_core/*", которыми пользуется код модуля. Берём первый target маппинга (как tsconfig-paths по
109
+ // умолчанию); wildcard `prefix/*` → regex с захватом хвоста, точный ключ → строковый алиас.
110
+ function tsconfigAliases(root) {
111
+ let json;
112
+ try {
113
+ json = parseJsonc(fs.readFileSync(path.join(root, "tsconfig.json"), "utf8"));
114
+ } catch {
115
+ return [];
116
+ }
117
+ const co = json?.compilerOptions ?? {};
118
+ const paths = co.paths ?? {};
119
+ const baseDir = path.resolve(root, co.baseUrl ?? ".");
120
+ const aliases = [];
121
+ for (const [pattern, targets] of Object.entries(paths)) {
122
+ const target = Array.isArray(targets) ? targets[0] : undefined;
123
+ if (!target) continue;
124
+ if (pattern.endsWith("/*") && target.endsWith("/*")) {
125
+ const replBase = path.resolve(baseDir, target.slice(0, -2)).replace(/\\/g, "/");
126
+ aliases.push({
127
+ find: new RegExp(`^${escapeRegExp(pattern.slice(0, -2))}/(.*)$`),
128
+ replacement: `${replBase}/$1`
129
+ });
130
+ } else {
131
+ aliases.push({ find: pattern, replacement: path.resolve(baseDir, target) });
132
+ }
133
+ }
134
+ return aliases;
135
+ }
136
+
92
137
  // Публичный SDK MDT: спецификаторы `@mdt/<имя>` отдаются модулю как externalize. Их экспорты
93
138
  // кладёт MDT в globalThis.__mdtSdk — по одному ключу на файл папки ui/mdt-sdk (см. _register.ts).
94
139
  // Добавить публичный API = создать файл в ui/mdt-sdk; перечислять имена/спецификаторы не нужно.
@@ -102,45 +147,16 @@ function globalRef(id) {
102
147
  : `globalThis.__mdtShared[${JSON.stringify(id)}]`;
103
148
  }
104
149
 
105
- // Tailwind для модуля (вариант A): билдер сам генерит утилиты ТОЛЬКО из исходников модуля.
106
- // preflight и токены даёт MDT глобально, поэтому здесь preflight выключен и эмитятся лишь
107
- // `@tailwind utilities`. Пресет берём из ics-ui-kit самого модуля (фолбэк — ics-ui-kit билдера,
108
- // чтобы dev-стенд работал даже до npm install в модуле) → те же утилиты/токены, что в MDT.
109
- async function loadIcsPreset(root) {
110
- for (const base of [path.join(root, "package.json"), import.meta.url]) {
111
- try {
112
- const resolved = createRequire(base).resolve("ics-ui-kit/tailwind.preset");
113
- const mod = await import(pathToFileURL(resolved).href);
114
- return mod.default ?? mod;
115
- } catch {
116
- // пробуем следующий источник
117
- }
118
- }
119
- return undefined;
120
- }
121
-
122
- // Генерит CSS утилит Tailwind, найденных в исходниках модуля. Зависимости подключаем лениво:
123
- // они есть у MDT (dev) и у mdt-module-builder (CI модуля), но не нужны для `--list`/импорта ядра.
124
- async function generateModuleTailwindCss(root) {
125
- const [{ default: postcss }, { default: tailwindcss }, { default: autoprefixer }] = await Promise.all([
126
- import("postcss"),
127
- import("tailwindcss"),
128
- import("autoprefixer")
129
- ]);
130
- const content = path.join(root, "src").replace(/\\/g, "/") + "/**/*.{ts,tsx,js,jsx,html}";
131
- const preset = await loadIcsPreset(root);
132
- const config = { presets: preset ? [preset] : [], content: [content], corePlugins: { preflight: false } };
133
- const { css } = await postcss([tailwindcss(config), autoprefixer()]).process("@tailwind utilities;\n", { from: undefined });
134
- return css;
135
- }
136
-
137
150
  // Инлайнит собранный CSS в JS-чанк (рантайм-инъекция <style>) — как старый webpack/style-loader:
138
- // оверлей копирует только index.js, отдельного css-файла нет. Сюда же подмешиваем утилиты Tailwind.
139
- function cssInjectPlugin(root) {
151
+ // оверлей копирует только index.js, отдельного css-файла нет. Модуль больше не собирает свой
152
+ // Tailwind (MP-16147, Шаг 3): здесь только компонентный CSS модуля (import "./x.css",
153
+ // postcss-nested + autoprefixer, см. css.postcss в moduleViteConfig). Tailwind-утилиты модулю
154
+ // больше не нужны — они приходят из курируемого safelist core-сборки MDT (ui/tailwind-safelist.mjs).
155
+ function cssInjectPlugin() {
140
156
  return {
141
157
  name: "mdt-css-inject",
142
158
  enforce: "post",
143
- async generateBundle(_opts, bundle) {
159
+ generateBundle(_opts, bundle) {
144
160
  let css = "";
145
161
  for (const [file, chunk] of Object.entries(bundle)) {
146
162
  if (chunk.type === "asset" && file.endsWith(".css")) {
@@ -148,7 +164,6 @@ function cssInjectPlugin(root) {
148
164
  delete bundle[file];
149
165
  }
150
166
  }
151
- css += await generateModuleTailwindCss(root);
152
167
  if (!css) return;
153
168
  const inject = `(function(){try{var s=document.createElement("style");s.textContent=${JSON.stringify(
154
169
  css
@@ -171,8 +186,28 @@ export function moduleViteConfig({ root, entry, outDir, watch = false, name = "_
171
186
  configFile: false,
172
187
  root,
173
188
  logLevel: "warn",
174
- resolve: { alias: sharedAliases(shimDir) },
175
- plugins: [cssInjectPlugin(root)],
189
+ // Библиотеки, которые бандлятся ВНУТРЬ модуля (react-virtual и др.), метят dev-ветки через
190
+ // `process.env.NODE_ENV` — де-факто стандарт npm (так же устроен entry самого React). Это
191
+ // маркер для сборщика, а не рантайм-чтение: он должен подставить литерал и стрипнуть ветку.
192
+ // В lib-режиме Vite такую подстановку сознательно пропускает (`isBuildLib`,
193
+ // src/node/plugins/define.ts) — рассчитывает, что артефакт дособерёт бандлер потребителя.
194
+ // У нас его нет: MDT грузит index.js как <script>, а `process` в браузере не существует →
195
+ // ReferenceError в момент первого исполнения dev-ветки (сам модуль при этом грузится
196
+ // нормально — проверки лежат внутри функций). Делаем подстановку явно: NODE_ENV="production" (стрипает
197
+ // dev-ветки), плюс `process.env` → {} как фолбэк для прочих `process.env.X`.
198
+ // Ключи Vite матчит по длине (NODE_ENV раньше env).
199
+ define: {
200
+ "process.env.NODE_ENV": JSON.stringify("production"),
201
+ "process.env": "{}"
202
+ },
203
+ // react/*-шимы + tsconfig `paths` модуля (напр. "_core/*") — иначе Vite их не резолвит.
204
+ resolve: { alias: [...sharedAliases(shimDir), ...tsconfigAliases(root)] },
205
+ plugins: [cssInjectPlugin()],
206
+ // Компонентный CSS модуля (import "./x.css") гоняем через postcss-nested (Sass-подобная
207
+ // вложенность `&`, как старый webpack builder) + autoprefixer. Инлайн-инъекцию делает
208
+ // cssInjectPlugin. Tailwind-утилиты модуль больше не собирает (MP-16147, Шаг 3) — приходят
209
+ // из курируемого safelist core-сборки MDT.
210
+ css: { postcss: { plugins: [postcssNested(), autoprefixer()] } },
176
211
  build: {
177
212
  outDir,
178
213
  emptyOutDir: true,
@@ -182,11 +217,12 @@ export function moduleViteConfig({ root, entry, outDir, watch = false, name = "_
182
217
  // externalize (инстанс даёт MDT через globalThis.__mdtShared, в модуль НЕ бандлятся): SHARED_GLOBALS
183
218
  // (точные спецификаторы) + ics-ui-kit с сабпасами (SHARED_PREFIXES). react/* — alias-шимы; lucide бандлится.
184
219
  external: (id) => SHARED_GLOBALS.includes(id) || isSharedPrefix(id) || id.startsWith(MDT_SDK_PREFIX),
185
- // host кладёт значения в __mdtShared/__mdtSdk с флагом __esModule, поэтому
186
- // interop модуля (__toESM) корректно отдаёт `.default` для default-импорта
187
- // (иначе default-импорт, напр. echarts-for-react, превращается в объект-неймспейс React
188
- // «Element type is invalid got: object»).
189
- output: { globals: globalRef }
220
+ // host кладёт значения в __mdtShared/__mdtSdk с флагом __esModule, поэтому
221
+ // interop берёт `.default` для default-импорта. rolldown (dev) делает это сам (__toESM),
222
+ // а Rollup (Vite 6, CI модуля) только с output.interop:"auto"; без него default-импорт
223
+ // биндится ко всему объекту-неймспейсу вместо .default (MDT.Navigation === undefined,
224
+ // echarts-for-react → «Element type is invalid … got: object»).
225
+ output: { globals: globalRef, interop: "auto" }
190
226
  },
191
227
  lib: {
192
228
  entry,