create-ax-trusted-plugin 1.0.4 → 1.0.5

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 CHANGED
@@ -1,7 +1,8 @@
1
1
  # create-ax-trusted-plugin
2
2
 
3
3
  可信(internal)插件**脚手架**:一条命令生成一个基于 `@ax-npm/host-trusted-sdk-v4` 的可信插件骨架
4
- (Module Federation remote + 共享 Vue + `activate(ctx)` + 自定义元素面板 + 调 `map.flyTo` + 遥测订阅)。
4
+ (默认「形态一」:ESM 直出 `activate` + 自带 Vue + 自定义元素面板 + 调 `map.flyTo` + 遥测订阅;
5
+ dev 走裸 `vite`,原生 sourcemap、无构建步)。
5
6
 
6
7
  > 脚手架本身已发**公网 npm**,生成这步**零配置**;生成出的工程才走私有源(`192.168.1.209` GitLab)拉
7
8
  > `@ax-npm/*` 依赖,届时只需一个 `GITLAB_TOKEN`(无需手建任何 `.npmrc`,工程自带)。
@@ -36,9 +37,9 @@ npm run dev
36
37
  锚点可选:`top-left/top-right/top-center/bottom-left/bottom-right/left-center/right-center/center`。
37
38
 
38
39
  ## 生成内容
39
- `template/` 经占位替换(`__ID__`/`__TITLE__`/`__ANCHOR__`/`__FED_NAME__`)后落地:`src/{index.ts,Panel.vue,state.ts}`、
40
- `manifest.json`(trust:internal)、`vite.config.ts`(federation remote)、`serve.mjs`(dev 静态源)、`package.json`、
41
- `tsconfig.json`、`.npmrc`、`.gitignore`、`README.md`。
40
+ `template/` 经占位替换(`__ID__`/`__TITLE__`/`__ANCHOR__`)后落地:`src/{index.ts,Panel.vue,state.ts}`、
41
+ `manifest.json`(trust:internal、entry:main.js)、`vite.config.ts`(形态一 lib 构建 + dev 市场源桥)
42
+ `package.json`、`tsconfig.json`、`.npmrc`、`.gitignore`、`README.md`。
42
43
 
43
44
  ## 发布(维护者)
44
45
  脚手架包发**公网 npm**(`publishConfig` 已指向 `registry.npmjs.org`、`access: public`):
package/bin/index.mjs CHANGED
@@ -87,7 +87,6 @@ async function main() {
87
87
  __ID__: id,
88
88
  __TITLE__: title,
89
89
  __ANCHOR__: anchor,
90
- __FED_NAME__: id.replace(/[^a-zA-Z0-9]/g, '_'),
91
90
  };
92
91
  await scaffold(TEMPLATE, destDir, vars);
93
92
 
@@ -100,7 +99,7 @@ async function main() {
100
99
  console.log(' $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell');
101
100
  console.log(' set GITLAB_TOKEN=<读权限 PAT> # cmd');
102
101
  console.log(' npm install');
103
- console.log(' npm run dev # build:watch + 静态托管 + host-dev,浏览器调试\n');
102
+ console.log(' npm run dev # vite(市场源,原生 sourcemap) + host-dev 真宿主,浏览器调试\n');
104
103
  } finally {
105
104
  rl.close();
106
105
  }
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "create-ax-trusted-plugin",
3
- "version": "1.0.4",
4
- "description": "可信(internal)插件脚手架:一条命令生成一个基于 @ax-npm/host-trusted-sdk-v4 的可信插件骨架(Module Federation remote + 共享 Vue + activate(ctx))。用法:npm create ax-trusted-plugin <id>。生成器本身无机密,公网可装;生成出的工程仍走私有源拉 @ax-npm/* 依赖。",
3
+ "version": "1.0.5",
4
+ "description": "可信(internal)插件脚手架:一条命令生成一个基于 @ax-npm/host-trusted-sdk-v4 的可信插件骨架(默认形态一:ESM 直出 activate + 自带 Vue,dev vite 原生 sourcemap)。用法:npm create ax-trusted-plugin <id>。生成器本身无机密,公网可装;生成出的工程仍走私有源拉 @ax-npm/* 依赖。",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "create-ax-trusted-plugin",
9
9
  "scaffold",
10
- "module-federation",
11
10
  "vue",
12
11
  "plugin"
13
12
  ],
@@ -1,7 +1,7 @@
1
1
  # __ID__
2
2
 
3
- 可信(internal)插件:经 Module Federation 融入宿主进程、共享宿主 Vue、拿完整 `ctx`。
4
- `create-ax-trusted-plugin` 脚手架生成。
3
+ 可信(internal)插件「形态一」:模块直接 `export activate`,壳 `import` 即用;进程内拿完整 `ctx`。
4
+ Vue 打进本插件 bundle(与宿主/其它插件隔离)。由 `create-ax-trusted-plugin` 脚手架生成。
5
5
 
6
6
  ## 开发
7
7
  ```bash
@@ -9,22 +9,25 @@ export GITLAB_TOKEN=<读权限 PAT> # bash / git-bash
9
9
  # $env:GITLAB_TOKEN="<读权限 PAT>" # PowerShell(别用 set / %VAR%)
10
10
  # set GITLAB_TOKEN=<读权限 PAT> # cmd
11
11
  npm install
12
- npm run dev # build:watch + serve(:5174) + host-dev(:5173)
12
+ npm run dev # concurrently: vite(:5174,市场源) + host-dev 真宿主(:5173)
13
13
  ```
14
- 浏览器打开 `http://localhost:5173`,锚点出现「__TITLE__」面板。改代码自动重建 → 刷新浏览器。
14
+ 浏览器打开 `http://localhost:5173`,锚点出现「__TITLE__」面板。**改代码刷新浏览器即生效**(原生 sourcemap)。
15
15
 
16
- > ⚠️ 可信插件不能用 `vite dev`(federation dev 不产可加载 remoteEntry),故用 `build --watch` + 静态托管。
16
+ > dev 走裸 `vite`:`vite.config.ts` `hostSourceBridge` 把单插件服务伪装成市场源,
17
+ > `/<id>/main.js` 实时转译 `src/index.ts`。多插件并行各自 `vite --port 5175/5176/…`。
17
18
 
18
19
  ## 构建 / 上架
19
20
  ```bash
20
- npm run build # → dist/assets/remoteEntry.js
21
+ npm run build # → dist/main.js(+ main.js.map)
21
22
  ```
22
- 把 `dist/` + `manifest.json` 部署到静态源,在宿主市场 `plugins.json` 登记本插件 id 即可加载。
23
+ 把 `dist/main.js`(+ `.map`)+ `manifest.json` 部署到静态源,在宿主市场 `plugins.json` 登记本插件 id(及 `baseUrl`)即可加载。
23
24
 
24
25
  ## 关键约定
25
- - federation:`exposes:{'./plugin':'./src/index.ts'}` + `shared:['vue','pinia']`(**别设 requiredVersion**)
26
- - `manifest.entry` = `assets/remoteEntry.js`,`mfExpose` exposes key 一致(`./plugin`)。
26
+ - 入口 `src/index.ts` 必须 `export function activate(ctx)`(可选 `elementTag`/`deactivate`);`manifest.entry`=`main.js`。
27
+ - `vue({customElement:true})` 把样式注入 shadow root;Vue 自带打包,不与宿主共享。
27
28
  - 提供能力须命名空间化(`__ID__/...`)且在 `contributes.capabilities` 声明;消费走 `uses.capabilities`。
28
29
  - 插件间不互相 import,只走 `ctx.commands` / `ctx.events`。
29
30
 
30
- 完整说明见 `@ax-npm/host-trusted-sdk-v4` README。
31
+ > 需要多个可信插件**共享单例** Vue / 跨边界共享响应式 store,或要省 Vue 体积时,才回退「形态二」
32
+ > (Module Federation):`vite.config` 改用 `@originjs/vite-plugin-federation`、`manifest` 加 `mfExpose`、
33
+ > dev 走 `vite build --watch` + 静态托管。详见 `@ax-npm/host-trusted-sdk-v4` 的 README 与《三角色开发方法.md》。
@@ -3,8 +3,7 @@
3
3
  "version": "0.1.0",
4
4
  "trust": "internal",
5
5
  "host": "^1.0.0",
6
- "entry": "assets/remoteEntry.js",
7
- "mfExpose": "./plugin",
6
+ "entry": "main.js",
8
7
  "activationEvents": ["onStartup"],
9
8
  "contributes": { "capabilities": ["__ID__/ping"] },
10
9
  "uses": { "capabilities": ["map.flyTo"] },
@@ -3,13 +3,12 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "type": "module",
6
- "description": "可信(internal)插件:经 Module Federation 融入宿主进程、共享 Vue、拿完整 ctx。只依赖 @ax-npm/host-trusted-sdk-v4。",
6
+ "description": "可信(internal)插件「形态一」:ESM 直出 activate,壳 import 即用、Vue 自带打包(隔离)、拿完整 ctx。dev 走裸 vite(原生 sourcemap、无构建步)。只依赖 @ax-npm/host-trusted-sdk-v4。",
7
7
  "scripts": {
8
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
- "dev:electron": "vite build && concurrently -k -n build,serve,host -c blue,magenta,green \"vite build --watch\" \"node serve.mjs\" \"host-dev --electron --plugin http://localhost:5174\"",
9
+ "serve": "vite",
10
+ "dev": "concurrently -k -n plugin,host -c blue,green \"vite\" \"host-dev --plugin http://localhost:5174\"",
11
+ "dev:electron": "concurrently -k -n plugin,host -c blue,green \"vite\" \"host-dev --electron --plugin http://localhost:5174\"",
13
12
  "typecheck": "vue-tsc --noEmit"
14
13
  },
15
14
  "dependencies": {
@@ -18,11 +17,9 @@
18
17
  },
19
18
  "devDependencies": {
20
19
  "@ax-npm/host-dev-v4": "^1.0.7",
21
- "@originjs/vite-plugin-federation": "^1.3.5",
22
20
  "@vitejs/plugin-vue": "^5.0.0",
23
21
  "concurrently": "^9.0.0",
24
22
  "electron": "^39.0.0",
25
- "pinia": "^2.1.7",
26
23
  "typescript": "^5.4.0",
27
24
  "vite": "^5.2.0",
28
25
  "vue-tsc": "^2.0.0"
@@ -1,5 +1,6 @@
1
- // 可信插件入口(打成 MF remote 的 expose './plugin')。
2
- // 宿主经容器协议加载本模块后调 activate(ctx),据 elementTag + manifest.component 挂自定义元素。
1
+ // 可信插件入口「形态一」:本模块直接导出 activate/elementTag(manifest.entry main.js)。
2
+ // 宿主 loader `import(url)` 命中「直出 activate」分支,调 activate(ctx),据 elementTag +
3
+ // manifest.component 挂自定义元素。(业务代码与形态二一致,只差打包/加载方式。)
3
4
  import { definePluginElement, type PluginCtx } from '@ax-npm/host-trusted-sdk-v4';
4
5
  import Panel from './Panel.vue';
5
6
  import { state, type FcsBasic } from './state';
@@ -1,33 +1,83 @@
1
1
  import { defineConfig } from 'vite';
2
2
  import vue from '@vitejs/plugin-vue';
3
- import federation from '@originjs/vite-plugin-federation';
3
+ import { fileURLToPath, URL } from 'node:url';
4
+ import { readFileSync } from 'node:fs';
4
5
 
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 指向的容器。
6
+ // 可信插件「形态一」(ESM 直出 activate)构建。与「形态二」(Module Federation 容器)对比:
7
+ // - 形态一:本模块 import(url) 后**直接导出 activate/elementTag**,壳 loader 命中直出分支。
8
+ // Vue 打进 bundle(自带依赖,与宿主/其它插件隔离 —— 宿主升级 Vue 波及不到本插件)
9
+ // - 形态二:打成 federation remoteEntry,运行时与宿主**共享单例** Vue/Pinia。
10
+ // 只经 ctx 交互的内部插件用形态一**功能零损失**,且换来:dev 走裸 `vite`(实时转译、原生
11
+ // sourcemap、无构建步),build 产单文件 `main.js`。仅当需要跨边界共享响应式 store / 省体积
12
+ // 到单份 Vue 时才回退形态二(见 README 与《三角色开发方法.md》)。
11
13
  //
12
- // 可信插件**不能**用 vite dev 调试(federation dev 不产可加载 remoteEntry),
13
- // 故走 `vite build --watch` + 静态托管 dist(见 serve.mjs / package.json dev)。
14
+ // vue({ customElement: true }):SFC 以自定义元素模式编译,样式自动注入 shadow root,
15
+ // 故无独立 CSS 产物,单个 main.js 即完整插件。
16
+
17
+ const MANIFEST = JSON.parse(
18
+ readFileSync(fileURLToPath(new URL('./manifest.json', import.meta.url)), 'utf-8'),
19
+ ) as { id: string };
20
+ const PLUGIN_ID = MANIFEST.id;
21
+
22
+ // —— dev 桥:让「单插件 vite 服务」对外长得像宿主的插件市场源 ——
23
+ // 宿主(LocalRegistry)会请求 /plugins.json、/<id>/manifest.json、/<id>/<entry>。
24
+ // 这里:① /plugins.json = [{ id }];② 把 /<id>/* 重写回 /*(插件本体在根目录);
25
+ // ③ manifest.entry 是 `main.js`(生产构建产物),dev 无此产物 → 把对它的请求
26
+ // 顶替为 `src/index.ts`,由 vite 实时转译(原生 sourcemap、无构建步)。
27
+ // 这样单跑 `vite` 即可被 harness/host-dev 当市场源加载,改代码刷新即生效。
28
+ function hostSourceBridge() {
29
+ return {
30
+ name: 'host-source-bridge',
31
+ configureServer(server: {
32
+ middlewares: { use: (fn: (req: any, res: any, next: () => void) => void) => void };
33
+ }) {
34
+ server.middlewares.use((req, res, next) => {
35
+ const url = (req.url || '').split('?')[0];
36
+ if (url === '/plugins.json') {
37
+ res.setHeader('content-type', 'application/json');
38
+ res.setHeader('access-control-allow-origin', '*');
39
+ res.end(JSON.stringify([{ id: PLUGIN_ID }]));
40
+ return;
41
+ }
42
+ // /<id>/xxx → /xxx(去掉宿主加的 id 前缀)
43
+ if (req.url === `/${PLUGIN_ID}` || req.url === `/${PLUGIN_ID}/`) {
44
+ req.url = '/';
45
+ } else if (req.url && req.url.startsWith(`/${PLUGIN_ID}/`)) {
46
+ req.url = req.url.slice(PLUGIN_ID.length + 1);
47
+ }
48
+ // 形态一 dev:把 main.js 入口请求顶替为可实时转译的源文件。
49
+ if ((req.url || '').split('?')[0] === '/main.js') req.url = '/src/index.ts';
50
+ next();
51
+ });
52
+ },
53
+ };
54
+ }
55
+
14
56
  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
- ],
57
+ plugins: [vue({ customElement: true }), hostSourceBridge()],
58
+ server: {
59
+ // 活跃集多插件并行时用 `vite --port 5175/5176/…` 覆盖,一份多源 plugins.json 列各端口。
60
+ port: 5174,
61
+ strictPort: true,
62
+ // 宿主(:5180 / app://)跨源拉清单/manifest/入口模块,需放开 CORS。
63
+ cors: { origin: '*' },
64
+ },
24
65
  build: {
25
66
  target: 'esnext',
26
67
  outDir: 'dist',
27
- // 不清空 outDir:`vite build --watch` 每次重建都会先清空再写,留下一个 remoteEntry.js
28
- // 缺失的窗口;若浏览器在该窗口请求 remoteEntry 就会 404、插件激活失败。保留旧产物、
29
- // 原地覆盖可消除该窗口(产物文件名带 hash,残留旧 chunk 无害)。
30
- emptyOutDir: false,
68
+ emptyOutDir: true,
69
+ // 源码级调试可信插件产物:留 sourcemap、不混淆。
70
+ sourcemap: true,
31
71
  minify: false,
72
+ // library 模式:入口导出(activate/elementTag/deactivate)即模块导出,壳 import(url) 直出。
73
+ lib: {
74
+ entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
75
+ formats: ['es'],
76
+ fileName: () => 'main.js',
77
+ },
78
+ rollupOptions: {
79
+ // 保住入口导出签名,别被当可执行 chunk 摇掉 activate。
80
+ preserveEntrySignatures: 'strict',
81
+ },
32
82
  },
33
83
  });
@@ -1,6 +0,0 @@
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>
@@ -1,77 +0,0 @@
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
- });