mdt-module-builder 1.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/README.md +64 -0
- package/bin/cli.mjs +33 -0
- package/package.json +35 -0
- package/src/index.mjs +117 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
- **ics-ui-kit, lucide-react, mdt-client** — бандлятся внутрь `index.js` (должны быть установлены).
|
|
31
|
+
Их внутренний `import "react"` тоже перехватывается шимом.
|
|
32
|
+
- **CSS** инлайнится в `index.js` (рантайм-инъекция `<style>`), как старый style-loader —
|
|
33
|
+
оверлей копирует только `index.js`, отдельного `.css` нет.
|
|
34
|
+
|
|
35
|
+
## Контракт с платформой
|
|
36
|
+
|
|
37
|
+
Модуль и MDT договариваются о:
|
|
38
|
+
|
|
39
|
+
- имени глобала **`window.__mdtShared`**;
|
|
40
|
+
- наборе ключей **`SHARED_EXACT`** (react-семья);
|
|
41
|
+
- **мажоре React** (от него зависит список именованных экспортов `SHARED_NAMES`).
|
|
42
|
+
|
|
43
|
+
MDT-сторона контракта — `ui/vite-app/src/legacy/config-global.ts` (ставит `__mdtShared`).
|
|
44
|
+
|
|
45
|
+
## Версионирование
|
|
46
|
+
|
|
47
|
+
SemVer пакета относительно платформы:
|
|
48
|
+
|
|
49
|
+
- **major** — смена контракта: имя глобала, набор `SHARED_EXACT`, **мажор React** (обновляются
|
|
50
|
+
`SHARED_NAMES`). Модули осознанно поднимают `mdt-module-builder` и проверяют совместимость.
|
|
51
|
+
- **minor/patch** — внутренние правки сборки; выходной артефакт остаётся совместимым.
|
|
52
|
+
|
|
53
|
+
| mdt-module-builder | React | Платформа MDT |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| 1.x | 19 | (заполнить при первом релизе) |
|
|
56
|
+
|
|
57
|
+
## Публикация
|
|
58
|
+
|
|
59
|
+
Публикуется **вручную** в публичный npm (как `mdt-client`), автопубликации в CI нет.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
cd ui/module-builder
|
|
63
|
+
npm publish
|
|
64
|
+
```
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +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`);
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mdt-module-builder",
|
|
3
|
+
"version": "1.0.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
|
+
"build": "node --check bin/cli.mjs && node -e \"import('./src/index.mjs').then(() => console.log('mdt-module-builder: ok'))\"",
|
|
15
|
+
"release": "npm run build && npm version patch --no-git-tag-version && npm publish"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"bin",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"vite": "^8.0.16"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mdt",
|
|
30
|
+
"module",
|
|
31
|
+
"builder",
|
|
32
|
+
"vite"
|
|
33
|
+
],
|
|
34
|
+
"license": "UNLICENSED"
|
|
35
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,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
|
+
|
|
15
|
+
// Спецификаторы, которые резолвятся на единственную копию MDT через window.__mdtShared.
|
|
16
|
+
export const SHARED_EXACT = ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "react/jsx-dev-runtime"];
|
|
17
|
+
|
|
18
|
+
// Именованные экспорты react-семьи (React 19). Зашиты статически — react не ставится при сборке.
|
|
19
|
+
// При смене мажора React обновляются вместе с major-версией пакета (см. README §версионирование).
|
|
20
|
+
export const SHARED_NAMES = {
|
|
21
|
+
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(
|
|
22
|
+
","
|
|
23
|
+
),
|
|
24
|
+
"react-dom":
|
|
25
|
+
"createPortal,flushSync,preconnect,prefetchDNS,preinit,preinitModule,preload,preloadModule,requestFormReset,unstable_batchedUpdates,useFormState,useFormStatus,version".split(
|
|
26
|
+
","
|
|
27
|
+
),
|
|
28
|
+
"react-dom/client": ["createRoot", "hydrateRoot", "version"],
|
|
29
|
+
"react/jsx-runtime": ["Fragment", "jsx", "jsxs"],
|
|
30
|
+
"react/jsx-dev-runtime": ["Fragment", "jsxDEV"]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Генерирует ESM shim-файлы (по одному на SHARED_EXACT) в свежей temp-папке.
|
|
34
|
+
// Шим читает MDT-инстанс из globalThis.__mdtShared[spec]; именованные экспорты — из SHARED_NAMES.
|
|
35
|
+
export function ensureShims() {
|
|
36
|
+
const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), "mdt-module-shims-"));
|
|
37
|
+
const j = JSON.stringify;
|
|
38
|
+
for (const spec of SHARED_EXACT) {
|
|
39
|
+
const acc = `(globalThis.__mdtShared && globalThis.__mdtShared[${j(spec)}])`;
|
|
40
|
+
const names = SHARED_NAMES[spec] || [];
|
|
41
|
+
const content = [
|
|
42
|
+
`const __ns = ${acc};`,
|
|
43
|
+
`export default __ns;`,
|
|
44
|
+
...names.map((n) => `export const ${n} = __ns && __ns[${j(n)}];`)
|
|
45
|
+
].join("\n");
|
|
46
|
+
fs.writeFileSync(path.join(shimDir, spec.replace(/[^a-zA-Z0-9]/g, "_") + ".js"), content);
|
|
47
|
+
}
|
|
48
|
+
return shimDir;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// resolve.alias для Vite: перехватывают react/* до Rolldown-резолюции npm-пакетов
|
|
52
|
+
// (resolveId-плагины Rolldown для npm-пакетов не зовёт — только alias работает).
|
|
53
|
+
function sharedAliases(shimDir) {
|
|
54
|
+
return SHARED_EXACT.map((spec) => ({
|
|
55
|
+
find: new RegExp(`^${spec.replace(/[/$]/g, (c) => `\\${c}`)}$`),
|
|
56
|
+
replacement: path.join(shimDir, spec.replace(/[^a-zA-Z0-9]/g, "_") + ".js")
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Инлайнит собранный CSS в JS-чанк (рантайм-инъекция <style>) — как старый webpack/style-loader:
|
|
61
|
+
// оверлей копирует только index.js, отдельного css-файла нет.
|
|
62
|
+
function cssInjectPlugin() {
|
|
63
|
+
return {
|
|
64
|
+
name: "mdt-css-inject",
|
|
65
|
+
enforce: "post",
|
|
66
|
+
generateBundle(_opts, bundle) {
|
|
67
|
+
let css = "";
|
|
68
|
+
for (const [file, chunk] of Object.entries(bundle)) {
|
|
69
|
+
if (chunk.type === "asset" && file.endsWith(".css")) {
|
|
70
|
+
css += typeof chunk.source === "string" ? chunk.source : Buffer.from(chunk.source).toString("utf8");
|
|
71
|
+
delete bundle[file];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!css) return;
|
|
75
|
+
const inject = `(function(){try{var s=document.createElement("style");s.textContent=${JSON.stringify(
|
|
76
|
+
css
|
|
77
|
+
)};document.head.appendChild(s);}catch(e){}})();\n`;
|
|
78
|
+
for (const chunk of Object.values(bundle)) {
|
|
79
|
+
if (chunk.type === "chunk" && chunk.isEntry) {
|
|
80
|
+
chunk.code = inject + chunk.code;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Единая Vite-конфигурация сборки одного модуля. Используется и CLI (bin/cli.mjs),
|
|
89
|
+
// и dev-watch внутри MDT (через build_tools/external-modules.mjs).
|
|
90
|
+
export function moduleViteConfig({ root, entry, outDir, watch = false, name = "__mdtModule" }) {
|
|
91
|
+
const shimDir = ensureShims();
|
|
92
|
+
return {
|
|
93
|
+
configFile: false,
|
|
94
|
+
root,
|
|
95
|
+
logLevel: "warn",
|
|
96
|
+
resolve: { alias: sharedAliases(shimDir) },
|
|
97
|
+
plugins: [cssInjectPlugin()],
|
|
98
|
+
build: {
|
|
99
|
+
outDir,
|
|
100
|
+
emptyOutDir: true,
|
|
101
|
+
minify: false,
|
|
102
|
+
...(watch ? { watch: {} } : {}),
|
|
103
|
+
rollupOptions: {
|
|
104
|
+
// Отключаем авто-externalize peerDeps (lib-режим Vite это делает по умолчанию):
|
|
105
|
+
// react/* уже перехвачены alias-шимами, ics-ui-kit/lucide должны забандлиться.
|
|
106
|
+
external: () => false
|
|
107
|
+
},
|
|
108
|
+
lib: {
|
|
109
|
+
entry,
|
|
110
|
+
// IIFE: грузится как <script> (config.modules → loadScript), самовыполняется.
|
|
111
|
+
formats: ["iife"],
|
|
112
|
+
name,
|
|
113
|
+
fileName: () => "index.js"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|