vitral 0.3.0 → 0.5.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/dist/angular.d.ts +2 -2
- package/dist/angular.js +7 -7
- package/dist/auto.js +7 -7
- package/dist/{capture-JYFERAPL.js → capture-TT5WBLJ6.js} +1 -1
- package/dist/channel-2YPYGX3J.js +95 -0
- package/dist/{chunk-AKNNHQ2H.js → chunk-463H6B56.js} +5 -0
- package/dist/{chunk-IHTUM6LT.js → chunk-4Z77XNBR.js} +2 -2
- package/dist/chunk-54CBIZES.js +105 -0
- package/dist/{chunk-TD4S3IHA.js → chunk-5JNV45BB.js} +10 -8
- package/dist/{chunk-SDB5AZEI.js → chunk-C3QOBAUE.js} +2 -2
- package/dist/chunk-DAESKNQ6.js +207 -0
- package/dist/{chunk-3YXI3G34.js → chunk-K6EG7DMO.js} +16 -7
- package/dist/{chunk-ZAC6QQI6.js → chunk-KU6HF2EW.js} +4 -4
- package/dist/{chunk-I3VSKSAZ.js → chunk-KZBH5MPD.js} +29 -27
- package/dist/chunk-NZEQGRBL.js +93 -0
- package/dist/chunk-PY7P5BF4.js +404 -0
- package/dist/{chunk-GNHCZRQP.js → chunk-TW7KYJQ6.js} +1 -1
- package/dist/{chunk-AH7KNMBW.js → chunk-UYOPJ4XQ.js} +1 -1
- package/dist/cli.js +1522 -37
- package/dist/devtools.d.ts +32 -2
- package/dist/devtools.js +27 -12
- package/dist/dict.d.ts +69 -0
- package/dist/dict.js +8 -0
- package/dist/edge.d.ts +118 -0
- package/dist/edge.js +398 -0
- package/dist/element.d.ts +12 -3
- package/dist/element.js +7 -7
- package/dist/embed.js +2 -0
- package/dist/{events-D5gs7rff.d.ts → events-0rxFlWJJ.d.ts} +1 -1
- package/dist/expose-IAKYHJXF.js +15 -0
- package/dist/federation.d.ts +89 -0
- package/dist/federation.js +101 -0
- package/dist/hydrate-YVZDJ2V7.js +168 -0
- package/dist/iframe.js +7 -7
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -5
- package/dist/otel.d.ts +29 -1
- package/dist/otel.js +13 -1
- package/dist/overrides-NP754HY7.js +27 -0
- package/dist/preact.d.ts +4 -4
- package/dist/preact.js +6 -6
- package/dist/prefetch.d.ts +25 -0
- package/dist/prefetch.js +178 -0
- package/dist/react.d.ts +3 -3
- package/dist/react.js +6 -6
- package/dist/reframe-LQL4DZYP.js +251 -0
- package/dist/routes.d.ts +2 -2
- package/dist/routes.js +5 -5
- package/dist/{shadow-6HELCHCJ.js → shadow-S4SKHTA7.js} +66 -5
- package/dist/single-spa.d.ts +40 -0
- package/dist/single-spa.js +52 -0
- package/dist/{slot-DqXTwGbs.d.ts → slot-D49L1rdU.d.ts} +1 -1
- package/dist/solid.d.ts +3 -3
- package/dist/solid.js +6 -6
- package/dist/{sri-JMX6JLQP.js → sri-576GTDIA.js} +41 -10
- package/dist/svelte.d.ts +4 -4
- package/dist/svelte.js +6 -6
- package/dist/testing.d.ts +171 -3
- package/dist/testing.js +193 -6
- package/dist/{types-Czvm-siZ.d.ts → types-DUtcF3HO.d.ts} +124 -5
- package/dist/vitals.d.ts +29 -0
- package/dist/vitals.js +152 -0
- package/dist/vite.d.ts +18 -0
- package/dist/vite.js +329 -417
- package/dist/vue.d.ts +3 -3
- package/dist/vue.js +6 -6
- package/package.json +170 -21
- package/dist/chunk-DXORSF4X.js +0 -42
- package/dist/expose-JUOUN5N5.js +0 -15
- package/dist/overrides-NZAIUGK5.js +0 -15
package/dist/svelte.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hostSlot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4Z77XNBR.js";
|
|
4
4
|
import {
|
|
5
5
|
defineRemote,
|
|
6
6
|
events,
|
|
7
7
|
navigate
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-KZBH5MPD.js";
|
|
9
|
+
import "./chunk-5JNV45BB.js";
|
|
10
|
+
import "./chunk-KU6HF2EW.js";
|
|
11
|
+
import "./chunk-TW7KYJQ6.js";
|
|
12
|
+
import "./chunk-463H6B56.js";
|
|
13
13
|
import "./chunk-GNPFWISJ.js";
|
|
14
14
|
|
|
15
15
|
// src/svelte.ts
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,9 +1,177 @@
|
|
|
1
|
-
import { P as Props, R as RemoteContext } from './types-
|
|
1
|
+
import { P as Props, R as RemoteContext, a as RemoteModule } from './types-DUtcF3HO.js';
|
|
2
2
|
export { r as resetVitral } from './state-BDdCwc21.js';
|
|
3
3
|
import './errors-kjBv6IH_.js';
|
|
4
4
|
|
|
5
|
+
/** Endereço do JSON Schema do contrato. Vai no `$schema` do arquivo gravado. */
|
|
6
|
+
declare const CONTRACT_SCHEMA = "https://joaogabriel15.github.io/vitral/schema/vitral-contract.json";
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
8
|
+
* Contrato do consumidor: o que um host passou para um remote e o que ele
|
|
9
|
+
* espera de volta, gravado com `record()`. Arquivo `<remote>.contract.json`.
|
|
10
|
+
*/
|
|
11
|
+
interface Contract {
|
|
12
|
+
$schema?: string;
|
|
13
|
+
/** Nome com que o host monta o remote. */
|
|
14
|
+
remote: string;
|
|
15
|
+
/** Quem gravou (o host). */
|
|
16
|
+
host?: string;
|
|
17
|
+
/** `version` do manifesto do remote na gravação. */
|
|
18
|
+
version?: string;
|
|
19
|
+
/** Uma montagem por caso, sem repetições. */
|
|
20
|
+
cases: ContractCase[];
|
|
21
|
+
}
|
|
22
|
+
/** Quando o remote emitiu: no `mount`, num `update`, ao receber um evento do host, ou depois (clique, timer). */
|
|
23
|
+
type EmitPhase = 'mount' | 'update' | 'input' | 'later';
|
|
24
|
+
/**
|
|
25
|
+
* Uma montagem gravada. Valores que o JSON não guarda viram marcadores:
|
|
26
|
+
* `{ "$fn": true }` (função), `{ "$date": "…" }` e `{ "$unknown": "Classe" }`.
|
|
27
|
+
*/
|
|
28
|
+
interface ContractCase {
|
|
29
|
+
/** Props do mount. */
|
|
30
|
+
props: Props;
|
|
31
|
+
/** Props de cada `update`, em ordem. */
|
|
32
|
+
updates?: Props[];
|
|
33
|
+
/** Eventos que o host emitiu e o remote escutava, em ordem. */
|
|
34
|
+
inputs?: {
|
|
35
|
+
type: string;
|
|
36
|
+
payload?: unknown;
|
|
37
|
+
}[];
|
|
38
|
+
/** Valores que o remote leu com `ctx.events.last`. */
|
|
39
|
+
last?: Record<string, unknown>;
|
|
40
|
+
/** Chaves de `ctx.shared` que o remote leu, com o primeiro valor lido. */
|
|
41
|
+
shared?: Record<string, unknown>;
|
|
42
|
+
/** Eventos que o remote emitiu e o host escuta. */
|
|
43
|
+
emits?: {
|
|
44
|
+
type: string;
|
|
45
|
+
phase: EmitPhase;
|
|
46
|
+
}[];
|
|
47
|
+
/** Eventos que o remote assinou. */
|
|
48
|
+
listens?: string[];
|
|
49
|
+
}
|
|
50
|
+
interface HarnessOptions<P extends Props = Props> {
|
|
51
|
+
props: P;
|
|
52
|
+
/** Nome do remote em `ctx.name`. Padrão `'remote'`. */
|
|
53
|
+
name?: string;
|
|
54
|
+
/** Monta dentro de um shadow root (`ctx.shadowRoot` e `ctx.portalContainer` ficam nele). */
|
|
55
|
+
shadow?: boolean;
|
|
56
|
+
/** Valores iniciais de `ctx.shared`. */
|
|
57
|
+
shared?: Record<string, unknown>;
|
|
58
|
+
/** Valores iniciais de `ctx.events.last`. */
|
|
59
|
+
last?: Record<string, unknown>;
|
|
60
|
+
/** Campos que substituem os do `ctx` simulado. */
|
|
61
|
+
ctx?: Partial<RemoteContext>;
|
|
62
|
+
/** Onde montar. Padrão: um `<div>` novo no `document.body`. */
|
|
63
|
+
target?: HTMLElement;
|
|
64
|
+
}
|
|
65
|
+
interface Harness<P extends Props = Props> {
|
|
66
|
+
/** Elemento onde o remote montou. */
|
|
67
|
+
readonly el: HTMLElement;
|
|
68
|
+
/** O `ctx` que o remote recebeu. */
|
|
69
|
+
readonly ctx: RemoteContext;
|
|
70
|
+
/** Eventos que o remote emitiu por `ctx.events`, em ordem. */
|
|
71
|
+
readonly events: {
|
|
72
|
+
type: string;
|
|
73
|
+
payload: unknown;
|
|
74
|
+
}[];
|
|
75
|
+
/** Erros de `ctx.reportError` e de handlers de eventos. */
|
|
76
|
+
readonly errors: unknown[];
|
|
77
|
+
/** Chamadas de `ctx.navigate`. */
|
|
78
|
+
readonly navigations: {
|
|
79
|
+
url: string;
|
|
80
|
+
options?: {
|
|
81
|
+
replace?: boolean;
|
|
82
|
+
};
|
|
83
|
+
}[];
|
|
84
|
+
/** Tipos de evento que o remote escuta agora. */
|
|
85
|
+
listens(): string[];
|
|
86
|
+
/** Emite um evento como se fosse o host. */
|
|
87
|
+
emit(type: string, payload?: unknown): void;
|
|
88
|
+
update(props: P): void;
|
|
89
|
+
/** Dispara `ctx.onActivate` (keep-alive). */
|
|
90
|
+
activate(): void;
|
|
91
|
+
/** Dispara `ctx.onDeactivate` (keep-alive). */
|
|
92
|
+
deactivate(): void;
|
|
93
|
+
unmount(): void;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Monta `mod` com um `ctx` simulado: barramento de eventos próprio, `shared`
|
|
97
|
+
* em memória, `portalContainer`, `customElements`, `navigate` e
|
|
98
|
+
* `reportError` gravados.
|
|
99
|
+
*
|
|
100
|
+
* const h = await harness(remote, { props: { itens: 2 } })
|
|
101
|
+
* expect(h.el.textContent).toContain('2 itens')
|
|
102
|
+
* h.emit('tema', 'escuro')
|
|
103
|
+
* expect(h.events).toContainEqual({ type: 'carrinho:aberto', payload: undefined })
|
|
104
|
+
*/
|
|
105
|
+
declare function harness<P extends Props = Props>(mod: RemoteModule<P> | {
|
|
106
|
+
default: RemoteModule<P>;
|
|
107
|
+
}, options: HarnessOptions<P>): Promise<Harness<P>>;
|
|
108
|
+
interface VerifyOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Código do remote (o texto dos arquivos JS). Um evento que o host escuta e
|
|
111
|
+
* que o remote só emite depois (clique, timer) é procurado aqui pelo nome.
|
|
112
|
+
*/
|
|
113
|
+
sources?: string[];
|
|
114
|
+
/** Quanto esperar depois de cada etapa, em ms. Padrão 0. */
|
|
115
|
+
wait?: number;
|
|
116
|
+
}
|
|
117
|
+
interface VerifyResult {
|
|
118
|
+
/** Casos reproduzidos. */
|
|
119
|
+
cases: number;
|
|
120
|
+
/** O que quebra o host. Vazio quando o contrato passa. */
|
|
121
|
+
failures: string[];
|
|
122
|
+
/** O que mudou sem quebrar. */
|
|
123
|
+
warnings: string[];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Reproduz cada caso de `contract` em `mod` (props, updates, eventos do host,
|
|
127
|
+
* `shared` e `last`) e confere: mount, update e unmount sem erro, nenhum
|
|
128
|
+
* `reportError`, e cada evento que o host escuta ainda emitido (ou, se só sai
|
|
129
|
+
* depois de um clique, ainda presente no código).
|
|
130
|
+
*/
|
|
131
|
+
declare function verify(contract: Contract, mod: RemoteModule<any> | {
|
|
132
|
+
default: RemoteModule<any>;
|
|
133
|
+
}, options?: VerifyOptions): Promise<VerifyResult>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gravação do contrato do consumidor, no host: para cada remote montado,
|
|
137
|
+
* guarda as props, os updates, os eventos que o host emitiu para ele, o que
|
|
138
|
+
* ele leu de `ctx.shared` e `ctx.events.last`, e os eventos dele que o host
|
|
139
|
+
* escuta. O remote reproduz o arquivo no CI com `vitral test --contract`.
|
|
140
|
+
*
|
|
141
|
+
* Funciona trocando métodos do protocolo global (`load` e o barramento), sem
|
|
142
|
+
* nada no núcleo: o host só paga por isto quando importa `vitral/testing`.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
interface RecordOptions {
|
|
146
|
+
/** Nome do host, gravado no contrato. */
|
|
147
|
+
host?: string;
|
|
148
|
+
/** Máximo de casos diferentes por remote. Padrão 50. */
|
|
149
|
+
max?: number;
|
|
150
|
+
}
|
|
151
|
+
interface Recorder {
|
|
152
|
+
/** Contratos gravados até agora, por nome do remote. */
|
|
153
|
+
contracts(): Record<string, Contract>;
|
|
154
|
+
/** Grava `<remote>.contract.json` em `dir` (só no Node). Devolve os caminhos. */
|
|
155
|
+
save(dir?: string): Promise<string[]>;
|
|
156
|
+
/** Para de gravar e devolve o protocolo como estava. */
|
|
157
|
+
stop(): void;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Começa a gravar. Chame antes de o host montar os remotes e de assinar
|
|
161
|
+
* eventos (o recorder só vê as assinaturas feitas depois dele).
|
|
162
|
+
*
|
|
163
|
+
* const rec = record({ host: 'loja' })
|
|
164
|
+
* // ...testes do host montam os remotes...
|
|
165
|
+
* await rec.save('contracts')
|
|
166
|
+
*
|
|
167
|
+
* Remotes em `isolate: 'iframe'` e partes (`carrinho/Mini`) não são gravados.
|
|
168
|
+
*/
|
|
169
|
+
declare function record(options?: RecordOptions): Recorder;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Utilidades de teste: monte o host sem subir nenhum remote de verdade, monte
|
|
173
|
+
* um remote com `ctx` simulado (`harness`) e grave o contrato do consumidor
|
|
174
|
+
* (`record`) que o remote reproduz no CI com `vitral test --contract`.
|
|
7
175
|
*
|
|
8
176
|
* import { mockRemote } from 'vitral/testing'
|
|
9
177
|
* const carrinho = mockRemote('carrinho', (el, props) => (el.textContent = `itens: ${props.itens}`))
|
|
@@ -26,4 +194,4 @@ declare function mockRemote<P extends Props = Props>(name: string, render?: (el:
|
|
|
26
194
|
/** Remote que sempre falha ao montar, para testar telas de erro. */
|
|
27
195
|
declare function failingRemote(name: string, message?: string): void;
|
|
28
196
|
|
|
29
|
-
export { type MockRemote, failingRemote, mockRemote };
|
|
197
|
+
export { CONTRACT_SCHEMA, type Contract, type ContractCase, type EmitPhase, type Harness, type HarnessOptions, type MockRemote, type RecordOptions, type Recorder, type VerifyOptions, type VerifyResult, failingRemote, harness, mockRemote, record, verify };
|
package/dist/testing.js
CHANGED
|
@@ -1,14 +1,197 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
CONTRACT_SCHEMA,
|
|
3
|
+
encode,
|
|
4
|
+
harness,
|
|
5
|
+
verify
|
|
6
|
+
} from "./chunk-DAESKNQ6.js";
|
|
7
|
+
import "./chunk-KZBH5MPD.js";
|
|
2
8
|
import {
|
|
3
9
|
register
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-5JNV45BB.js";
|
|
11
|
+
import "./chunk-KU6HF2EW.js";
|
|
12
|
+
import "./chunk-TW7KYJQ6.js";
|
|
13
|
+
import "./chunk-463H6B56.js";
|
|
8
14
|
import {
|
|
15
|
+
protocol,
|
|
9
16
|
resetProtocol
|
|
10
17
|
} from "./chunk-GNPFWISJ.js";
|
|
11
18
|
|
|
19
|
+
// src/recorder.ts
|
|
20
|
+
function record(options = {}) {
|
|
21
|
+
const p = protocol();
|
|
22
|
+
const bus = p.events;
|
|
23
|
+
const { load, emit, on } = { load: p.load, emit: bus.emit, on: bus.on };
|
|
24
|
+
const cases = /* @__PURE__ */ new Map();
|
|
25
|
+
const versions = /* @__PURE__ */ new Map();
|
|
26
|
+
const hostListens = /* @__PURE__ */ new Set();
|
|
27
|
+
const wrapped = /* @__PURE__ */ new WeakMap();
|
|
28
|
+
let viaRemote = 0;
|
|
29
|
+
let hostEmitting = [];
|
|
30
|
+
bus.on = (type, handler) => {
|
|
31
|
+
if (viaRemote) return on.call(bus, type, handler);
|
|
32
|
+
hostListens.add(type);
|
|
33
|
+
return on.call(bus, type, (payload) => {
|
|
34
|
+
const before = viaRemote;
|
|
35
|
+
viaRemote = 0;
|
|
36
|
+
try {
|
|
37
|
+
handler(payload);
|
|
38
|
+
} finally {
|
|
39
|
+
viaRemote = before;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
bus.emit = (type, payload) => {
|
|
44
|
+
hostEmitting.push(!viaRemote);
|
|
45
|
+
try {
|
|
46
|
+
return emit.call(bus, type, payload);
|
|
47
|
+
} finally {
|
|
48
|
+
hostEmitting.pop();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const scope = (live, events) => ({
|
|
52
|
+
emit(type, payload) {
|
|
53
|
+
live.emits.push({ type, phase: live.phase });
|
|
54
|
+
viaRemote++;
|
|
55
|
+
try {
|
|
56
|
+
events.emit(type, payload);
|
|
57
|
+
} finally {
|
|
58
|
+
viaRemote--;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
on(type, handler) {
|
|
62
|
+
live.listens.add(type);
|
|
63
|
+
viaRemote++;
|
|
64
|
+
try {
|
|
65
|
+
return events.on(type, (payload) => {
|
|
66
|
+
if (!hostEmitting.at(-1)) return handler(payload);
|
|
67
|
+
live.inputs.push({ type, payload: encode(payload) });
|
|
68
|
+
const before = live.phase;
|
|
69
|
+
live.phase = "input";
|
|
70
|
+
try {
|
|
71
|
+
handler(payload);
|
|
72
|
+
} finally {
|
|
73
|
+
live.phase = before;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
} finally {
|
|
77
|
+
viaRemote--;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
last(type) {
|
|
81
|
+
const v = events.last(type);
|
|
82
|
+
if (v !== void 0 && !(type in live.last)) live.last[type] = encode(v);
|
|
83
|
+
return v;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const sharedOf = (live, api) => {
|
|
87
|
+
const seen = (key, v) => {
|
|
88
|
+
if (v !== void 0 && !(key in live.shared)) live.shared[key] = encode(v);
|
|
89
|
+
return v;
|
|
90
|
+
};
|
|
91
|
+
const wrap = (key) => {
|
|
92
|
+
const h = api(key);
|
|
93
|
+
return { ...h, get: () => seen(key, h.get()), subscribe: (fn) => h.subscribe((v) => fn(seen(key, v))) };
|
|
94
|
+
};
|
|
95
|
+
return Object.assign(wrap, { provide: api.provide, batch: api.batch });
|
|
96
|
+
};
|
|
97
|
+
const wrapModule = (mod) => {
|
|
98
|
+
let w = wrapped.get(mod);
|
|
99
|
+
if (w) return w;
|
|
100
|
+
w = Object.create(mod, {
|
|
101
|
+
mount: {
|
|
102
|
+
value: async (el, props, ctx) => {
|
|
103
|
+
const live = { props: encode(props), updates: [], inputs: [], last: {}, shared: {}, emits: [], listens: /* @__PURE__ */ new Set(), phase: "mount" };
|
|
104
|
+
const list = cases.get(ctx.name) ?? [];
|
|
105
|
+
cases.set(ctx.name, list);
|
|
106
|
+
list.push(live);
|
|
107
|
+
const own = { ...ctx, events: scope(live, ctx.events) };
|
|
108
|
+
if (ctx.shared) own.shared = sharedOf(live, ctx.shared);
|
|
109
|
+
let mounted;
|
|
110
|
+
try {
|
|
111
|
+
mounted = await mod.mount(el, props, own);
|
|
112
|
+
} finally {
|
|
113
|
+
live.phase = "later";
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
update(next) {
|
|
117
|
+
live.updates.push(encode(next));
|
|
118
|
+
live.phase = "update";
|
|
119
|
+
try {
|
|
120
|
+
mounted.update?.(next);
|
|
121
|
+
} finally {
|
|
122
|
+
live.phase = "later";
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
unmount: () => mounted.unmount()
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
wrapped.set(mod, w);
|
|
131
|
+
return w;
|
|
132
|
+
};
|
|
133
|
+
p.load = (name, fetcher) => load.call(p, name, fetcher).then((r) => {
|
|
134
|
+
if (r.version) versions.set(r.name ?? name, r.version);
|
|
135
|
+
return typeof r.module?.mount === "function" ? { ...r, module: wrapModule(r.module) } : r;
|
|
136
|
+
});
|
|
137
|
+
const contracts = () => {
|
|
138
|
+
const out = {};
|
|
139
|
+
for (const [remote, lives] of cases) {
|
|
140
|
+
const unique = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const live of lives) {
|
|
142
|
+
const c = { props: live.props };
|
|
143
|
+
if (live.updates.length) c.updates = live.updates;
|
|
144
|
+
if (live.inputs.length) c.inputs = live.inputs;
|
|
145
|
+
if (Object.keys(live.last).length) c.last = live.last;
|
|
146
|
+
if (Object.keys(live.shared).length) c.shared = live.shared;
|
|
147
|
+
const emits = /* @__PURE__ */ new Map();
|
|
148
|
+
for (const e of live.emits) if (hostListens.has(e.type) && (!emits.has(e.type) || emits.get(e.type) === "later")) emits.set(e.type, e.phase);
|
|
149
|
+
if (emits.size) c.emits = [...emits].map(([type, phase]) => ({ type, phase }));
|
|
150
|
+
if (live.listens.size) c.listens = [...live.listens].sort();
|
|
151
|
+
const key = JSON.stringify(c);
|
|
152
|
+
if (!unique.has(key) && unique.size < (options.max ?? 50)) unique.set(key, c);
|
|
153
|
+
}
|
|
154
|
+
out[remote] = {
|
|
155
|
+
$schema: CONTRACT_SCHEMA,
|
|
156
|
+
remote,
|
|
157
|
+
...options.host && { host: options.host },
|
|
158
|
+
...versions.has(remote) && { version: versions.get(remote) },
|
|
159
|
+
cases: [...unique.values()]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
contracts,
|
|
166
|
+
async save(dir = ".") {
|
|
167
|
+
const fs = "node:fs/promises";
|
|
168
|
+
const path = "node:path";
|
|
169
|
+
const { mkdir, writeFile } = await import(
|
|
170
|
+
/* @vite-ignore */
|
|
171
|
+
fs
|
|
172
|
+
);
|
|
173
|
+
const { join } = await import(
|
|
174
|
+
/* @vite-ignore */
|
|
175
|
+
path
|
|
176
|
+
);
|
|
177
|
+
await mkdir(dir, { recursive: true });
|
|
178
|
+
const files = [];
|
|
179
|
+
for (const [name, c] of Object.entries(contracts())) {
|
|
180
|
+
const file = join(dir, `${name.replace(/[^\w.-]+/g, "_")}.contract.json`);
|
|
181
|
+
await writeFile(file, JSON.stringify(c, null, 2) + "\n");
|
|
182
|
+
files.push(file);
|
|
183
|
+
}
|
|
184
|
+
return files;
|
|
185
|
+
},
|
|
186
|
+
stop() {
|
|
187
|
+
p.load = load;
|
|
188
|
+
bus.emit = emit;
|
|
189
|
+
bus.on = on;
|
|
190
|
+
hostEmitting = [];
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
12
195
|
// src/testing.ts
|
|
13
196
|
function mockRemote(name, render = (el) => {
|
|
14
197
|
el.setAttribute("data-mock-remote", name);
|
|
@@ -47,7 +230,11 @@ function failingRemote(name, message = "falha simulada") {
|
|
|
47
230
|
});
|
|
48
231
|
}
|
|
49
232
|
export {
|
|
233
|
+
CONTRACT_SCHEMA,
|
|
50
234
|
failingRemote,
|
|
235
|
+
harness,
|
|
51
236
|
mockRemote,
|
|
52
|
-
|
|
237
|
+
record,
|
|
238
|
+
resetProtocol as resetVitral,
|
|
239
|
+
verify
|
|
53
240
|
};
|
|
@@ -99,6 +99,17 @@ interface RemoteContext {
|
|
|
99
99
|
* Hosts antigos não mandam este campo.
|
|
100
100
|
*/
|
|
101
101
|
portalContainer?: HTMLElement;
|
|
102
|
+
/**
|
|
103
|
+
* Registro de custom elements do remote. No Shadow DOM, onde o navegador
|
|
104
|
+
* tem Scoped Custom Element Registries (Chrome 146+, Safari 26+), é um
|
|
105
|
+
* registro só deste remote, usado pelo shadow root e pelo elemento `el`:
|
|
106
|
+
* dois remotes podem definir `<ds-button>` de versões diferentes. Sem
|
|
107
|
+
* suporte, e fora do Shadow DOM, é o `customElements` global. No Shadow
|
|
108
|
+
* DOM, `define` de uma tag já definida não lança: fica a definição que já
|
|
109
|
+
* existe, com um aviso em dev quando ela veio de outro remote. Hosts
|
|
110
|
+
* antigos não mandam este campo: use `ctx.customElements ?? customElements`.
|
|
111
|
+
*/
|
|
112
|
+
customElements?: CustomElementRegistry;
|
|
102
113
|
/** Avisa o host de um erro em tempo de execução (os adaptadores já chamam). */
|
|
103
114
|
reportError(error: unknown): void;
|
|
104
115
|
/** Navega a página inteira (host e todos os roteadores) para `url`. */
|
|
@@ -124,6 +135,21 @@ interface RemoteContext {
|
|
|
124
135
|
* (`provide`, `shared` ou um hook); senão, importe `shared` de 'vitral'.
|
|
125
136
|
*/
|
|
126
137
|
shared?: SharedApi;
|
|
138
|
+
/**
|
|
139
|
+
* Só quando o remote roda como MCP App (`vitral({ target: 'mcp-app' })`):
|
|
140
|
+
* fala JSON-RPC direto com o host MCP, para o que `events` não cobre
|
|
141
|
+
* (`tools/call`, `resources/read`, `ui/request-display-mode`…).
|
|
142
|
+
*/
|
|
143
|
+
mcp?: McpAppBridge;
|
|
144
|
+
}
|
|
145
|
+
/** `ctx.mcp` de um remote empacotado como MCP App. */
|
|
146
|
+
interface McpAppBridge {
|
|
147
|
+
/** Pedido ao host; resolve com `result` ou rejeita com `error.message`. */
|
|
148
|
+
request<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
|
|
149
|
+
/** Notificação ao host (sem resposta). */
|
|
150
|
+
notify(method: string, params?: Record<string, unknown>): void;
|
|
151
|
+
/** O `hostContext` atual (tema, idioma, tamanho do container…), já com as mudanças. */
|
|
152
|
+
hostContext(): Record<string, unknown> | undefined;
|
|
127
153
|
}
|
|
128
154
|
interface MountedRemote<P extends Props = Props> {
|
|
129
155
|
update?(props: P): void;
|
|
@@ -131,6 +157,15 @@ interface MountedRemote<P extends Props = Props> {
|
|
|
131
157
|
}
|
|
132
158
|
interface RemoteModule<P extends Props = Props> {
|
|
133
159
|
mount(el: HTMLElement, props: P, ctx: RemoteContext): MountedRemote<P> | Promise<MountedRemote<P>>;
|
|
160
|
+
/**
|
|
161
|
+
* Opcional. Quando o gateway de `vitral/edge` já renderizou o remote no
|
|
162
|
+
* servidor, o `<vitral-remote>` chama `hydrate` com `el` cheio do HTML de
|
|
163
|
+
* `render` (no Shadow DOM declarativo, se for o caso), em vez de limpar e
|
|
164
|
+
* chamar `mount`. Deve assumir o DOM que existe, sem recriar.
|
|
165
|
+
*/
|
|
166
|
+
hydrate?(el: HTMLElement, props: P, ctx: RemoteContext): MountedRemote<P> | Promise<MountedRemote<P>>;
|
|
167
|
+
/** Opcional, só no servidor: o HTML do remote para estas props (SSR pelo gateway). */
|
|
168
|
+
render?(props: P): string | Promise<string>;
|
|
134
169
|
}
|
|
135
170
|
interface RemoteEntry {
|
|
136
171
|
/** URL do módulo JS do remote, ou do `vitral.json` gerado pelo plugin. */
|
|
@@ -149,7 +184,7 @@ interface RemoteEntry {
|
|
|
149
184
|
/** Isolamento padrão para este remote. */
|
|
150
185
|
isolate?: Isolation;
|
|
151
186
|
/**
|
|
152
|
-
* O `vitral.json` já baixado (ex.:
|
|
187
|
+
* O `vitral.json` já baixado (ex.: no build do host), para não buscá-lo
|
|
153
188
|
* na hora. Os caminhos dele são relativos a `url`, e os hashes ficam fixos
|
|
154
189
|
* no host.
|
|
155
190
|
*/
|
|
@@ -159,12 +194,68 @@ interface RemoteEntry {
|
|
|
159
194
|
* Vence a de `configure`. Veja `VitralConfig.contract`.
|
|
160
195
|
*/
|
|
161
196
|
contract?: string;
|
|
197
|
+
/**
|
|
198
|
+
* Com `url` apontando para um canal (`canal.json`): escolhe a versão deste
|
|
199
|
+
* usuário. Devolve a versão (`'1.5.0'`) ou a URL de um `vitral.json`;
|
|
200
|
+
* `undefined` usa a regra padrão (`canary.percent`). Veja `Channel`.
|
|
201
|
+
*/
|
|
202
|
+
resolve?: (channel: Channel, ctx: ChannelContext) => string | undefined | Promise<string | undefined>;
|
|
203
|
+
/**
|
|
204
|
+
* Volta para a estável, até o fim da sessão, quando a versão que não é a
|
|
205
|
+
* estável gera `errors` erros numa janela de `mounts` montagens. Padrão
|
|
206
|
+
* `{ errors: 3, mounts: 20 }`. `false` desliga.
|
|
207
|
+
*/
|
|
208
|
+
rollback?: {
|
|
209
|
+
errors?: number;
|
|
210
|
+
mounts?: number;
|
|
211
|
+
} | false;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Arquivo-ponteiro de um remote (o canal, ex.: `canal.json`): diz que versão
|
|
215
|
+
* cada usuário recebe. Cada versão fica na própria pasta, ao lado do canal
|
|
216
|
+
* (`'1.4.0'` → `1.4.0/vitral.json`), ou é a URL de um `vitral.json`.
|
|
217
|
+
*
|
|
218
|
+
* { "stable": "1.4.0", "canary": { "version": "1.5.0", "percent": 10 } }
|
|
219
|
+
*/
|
|
220
|
+
interface Channel {
|
|
221
|
+
/** Versão de todos os usuários fora da canary. */
|
|
222
|
+
stable: string;
|
|
223
|
+
/** Versão nova para `percent`% dos usuários (0 a 100). */
|
|
224
|
+
canary?: {
|
|
225
|
+
version: string;
|
|
226
|
+
percent: number;
|
|
227
|
+
};
|
|
228
|
+
/** Campos próprios, para o `resolve` do host ler (ex.: `"beta": "1.6.0"`). */
|
|
229
|
+
[key: string]: unknown;
|
|
230
|
+
}
|
|
231
|
+
/** O que o `resolve` de um canal recebe além do canal. */
|
|
232
|
+
interface ChannelContext {
|
|
233
|
+
/** Nome do remote. */
|
|
234
|
+
name: string;
|
|
235
|
+
/**
|
|
236
|
+
* Número de 0 a 99, sempre o mesmo para este navegador (ou para
|
|
237
|
+
* `canaryKey`) e este remote. A regra padrão dá a canary a quem tem
|
|
238
|
+
* `bucket < percent`.
|
|
239
|
+
*/
|
|
240
|
+
bucket: number;
|
|
241
|
+
}
|
|
242
|
+
/** Aviso de rollback automático, para `configure({ onRollback })`. */
|
|
243
|
+
interface RollbackInfo {
|
|
244
|
+
name: string;
|
|
245
|
+
/** Versão abandonada (como o canal ou o `resolve` a escreveram). */
|
|
246
|
+
from: string;
|
|
247
|
+
/** Versão estável, usada daqui até o fim da sessão. */
|
|
248
|
+
to: string;
|
|
249
|
+
errors: number;
|
|
250
|
+
mounts: number;
|
|
162
251
|
}
|
|
163
252
|
/**
|
|
164
253
|
* `none`: CSS com escopo. `shadow`: Shadow DOM. `iframe`: iframe com
|
|
165
254
|
* `sandbox`, para código de terceiros (precisa de `enableIframe()`).
|
|
255
|
+
* `reframe`: JS num iframe oculto de mesma origem (globals próprios) e DOM
|
|
256
|
+
* no Shadow DOM da página. Não é fronteira de segurança.
|
|
166
257
|
*/
|
|
167
|
-
type Isolation = 'shadow' | 'none' | 'iframe';
|
|
258
|
+
type Isolation = 'shadow' | 'none' | 'iframe' | 'reframe';
|
|
168
259
|
/** Estilos do host dentro do Shadow DOM. Sem efeito no modo escopo. */
|
|
169
260
|
interface StyleOptions {
|
|
170
261
|
/**
|
|
@@ -178,6 +269,15 @@ interface StyleOptions {
|
|
|
178
269
|
*/
|
|
179
270
|
mirrorTheme?: boolean;
|
|
180
271
|
}
|
|
272
|
+
/** Opções de `configure({ devOverrides })`. */
|
|
273
|
+
interface DevOverrides {
|
|
274
|
+
/**
|
|
275
|
+
* Modelo da URL de um apelido: `?vitral=checkout@pr-123` com
|
|
276
|
+
* `'https://{name}-{ref}.preview.empresa.com'` vira
|
|
277
|
+
* `https://checkout-pr-123.preview.empresa.com/vitral.json`.
|
|
278
|
+
*/
|
|
279
|
+
preview?: string;
|
|
280
|
+
}
|
|
181
281
|
interface VitralConfig {
|
|
182
282
|
/** Mapa nome → URL (ou entrada completa), ou a URL de um JSON com esse mapa. */
|
|
183
283
|
remotes?: Record<string, string | RemoteEntry> | string;
|
|
@@ -196,9 +296,10 @@ interface VitralConfig {
|
|
|
196
296
|
* Liga os overrides de desenvolvimento (`?vitral=nome@url` e
|
|
197
297
|
* `localStorage['vitral:overrides']`) fora de localhost. Em localhost já
|
|
198
298
|
* valem. Nunca ligue em produção: um link com `?vitral=` executaria código
|
|
199
|
-
* de qualquer origem.
|
|
299
|
+
* de qualquer origem. O objeto também liga e aceita `preview`, o modelo de
|
|
300
|
+
* URL dos apelidos (`?vitral=checkout@pr-123`).
|
|
200
301
|
*/
|
|
201
|
-
devOverrides?: boolean;
|
|
302
|
+
devOverrides?: boolean | DevOverrides;
|
|
202
303
|
/** Estilos do host no Shadow DOM: `{ inherit: ['/assets/base.css'] }`. */
|
|
203
304
|
styles?: StyleOptions;
|
|
204
305
|
/** Recebe o tempo de cada etapa do carregamento (para RUM). */
|
|
@@ -232,6 +333,16 @@ interface VitralConfig {
|
|
|
232
333
|
* conferido. `'*'` confere só as dependências.
|
|
233
334
|
*/
|
|
234
335
|
contract?: string;
|
|
336
|
+
/**
|
|
337
|
+
* Chave estável do usuário (ex.: o id dele) para sortear quem recebe a
|
|
338
|
+
* canary de um canal. Padrão: um id aleatório guardado no `localStorage`.
|
|
339
|
+
*/
|
|
340
|
+
canaryKey?: string;
|
|
341
|
+
/**
|
|
342
|
+
* Chamado quando a versão canary de um canal passa do limite de erros e o
|
|
343
|
+
* host volta para a estável (veja `RemoteEntry.rollback`).
|
|
344
|
+
*/
|
|
345
|
+
onRollback?: (info: RollbackInfo) => void;
|
|
235
346
|
}
|
|
236
347
|
/** Módulos de um grupo de `dedupe`, por especificador (`'react-dom/client'`). */
|
|
237
348
|
type SharedModules = Record<string, unknown>;
|
|
@@ -294,6 +405,14 @@ interface RemoteManifest {
|
|
|
294
405
|
* antigos), o runtime faz esse trabalho em cada página.
|
|
295
406
|
*/
|
|
296
407
|
shadow?: ShadowCss;
|
|
408
|
+
/**
|
|
409
|
+
* SBOM CycloneDX do remote (opção `sbom` do plugin): o arquivo, relativo
|
|
410
|
+
* ao manifesto, e o hash dele. O runtime não usa; é para auditoria.
|
|
411
|
+
*/
|
|
412
|
+
sbom?: {
|
|
413
|
+
file: string;
|
|
414
|
+
integrity: string;
|
|
415
|
+
};
|
|
297
416
|
}
|
|
298
417
|
/** Contrato do remote no `vitral.json`. */
|
|
299
418
|
interface RemoteContract {
|
|
@@ -324,4 +443,4 @@ interface EventBus {
|
|
|
324
443
|
last<T = unknown>(type: string): T | undefined;
|
|
325
444
|
}
|
|
326
445
|
|
|
327
|
-
export { type CheckedProps as C, type EventBus as E, type Handler as H, type Isolation as I, type MountedRemote as M, type Props as P, type RemoteContext as R, type Shared as S, type VitralConfig as V, type RemoteModule as a, type
|
|
446
|
+
export { type CheckedProps as C, type DevOverrides as D, type EventBus as E, type Handler as H, type Isolation as I, type MountedRemote as M, type Props as P, type RemoteContext as R, type Shared as S, type VitralConfig as V, type RemoteModule as a, type SharedModules as b, type RemoteEntry as c, type StyleOptions as d, type SharedValue as e, type Channel as f, type ChannelContext as g, type McpAppBridge as h, type PropsOf as i, type ProvideOptions as j, type ProvidedValue as k, type RemoteContract as l, type RemoteManifest as m, type RemoteMetric as n, type Remotes as o, type RollbackInfo as p, type SharedApi as q, type SharedType as r, batch as s, provide as t, shared as u };
|
package/dist/vitals.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type VitalName = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB';
|
|
2
|
+
type VitalRating = 'good' | 'needs-improvement' | 'poor';
|
|
3
|
+
interface VitalReport {
|
|
4
|
+
name: VitalName;
|
|
5
|
+
/** Remote dono do elemento. Ausente quando é do host ou da página. */
|
|
6
|
+
remote?: string;
|
|
7
|
+
/** Versão do remote (do manifesto) na hora da medição. */
|
|
8
|
+
version?: string;
|
|
9
|
+
/** Rota: pathname da navegação (soft ou não). */
|
|
10
|
+
route: string;
|
|
11
|
+
/** `navigationId` das entradas, só com Soft Navigations. */
|
|
12
|
+
navigationId?: string;
|
|
13
|
+
/** ms para LCP, INP, FCP e TTFB; sem unidade para CLS. */
|
|
14
|
+
value: number;
|
|
15
|
+
rating: VitalRating;
|
|
16
|
+
}
|
|
17
|
+
interface VitalsOptions {
|
|
18
|
+
onReport: (report: VitalReport) => void;
|
|
19
|
+
}
|
|
20
|
+
declare const rating: (name: VitalName, value: number) => VitalRating;
|
|
21
|
+
/** Dono de `node`: o `data-vitral-root` mais próximo, atravessando shadow roots. `''` é o host. */
|
|
22
|
+
declare function owner(node: Node | null | undefined): string;
|
|
23
|
+
/**
|
|
24
|
+
* Observa as Web Vitals e chama `onReport` com cada uma, por remote e rota.
|
|
25
|
+
* Devolve `stop`, que relata o que falta e desliga os observers.
|
|
26
|
+
*/
|
|
27
|
+
declare function vitals({ onReport }: VitalsOptions): () => void;
|
|
28
|
+
|
|
29
|
+
export { type VitalName, type VitalRating, type VitalReport, type VitalsOptions, owner, rating, vitals };
|