mdt-module-builder 1.0.1 → 1.0.2
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/README.md +105 -71
- package/SHARED-DEPENDENCIES.md +119 -0
- package/bin/cli.mjs +33 -33
- package/package.json +38 -34
- package/src/index.mjs +186 -117
package/README.md
CHANGED
|
@@ -1,71 +1,105 @@
|
|
|
1
|
-
# mdt-module-builder
|
|
2
|
-
|
|
3
|
-
Сборщик внешнего UI-модуля MDT билдером MDT (Vite). Заменяет webpack/ics-builder в `npm run build`
|
|
4
|
-
модуля, выдавая **тот же по контракту артефакт**: один `dist/index.js` (IIFE), который при загрузке
|
|
5
|
-
через `<script>` самовыполняется и зовёт `window.MDT.registerModule`.
|
|
6
|
-
|
|
7
|
-
## Использование
|
|
8
|
-
|
|
9
|
-
В `package.json` модуля:
|
|
10
|
-
|
|
11
|
-
```json
|
|
12
|
-
{
|
|
13
|
-
"scripts": { "build": "mdt-module-build" },
|
|
14
|
-
"devDependencies": { "mdt-module-builder": "^1.0.0" },
|
|
15
|
-
"dependencies": { "mdt-client": "^31", "ics-ui-kit": "...", "lucide-react": "..." },
|
|
16
|
-
"peerDependencies": { "react": "^19", "react-dom": "^19" }
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Entry — `src/index.ts` (переопределяется `"mdt": { "entry": "..." }`). Сборка: `npm run build` → `dist/index.js`.
|
|
21
|
-
|
|
22
|
-
devtool/builder вызывают `npm run build` в репе модуля — менять их не нужно, контракт `dist/` соблюдён.
|
|
23
|
-
|
|
24
|
-
## Дедуп (вариант B)
|
|
25
|
-
|
|
26
|
-
- **react, react-dom, react-dom/client, react/jsx-runtime, react/jsx-dev-runtime** — НЕ бандлятся.
|
|
27
|
-
Заменяются alias-шимами, читающими `globalThis.__mdtShared[spec]` (MDT ставит его при старте).
|
|
28
|
-
→ один инстанс React во всём рантайме, нет «Invalid hook call».
|
|
29
|
-
→ react/react-dom **не обязаны быть установлены** при сборке (имена их экспортов зашиты в пакет).
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
##
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
1
|
+
# mdt-module-builder
|
|
2
|
+
|
|
3
|
+
Сборщик внешнего UI-модуля MDT билдером MDT (Vite). Заменяет webpack/ics-builder в `npm run build`
|
|
4
|
+
модуля, выдавая **тот же по контракту артефакт**: один `dist/index.js` (IIFE), который при загрузке
|
|
5
|
+
через `<script>` самовыполняется и зовёт `window.MDT.registerModule`.
|
|
6
|
+
|
|
7
|
+
## Использование
|
|
8
|
+
|
|
9
|
+
В `package.json` модуля:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"scripts": { "build": "mdt-module-build" },
|
|
14
|
+
"devDependencies": { "mdt-module-builder": "^1.0.0" },
|
|
15
|
+
"dependencies": { "mdt-client": "^31", "ics-ui-kit": "...", "lucide-react": "..." },
|
|
16
|
+
"peerDependencies": { "react": "^19", "react-dom": "^19" }
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Entry — `src/index.ts` (переопределяется `"mdt": { "entry": "..." }`). Сборка: `npm run build` → `dist/index.js`.
|
|
21
|
+
|
|
22
|
+
devtool/builder вызывают `npm run build` в репе модуля — менять их не нужно, контракт `dist/` соблюдён.
|
|
23
|
+
|
|
24
|
+
## Дедуп (вариант B)
|
|
25
|
+
|
|
26
|
+
- **react, react-dom, react-dom/client, react/jsx-runtime, react/jsx-dev-runtime** — НЕ бандлятся.
|
|
27
|
+
Заменяются alias-шимами, читающими `globalThis.__mdtShared[spec]` (MDT ставит его при старте).
|
|
28
|
+
→ один инстанс React во всём рантайме, нет «Invalid hook call».
|
|
29
|
+
→ react/react-dom **не обязаны быть установлены** при сборке (имена их экспортов зашиты в пакет).
|
|
30
|
+
- **`SHARED_GLOBALS`** (`@tanstack/react-query`, `zustand` + подпути, `echarts`, `echarts-for-react`)
|
|
31
|
+
— НЕ бандлятся: externalize + чтение `globalThis.__mdtShared[spec]`. В отличие от react-семьи,
|
|
32
|
+
имена экспортов перечислять не нужно. Как вынести новую такую зависимость — см. [SHARED-DEPENDENCIES.md](./SHARED-DEPENDENCIES.md).
|
|
33
|
+
- **ics-ui-kit, lucide-react, mdt-client** — бандлятся внутрь `index.js` (должны быть установлены).
|
|
34
|
+
Их внутренний `import "react"` тоже перехватывается шимом.
|
|
35
|
+
- **CSS** инлайнится в `index.js` (рантайм-инъекция `<style>`), как старый style-loader —
|
|
36
|
+
оверлей копирует только `index.js`, отдельного `.css` нет.
|
|
37
|
+
- **Tailwind (вариант A)** — билдер сам генерит утилиты Tailwind, найденные **только в `src`
|
|
38
|
+
модуля**, и подмешивает их в ту же инъекцию CSS. Модулю не нужен ни `tailwindcss`, ни конфиг:
|
|
39
|
+
пресет берётся из `ics-ui-kit` модуля, `preflight` выключен, базовый reset и токены
|
|
40
|
+
(`--primary` и пр.) даёт MDT глобально. ui-kit из `node_modules` повторно не сканируется —
|
|
41
|
+
его классы уже эмитит основная сборка MDT. Возможны дубли утилит между модулями — это принято.
|
|
42
|
+
|
|
43
|
+
## Доступ к функционалу MDT — `@mdt/*`
|
|
44
|
+
|
|
45
|
+
Любой `@mdt/<имя>` билдер externalize-ит и резолвит в `globalThis.__mdtSdk["<имя>"]`. Наполняет этот
|
|
46
|
+
глобал MDT: в папке **`ui/mdt-sdk/`** каждый файл = модуль `@mdt/<имя файла>`, и `_register.ts`
|
|
47
|
+
(через `import.meta.glob`) кладёт экспорты каждого файла в `__mdtSdk` при старте. Добавить публичный
|
|
48
|
+
API = создать файл в `ui/mdt-sdk/`; перечислять имена/спецификаторы нигде не нужно.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import MDT, { registerModule } from "@mdt/facade"; // живой window.MDT (ленивый Proxy) + обёртка
|
|
52
|
+
import { createPageControl } from "@mdt/react-utils"; // ui/mdt-sdk/react-utils.ts
|
|
53
|
+
import { useThemeToken } from "@mdt/theme"; // токены темы MDT в JS, реактивно
|
|
54
|
+
import { useChartColors, useChartFont } from "@mdt/charts"; // дефолтная тема чартов MDT
|
|
55
|
+
|
|
56
|
+
registerModule(() => MDT.Navigation.addNode({ code: "x", title: "X" }));
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Чтобы дать модулям новую возможность — либо добавь её в фасад MDT (видно как `MDT.x` через
|
|
60
|
+
`@mdt/facade`), либо положи файл в `ui/mdt-sdk/`. Ни релиза, ни списка экспортов. Хэндлы пока
|
|
61
|
+
нетипизированы (`any`); типы навешиваются позже без изменения рантайма. `mdt-client` остаётся для
|
|
62
|
+
обратной совместимости и сосуществует с `@mdt/*`.
|
|
63
|
+
|
|
64
|
+
## Контракт с платформой
|
|
65
|
+
|
|
66
|
+
Модуль и MDT договариваются о:
|
|
67
|
+
|
|
68
|
+
- имени глобала **`window.__mdtShared`**;
|
|
69
|
+
- наборе ключей **`SHARED_EXACT`** (react-семья);
|
|
70
|
+
- **мажоре React** (от него зависит список именованных экспортов `SHARED_NAMES`).
|
|
71
|
+
|
|
72
|
+
MDT-сторона контракта — `ui/vite-app/src/legacy/config-global.ts` (ставит `__mdtShared`).
|
|
73
|
+
|
|
74
|
+
## Vite / Node
|
|
75
|
+
|
|
76
|
+
Пакет зависит от **Vite 6 (Rollup)**, а не Vite 8 (Rolldown), как сам MDT-app. Причина — раннер CI
|
|
77
|
+
`publish-module` на **Node 18**, а Vite 8 требует Node ≥20.19. Это осознанный техдолг высшего
|
|
78
|
+
приоритета (`ui/vite-app/TECH_DEBT.md`, P0): вернуть на Vite 8, когда раннеры переедут на Node 20+.
|
|
79
|
+
На выход (IIFE + дедуп-шимы) мажор Vite не влияет — артефакт совместим.
|
|
80
|
+
|
|
81
|
+
## Версионирование
|
|
82
|
+
|
|
83
|
+
SemVer пакета относительно платформы:
|
|
84
|
+
|
|
85
|
+
- **major** — смена контракта: имя глобала, набор `SHARED_EXACT`, **мажор React** (обновляются
|
|
86
|
+
`SHARED_NAMES`). Модули осознанно поднимают `mdt-module-builder` и проверяют совместимость.
|
|
87
|
+
- **minor/patch** — внутренние правки сборки; выходной артефакт остаётся совместимым.
|
|
88
|
+
|
|
89
|
+
| mdt-module-builder | React | Платформа MDT |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| 1.x | 19 | (заполнить при первом релизе) |
|
|
92
|
+
|
|
93
|
+
## Публикация
|
|
94
|
+
|
|
95
|
+
Публикуется **вручную** в публичный npm (как `mdt-client`), автопубликации в CI нет.
|
|
96
|
+
Версию руками в `package.json` не правим — скрипт `release` сам поднимает patch (без git-тега)
|
|
97
|
+
и публикует:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
cd ui/module-builder
|
|
101
|
+
npm run release # = npm version patch --no-git-tag-version && npm publish
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Для minor/major (смена контракта — см. §Версионирование) сначала подними версию явно
|
|
105
|
+
(`npm version minor|major --no-git-tag-version`), затем `npm publish`.
|
|
@@ -0,0 +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`.
|
package/bin/cli.mjs
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// mdt-module-build — собирает внешний UI-модуль MDT из текущей папки (cwd).
|
|
3
|
-
// Вызывается как `npm run build` модуля (devtool/builder делают это в репе модуля).
|
|
4
|
-
// Контракт артефакта: dist/index.js (IIFE), как у старого билдера.
|
|
5
|
-
//
|
|
6
|
-
// Entry: package.json → "mdt": { "entry": "src/index.ts" } (по умолчанию src/index.ts).
|
|
7
|
-
|
|
8
|
-
import { build } from "vite";
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { moduleViteConfig } from "../src/index.mjs";
|
|
12
|
-
|
|
13
|
-
const root = process.cwd();
|
|
14
|
-
|
|
15
|
-
let pkg = {};
|
|
16
|
-
try {
|
|
17
|
-
pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
18
|
-
} catch {
|
|
19
|
-
console.error("[mdt-module-build] не найден package.json в текущей папке");
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const entry = path.resolve(root, pkg?.mdt?.entry || "src/index.ts");
|
|
24
|
-
if (!fs.existsSync(entry)) {
|
|
25
|
-
console.error(`[mdt-module-build] entry не найден: ${path.relative(root, entry)} (задаётся в package.json "mdt".entry)`);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const outDir = path.resolve(root, "dist");
|
|
30
|
-
const name = "__mdtMod_" + String(pkg.name || "module").replace(/[^a-zA-Z0-9_]/g, "_");
|
|
31
|
-
|
|
32
|
-
await build(moduleViteConfig({ root, entry, outDir, name }));
|
|
33
|
-
console.log(`[mdt-module-build] ${pkg.name || "module"} → dist/index.js`);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// mdt-module-build — собирает внешний UI-модуль MDT из текущей папки (cwd).
|
|
3
|
+
// Вызывается как `npm run build` модуля (devtool/builder делают это в репе модуля).
|
|
4
|
+
// Контракт артефакта: dist/index.js (IIFE), как у старого билдера.
|
|
5
|
+
//
|
|
6
|
+
// Entry: package.json → "mdt": { "entry": "src/index.ts" } (по умолчанию src/index.ts).
|
|
7
|
+
|
|
8
|
+
import { build } from "vite";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { moduleViteConfig } from "../src/index.mjs";
|
|
12
|
+
|
|
13
|
+
const root = process.cwd();
|
|
14
|
+
|
|
15
|
+
let pkg = {};
|
|
16
|
+
try {
|
|
17
|
+
pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
18
|
+
} catch {
|
|
19
|
+
console.error("[mdt-module-build] не найден package.json в текущей папке");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const entry = path.resolve(root, pkg?.mdt?.entry || "src/index.ts");
|
|
24
|
+
if (!fs.existsSync(entry)) {
|
|
25
|
+
console.error(`[mdt-module-build] entry не найден: ${path.relative(root, entry)} (задаётся в package.json "mdt".entry)`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const outDir = path.resolve(root, "dist");
|
|
30
|
+
const name = "__mdtMod_" + String(pkg.name || "module").replace(/[^a-zA-Z0-9_]/g, "_");
|
|
31
|
+
|
|
32
|
+
await build(moduleViteConfig({ root, entry, outDir, name }));
|
|
33
|
+
console.log(`[mdt-module-build] ${pkg.name || "module"} → dist/index.js`);
|
package/package.json
CHANGED
|
@@ -1,34 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mdt-module-builder",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Сборщик внешних UI-модулей MDT (Vite).",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"mdt-module-build": "bin/cli.mjs"
|
|
8
|
-
},
|
|
9
|
-
"main": "src/index.mjs",
|
|
10
|
-
"exports": {
|
|
11
|
-
".": "./src/index.mjs"
|
|
12
|
-
},
|
|
13
|
-
"scripts": {
|
|
14
|
-
"release": "npm version patch --no-git-tag-version && npm publish"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"src",
|
|
18
|
-
"bin",
|
|
19
|
-
"README.md"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "mdt-module-builder",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Сборщик внешних UI-модулей MDT (Vite).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mdt-module-build": "bin/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.mjs",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"release": "npm version patch --no-git-tag-version && npm publish"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"bin",
|
|
19
|
+
"README.md",
|
|
20
|
+
"SHARED-DEPENDENCIES.md"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.18.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"autoprefixer": "^10.4.0",
|
|
27
|
+
"postcss": "^8.4.0",
|
|
28
|
+
"tailwindcss": "^3.4.0",
|
|
29
|
+
"vite": "^6.0.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mdt",
|
|
33
|
+
"module",
|
|
34
|
+
"builder",
|
|
35
|
+
"vite"
|
|
36
|
+
],
|
|
37
|
+
"license": "UNLICENSED"
|
|
38
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -1,117 +1,186 @@
|
|
|
1
|
-
// mdt-module-builder — сборка внешнего UI-модуля MDT билдером MDT (Vite), артефакт «по старому»:
|
|
2
|
-
// один самовыполняющийся dist/index.js (IIFE), который зовёт window.MDT.registerModule.
|
|
3
|
-
//
|
|
4
|
-
// Дедуп (MP-16057, вариант B): react-семья НЕ бандлится — заменяется alias-шимами,
|
|
5
|
-
// читающими globalThis.__mdtShared[spec] (его ставит MDT при старте). ics-ui-kit/lucide-react
|
|
6
|
-
// бандлятся внутрь; их внутренний import "react" тоже ловит alias → один инстанс React.
|
|
7
|
-
//
|
|
8
|
-
// react/* НЕ обязаны быть установлены в модуле (не попадают в бандл): имена их экспортов
|
|
9
|
-
// зашиты статически (SHARED_NAMES) и версионируются мажором React вместе с пакетом.
|
|
10
|
-
|
|
11
|
-
import * as fs from "node:fs";
|
|
12
|
-
import * as os from "node:os";
|
|
13
|
-
import * as path from "node:path";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"react-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
117
|
-
|
|
1
|
+
// mdt-module-builder — сборка внешнего UI-модуля MDT билдером MDT (Vite), артефакт «по старому»:
|
|
2
|
+
// один самовыполняющийся dist/index.js (IIFE), который зовёт window.MDT.registerModule.
|
|
3
|
+
//
|
|
4
|
+
// Дедуп (MP-16057, вариант B): react-семья НЕ бандлится — заменяется alias-шимами,
|
|
5
|
+
// читающими globalThis.__mdtShared[spec] (его ставит MDT при старте). ics-ui-kit/lucide-react
|
|
6
|
+
// бандлятся внутрь; их внутренний import "react" тоже ловит alias → один инстанс React.
|
|
7
|
+
//
|
|
8
|
+
// react/* НЕ обязаны быть установлены в модуле (не попадают в бандл): имена их экспортов
|
|
9
|
+
// зашиты статически (SHARED_NAMES) и версионируются мажором React вместе с пакетом.
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as os from "node:os";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { createRequire } from "node:module";
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
16
|
+
|
|
17
|
+
// Спецификаторы, которые резолвятся на единственную копию MDT через window.__mdtShared.
|
|
18
|
+
export const SHARED_EXACT = ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "react/jsx-dev-runtime"];
|
|
19
|
+
|
|
20
|
+
// Тяжёлые/контекстные зависимости, которые MDT хостит и отдаёт модулям единым инстансом (как react):
|
|
21
|
+
// react-query (один QueryClient/контекст), zustand, echarts (вес ~1 МБ). В модуль НЕ бандлятся —
|
|
22
|
+
// externalize + чтение `globalThis.__mdtShared[<спецификатор>]`. В отличие от react-семьи, имена
|
|
23
|
+
// экспортов перечислять НЕ нужно: Rollup сам обращается к членам глобала, поэтому подходит для
|
|
24
|
+
// библиотек с большим/растущим API и подпутями. Каждый подпуть (zustand/middleware и т.п.) —
|
|
25
|
+
// отдельный спецификатор: его и externalize, и экспонировать в __mdtShared нужно отдельно.
|
|
26
|
+
// Добавление новой shared-зависимости: см. SHARED-DEPENDENCIES.md.
|
|
27
|
+
export const SHARED_GLOBALS = [
|
|
28
|
+
"@tanstack/react-query",
|
|
29
|
+
"zustand",
|
|
30
|
+
"zustand/middleware",
|
|
31
|
+
"zustand/react/shallow",
|
|
32
|
+
"echarts",
|
|
33
|
+
"echarts-for-react"
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// Именованные экспорты react-семьи (React 19). Зашиты статически — react не ставится при сборке.
|
|
37
|
+
// При смене мажора React обновляются вместе с major-версией пакета (см. README §версионирование).
|
|
38
|
+
export const SHARED_NAMES = {
|
|
39
|
+
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(
|
|
40
|
+
","
|
|
41
|
+
),
|
|
42
|
+
"react-dom":
|
|
43
|
+
"createPortal,flushSync,preconnect,prefetchDNS,preinit,preinitModule,preload,preloadModule,requestFormReset,unstable_batchedUpdates,useFormState,useFormStatus,version".split(
|
|
44
|
+
","
|
|
45
|
+
),
|
|
46
|
+
"react-dom/client": ["createRoot", "hydrateRoot", "version"],
|
|
47
|
+
"react/jsx-runtime": ["Fragment", "jsx", "jsxs"],
|
|
48
|
+
"react/jsx-dev-runtime": ["Fragment", "jsxDEV"]
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Генерирует ESM shim-файлы (по одному на SHARED_EXACT) в свежей temp-папке.
|
|
52
|
+
// Шим читает MDT-инстанс из globalThis.__mdtShared[spec]; именованные экспорты — из SHARED_NAMES.
|
|
53
|
+
export function ensureShims() {
|
|
54
|
+
const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), "mdt-module-shims-"));
|
|
55
|
+
const j = JSON.stringify;
|
|
56
|
+
for (const spec of SHARED_EXACT) {
|
|
57
|
+
const acc = `(globalThis.__mdtShared && globalThis.__mdtShared[${j(spec)}])`;
|
|
58
|
+
const names = SHARED_NAMES[spec] || [];
|
|
59
|
+
const content = [
|
|
60
|
+
`const __ns = ${acc};`,
|
|
61
|
+
`export default __ns;`,
|
|
62
|
+
...names.map((n) => `export const ${n} = __ns && __ns[${j(n)}];`)
|
|
63
|
+
].join("\n");
|
|
64
|
+
fs.writeFileSync(path.join(shimDir, spec.replace(/[^a-zA-Z0-9]/g, "_") + ".js"), content);
|
|
65
|
+
}
|
|
66
|
+
return shimDir;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// resolve.alias для Vite: перехватывают react/* до Rolldown-резолюции npm-пакетов
|
|
70
|
+
// (resolveId-плагины Rolldown для npm-пакетов не зовёт — только alias работает).
|
|
71
|
+
function sharedAliases(shimDir) {
|
|
72
|
+
return SHARED_EXACT.map((spec) => ({
|
|
73
|
+
find: new RegExp(`^${spec.replace(/[/$]/g, (c) => `\\${c}`)}$`),
|
|
74
|
+
replacement: path.join(shimDir, spec.replace(/[^a-zA-Z0-9]/g, "_") + ".js")
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Публичный SDK MDT: спецификаторы `@mdt/<имя>` отдаются модулю как externalize. Их экспорты
|
|
79
|
+
// кладёт MDT в globalThis.__mdtSdk — по одному ключу на файл папки ui/mdt-sdk (см. _register.ts).
|
|
80
|
+
// Добавить публичный API = создать файл в ui/mdt-sdk; перечислять имена/спецификаторы не нужно.
|
|
81
|
+
export const MDT_SDK_PREFIX = "@mdt/";
|
|
82
|
+
|
|
83
|
+
// Куда внешний модуль адресует externalize-import: `@mdt/foo` → globalThis.__mdtSdk["foo"],
|
|
84
|
+
// shared-dep → globalThis.__mdtShared["spec"]. Используется как output.globals.
|
|
85
|
+
function globalRef(id) {
|
|
86
|
+
return id.startsWith(MDT_SDK_PREFIX)
|
|
87
|
+
? `globalThis.__mdtSdk[${JSON.stringify(id.slice(MDT_SDK_PREFIX.length))}]`
|
|
88
|
+
: `globalThis.__mdtShared[${JSON.stringify(id)}]`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Tailwind для модуля (вариант A): билдер сам генерит утилиты ТОЛЬКО из исходников модуля.
|
|
92
|
+
// preflight и токены даёт MDT глобально, поэтому здесь preflight выключен и эмитятся лишь
|
|
93
|
+
// `@tailwind utilities`. Пресет берём из ics-ui-kit самого модуля (фолбэк — ics-ui-kit билдера,
|
|
94
|
+
// чтобы dev-стенд работал даже до npm install в модуле) → те же утилиты/токены, что в MDT.
|
|
95
|
+
async function loadIcsPreset(root) {
|
|
96
|
+
for (const base of [path.join(root, "package.json"), import.meta.url]) {
|
|
97
|
+
try {
|
|
98
|
+
const resolved = createRequire(base).resolve("ics-ui-kit/tailwind.preset");
|
|
99
|
+
const mod = await import(pathToFileURL(resolved).href);
|
|
100
|
+
return mod.default ?? mod;
|
|
101
|
+
} catch {
|
|
102
|
+
// пробуем следующий источник
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Генерит CSS утилит Tailwind, найденных в исходниках модуля. Зависимости подключаем лениво:
|
|
109
|
+
// они есть у MDT (dev) и у mdt-module-builder (CI модуля), но не нужны для `--list`/импорта ядра.
|
|
110
|
+
async function generateModuleTailwindCss(root) {
|
|
111
|
+
const [{ default: postcss }, { default: tailwindcss }, { default: autoprefixer }] = await Promise.all([
|
|
112
|
+
import("postcss"),
|
|
113
|
+
import("tailwindcss"),
|
|
114
|
+
import("autoprefixer")
|
|
115
|
+
]);
|
|
116
|
+
const content = path.join(root, "src").replace(/\\/g, "/") + "/**/*.{ts,tsx,js,jsx,html}";
|
|
117
|
+
const preset = await loadIcsPreset(root);
|
|
118
|
+
const config = { presets: preset ? [preset] : [], content: [content], corePlugins: { preflight: false } };
|
|
119
|
+
const { css } = await postcss([tailwindcss(config), autoprefixer()]).process("@tailwind utilities;\n", { from: undefined });
|
|
120
|
+
return css;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Инлайнит собранный CSS в JS-чанк (рантайм-инъекция <style>) — как старый webpack/style-loader:
|
|
124
|
+
// оверлей копирует только index.js, отдельного css-файла нет. Сюда же подмешиваем утилиты Tailwind.
|
|
125
|
+
function cssInjectPlugin(root) {
|
|
126
|
+
return {
|
|
127
|
+
name: "mdt-css-inject",
|
|
128
|
+
enforce: "post",
|
|
129
|
+
async generateBundle(_opts, bundle) {
|
|
130
|
+
let css = "";
|
|
131
|
+
for (const [file, chunk] of Object.entries(bundle)) {
|
|
132
|
+
if (chunk.type === "asset" && file.endsWith(".css")) {
|
|
133
|
+
css += typeof chunk.source === "string" ? chunk.source : Buffer.from(chunk.source).toString("utf8");
|
|
134
|
+
delete bundle[file];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
css += await generateModuleTailwindCss(root);
|
|
138
|
+
if (!css) return;
|
|
139
|
+
const inject = `(function(){try{var s=document.createElement("style");s.textContent=${JSON.stringify(
|
|
140
|
+
css
|
|
141
|
+
)};document.head.appendChild(s);}catch(e){}})();\n`;
|
|
142
|
+
for (const chunk of Object.values(bundle)) {
|
|
143
|
+
if (chunk.type === "chunk" && chunk.isEntry) {
|
|
144
|
+
chunk.code = inject + chunk.code;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Единая Vite-конфигурация сборки одного модуля. Используется и CLI (bin/cli.mjs),
|
|
153
|
+
// и dev-watch внутри MDT (через build_tools/external-modules.mjs).
|
|
154
|
+
export function moduleViteConfig({ root, entry, outDir, watch = false, name = "__mdtModule" }) {
|
|
155
|
+
const shimDir = ensureShims();
|
|
156
|
+
return {
|
|
157
|
+
configFile: false,
|
|
158
|
+
root,
|
|
159
|
+
logLevel: "warn",
|
|
160
|
+
resolve: { alias: sharedAliases(shimDir) },
|
|
161
|
+
plugins: [cssInjectPlugin(root)],
|
|
162
|
+
build: {
|
|
163
|
+
outDir,
|
|
164
|
+
emptyOutDir: true,
|
|
165
|
+
minify: false,
|
|
166
|
+
...(watch ? { watch: {} } : {}),
|
|
167
|
+
rollupOptions: {
|
|
168
|
+
// SHARED_GLOBALS externalize: их инстанс даёт MDT через globalThis.__mdtShared, в модуль не бандлятся.
|
|
169
|
+
// react/* перехвачены alias-шимами; ics-ui-kit/lucide бандлятся целиком.
|
|
170
|
+
external: (id) => SHARED_GLOBALS.includes(id) || id.startsWith(MDT_SDK_PREFIX),
|
|
171
|
+
// host кладёт значения в __mdtShared/__mdtSdk с флагом __esModule, поэтому
|
|
172
|
+
// interop модуля (__toESM) корректно отдаёт `.default` для default-импорта
|
|
173
|
+
// (иначе default-импорт, напр. echarts-for-react, превращается в объект-неймспейс → React
|
|
174
|
+
// «Element type is invalid … got: object»).
|
|
175
|
+
output: { globals: globalRef }
|
|
176
|
+
},
|
|
177
|
+
lib: {
|
|
178
|
+
entry,
|
|
179
|
+
// IIFE: грузится как <script> (config.modules → loadScript), самовыполняется.
|
|
180
|
+
formats: ["iife"],
|
|
181
|
+
name,
|
|
182
|
+
fileName: () => "index.js"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|