mdt-module-builder
v2.1.0
Published
Сборщик внешних UI-модулей MDT (Vite).
Readme
mdt-module-builder
Сборщик внешнего UI-модуля MDT билдером MDT (Vite). Заменяет webpack/ics-builder в npm run build
модуля, выдавая тот же по контракту артефакт: один dist/index.js (IIFE), который при загрузке
через <script> самовыполняется и зовёт window.MDT.registerModule.
История изменений — CHANGELOG.md.
Использование
В package.json модуля:
{
"scripts": { "build": "mdt-module-build" },
"devDependencies": { "mdt-module-builder": "^2.0.0" },
"dependencies": { "mdt-client": "^31", "ics-ui-kit": "...", "lucide-react": "..." },
"peerDependencies": { "react": "^19", "react-dom": "^19" }
}Entry — src/index.ts (переопределяется "mdt": { "entry": "..." }). Сборка: npm run build → dist/index.js.
devtool/builder вызывают npm run build в репе модуля — менять их не нужно, контракт dist/ соблюдён.
Дедуп (вариант B)
- react, react-dom, react-dom/client, react/jsx-runtime, react/jsx-dev-runtime — НЕ бандлятся.
Заменяются alias-шимами, читающими
globalThis.__mdtShared[spec](MDT ставит его при старте). → один инстанс React во всём рантайме, нет «Invalid hook call». → react/react-dom не обязаны быть установлены при сборке (имена их экспортов зашиты в пакет). SHARED_GLOBALS(@tanstack/react-query,zustand+ подпути,echarts,echarts-for-react,@tanstack/react-virtual,@tanstack/react-table,@dnd-kit/core,@dnd-kit/utilities,dnd-timeline,tailwind-variants,react-hook-form,zod) — НЕ бандлятся: externalize + чтениеglobalThis.__mdtShared[spec]. В отличие от react-семьи, имена экспортов перечислять не нужно. Полный список с версиями и обоснованием, а также порядок выноса новой такой зависимости — см. SHARED-DEPENDENCIES.md. Перечислены только корневые спецификаторы (кромеzustand): сабпас — отдельный спецификатор, добавляется по факту потребности модуля, не «про запас».- ics-ui-kit (сам пакет и любой сабпас —
ics-ui-kit/components/*,ics-ui-kit/lib/*, …) — НЕ бандлится: externalize по префиксу (SHARED_PREFIXES) + чтениеglobalThis.__mdtShared[spec]. Сабпасы перечислять не нужно. MDT кладёт туда свой инстанс (vite-applegacy/shared-ui-kit.ts), по ключу на каждый сабпас. → один инстанс ui-kit → общий React-контекст (тема, провайдеры): иначе компоненты модуля читали бы чужойThemeContext. CSS-сабпасы (styles.css) не externalize-ятся. - lucide-react, mdt-client — бандлятся внутрь
index.js(должны быть установлены). Их внутреннийimport "react"тоже перехватывается шимом. Почему lucide бандлится, а ui-kit шарится (решение зафиксировано, не пересматривать без новых данных): ui-kit обязан быть общим по корректности — у него React-контекст (тема, провайдеры), две копии = сломанная тема. Иконки lucide — stateless SVG без контекста, дубли безвредны. При этом lucide импортят именованно из корня (import { X } from "lucide-react"), так что шаринг = отдать весь namespace → MDT забандлил бы все ~1900 иконок без tree-shaking. Per-module бандл, наоборот, tree-shaking-ом тянет только used-иконки (единицы, ~КБ). Пара дублей по модулям несравнимо дешевле, чем вся lucide в MDT. - CSS инлайнится в
index.js(рантайм-инъекция<style>), как старый style-loader — оверлей копирует толькоindex.js, отдельного.cssнет. - Tailwind модуль больше не собирает (с версии 2.0.0, MP-16147). Раньше билдер сам генерил
утилиты Tailwind из
srcмодуля второй, независимой сборкой — это ломало гарантию порядка правил (baseпередvariant), на которой держится mobile-first: голая утилита модуля могла перебить вариант ui-kit (.h-10модуля поверх.lg:h-9изics-ui-kit). Теперь Tailwind-классы модулю даёт единственная, core-сборка MDT — по курируемомуtailwind-safelist.mjs(собран из реально используемых модулями классов, см.tailwind-inventory.mjs). Класс, которого нет в safelist, физически не применится в рантайме — не задача сборки, а несовпадение safelist; добавляется в MDT по мере реальной потребности. Модуль-специфичная стилизация — обычный CSS (import "./x.css", см. ниже), не Tailwind.
Совместимость исходников модуля (webpack → Vite)
Ядро воспроизводит удобства старого (webpack) билдера, чтобы исходники модуля не переписывать:
- tsconfig
paths→resolve.alias. Билдер читаетcompilerOptions.paths/baseUrlизtsconfig.jsonмодуля и разворачивает в alias'ы Vite (аналогtsconfig-paths-webpack-plugin). Импорты видаimport x from "_core/scripts/..."резолвятся без правок. Берётся первый target маппинга;prefix/*→ wildcard с захватом хвоста, точный ключ → строковый алиас. - CSS-вложенность. Компонентный CSS модуля (
import "./x.css") прогоняется черезpostcss-nested(Sass-подобный&) +autoprefixer— та же семантика, что у старогоpostcss-loader. Native CSS nesting не задействуется. - CSS-модули работают —
import styles from "./x.module.css"даёт хешированные имена (.loadsTable→._loadsTable_1brdq_1) и объект-маппинг,postcss-nestedвнутри тоже работает. Это предпочтительный способ модуль-специфичной стилизации: имена не надо придумывать уникальными, коллизия с core/ui-kit невозможна by construction (обычный.cssинжектится как есть, и generic-имя вроде.cardвполне может столкнуться с чужим). .svgкак строка. SVG импортится встроенным Vite-суффиксом?raw(import logo from "./logo.svg?raw") → строка с разметкой, инлайнится, без отдельного ассет-файла. Байт-в-байт как старыйsvg-inline-loader. Билдер SVG специально не обрабатывает: bare-импорт.svgбез?rawуедет в отдельный файл, который контракт модуля (копируется толькоindex.js) потеряет — всегда с?raw.
Доступ к функционалу MDT — @mdt/*
Любой @mdt/<имя> билдер externalize-ит и резолвит в globalThis.__mdtSdk["<имя>"]. Наполняет этот
глобал MDT: в папке ui/mdt-sdk/ каждый файл = модуль @mdt/<имя файла>, и _register.ts
(через import.meta.glob) кладёт экспорты каждого файла в __mdtSdk при старте. Добавить публичный
API = создать файл в ui/mdt-sdk/; перечислять имена/спецификаторы нигде не нужно.
import MDT, { registerModule } from "@mdt/facade"; // живой window.MDT (ленивый Proxy) + обёртка
import { createPageControl } from "@mdt/react-utils"; // ui/mdt-sdk/react-utils.ts
import { useThemeToken } from "@mdt/theme"; // токены темы MDT в JS, реактивно
import { useChartColors, useChartFont } from "@mdt/charts"; // дефолтная тема чартов MDT
registerModule(() => MDT.Navigation.addNode({ code: "x", title: "X" }));Чтобы дать модулям новую возможность — либо добавь её в фасад MDT (видно как MDT.x через
@mdt/facade), либо положи файл в ui/mdt-sdk/. Ни релиза, ни списка экспортов. Хэндлы пока
нетипизированы (any); типы навешиваются позже без изменения рантайма. mdt-client остаётся для
обратной совместимости и сосуществует с @mdt/*.
Контракт с платформой
Модуль и MDT договариваются о:
- имени глобала
window.__mdtShared; - наборе ключей
SHARED_EXACT(react-семья),SHARED_GLOBALSи префиксахSHARED_PREFIXES(ics-ui-kit) — все они должны присутствовать в__mdtSharedхоста; - мажоре React (от него зависит список именованных экспортов
SHARED_NAMES).
Вынос ics-ui-kit в __mdtShared — ломающее изменение контракта: модуль, собранный новым
билдером, больше не бандлит ui-kit и требует MDT, который его экспонирует (legacy/shared-ui-kit.ts).
Версию билдера поднимать соответственно (см. §Версионирование).
MDT-сторона контракта — ui/vite-app/src/legacy/config-global.ts (ставит __mdtShared).
Vite / Node
Пакет зависит от Vite 6 (Rollup), а не Vite 8 (Rolldown), как сам MDT-app. Причина — раннер CI
publish-module на Node 18, а Vite 8 требует Node ≥20.19. Это осознанный техдолг высшего
приоритета (ui/vite-app/TECH_DEBT.md, P0): вернуть на Vite 8, когда раннеры переедут на Node 20+.
На выход (IIFE + дедуп-шимы) мажор Vite не влияет — артефакт совместим.
Версионирование
SemVer пакета относительно платформы:
- major — изменение, которое молча ломает уже существующий, никак не менявшийся код модуля
при обычном (не ручном) апгрейде: имя глобала, переименование/удаление ключа в
SHARED_EXACT/SHARED_GLOBALS/SHARED_PREFIXES, мажор React (обновляютсяSHARED_NAMES), изменение источника Tailwind-классов модуля. Модули осознанно поднимаютmdt-module-builderи проверяют совместимость. - minor — обратно совместимое добавление: новый ключ в
SHARED_GLOBALS/SHARED_PREFIXES(библиотека, которую раньше никто не шарил, начинает шариться), новая опция сборки и т.п. Модуль, который эту библиотеку не импортирует, ничего не замечает. Единственный риск — модуль, который её уже импортирует и рассчитывает на бандлинг: после апгрейда билдера тот же импорт станет externalize-иться, и на MDT без соответствующего патча платформы превратится вundefinedбез ошибки сборки. Поэтому перед каждым добавлением вSHARED_GLOBALS— проверить всех известных потребителей билдера на совместимом диапазоне версий на предмет прямого импорта именно этой библиотеки. Нашёлся такой — для НЕЁ это major, а не minor. - patch — внутренние правки сборки; выходной артефакт остаётся совместимым.
Пример применения правила — см. SHARED-DEPENDENCIES.md, «Расширение SHARED_GLOBALS».
| mdt-module-builder | React | Платформа MDT |
|---|---|---|
| 1.x | 19 | неизвестно — публиковался до введения CHANGELOG.md, надёжного источника нет |
| 2.x (опубликован 2026-07-27) | 19 | требует MDT с курируемым Tailwind-safelist (ui/tailwind-safelist.mjs в safelist core-сборки, MP-16147) — модуль больше не собирает свой Tailwind. Модули должны сами поднять зависимость на ^2.0.0 — на ^1.0.x не подхватится автоматически (иначе сломались бы молча, см. CHANGELOG) |
| 2.1.x (не опубликован) | 19 | всё из 2.x + 8 новых ключей SHARED_GLOBALS (MP-16155): react-virtual, react-table, dnd-kit core/utilities, dnd-timeline, tailwind-variants, react-hook-form, zod. Minor — обратно совместимо, модули на ^2.0.0 подхватят автоматически; библиотеку в devDependencies подтягивают только когда реально начинают её импортировать. Реально начать пользоваться выносом можно только на MDT с этим патчем |
Публикация
Публикуется вручную в публичный npm (как mdt-client), автопубликации в CI нет.
Перед любым релизом — перенеси ## [Unreleased] в CHANGELOG.md в датированную
секцию ## [x.y.z] - YYYY-MM-DD (формат Keep a Changelog),
оставив пустой ## [Unreleased] сверху для следующих изменений.
Версию руками в package.json не правим для patch — скрипт release сам поднимает patch (без
git-тега) и публикует:
cd ui/module-builder
npm run release # = npm version patch --no-git-tag-version && npm publishДля minor/major (смена контракта — см. §Версионирование) сначала подними версию явно
(npm version minor|major --no-git-tag-version), затем npm publish.
