vitral 0.4.0 → 0.6.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 +5 -5
- package/dist/auto.js +5 -5
- package/dist/chunk-B2MPYF4M.js +27 -0
- package/dist/chunk-DAESKNQ6.js +207 -0
- package/dist/{chunk-WD4CDGDZ.js → chunk-HIVMSPGM.js} +12 -11
- package/dist/{chunk-IH3ZT3QU.js → chunk-JBNITHPI.js} +1 -1
- package/dist/{chunk-6EX5KYT5.js → chunk-KU6HF2EW.js} +2 -2
- package/dist/chunk-NZEQGRBL.js +93 -0
- package/dist/chunk-OVVGVPIV.js +21 -0
- package/dist/{chunk-BCC25ANK.js → chunk-PLM7U7LR.js} +10 -6
- package/dist/chunk-PY7P5BF4.js +404 -0
- package/dist/{chunk-EMSB2NVX.js → chunk-S7PIVPBX.js} +10 -4
- package/dist/{chunk-W6322KGP.js → chunk-UXLKRSOM.js} +2 -2
- package/dist/cli.js +414 -44
- package/dist/dict.d.ts +69 -0
- package/dist/dict.js +8 -0
- package/dist/edge.d.ts +124 -0
- package/dist/edge.js +413 -0
- package/dist/element.d.ts +2 -2
- package/dist/element.js +5 -5
- package/dist/embed.js +2 -2
- package/dist/{events-Dkpkw_Sv.d.ts → events-0rxFlWJJ.d.ts} +1 -1
- package/dist/{expose-OVWTPHH5.js → expose-EQOWTJNC.js} +3 -3
- package/dist/federation.d.ts +1 -1
- package/dist/hydrate-R76V2I36.js +182 -0
- package/dist/iframe.js +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/match-CMBM_jLW.d.ts +16 -0
- package/dist/otel.d.ts +29 -1
- package/dist/otel.js +13 -1
- package/dist/preact.d.ts +4 -4
- package/dist/preact.js +4 -4
- package/dist/prefetch.d.ts +25 -0
- package/dist/prefetch.js +178 -0
- package/dist/react-server.d.ts +20 -0
- package/dist/react-server.js +13 -0
- package/dist/react.d.ts +7 -4
- package/dist/react.js +19 -31
- package/dist/reframe-MUV472OU.js +251 -0
- package/dist/routes.d.ts +4 -13
- package/dist/routes.js +27 -23
- package/dist/{shadow-PVSETNMZ.js → shadow-S4SKHTA7.js} +2 -2
- package/dist/single-spa.d.ts +1 -1
- package/dist/{slot-UPg6ob9v.d.ts → slot-D49L1rdU.d.ts} +1 -1
- package/dist/solid.d.ts +3 -3
- package/dist/solid.js +4 -4
- package/dist/{sri-M7ZRU57G.js → sri-576GTDIA.js} +2 -1
- package/dist/svelte.d.ts +4 -4
- package/dist/svelte.js +4 -4
- package/dist/testing.d.ts +171 -3
- package/dist/testing.js +191 -4
- package/dist/{types-CA2p9KHC.d.ts → types-DUtcF3HO.d.ts} +12 -1
- package/dist/vitals.d.ts +29 -0
- package/dist/vitals.js +152 -0
- package/dist/vite.js +28 -422
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +4 -4
- package/package.json +111 -16
package/dist/routes.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
matchRoute
|
|
3
|
+
} from "./chunk-OVVGVPIV.js";
|
|
1
4
|
import {
|
|
2
5
|
createSlot,
|
|
3
6
|
navigate
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
+
} from "./chunk-HIVMSPGM.js";
|
|
8
|
+
import {
|
|
9
|
+
warmSsr
|
|
10
|
+
} from "./chunk-S7PIVPBX.js";
|
|
11
|
+
import "./chunk-KU6HF2EW.js";
|
|
7
12
|
import "./chunk-TW7KYJQ6.js";
|
|
8
13
|
import "./chunk-463H6B56.js";
|
|
9
14
|
import "./chunk-GNPFWISJ.js";
|
|
@@ -11,21 +16,8 @@ import "./chunk-GNPFWISJ.js";
|
|
|
11
16
|
// src/routes.ts
|
|
12
17
|
var table = {};
|
|
13
18
|
var outlets = /* @__PURE__ */ new Set();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
let re = compiled.get(pattern);
|
|
17
|
-
if (!re) {
|
|
18
|
-
const body = pattern.replace(/\/\*$|^\*$/, "\0").replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/:(\w+)/g, "(?<$1>[^/]+)").replace(/\/$/, "").replace("\0", "(?:/.*)?");
|
|
19
|
-
compiled.set(pattern, re = new RegExp(`^${body}/?$`));
|
|
20
|
-
}
|
|
21
|
-
return re;
|
|
22
|
-
}
|
|
23
|
-
function matchRoute(path, routes2 = table) {
|
|
24
|
-
for (const [pattern, name] of Object.entries(routes2)) {
|
|
25
|
-
const m = compile(pattern).exec(path);
|
|
26
|
-
if (m) return { name, pattern, params: { ...m.groups } };
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
19
|
+
function matchRoute2(path, routes2 = table) {
|
|
20
|
+
return matchRoute(path, routes2);
|
|
29
21
|
}
|
|
30
22
|
function routes(map, options = {}) {
|
|
31
23
|
table = map;
|
|
@@ -44,7 +36,7 @@ function onClick(e) {
|
|
|
44
36
|
const a = e.composedPath().find((n) => n.localName === "a" && !!n.href);
|
|
45
37
|
if (!a || a.target && a.target !== "_self" || a.hasAttribute("download")) return;
|
|
46
38
|
const url = new URL(a.href);
|
|
47
|
-
if (url.origin !== location.origin || !
|
|
39
|
+
if (url.origin !== location.origin || !matchRoute2(url.pathname)) return;
|
|
48
40
|
e.preventDefault();
|
|
49
41
|
navigate(url.href);
|
|
50
42
|
}
|
|
@@ -99,7 +91,7 @@ var VitralOutletElement = class extends Base {
|
|
|
99
91
|
}
|
|
100
92
|
/** Confere a URL atual e troca de remote se a rota mudou. */
|
|
101
93
|
sync() {
|
|
102
|
-
if (this.isConnected) this.#show(
|
|
94
|
+
if (this.isConnected) this.#show(matchRoute2(location.pathname)?.name ?? null);
|
|
103
95
|
}
|
|
104
96
|
#show(name) {
|
|
105
97
|
if (name === this.#route) return;
|
|
@@ -113,12 +105,24 @@ var VitralOutletElement = class extends Base {
|
|
|
113
105
|
} else if (this.#slot) {
|
|
114
106
|
this.#slot.set({ name });
|
|
115
107
|
} else {
|
|
116
|
-
|
|
108
|
+
const options = {
|
|
117
109
|
name,
|
|
118
110
|
props: this.#props,
|
|
119
111
|
isolate: this.getAttribute("isolate") || void 0,
|
|
120
112
|
onStatus: (s, e) => this.#status(s, e)
|
|
121
|
-
}
|
|
113
|
+
};
|
|
114
|
+
const ssr = this.querySelector(":scope>[data-vitral-ssr]");
|
|
115
|
+
if (ssr?.getAttribute("data-vitral-slot") === name) {
|
|
116
|
+
this.#status("loading", null);
|
|
117
|
+
warmSsr(ssr);
|
|
118
|
+
import("./hydrate-R76V2I36.js").then((m) => {
|
|
119
|
+
if (this.#route === name && !this.#slot) this.#slot = m.hydrateSlot(this, ssr, { ...options, props: this.#props });
|
|
120
|
+
else ssr.remove();
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
ssr?.remove();
|
|
125
|
+
this.#slot = createSlot(this, options);
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
#status(status, error) {
|
|
@@ -139,6 +143,6 @@ function defineVitralOutlet(tag = "vitral-outlet") {
|
|
|
139
143
|
export {
|
|
140
144
|
VitralOutletElement,
|
|
141
145
|
defineVitralOutlet,
|
|
142
|
-
matchRoute,
|
|
146
|
+
matchRoute2 as matchRoute,
|
|
143
147
|
routes
|
|
144
148
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fetchCssText,
|
|
3
3
|
injectCss
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KU6HF2EW.js";
|
|
5
5
|
import "./chunk-TW7KYJQ6.js";
|
|
6
6
|
import "./chunk-463H6B56.js";
|
|
7
7
|
import "./chunk-GNPFWISJ.js";
|
|
@@ -43,7 +43,7 @@ async function attachShadowRoot(target, make, r, global = {}, local = {}) {
|
|
|
43
43
|
el = make({ customElementRegistry: own });
|
|
44
44
|
} catch {
|
|
45
45
|
}
|
|
46
|
-
root.appendChild(el);
|
|
46
|
+
if (!el.isConnected) root.appendChild(el);
|
|
47
47
|
return [...await attachStyles(root, el, r, global, local), root, own, el];
|
|
48
48
|
}
|
|
49
49
|
var registries = /* @__PURE__ */ new Map();
|
package/dist/single-spa.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { R as RemoteError } from './errors-kjBv6IH_.js';
|
|
2
|
-
import { P as Props, I as Isolation, d as StyleOptions } from './types-
|
|
2
|
+
import { P as Props, I as Isolation, d as StyleOptions } from './types-DUtcF3HO.js';
|
|
3
3
|
|
|
4
4
|
type SlotStatus = 'loading' | 'ready' | 'error';
|
|
5
5
|
interface SlotOptions<P extends Props = Props> {
|
package/dist/solid.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { JSX, Component, Accessor } from 'solid-js';
|
|
2
|
-
import { P as Props, I as Isolation, C as CheckedProps, a as RemoteModule, S as Shared } from './types-
|
|
3
|
-
export { R as RemoteContext } from './types-
|
|
2
|
+
import { P as Props, I as Isolation, C as CheckedProps, a as RemoteModule, S as Shared } from './types-DUtcF3HO.js';
|
|
3
|
+
export { R as RemoteContext } from './types-DUtcF3HO.js';
|
|
4
4
|
import { R as RemoteError } from './errors-kjBv6IH_.js';
|
|
5
|
-
export { e as events } from './events-
|
|
5
|
+
export { e as events } from './events-0rxFlWJJ.js';
|
|
6
6
|
export { n as navigate } from './navigate-BRG1WJUM.js';
|
|
7
7
|
import { L as Loading, P as Prefetch } from './host-slot-Cz6EiGsB.js';
|
|
8
8
|
|
package/dist/solid.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hostSlot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UXLKRSOM.js";
|
|
4
4
|
import {
|
|
5
5
|
defineRemote,
|
|
6
6
|
events,
|
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
navigate,
|
|
9
9
|
same,
|
|
10
10
|
shared
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-HIVMSPGM.js";
|
|
12
|
+
import "./chunk-S7PIVPBX.js";
|
|
13
|
+
import "./chunk-KU6HF2EW.js";
|
|
14
14
|
import "./chunk-TW7KYJQ6.js";
|
|
15
15
|
import "./chunk-463H6B56.js";
|
|
16
16
|
import "./chunk-GNPFWISJ.js";
|
|
@@ -41,6 +41,7 @@ var STRICT = /^(sha(256|384|512)-[A-Za-z0-9+/]+={0,2}( +|$))+$/;
|
|
|
41
41
|
var PROBE = "data:text/javascript,export{}";
|
|
42
42
|
var PROBE_SRI = "sha256-d3bjIZdvLjBurwdLwkSVOnEBPFFly+T1SR9r3VVHBGg=";
|
|
43
43
|
var maps;
|
|
44
|
+
var mapped = /* @__PURE__ */ new Set();
|
|
44
45
|
var probing;
|
|
45
46
|
var preloads;
|
|
46
47
|
var KEY = "vitral:sri-map";
|
|
@@ -98,7 +99,7 @@ async function verified(build, run) {
|
|
|
98
99
|
async function addMap(sri, probe) {
|
|
99
100
|
if (!HTMLScriptElement.supports?.("importmap")) return false;
|
|
100
101
|
const integrity = {};
|
|
101
|
-
for (const u in sri) if (!/\.css([?#]|$)/.test(u)) integrity[u] = sri[u];
|
|
102
|
+
for (const u in sri) if (!/\.css([?#]|$)/.test(u) && !mapped.has(u)) mapped.add(u), integrity[u] = sri[u];
|
|
102
103
|
const urls = probe ? ["export{}", "export{}//"].map((c) => URL.createObjectURL(new Blob([c], { type: "text/javascript" }))) : [];
|
|
103
104
|
for (const u of urls) integrity[u] = PROBE_SRI;
|
|
104
105
|
let ok = true;
|
package/dist/svelte.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Component } from 'svelte';
|
|
2
|
-
import { P as Props, I as Isolation, a as RemoteModule } from './types-
|
|
3
|
-
export { R as RemoteContext } from './types-
|
|
2
|
+
import { P as Props, I as Isolation, a as RemoteModule } from './types-DUtcF3HO.js';
|
|
3
|
+
export { R as RemoteContext } from './types-DUtcF3HO.js';
|
|
4
4
|
import { R as RemoteError } from './errors-kjBv6IH_.js';
|
|
5
|
-
import { S as SlotStatus } from './slot-
|
|
6
|
-
export { e as events } from './events-
|
|
5
|
+
import { S as SlotStatus } from './slot-D49L1rdU.js';
|
|
6
|
+
export { e as events } from './events-0rxFlWJJ.js';
|
|
7
7
|
export { n as navigate } from './navigate-BRG1WJUM.js';
|
|
8
8
|
import { L as Loading, P as Prefetch } from './host-slot-Cz6EiGsB.js';
|
|
9
9
|
|
package/dist/svelte.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hostSlot
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UXLKRSOM.js";
|
|
4
4
|
import {
|
|
5
5
|
defineRemote,
|
|
6
6
|
events,
|
|
7
7
|
navigate
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-HIVMSPGM.js";
|
|
9
|
+
import "./chunk-S7PIVPBX.js";
|
|
10
|
+
import "./chunk-KU6HF2EW.js";
|
|
11
11
|
import "./chunk-TW7KYJQ6.js";
|
|
12
12
|
import "./chunk-463H6B56.js";
|
|
13
13
|
import "./chunk-GNPFWISJ.js";
|
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-HIVMSPGM.js";
|
|
2
8
|
import {
|
|
3
9
|
register
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-S7PIVPBX.js";
|
|
11
|
+
import "./chunk-KU6HF2EW.js";
|
|
6
12
|
import "./chunk-TW7KYJQ6.js";
|
|
7
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
|
};
|
|
@@ -157,6 +157,15 @@ interface MountedRemote<P extends Props = Props> {
|
|
|
157
157
|
}
|
|
158
158
|
interface RemoteModule<P extends Props = Props> {
|
|
159
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>;
|
|
160
169
|
}
|
|
161
170
|
interface RemoteEntry {
|
|
162
171
|
/** URL do módulo JS do remote, ou do `vitral.json` gerado pelo plugin. */
|
|
@@ -243,8 +252,10 @@ interface RollbackInfo {
|
|
|
243
252
|
/**
|
|
244
253
|
* `none`: CSS com escopo. `shadow`: Shadow DOM. `iframe`: iframe com
|
|
245
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.
|
|
246
257
|
*/
|
|
247
|
-
type Isolation = 'shadow' | 'none' | 'iframe';
|
|
258
|
+
type Isolation = 'shadow' | 'none' | 'iframe' | 'reframe';
|
|
248
259
|
/** Estilos do host dentro do Shadow DOM. Sem efeito no modo escopo. */
|
|
249
260
|
interface StyleOptions {
|
|
250
261
|
/**
|
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 };
|