create-ax-trusted-plugin 1.0.1

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 ADDED
@@ -0,0 +1,43 @@
1
+ # @ax-npm/create-trusted-plugin
2
+
3
+ 可信(internal)插件**脚手架**:一条命令生成一个基于 `@ax-npm/host-trusted-sdk-v4` 的可信插件骨架
4
+ (Module Federation remote + 共享 Vue + `activate(ctx)` + 自定义元素面板 + 调 `map.flyTo` + 遥测订阅)。
5
+
6
+ ## 用法
7
+ ```bash
8
+ # 交互式(缺参数会提示输入 id/标题/锚点)
9
+ npm create @ax-npm/trusted-plugin@latest my-panel
10
+
11
+ # 非交互(传参)
12
+ npm create @ax-npm/trusted-plugin@latest my-panel -- --title "我的面板" --anchor top-right
13
+ ```
14
+ 等价于直接跑 bin:`node bin/index.mjs my-panel --title "我的面板" --anchor top-right`。
15
+
16
+ 生成 `my-panel/` 后:
17
+ ```bash
18
+ cd my-panel
19
+ export GITLAB_TOKEN=<读权限 PAT>
20
+ npm install
21
+ npm run dev
22
+ ```
23
+
24
+ ## 参数
25
+ | 参数 | 说明 | 默认 |
26
+ |---|---|---|
27
+ | `<id>`(位置参数) | 插件 id,小写短横线 | 必填(缺则提示) |
28
+ | `--title` | 面板标题 | 由 id 推导 |
29
+ | `--anchor` | 浮层锚点 | `top-right` |
30
+
31
+ 锚点可选:`top-left/top-right/top-center/bottom-left/bottom-right/left-center/right-center/center`。
32
+
33
+ ## 生成内容
34
+ `template/` 经占位替换(`__ID__`/`__TITLE__`/`__ANCHOR__`/`__FED_NAME__`)后落地:`src/{index.ts,Panel.vue,state.ts}`、
35
+ `manifest.json`(trust:internal)、`vite.config.ts`(federation remote)、`serve.mjs`(dev 静态源)、`package.json`、
36
+ `tsconfig.json`、`.npmrc`、`.gitignore`、`README.md`。
37
+
38
+ ## 发布(维护者)
39
+ ```bash
40
+ export GITLAB_TOKEN=<写权限 PAT>
41
+ npm publish # publishConfig → GitLab 项目 1243
42
+ ```
43
+ 源码仓:GitLab 项目 1244。
package/bin/index.mjs ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ // @ax-npm/create-trusted-plugin —— 可信(internal)插件脚手架。
3
+ // 用法:
4
+ // npm create @ax-npm/trusted-plugin <id> [-- --title "标题" --anchor top-right]
5
+ // node bin/index.mjs <id> --title "标题" --anchor top-right
6
+ // 把 template/ 复制到 <id>/,按 id/title/anchor 做占位替换(dotfile 以 _ 前缀存放,生成时改名)。
7
+ import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
8
+ import { existsSync } from 'node:fs';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { dirname, join, resolve, relative } from 'node:path';
11
+ import { createInterface } from 'node:readline/promises';
12
+
13
+ const here = dirname(fileURLToPath(import.meta.url));
14
+ const TEMPLATE = resolve(here, '..', 'template');
15
+
16
+ const ANCHORS = [
17
+ 'top-left', 'top-right', 'top-center',
18
+ 'bottom-left', 'bottom-right',
19
+ 'left-center', 'right-center', 'center',
20
+ ];
21
+
22
+ function parseArgs(argv) {
23
+ const out = { _: [] };
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (a.startsWith('--')) out[a.slice(2)] = argv[++i] ?? '';
27
+ else out._.push(a);
28
+ }
29
+ return out;
30
+ }
31
+
32
+ function toTitle(id) {
33
+ return id.replace(/[-_]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
34
+ }
35
+
36
+ async function prompt(rl, q, def) {
37
+ if (!process.stdin.isTTY) return def;
38
+ const ans = (await rl.question(`${q}${def ? ` (${def})` : ''}: `)).trim();
39
+ return ans || def;
40
+ }
41
+
42
+ // 复制 + 占位替换 + dotfile 改名(_gitignore→.gitignore、_npmrc→.npmrc)。
43
+ async function scaffold(srcDir, destDir, vars) {
44
+ await mkdir(destDir, { recursive: true });
45
+ for (const name of await readdir(srcDir)) {
46
+ const src = join(srcDir, name);
47
+ const s = await stat(src);
48
+ const outName = name === '_gitignore' ? '.gitignore' : name === '_npmrc' ? '.npmrc' : name;
49
+ const dest = join(destDir, outName);
50
+ if (s.isDirectory()) {
51
+ await scaffold(src, dest, vars);
52
+ } else {
53
+ let body = await readFile(src, 'utf8');
54
+ for (const [k, v] of Object.entries(vars)) body = body.split(k).join(v);
55
+ await writeFile(dest, body);
56
+ }
57
+ }
58
+ }
59
+
60
+ async function main() {
61
+ const args = parseArgs(process.argv.slice(2));
62
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
63
+ try {
64
+ let id = args._[0] || args.id;
65
+ if (!id) id = await prompt(rl, '插件 id(小写短横线,如 my-panel)', '');
66
+ id = String(id || '').trim().toLowerCase();
67
+ if (!/^[a-z][a-z0-9-]*$/.test(id)) {
68
+ console.error('✗ 非法 id:须小写字母开头,仅含小写字母/数字/短横线。');
69
+ process.exit(1);
70
+ }
71
+
72
+ const title = args.title || (await prompt(rl, '面板标题', toTitle(id)));
73
+ let anchor = args.anchor || (await prompt(rl, `锚点(${ANCHORS.join('/')})`, 'top-right'));
74
+ anchor = String(anchor).trim();
75
+ if (!ANCHORS.includes(anchor)) {
76
+ console.error(`✗ 非法 anchor:${anchor}。可选:${ANCHORS.join(', ')}`);
77
+ process.exit(1);
78
+ }
79
+
80
+ const destDir = resolve(process.cwd(), args.dir || id);
81
+ if (existsSync(destDir)) {
82
+ console.error(`✗ 目标已存在:${destDir}`);
83
+ process.exit(1);
84
+ }
85
+
86
+ const vars = {
87
+ __ID__: id,
88
+ __TITLE__: title,
89
+ __ANCHOR__: anchor,
90
+ __FED_NAME__: id.replace(/[^a-zA-Z0-9]/g, '_'),
91
+ };
92
+ await scaffold(TEMPLATE, destDir, vars);
93
+
94
+ const rel = relative(process.cwd(), destDir) || '.';
95
+ console.log(`\n✓ 已生成可信插件:${rel}`);
96
+ console.log('\n下一步:');
97
+ console.log(` cd ${rel}`);
98
+ console.log(' export GITLAB_TOKEN=<读权限 PAT>');
99
+ console.log(' npm install');
100
+ console.log(' npm run dev # build:watch + 静态托管 + host-dev,浏览器调试\n');
101
+ } finally {
102
+ rl.close();
103
+ }
104
+ }
105
+
106
+ main().catch((e) => {
107
+ console.error(e);
108
+ process.exit(1);
109
+ });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "create-ax-trusted-plugin",
3
+ "version": "1.0.1",
4
+ "description": "可信(internal)插件脚手架:一条命令生成一个基于 @ax-npm/host-trusted-sdk-v4 的可信插件骨架(Module Federation remote + 共享 Vue + activate(ctx))。用法:npm create ax-trusted-plugin <id>。生成器本身无机密,公网可装;生成出的工程仍走私有源拉 @ax-npm/* 依赖。",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "create-ax-trusted-plugin",
9
+ "scaffold",
10
+ "module-federation",
11
+ "vue",
12
+ "plugin"
13
+ ],
14
+ "bin": {
15
+ "create-ax-trusted-plugin": "bin/index.mjs"
16
+ },
17
+ "files": [
18
+ "bin",
19
+ "template"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "publishConfig": {
25
+ "registry": "https://registry.npmjs.org/",
26
+ "access": "public"
27
+ }
28
+ }
@@ -0,0 +1,28 @@
1
+ # __ID__
2
+
3
+ 可信(internal)插件:经 Module Federation 融入宿主进程、共享宿主 Vue、拿完整 `ctx`。
4
+ 由 `@ax-npm/create-trusted-plugin` 脚手架生成。
5
+
6
+ ## 开发
7
+ ```bash
8
+ export GITLAB_TOKEN=<读权限 PAT>
9
+ npm install
10
+ npm run dev # build:watch + serve(:5174) + host-dev(:5173)
11
+ ```
12
+ 浏览器打开 `http://localhost:5173`,锚点出现「__TITLE__」面板。改代码 → 自动重建 → 刷新浏览器。
13
+
14
+ > ⚠️ 可信插件不能用 `vite dev`(federation dev 不产可加载 remoteEntry),故用 `build --watch` + 静态托管。
15
+
16
+ ## 构建 / 上架
17
+ ```bash
18
+ npm run build # → dist/assets/remoteEntry.js
19
+ ```
20
+ 把 `dist/` + `manifest.json` 部署到静态源,在宿主市场 `plugins.json` 登记本插件 id 即可加载。
21
+
22
+ ## 关键约定
23
+ - federation:`exposes:{'./plugin':'./src/index.ts'}` + `shared:['vue','pinia']`(**别设 requiredVersion**)。
24
+ - `manifest.entry` = `assets/remoteEntry.js`,`mfExpose` 与 exposes key 一致(`./plugin`)。
25
+ - 提供能力须命名空间化(`__ID__/...`)且在 `contributes.capabilities` 声明;消费走 `uses.capabilities`。
26
+ - 插件间不互相 import,只走 `ctx.commands` / `ctx.events`。
27
+
28
+ 完整说明见 `@ax-npm/host-trusted-sdk-v4` 的 README。
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ *.tgz
@@ -0,0 +1,4 @@
1
+ # 安装 @ax-npm/* 走局域网 GitLab(192.168.1.209)实例端点(scope == 顶级群组 ax-npm)。
2
+ # token 用环境变量注入:export GITLAB_TOKEN=<read_package_registry PAT>
3
+ @ax-npm:registry=http://192.168.1.209/api/v4/packages/npm/
4
+ //192.168.1.209/api/v4/packages/npm/:_authToken=${GITLAB_TOKEN}
@@ -0,0 +1,6 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head><meta charset="utf-8" /><title>__ID__ (build entry)</title></head>
4
+ <!-- 仅作 federation 构建入口;宿主实际加载的是 assets/remoteEntry.js 容器,不是本页。 -->
5
+ <body></body>
6
+ </html>
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "__ID__",
3
+ "version": "0.1.0",
4
+ "trust": "internal",
5
+ "host": "^1.0.0",
6
+ "entry": "assets/remoteEntry.js",
7
+ "mfExpose": "./plugin",
8
+ "activationEvents": ["onStartup"],
9
+ "contributes": { "capabilities": ["__ID__/ping"] },
10
+ "uses": { "capabilities": ["map.flyTo"] },
11
+ "component": {
12
+ "anchor": "__ANCHOR__",
13
+ "width": 320,
14
+ "height": 220,
15
+ "title": "__TITLE__",
16
+ "visible": true
17
+ }
18
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "__ID__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "可信(internal)插件:经 Module Federation 融入宿主进程、共享 Vue、拿完整 ctx。只依赖 @ax-npm/host-trusted-sdk-v4。",
7
+ "scripts": {
8
+ "build": "vue-tsc --noEmit && vite build",
9
+ "build:watch": "vite build --watch",
10
+ "serve": "node serve.mjs",
11
+ "dev": "vite build && concurrently -k -n build,serve,host -c blue,magenta,green \"vite build --watch\" \"node serve.mjs\" \"host-dev --plugin http://localhost:5174\"",
12
+ "typecheck": "vue-tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@ax-npm/host-trusted-sdk-v4": "^1.0.0",
16
+ "vue": "^3.4.0"
17
+ },
18
+ "devDependencies": {
19
+ "@ax-npm/host-dev-v4": "^1.0.4",
20
+ "@originjs/vite-plugin-federation": "^1.3.5",
21
+ "@vitejs/plugin-vue": "^5.0.0",
22
+ "concurrently": "^9.0.0",
23
+ "pinia": "^2.1.7",
24
+ "typescript": "^5.4.0",
25
+ "vite": "^5.2.0",
26
+ "vue-tsc": "^2.0.0"
27
+ }
28
+ }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ // 零依赖静态服务:把构建产物 dist/ 对外伪装成「宿主插件源」。
3
+ // 宿主会请求 /plugins.json、/<id>/manifest.json、/<id>/assets/remoteEntry.js…
4
+ // 配合 host-dev --plugin http://localhost:5174 调试。改代码 → vite build --watch 自动重建 → 刷新浏览器。
5
+ import { createServer } from 'node:http';
6
+ import { readFile, stat } from 'node:fs/promises';
7
+ import { readFileSync, existsSync } from 'node:fs';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { dirname, join, normalize, extname, resolve } from 'node:path';
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const DIST = resolve(here, 'dist');
13
+ const MANIFEST = JSON.parse(readFileSync(resolve(here, 'manifest.json'), 'utf8'));
14
+ const ID = MANIFEST.id;
15
+ const PORT = Number(process.env.PORT || 5174);
16
+ // 真正决定「插件就绪」的产物 = manifest.entry 指向的 remoteEntry。
17
+ // /plugins.json 的就绪信号必须以它为准,否则 host-dev 会在 dist 尚未产出(或 watch 重建
18
+ // 清空 dist 的窗口内)就判定就绪并开浏览器,导致 remoteEntry 404、插件激活失败。
19
+ const REMOTE_ENTRY = resolve(DIST, MANIFEST.entry || 'assets/remoteEntry.js');
20
+
21
+ const MIME = {
22
+ '.js': 'text/javascript; charset=utf-8',
23
+ '.mjs': 'text/javascript; charset=utf-8',
24
+ '.json': 'application/json; charset=utf-8',
25
+ '.css': 'text/css; charset=utf-8',
26
+ '.html': 'text/html; charset=utf-8',
27
+ '.svg': 'image/svg+xml',
28
+ '.map': 'application/json; charset=utf-8',
29
+ };
30
+
31
+ createServer(async (req, res) => {
32
+ res.setHeader('access-control-allow-origin', '*');
33
+ const url = (req.url || '/').split('?')[0];
34
+
35
+ if (url === '/plugins.json') {
36
+ // 产物未就绪(首次构建未完成 / watch 重建窗口)时回 503,让 host-dev 的就绪轮询继续等。
37
+ if (!existsSync(REMOTE_ENTRY)) {
38
+ res.writeHead(503, { 'content-type': MIME['.json'], 'retry-after': '1' });
39
+ return res.end(JSON.stringify({ error: 'building', detail: 'remoteEntry 未就绪,等 vite build 完成' }));
40
+ }
41
+ res.writeHead(200, { 'content-type': MIME['.json'] });
42
+ return res.end(JSON.stringify([{ id: ID }]));
43
+ }
44
+
45
+ let rel = url;
46
+ if (rel === `/${ID}` || rel === `/${ID}/`) rel = '/';
47
+ else if (rel.startsWith(`/${ID}/`)) rel = rel.slice(ID.length + 1);
48
+
49
+ if (rel === '/manifest.json') {
50
+ res.writeHead(200, { 'content-type': MIME['.json'] });
51
+ return res.end(JSON.stringify(MANIFEST));
52
+ }
53
+
54
+ let file = normalize(join(DIST, rel));
55
+ if (file !== DIST && !file.startsWith(DIST + (process.platform === 'win32' ? '\\' : '/'))) {
56
+ res.writeHead(403);
57
+ return res.end('forbidden');
58
+ }
59
+ try {
60
+ const s = await stat(file);
61
+ if (s.isDirectory()) file = join(file, 'index.html');
62
+ } catch {
63
+ res.writeHead(404);
64
+ return res.end('not found (dist 未就绪? 等 vite build --watch 首次完成)');
65
+ }
66
+ try {
67
+ const body = await readFile(file);
68
+ res.writeHead(200, { 'content-type': MIME[extname(file).toLowerCase()] || 'application/octet-stream' });
69
+ res.end(body);
70
+ } catch {
71
+ res.writeHead(404);
72
+ res.end('not found');
73
+ }
74
+ }).listen(PORT, () => {
75
+ console.log(`[__ID__] 插件源 → http://localhost:${PORT} (id=${ID})`);
76
+ console.log('[__ID__] 配合 host-dev --plugin http://localhost:' + PORT + ' 调试');
77
+ });
@@ -0,0 +1,71 @@
1
+ <script setup lang="ts">
2
+ // 可信插件 UI:作为带 Shadow DOM 的自定义元素挂载。仅渲染共享状态 + 触发演示动作;
3
+ // 能力接线在 activate(ctx) 里完成,经 window 事件桥回(自定义元素拿不到 ctx)。
4
+ import { state } from './state';
5
+
6
+ function ping() {
7
+ window.dispatchEvent(new CustomEvent('__ID__:ping'));
8
+ }
9
+ function flyTo() {
10
+ window.dispatchEvent(new CustomEvent('__ID__:flyto'));
11
+ }
12
+ </script>
13
+
14
+ <template>
15
+ <div class="panel">
16
+ <div class="title">__TITLE__</div>
17
+ <p class="hint">可信插件 · 经 Module Federation 融入宿主 · 共享 Vue</p>
18
+
19
+ <div class="row">
20
+ <button @click="ping">调用自有能力 ping</button>
21
+ <span class="val">{{ state.lastPing || '—' }}</span>
22
+ </div>
23
+
24
+ <div class="row">
25
+ <button @click="flyTo">map.flyTo 北京</button>
26
+ <span class="val">{{ state.flyToNote || '—' }}</span>
27
+ </div>
28
+
29
+ <div class="tele">
30
+ <div class="tele-h">遥测 telemetry:fcs.basic</div>
31
+ <div v-if="state.telemetry" class="tele-b">
32
+ lon {{ state.telemetry.pos_lon ?? '—' }} ·
33
+ lat {{ state.telemetry.pos_lat ?? '—' }} ·
34
+ alt {{ state.telemetry.pos_alt ?? '—' }}
35
+ </div>
36
+ <div v-else class="tele-b muted">等待遥测…</div>
37
+ </div>
38
+ </div>
39
+ </template>
40
+
41
+ <style>
42
+ .panel {
43
+ box-sizing: border-box;
44
+ height: 100%;
45
+ padding: 12px 14px;
46
+ font: 13px/1.5 system-ui, sans-serif;
47
+ color: var(--color-text, #e6edf3);
48
+ background: var(--color-surface, rgba(20, 26, 33, 0.82));
49
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.12));
50
+ border-radius: 10px;
51
+ backdrop-filter: blur(8px);
52
+ }
53
+ .title { font-weight: 600; font-size: 14px; }
54
+ .hint { margin: 4px 0 10px; opacity: 0.7; font-size: 11px; }
55
+ .row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
56
+ button {
57
+ cursor: pointer;
58
+ padding: 4px 10px;
59
+ border-radius: 6px;
60
+ border: 1px solid var(--color-border, rgba(255, 255, 255, 0.18));
61
+ background: var(--color-accent, #2f6feb);
62
+ color: #fff;
63
+ font-size: 12px;
64
+ }
65
+ button:hover { filter: brightness(1.08); }
66
+ .val { font-variant-numeric: tabular-nums; opacity: 0.9; }
67
+ .tele { margin-top: 10px; padding-top: 8px; border-top: 1px dashed var(--color-border, rgba(255, 255, 255, 0.12)); }
68
+ .tele-h { font-size: 11px; opacity: 0.7; margin-bottom: 2px; }
69
+ .tele-b { font-variant-numeric: tabular-nums; }
70
+ .muted { opacity: 0.5; }
71
+ </style>
@@ -0,0 +1,41 @@
1
+ // 可信插件入口(打成 MF remote 的 expose './plugin')。
2
+ // 宿主经容器协议加载本模块后调 activate(ctx),据 elementTag + manifest.component 挂自定义元素。
3
+ import { definePluginElement, type PluginCtx } from '@ax-npm/host-trusted-sdk-v4';
4
+ import Panel from './Panel.vue';
5
+ import { state, type FcsBasic } from './state';
6
+
7
+ export const elementTag = definePluginElement(Panel, { tag: 'plugin-__ID__' });
8
+
9
+ export function activate(ctx: PluginCtx) {
10
+ // ① 提供能力:id 须命名空间化 + 在 manifest.contributes.capabilities 声明。
11
+ ctx.commands.register('__ID__/ping', async () => 'pong @ ' + new Date().toLocaleTimeString());
12
+
13
+ // ② 订阅宿主遥测(广播)。
14
+ ctx.events.on('telemetry:fcs.basic', (p) => {
15
+ state.telemetry = p as FcsBasic;
16
+ });
17
+
18
+ // ③ Panel 是自定义元素,拿不到 ctx;经 window 事件把用户动作桥回 activate 作用域。
19
+ window.addEventListener('__ID__:ping', async () => {
20
+ state.lastPing = String(await ctx.commands.execute('__ID__/ping'));
21
+ });
22
+ window.addEventListener('__ID__:flyto', async () => {
23
+ try {
24
+ // 调宿主地图能力(需在 manifest.uses.capabilities 声明 map.flyTo)。
25
+ await ctx.commands.execute('map.flyTo', 116.397, 39.908);
26
+ state.flyToNote = '已飞至北京';
27
+ } catch (e) {
28
+ state.flyToNote = '失败:' + (e instanceof Error ? e.message : String(e));
29
+ }
30
+ });
31
+
32
+ return {
33
+ elementTag,
34
+ onVisible() {},
35
+ onHidden() {},
36
+ };
37
+ }
38
+
39
+ export function deactivate() {
40
+ // 仅收自有资源;经 ctx 注册的命令/事件由运行时自动回收。
41
+ }
@@ -0,0 +1,16 @@
1
+ // activate(ctx) 与 Panel 自定义元素之间的共享响应式状态。
2
+ // 二者 import 同一模块 → 同一宿主 vue reactive(MF 共享,单例),activate 更新即触发 Panel 重渲染。
3
+ import { reactive } from 'vue';
4
+
5
+ export interface FcsBasic {
6
+ pos_lon?: number;
7
+ pos_lat?: number;
8
+ pos_alt?: number;
9
+ [k: string]: unknown;
10
+ }
11
+
12
+ export const state = reactive({
13
+ lastPing: '',
14
+ telemetry: null as FcsBasic | null,
15
+ flyToNote: '',
16
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "types": []
13
+ },
14
+ "include": ["src", "vite.config.ts"]
15
+ }
@@ -0,0 +1,33 @@
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+ import federation from '@originjs/vite-plugin-federation';
4
+
5
+ // 可信插件构建:打成 Module Federation remote。
6
+ // - vue({ customElement: true }):SFC 以自定义元素模式编译,样式自动进 shadow root。
7
+ // - federation exposes './plugin' → src/index.ts(导出 activate/elementTag);
8
+ // shared ['vue','pinia'] 让本插件对 vue/pinia 的引用运行时复用宿主单例。
9
+ // **不要给 shared 设 requiredVersion**(宿主按首个注册版本提供,无需精确匹配)。
10
+ // 产物 dist/assets/remoteEntry.js 即 manifest.entry 指向的容器。
11
+ //
12
+ // 可信插件**不能**用 vite dev 调试(federation dev 不产可加载 remoteEntry),
13
+ // 故走 `vite build --watch` + 静态托管 dist(见 serve.mjs / package.json dev)。
14
+ export default defineConfig({
15
+ plugins: [
16
+ vue({ customElement: true }),
17
+ federation({
18
+ name: '__FED_NAME__',
19
+ filename: 'remoteEntry.js',
20
+ exposes: { './plugin': './src/index.ts' },
21
+ shared: ['vue', 'pinia'],
22
+ }),
23
+ ],
24
+ build: {
25
+ target: 'esnext',
26
+ outDir: 'dist',
27
+ // 不清空 outDir:`vite build --watch` 每次重建都会先清空再写,留下一个 remoteEntry.js
28
+ // 缺失的窗口;若浏览器在该窗口请求 remoteEntry 就会 404、插件激活失败。保留旧产物、
29
+ // 原地覆盖可消除该窗口(产物文件名带 hash,残留旧 chunk 无害)。
30
+ emptyOutDir: false,
31
+ minify: false,
32
+ },
33
+ });