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 +5 -4
- package/bin/index.mjs +1 -2
- package/package.json +2 -3
- package/template/README.md +13 -10
- package/template/manifest.json +1 -2
- package/template/package.json +4 -7
- package/template/src/index.ts +3 -2
- package/template/vite.config.ts +72 -22
- package/template/index.html +0 -6
- package/template/serve.mjs +0 -77
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
|
-
(
|
|
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__
|
|
40
|
-
`manifest.json`(trust:internal)、`vite.config.ts`(
|
|
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 #
|
|
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
|
-
"description": "可信(internal)插件脚手架:一条命令生成一个基于 @ax-npm/host-trusted-sdk-v4 的可信插件骨架(
|
|
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
|
],
|
package/template/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# __ID__
|
|
2
2
|
|
|
3
|
-
可信(internal)
|
|
4
|
-
|
|
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 #
|
|
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
|
-
>
|
|
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/
|
|
21
|
+
npm run build # → dist/main.js(+ main.js.map)
|
|
21
22
|
```
|
|
22
|
-
把 `dist
|
|
23
|
+
把 `dist/main.js`(+ `.map`)+ `manifest.json` 部署到静态源,在宿主市场 `plugins.json` 登记本插件 id(及 `baseUrl`)即可加载。
|
|
23
24
|
|
|
24
25
|
## 关键约定
|
|
25
|
-
-
|
|
26
|
-
- `
|
|
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
|
-
|
|
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》。
|
package/template/manifest.json
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"trust": "internal",
|
|
5
5
|
"host": "^1.0.0",
|
|
6
|
-
"entry": "
|
|
7
|
-
"mfExpose": "./plugin",
|
|
6
|
+
"entry": "main.js",
|
|
8
7
|
"activationEvents": ["onStartup"],
|
|
9
8
|
"contributes": { "capabilities": ["__ID__/ping"] },
|
|
10
9
|
"uses": { "capabilities": ["map.flyTo"] },
|
package/template/package.json
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "可信(internal)
|
|
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
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"dev": "
|
|
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"
|
package/template/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
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';
|
package/template/vite.config.ts
CHANGED
|
@@ -1,33 +1,83 @@
|
|
|
1
1
|
import { defineConfig } from 'vite';
|
|
2
2
|
import vue from '@vitejs/plugin-vue';
|
|
3
|
-
import
|
|
3
|
+
import { fileURLToPath, URL } from 'node:url';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
4
5
|
|
|
5
|
-
//
|
|
6
|
-
// -
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
-
//
|
|
13
|
-
//
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
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
|
});
|
package/template/index.html
DELETED
package/template/serve.mjs
DELETED
|
@@ -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
|
-
});
|