vitral 0.1.0 → 0.3.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/README.md +250 -217
- package/dist/angular.d.ts +16 -4
- package/dist/angular.js +26 -5
- package/dist/auto.d.ts +2 -0
- package/dist/auto.js +13 -0
- package/dist/capture-JYFERAPL.js +42 -0
- package/dist/chunk-3YXI3G34.js +181 -0
- package/dist/chunk-AH7KNMBW.js +20 -0
- package/dist/chunk-AKNNHQ2H.js +45 -0
- package/dist/chunk-DXORSF4X.js +42 -0
- package/dist/chunk-GNHCZRQP.js +79 -0
- package/dist/chunk-GNPFWISJ.js +80 -0
- package/dist/chunk-I3VSKSAZ.js +634 -0
- package/dist/chunk-IHTUM6LT.js +152 -0
- package/dist/chunk-SDB5AZEI.js +51 -0
- package/dist/chunk-TD4S3IHA.js +201 -0
- package/dist/chunk-ZAC6QQI6.js +64 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +138 -0
- package/dist/contract-46EZFQPQ.js +17 -0
- package/dist/dedupe-S7BHQMOZ.js +82 -0
- package/dist/devtools.d.ts +12 -0
- package/dist/devtools.js +86 -0
- package/dist/element.d.ts +60 -2
- package/dist/element.js +10 -2
- package/dist/errors-kjBv6IH_.d.ts +17 -0
- package/dist/events-D5gs7rff.d.ts +6 -0
- package/dist/expose-JUOUN5N5.js +15 -0
- package/dist/host-slot-Cz6EiGsB.d.ts +6 -0
- package/dist/iframe.d.ts +28 -0
- package/dist/iframe.js +239 -0
- package/dist/index.d.ts +144 -147
- package/dist/index.js +40 -10
- package/dist/navigate-BRG1WJUM.d.ts +9 -0
- package/dist/otel.d.ts +44 -0
- package/dist/otel.js +27 -0
- package/dist/overrides-NZAIUGK5.js +15 -0
- package/dist/preact.d.ts +30 -7
- package/dist/preact.js +38 -9
- package/dist/react.d.ts +46 -7
- package/dist/react.js +97 -42
- package/dist/routes.d.ts +56 -0
- package/dist/routes.js +144 -0
- package/dist/sentry.d.ts +25 -0
- package/dist/sentry.js +11 -0
- package/dist/shadow-6HELCHCJ.js +98 -0
- package/dist/slot-DqXTwGbs.d.ts +36 -0
- package/dist/solid.d.ts +29 -6
- package/dist/solid.js +32 -8
- package/dist/sri-JMX6JLQP.js +116 -0
- package/dist/state-BDdCwc21.d.ts +4 -0
- package/dist/svelte.d.ts +19 -3
- package/dist/svelte.js +11 -4
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +9 -3
- package/dist/types-Czvm-siZ.d.ts +327 -0
- package/dist/vite.d.ts +66 -4
- package/dist/vite.js +1044 -25
- package/dist/vue.d.ts +86 -7
- package/dist/vue.js +74 -46
- package/package.json +181 -11
- package/dist/chunk-IZK6ITIJ.js +0 -96
- package/dist/chunk-TTL52BF2.js +0 -547
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/core/semver.ts
|
|
2
|
+
function satisfies(version, range) {
|
|
3
|
+
if (range === "*") return true;
|
|
4
|
+
const [, op = "", want = ""] = /^(\^|~|>=|=)?(.*)$/.exec(range.trim());
|
|
5
|
+
if (!op || op === "=" || version.includes("-")) return version === want;
|
|
6
|
+
const [v, w] = [version, want].map((s) => (s + ".0.0").split(".", 3).map(Number));
|
|
7
|
+
for (let i = 0; i < 3; i++) if (v[i] !== w[i]) {
|
|
8
|
+
if (!(v[i] > w[i])) return false;
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
if (op === ">=") return true;
|
|
12
|
+
const fixed = Math.min(want.split(".").length, op === "~" ? 2 : w[0] ? 1 : w[1] ? 2 : 3);
|
|
13
|
+
return v.slice(0, fixed).join(".") === w.slice(0, fixed).join(".");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/core/dedupe.ts
|
|
17
|
+
var KEY = "vitral:dedupe:";
|
|
18
|
+
var remembered = (k) => {
|
|
19
|
+
try {
|
|
20
|
+
return localStorage.getItem(KEY + k);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var remember = (k, url) => {
|
|
26
|
+
try {
|
|
27
|
+
localStorage.setItem(KEY + k, url);
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function shared(p) {
|
|
32
|
+
if (!p.dedupe) {
|
|
33
|
+
const offers = /* @__PURE__ */ new Map();
|
|
34
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
35
|
+
const drop = (list, o) => list.includes(o) && list.splice(list.indexOf(o), 1);
|
|
36
|
+
const start = (name, list, o) => {
|
|
37
|
+
if (!o.p) {
|
|
38
|
+
;
|
|
39
|
+
(o.p = o.load()).catch(() => drop(list, o));
|
|
40
|
+
if (o.url) o.p.then(() => remember(`${name}@${o.version}`, o.url), () => {
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return o.p;
|
|
44
|
+
};
|
|
45
|
+
p.dedupe = (name, range, offer2) => {
|
|
46
|
+
const own = offer2;
|
|
47
|
+
const list = offers.get(name) ?? [];
|
|
48
|
+
offers.set(name, list);
|
|
49
|
+
if (own?.url) waiting.get(name + own.url)?.(own);
|
|
50
|
+
let hit = list.find((o) => satisfies(o.version, range));
|
|
51
|
+
if (!hit && own) {
|
|
52
|
+
hit = list[list.push(own) - 1];
|
|
53
|
+
const want = own.url && remembered(`${name}@${own.version}`);
|
|
54
|
+
if (want && want !== own.url) {
|
|
55
|
+
own.p = new Promise((resolve) => {
|
|
56
|
+
waiting.set(name + want, resolve);
|
|
57
|
+
setTimeout(resolve, 300, own);
|
|
58
|
+
}).then((o) => {
|
|
59
|
+
waiting.delete(name + want);
|
|
60
|
+
if (o === own) own.p = void 0;
|
|
61
|
+
return start(name, list, o);
|
|
62
|
+
});
|
|
63
|
+
own.p.catch(() => drop(list, own));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return hit && start(name, list, hit);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return p.dedupe;
|
|
70
|
+
}
|
|
71
|
+
function offer(p, groups) {
|
|
72
|
+
for (const name in groups) {
|
|
73
|
+
const { version, modules } = groups[name];
|
|
74
|
+
shared(p)(name, version, { version, load: async () => modules });
|
|
75
|
+
p.configure({ offered: { ...p.settings().offered, [name]: version } });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
offer,
|
|
80
|
+
satisfies,
|
|
81
|
+
shared
|
|
82
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Overrides salvos neste navegador: `{ nome: url }`. */
|
|
2
|
+
declare function getOverrides(): Record<string, string>;
|
|
3
|
+
/** Salva (ou remove, com `null`) o override de um remote. Vale a partir do próximo carregamento. */
|
|
4
|
+
declare function setOverride(name: string, url: string | null): void;
|
|
5
|
+
interface DevtoolsOptions {
|
|
6
|
+
/** Recarrega a página depois de mudar um override. Padrão: true. */
|
|
7
|
+
reload?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Mostra o painel no canto da página. Devolve uma função que o remove. */
|
|
10
|
+
declare function devtools(options?: DevtoolsOptions): () => void;
|
|
11
|
+
|
|
12
|
+
export { type DevtoolsOptions, devtools, getOverrides, setOverride };
|
package/dist/devtools.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OVERRIDES_KEY,
|
|
3
|
+
manifestUrl,
|
|
4
|
+
overridesEnabled,
|
|
5
|
+
storedOverrides
|
|
6
|
+
} from "./chunk-DXORSF4X.js";
|
|
7
|
+
import "./chunk-GNPFWISJ.js";
|
|
8
|
+
|
|
9
|
+
// src/devtools.ts
|
|
10
|
+
function getOverrides() {
|
|
11
|
+
return storedOverrides();
|
|
12
|
+
}
|
|
13
|
+
function setOverride(name, url) {
|
|
14
|
+
const all = storedOverrides();
|
|
15
|
+
if (url) all[name] = manifestUrl(url);
|
|
16
|
+
else delete all[name];
|
|
17
|
+
if (Object.keys(all).length) localStorage.setItem(OVERRIDES_KEY, JSON.stringify(all));
|
|
18
|
+
else localStorage.removeItem(OVERRIDES_KEY);
|
|
19
|
+
}
|
|
20
|
+
var STYLE = `
|
|
21
|
+
:host { position: fixed; right: 12px; bottom: 12px; z-index: 2147483647; font: 13px/1.4 system-ui, sans-serif; color: #111 }
|
|
22
|
+
button { font: inherit; cursor: pointer }
|
|
23
|
+
.toggle { border: 1px solid #888; border-radius: 6px; background: #fff; padding: 4px 10px }
|
|
24
|
+
.panel { margin-bottom: 8px; padding: 10px; width: 320px; background: #fff; border: 1px solid #888; border-radius: 8px; box-shadow: 0 4px 16px #0003 }
|
|
25
|
+
ul { list-style: none; margin: 0 0 8px; padding: 0 }
|
|
26
|
+
li { display: flex; gap: 6px; align-items: center; margin-bottom: 4px; word-break: break-all }
|
|
27
|
+
form { display: grid; gap: 4px }
|
|
28
|
+
input { font: inherit; padding: 3px 6px }
|
|
29
|
+
.aviso { color: #a40000; margin: 0 0 8px }
|
|
30
|
+
`;
|
|
31
|
+
function devtools(options = {}) {
|
|
32
|
+
const host = document.createElement("div");
|
|
33
|
+
host.setAttribute("data-vitral-devtools", "");
|
|
34
|
+
const root = host.attachShadow({ mode: "open" });
|
|
35
|
+
const style = document.createElement("style");
|
|
36
|
+
style.textContent = STYLE;
|
|
37
|
+
const panel = document.createElement("div");
|
|
38
|
+
panel.className = "panel";
|
|
39
|
+
panel.hidden = true;
|
|
40
|
+
const toggle = document.createElement("button");
|
|
41
|
+
toggle.className = "toggle";
|
|
42
|
+
toggle.type = "button";
|
|
43
|
+
toggle.textContent = "vitral";
|
|
44
|
+
toggle.onclick = () => panel.hidden = !panel.hidden;
|
|
45
|
+
root.append(style, panel, toggle);
|
|
46
|
+
const changed = () => options.reload === false ? draw() : location.reload();
|
|
47
|
+
function draw() {
|
|
48
|
+
panel.replaceChildren();
|
|
49
|
+
if (!overridesEnabled()) {
|
|
50
|
+
const p = document.createElement("p");
|
|
51
|
+
p.className = "aviso";
|
|
52
|
+
p.textContent = "Overrides desligados: use configure({ devOverrides: true }) ou rode em localhost.";
|
|
53
|
+
panel.append(p);
|
|
54
|
+
}
|
|
55
|
+
const list = document.createElement("ul");
|
|
56
|
+
for (const [name2, url2] of Object.entries(storedOverrides())) {
|
|
57
|
+
const item = document.createElement("li");
|
|
58
|
+
const remove = document.createElement("button");
|
|
59
|
+
remove.type = "button";
|
|
60
|
+
remove.textContent = "remover";
|
|
61
|
+
remove.setAttribute("aria-label", `remover override de ${name2}`);
|
|
62
|
+
remove.onclick = () => (setOverride(name2, null), changed());
|
|
63
|
+
item.append(`${name2} \u2192 ${url2}`, remove);
|
|
64
|
+
list.append(item);
|
|
65
|
+
}
|
|
66
|
+
const form = document.createElement("form");
|
|
67
|
+
const name = Object.assign(document.createElement("input"), { placeholder: "nome (ex.: carrinho)", required: true, name: "name" });
|
|
68
|
+
const url = Object.assign(document.createElement("input"), { placeholder: "http://localhost:5174", required: true, name: "url" });
|
|
69
|
+
const save = Object.assign(document.createElement("button"), { type: "submit", textContent: "Salvar" });
|
|
70
|
+
form.append(name, url, save);
|
|
71
|
+
form.onsubmit = (e) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
setOverride(name.value.trim(), url.value.trim());
|
|
74
|
+
changed();
|
|
75
|
+
};
|
|
76
|
+
panel.append(list, form);
|
|
77
|
+
}
|
|
78
|
+
draw();
|
|
79
|
+
document.body.append(host);
|
|
80
|
+
return () => host.remove();
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
devtools,
|
|
84
|
+
getOverrides,
|
|
85
|
+
setOverride
|
|
86
|
+
};
|
package/dist/element.d.ts
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
|
-
import { Props,
|
|
1
|
+
import { P as Props, S as Shared, a as RemoteModule } from './types-Czvm-siZ.js';
|
|
2
|
+
import { S as SlotStatus } from './slot-DqXTwGbs.js';
|
|
3
|
+
import './errors-kjBv6IH_.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Host universal: `<vitral-remote>` funciona em qualquer lugar que renderize
|
|
7
|
+
* HTML (JS/TS puro, Angular, Svelte, Solid, Lit, Astro, Qwik, jQuery, server
|
|
8
|
+
* templates…).
|
|
9
|
+
*
|
|
10
|
+
* import { defineVitralElement } from 'vitral/element'
|
|
11
|
+
* defineVitralElement()
|
|
12
|
+
*
|
|
13
|
+
* <vitral-remote name="carrinho" props='{"userId": 42}' isolate="shadow"
|
|
14
|
+
* inherit-styles="/assets/base.css">
|
|
15
|
+
* <p slot="fallback">Carregando…</p>
|
|
16
|
+
* <p slot="error">Indisponível</p>
|
|
17
|
+
* </vitral-remote>
|
|
18
|
+
*
|
|
19
|
+
* <vitral-remote src="https://cdn.exemplo.com/carrinho/vitral.json"></vitral-remote>
|
|
20
|
+
*
|
|
21
|
+
* Sem os slots, mostra um carregando acessível e um erro com "Tentar de novo".
|
|
22
|
+
*
|
|
23
|
+
* el.props = { userId: 43 } // atualiza sem remontar
|
|
24
|
+
* el.addEventListener('vitral-error', (e) => e.detail.error)
|
|
25
|
+
* el.addEventListener('vitral-ready', (e) => e.detail.timings) // { manifest, js, css, mount } em ms
|
|
26
|
+
* el.retry()
|
|
27
|
+
*
|
|
28
|
+
* Atributos de performance: `loading="lazy|idle"` (monta perto de aparecer ou
|
|
29
|
+
* com o navegador ocioso; reserve o espaço com `min-height`),
|
|
30
|
+
* `prefetch="hover|visible|idle"` (baixa antes de montar), `priority="high"`
|
|
31
|
+
* e `keep-alive` (ou `keep-alive="3"`: máximo de remotes guardados).
|
|
32
|
+
*
|
|
33
|
+
* Também exporta `elementRemote()` para publicar um Web Component (Lit,
|
|
34
|
+
* Stencil, vanilla) como remote.
|
|
35
|
+
*/
|
|
2
36
|
|
|
3
37
|
declare const Base: typeof HTMLElement;
|
|
4
38
|
declare class VitralRemoteElement extends Base {
|
|
@@ -21,10 +55,34 @@ declare function defineVitralElement(tag?: string): void;
|
|
|
21
55
|
* export default elementRemote('meu-widget')
|
|
22
56
|
*/
|
|
23
57
|
declare function elementRemote<P extends Props = Props>(tag: string, setup?: () => void): RemoteModule<P>;
|
|
58
|
+
/** O que o `SharedController` precisa do elemento (o `ReactiveControllerHost` do Lit). */
|
|
59
|
+
interface SharedControllerHost {
|
|
60
|
+
addController(controller: {
|
|
61
|
+
hostConnected?(): void;
|
|
62
|
+
hostDisconnected?(): void;
|
|
63
|
+
}): void;
|
|
64
|
+
requestUpdate(): void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Controller do Lit para uma chave compartilhada (`provide`/`shared`). Assina
|
|
68
|
+
* enquanto o elemento está conectado e pede um novo render quando o valor
|
|
69
|
+
* (ou o resultado do seletor) muda.
|
|
70
|
+
*
|
|
71
|
+
* usuario = new SharedController(this, 'usuario')
|
|
72
|
+
* render() { return html`${this.usuario.value?.nome}` }
|
|
73
|
+
*/
|
|
74
|
+
declare class SharedController<T = unknown, S = T | undefined> {
|
|
75
|
+
#private;
|
|
76
|
+
/** Valor atual (ou o resultado do seletor). */
|
|
77
|
+
value: S;
|
|
78
|
+
constructor(host: SharedControllerHost, key: keyof Shared | (string & {}), select?: (value: T | undefined) => S);
|
|
79
|
+
hostConnected(): void;
|
|
80
|
+
hostDisconnected(): void;
|
|
81
|
+
}
|
|
24
82
|
declare global {
|
|
25
83
|
interface HTMLElementTagNameMap {
|
|
26
84
|
'vitral-remote': VitralRemoteElement;
|
|
27
85
|
}
|
|
28
86
|
}
|
|
29
87
|
|
|
30
|
-
export { VitralRemoteElement, defineVitralElement, elementRemote };
|
|
88
|
+
export { SharedController, type SharedControllerHost, VitralRemoteElement, defineVitralElement, elementRemote };
|
package/dist/element.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SharedController,
|
|
2
3
|
VitralRemoteElement,
|
|
3
4
|
defineVitralElement,
|
|
4
5
|
elementRemote
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-3YXI3G34.js";
|
|
7
|
+
import "./chunk-IHTUM6LT.js";
|
|
8
|
+
import "./chunk-I3VSKSAZ.js";
|
|
9
|
+
import "./chunk-TD4S3IHA.js";
|
|
10
|
+
import "./chunk-ZAC6QQI6.js";
|
|
11
|
+
import "./chunk-GNHCZRQP.js";
|
|
12
|
+
import "./chunk-AKNNHQ2H.js";
|
|
13
|
+
import "./chunk-GNPFWISJ.js";
|
|
7
14
|
export {
|
|
15
|
+
SharedController,
|
|
8
16
|
VitralRemoteElement,
|
|
9
17
|
defineVitralElement,
|
|
10
18
|
elementRemote
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type RemoteErrorCode = 'NOT_REGISTERED' | 'NOT_EXPOSED' | 'ORIGIN_BLOCKED' | 'TIMEOUT' | 'INTEGRITY' | 'INVALID_MODULE' | 'LOAD_FAILED' | 'MOUNT_FAILED' | 'RENDER_FAILED' | 'SHARED_READONLY' | 'CONTRACT_MISMATCH';
|
|
2
|
+
declare class RemoteError extends Error {
|
|
3
|
+
code: RemoteErrorCode;
|
|
4
|
+
remote: string;
|
|
5
|
+
cause?: unknown | undefined;
|
|
6
|
+
/** Link da página deste erro na documentação. */
|
|
7
|
+
docs: string;
|
|
8
|
+
/** URL do JS do remote (ou a registrada, se o erro veio antes do manifesto). */
|
|
9
|
+
entry?: string;
|
|
10
|
+
/** Versão do remote, do campo `version` do `vitral.json`. */
|
|
11
|
+
version?: string;
|
|
12
|
+
/** `location.pathname` no momento do erro. */
|
|
13
|
+
route?: string;
|
|
14
|
+
constructor(code: RemoteErrorCode, remote: string, message: string, cause?: unknown | undefined);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { RemoteError as R, type RemoteErrorCode as a };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveModule,
|
|
3
|
+
resolveTarget,
|
|
4
|
+
splitName
|
|
5
|
+
} from "./chunk-SDB5AZEI.js";
|
|
6
|
+
import "./chunk-TD4S3IHA.js";
|
|
7
|
+
import "./chunk-ZAC6QQI6.js";
|
|
8
|
+
import "./chunk-GNHCZRQP.js";
|
|
9
|
+
import "./chunk-AKNNHQ2H.js";
|
|
10
|
+
import "./chunk-GNPFWISJ.js";
|
|
11
|
+
export {
|
|
12
|
+
resolveModule,
|
|
13
|
+
resolveTarget,
|
|
14
|
+
splitName
|
|
15
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Quando montar: já (`eager`, padrão), perto de aparecer na tela (`lazy`) ou com o navegador ocioso (`idle`). */
|
|
2
|
+
type Loading = 'eager' | 'lazy' | 'idle';
|
|
3
|
+
/** Quando baixar antes de montar: no hover ou foco (`hover`), perto de aparecer (`visible`) ou com o navegador ocioso (`idle`). */
|
|
4
|
+
type Prefetch = 'hover' | 'visible' | 'idle';
|
|
5
|
+
|
|
6
|
+
export type { Loading as L, Prefetch as P };
|
package/dist/iframe.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Script inline do iframe. Libere o hash dele no CSP (`script-src 'sha256-…'`). */
|
|
2
|
+
declare const FRAME_SCRIPT = "(function (w, load) {\n var parent = w.parent, doc = w.document, handlers = {}, last = {}, channel = {}, mounted = null, stage = 'LOAD_FAILED'\n function post(type, data) { parent.postMessage({ vitral: 1, type: type, data: data }, '*') }\n function call(fn, arg) { try { fn(arg) } catch (e) { w.console.error(e) } }\n var events = {\n emit: function (type, payload) { post('emit', { type: type, payload: payload }) },\n on: function (type, fn) {\n if (!handlers[type]) { handlers[type] = []; post('on', { type: type }) }\n handlers[type].push(fn)\n return function () { handlers[type] = handlers[type].filter(function (h) { return h !== fn }) }\n },\n last: function (type) { return last[type] }\n }\n function start(d) {\n var root = doc.createElement('div')\n root.setAttribute('data-vitral-root', d.name)\n if (d.scope) root.setAttribute('data-vitral-scope', d.scope)\n doc.body.appendChild(root)\n // Ponto de extens\u00E3o: uma ponte nova registra handlers em channel[tipo] e\n // acrescenta campos aqui (a Fase 2 acrescenta ctx.shared).\n var ctx = {\n name: d.name, baseUrl: d.baseUrl, events: events, shadowRoot: null, portalContainer: doc.body, protocol: 1,\n reportError: function (e) { post('report', { message: String((e && e.message) || e) }) },\n navigate: function (url, o) { post('navigate', { url: String(url), replace: !!(o && o.replace) }) }\n }\n Promise.all(d.css.map(function (href) {\n return new Promise(function (done) {\n var link = doc.createElement('link')\n link.rel = 'stylesheet'; link.href = href; link.crossOrigin = 'anonymous'\n link.onload = link.onerror = done\n doc.head.appendChild(link)\n })\n })).then(function () {\n return load(d.code ? w.URL.createObjectURL(new w.Blob([d.code], { type: 'text/javascript' })) : d.url)\n }).then(function (ns) {\n if (!d.key) return ns\n var table = ns.exposes, part = table && table[d.key]\n if (!part) { stage = 'NOT_EXPOSED'; throw new Error(d.name + ' n\u00E3o exp\u00F5e ' + d.key) }\n return typeof part === 'function' ? part() : part\n }).then(function (ns) {\n var mod = (ns && ns.default) || ns\n if (!mod || typeof mod.mount !== 'function') { stage = 'INVALID_MODULE'; throw new Error('o m\u00F3dulo n\u00E3o exporta mount()') }\n stage = 'MOUNT_FAILED'\n return mod.mount(root, d.props, ctx)\n }).then(function (handle) {\n mounted = handle\n post('mounted')\n var size = function () { post('size', { height: Math.ceil(doc.body.getBoundingClientRect().height) }) }\n size()\n if (w.ResizeObserver) new w.ResizeObserver(size).observe(doc.body)\n }, function (e) {\n post('error', { code: stage, message: String((e && e.message) || e) })\n })\n }\n w.addEventListener('message', function (e) {\n var m = e.data\n if (e.source !== parent || !m || m.vitral !== 1) return\n var d = m.data\n if (m.type === 'mount') start(d)\n else if (m.type === 'update') { if (mounted && mounted.update) mounted.update(d) }\n else if (m.type === 'unmount') { if (mounted) mounted.unmount(); mounted = null }\n else if (m.type === 'last') last[d.type] = d.payload\n else if (m.type === 'event') {\n last[d.type] = d.payload\n ;(handlers[d.type] || []).slice().forEach(function (h) { call(h, d.payload) })\n } else (channel[m.type] || []).forEach(function (h) { call(h, d) })\n })\n post('ready')\n})(window, function (u) { return import(u) })";
|
|
3
|
+
|
|
4
|
+
/** Canal com um iframe montado, para pontes novas (ex.: estado compartilhado). */
|
|
5
|
+
interface FramePort {
|
|
6
|
+
/** Manda `data` (serializável) para o iframe com o tipo `type`. */
|
|
7
|
+
post(type: string, data?: unknown): void;
|
|
8
|
+
/** Recebe as mensagens do iframe com o tipo `type`. */
|
|
9
|
+
on(type: string, handler: (data: unknown) => void): () => void;
|
|
10
|
+
}
|
|
11
|
+
/** Chamada a cada iframe montado. Devolve a limpeza. */
|
|
12
|
+
type FrameBridge = (port: FramePort, info: {
|
|
13
|
+
name: string;
|
|
14
|
+
}) => void | (() => void);
|
|
15
|
+
interface IframeOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Valor do atributo `sandbox`. Padrão `'allow-scripts'`. Acrescente o que o
|
|
18
|
+
* remote precisa (`'allow-scripts allow-forms allow-popups'`). Com
|
|
19
|
+
* `allow-same-origin` o iframe ganha a origem da página e deixa de isolar.
|
|
20
|
+
*/
|
|
21
|
+
sandbox?: string;
|
|
22
|
+
/** Pontes extras, além de props e eventos. */
|
|
23
|
+
bridges?: FrameBridge[];
|
|
24
|
+
}
|
|
25
|
+
/** Liga `isolate="iframe"` em todos os componentes de host e em `mountRemote`. */
|
|
26
|
+
declare function enableIframe(options?: IframeOptions): void;
|
|
27
|
+
|
|
28
|
+
export { FRAME_SCRIPT, type FrameBridge, type FramePort, type IframeOptions, enableIframe };
|
package/dist/iframe.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import {
|
|
2
|
+
splitName
|
|
3
|
+
} from "./chunk-SDB5AZEI.js";
|
|
4
|
+
import {
|
|
5
|
+
verifyIntegrity
|
|
6
|
+
} from "./chunk-AH7KNMBW.js";
|
|
7
|
+
import {
|
|
8
|
+
events,
|
|
9
|
+
navigate,
|
|
10
|
+
setFrameMounter,
|
|
11
|
+
track
|
|
12
|
+
} from "./chunk-I3VSKSAZ.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveEntry
|
|
15
|
+
} from "./chunk-TD4S3IHA.js";
|
|
16
|
+
import "./chunk-ZAC6QQI6.js";
|
|
17
|
+
import {
|
|
18
|
+
fetchBytes,
|
|
19
|
+
withRetry,
|
|
20
|
+
withTimeout
|
|
21
|
+
} from "./chunk-GNHCZRQP.js";
|
|
22
|
+
import {
|
|
23
|
+
RemoteError,
|
|
24
|
+
report
|
|
25
|
+
} from "./chunk-AKNNHQ2H.js";
|
|
26
|
+
import {
|
|
27
|
+
protocol
|
|
28
|
+
} from "./chunk-GNPFWISJ.js";
|
|
29
|
+
|
|
30
|
+
// src/iframe-frame.ts
|
|
31
|
+
var FRAME_RUNTIME = `function (w, load) {
|
|
32
|
+
var parent = w.parent, doc = w.document, handlers = {}, last = {}, channel = {}, mounted = null, stage = 'LOAD_FAILED'
|
|
33
|
+
function post(type, data) { parent.postMessage({ vitral: 1, type: type, data: data }, '*') }
|
|
34
|
+
function call(fn, arg) { try { fn(arg) } catch (e) { w.console.error(e) } }
|
|
35
|
+
var events = {
|
|
36
|
+
emit: function (type, payload) { post('emit', { type: type, payload: payload }) },
|
|
37
|
+
on: function (type, fn) {
|
|
38
|
+
if (!handlers[type]) { handlers[type] = []; post('on', { type: type }) }
|
|
39
|
+
handlers[type].push(fn)
|
|
40
|
+
return function () { handlers[type] = handlers[type].filter(function (h) { return h !== fn }) }
|
|
41
|
+
},
|
|
42
|
+
last: function (type) { return last[type] }
|
|
43
|
+
}
|
|
44
|
+
function start(d) {
|
|
45
|
+
var root = doc.createElement('div')
|
|
46
|
+
root.setAttribute('data-vitral-root', d.name)
|
|
47
|
+
if (d.scope) root.setAttribute('data-vitral-scope', d.scope)
|
|
48
|
+
doc.body.appendChild(root)
|
|
49
|
+
// Ponto de extens\xE3o: uma ponte nova registra handlers em channel[tipo] e
|
|
50
|
+
// acrescenta campos aqui (a Fase 2 acrescenta ctx.shared).
|
|
51
|
+
var ctx = {
|
|
52
|
+
name: d.name, baseUrl: d.baseUrl, events: events, shadowRoot: null, portalContainer: doc.body, protocol: 1,
|
|
53
|
+
reportError: function (e) { post('report', { message: String((e && e.message) || e) }) },
|
|
54
|
+
navigate: function (url, o) { post('navigate', { url: String(url), replace: !!(o && o.replace) }) }
|
|
55
|
+
}
|
|
56
|
+
Promise.all(d.css.map(function (href) {
|
|
57
|
+
return new Promise(function (done) {
|
|
58
|
+
var link = doc.createElement('link')
|
|
59
|
+
link.rel = 'stylesheet'; link.href = href; link.crossOrigin = 'anonymous'
|
|
60
|
+
link.onload = link.onerror = done
|
|
61
|
+
doc.head.appendChild(link)
|
|
62
|
+
})
|
|
63
|
+
})).then(function () {
|
|
64
|
+
return load(d.code ? w.URL.createObjectURL(new w.Blob([d.code], { type: 'text/javascript' })) : d.url)
|
|
65
|
+
}).then(function (ns) {
|
|
66
|
+
if (!d.key) return ns
|
|
67
|
+
var table = ns.exposes, part = table && table[d.key]
|
|
68
|
+
if (!part) { stage = 'NOT_EXPOSED'; throw new Error(d.name + ' n\xE3o exp\xF5e ' + d.key) }
|
|
69
|
+
return typeof part === 'function' ? part() : part
|
|
70
|
+
}).then(function (ns) {
|
|
71
|
+
var mod = (ns && ns.default) || ns
|
|
72
|
+
if (!mod || typeof mod.mount !== 'function') { stage = 'INVALID_MODULE'; throw new Error('o m\xF3dulo n\xE3o exporta mount()') }
|
|
73
|
+
stage = 'MOUNT_FAILED'
|
|
74
|
+
return mod.mount(root, d.props, ctx)
|
|
75
|
+
}).then(function (handle) {
|
|
76
|
+
mounted = handle
|
|
77
|
+
post('mounted')
|
|
78
|
+
var size = function () { post('size', { height: Math.ceil(doc.body.getBoundingClientRect().height) }) }
|
|
79
|
+
size()
|
|
80
|
+
if (w.ResizeObserver) new w.ResizeObserver(size).observe(doc.body)
|
|
81
|
+
}, function (e) {
|
|
82
|
+
post('error', { code: stage, message: String((e && e.message) || e) })
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
w.addEventListener('message', function (e) {
|
|
86
|
+
var m = e.data
|
|
87
|
+
if (e.source !== parent || !m || m.vitral !== 1) return
|
|
88
|
+
var d = m.data
|
|
89
|
+
if (m.type === 'mount') start(d)
|
|
90
|
+
else if (m.type === 'update') { if (mounted && mounted.update) mounted.update(d) }
|
|
91
|
+
else if (m.type === 'unmount') { if (mounted) mounted.unmount(); mounted = null }
|
|
92
|
+
else if (m.type === 'last') last[d.type] = d.payload
|
|
93
|
+
else if (m.type === 'event') {
|
|
94
|
+
last[d.type] = d.payload
|
|
95
|
+
;(handlers[d.type] || []).slice().forEach(function (h) { call(h, d.payload) })
|
|
96
|
+
} else (channel[m.type] || []).forEach(function (h) { call(h, d) })
|
|
97
|
+
})
|
|
98
|
+
post('ready')
|
|
99
|
+
}`;
|
|
100
|
+
var FRAME_SCRIPT = `(${FRAME_RUNTIME})(window, function (u) { return import(u) })`;
|
|
101
|
+
var FRAME_HTML = `<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0}body{display:flow-root}</style></head><body><script>${FRAME_SCRIPT}</script></body></html>`;
|
|
102
|
+
|
|
103
|
+
// src/iframe.ts
|
|
104
|
+
var CODES = ["LOAD_FAILED", "NOT_EXPOSED", "INVALID_MODULE", "MOUNT_FAILED"];
|
|
105
|
+
function enableIframe(options = {}) {
|
|
106
|
+
const sandbox = options.sandbox ?? "allow-scripts";
|
|
107
|
+
if (/\ballow-same-origin\b/.test(sandbox) && /\ballow-scripts\b/.test(sandbox)) {
|
|
108
|
+
console.warn("[vitral] sandbox com allow-scripts e allow-same-origin n\xE3o isola o remote da p\xE1gina.");
|
|
109
|
+
}
|
|
110
|
+
setFrameMounter((target, name, props, mountOptions) => mountFrame(target, name, props, mountOptions, sandbox, options.bridges ?? []));
|
|
111
|
+
}
|
|
112
|
+
async function mountFrame(target, name, props, options, sandbox, bridges) {
|
|
113
|
+
const p = protocol();
|
|
114
|
+
const { timeout, retries } = p.settings();
|
|
115
|
+
const [remote, key] = splitName(name);
|
|
116
|
+
const entry = p.entry(remote);
|
|
117
|
+
if (!entry) throw new RemoteError("NOT_REGISTERED", name, "remote n\xE3o registrado. Use configure({ remotes }).");
|
|
118
|
+
if (!entry.url) throw new RemoteError("MOUNT_FAILED", name, "remote local (module) n\xE3o roda em iframe: registre a URL dele");
|
|
119
|
+
const { integrity, ...resolved } = await resolveEntry(remote, entry);
|
|
120
|
+
const code = integrity ? await withRetry(retries, async () => {
|
|
121
|
+
const bytes = await fetchBytes(resolved.entry, timeout, name);
|
|
122
|
+
await verifyIntegrity(name, bytes, integrity);
|
|
123
|
+
return bytes;
|
|
124
|
+
}) : void 0;
|
|
125
|
+
const baseUrl = /^https?:/.test(resolved.entry) ? new URL(".", resolved.entry).href : resolved.entry;
|
|
126
|
+
const frame = document.createElement("iframe");
|
|
127
|
+
frame.setAttribute("sandbox", sandbox);
|
|
128
|
+
frame.setAttribute("data-vitral-root", name);
|
|
129
|
+
frame.title = name;
|
|
130
|
+
frame.style.cssText = "display:block;width:100%;height:0;border:0";
|
|
131
|
+
frame.srcdoc = FRAME_HTML;
|
|
132
|
+
let warned = false;
|
|
133
|
+
const post = (type, data) => {
|
|
134
|
+
const win = frame.contentWindow;
|
|
135
|
+
if (!win) return;
|
|
136
|
+
try {
|
|
137
|
+
win.postMessage({ vitral: 1, type, data }, "*");
|
|
138
|
+
} catch {
|
|
139
|
+
if (!warned) console.warn(`[vitral] ${name}: props e eventos no modo iframe precisam ser serializ\xE1veis; o que n\xE3o \xE9 foi descartado.`);
|
|
140
|
+
warned = true;
|
|
141
|
+
win.postMessage({ vitral: 1, type, data: JSON.parse(JSON.stringify(data ?? null)) }, "*");
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const subs = /* @__PURE__ */ new Map();
|
|
145
|
+
const channel = /* @__PURE__ */ new Map();
|
|
146
|
+
const port = {
|
|
147
|
+
post,
|
|
148
|
+
on(type, handler) {
|
|
149
|
+
if (!channel.has(type)) channel.set(type, /* @__PURE__ */ new Set());
|
|
150
|
+
channel.get(type).add(handler);
|
|
151
|
+
return () => channel.get(type)?.delete(handler);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
let settle = null;
|
|
155
|
+
const ready = new Promise((ok, fail) => settle = { ok, fail });
|
|
156
|
+
const onMessage = (e) => {
|
|
157
|
+
const m = e.data;
|
|
158
|
+
if (e.source !== frame.contentWindow || !m || m.vitral !== 1) return;
|
|
159
|
+
const d = m.data;
|
|
160
|
+
switch (m.type) {
|
|
161
|
+
case "ready":
|
|
162
|
+
if (settle) post("mount", { name, key, code, url: resolved.entry, css: resolved.css, scope: resolved.scope, baseUrl, props });
|
|
163
|
+
break;
|
|
164
|
+
case "mounted":
|
|
165
|
+
settle?.ok();
|
|
166
|
+
settle = null;
|
|
167
|
+
break;
|
|
168
|
+
case "error":
|
|
169
|
+
settle?.fail(new RemoteError(CODES.includes(d?.code) ? d.code : "MOUNT_FAILED", name, String(d?.message)));
|
|
170
|
+
settle = null;
|
|
171
|
+
break;
|
|
172
|
+
case "size":
|
|
173
|
+
frame.style.height = `${Math.max(0, Number(d?.height) || 0)}px`;
|
|
174
|
+
break;
|
|
175
|
+
case "emit":
|
|
176
|
+
events.emit(String(d?.type), d?.payload);
|
|
177
|
+
break;
|
|
178
|
+
case "on": {
|
|
179
|
+
const type = String(d?.type);
|
|
180
|
+
if (subs.has(type)) break;
|
|
181
|
+
subs.set(type, events.on(type, (payload) => post("event", { type, payload })));
|
|
182
|
+
const value = events.last(type);
|
|
183
|
+
if (value !== void 0) post("last", { type, payload: value });
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "navigate": {
|
|
187
|
+
const url = new URL(String(d?.url), location.href);
|
|
188
|
+
if (url.origin === location.origin) navigate(url.href, { replace: !!d?.replace });
|
|
189
|
+
else console.warn(`[vitral] ${name}: navega\xE7\xE3o para outra origem bloqueada (${url.origin})`);
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case "report": {
|
|
193
|
+
const err = report(new RemoteError("RENDER_FAILED", name, String(d?.message)), resolved);
|
|
194
|
+
if (options.onError) options.onError(err);
|
|
195
|
+
else console.error(err);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
default:
|
|
199
|
+
for (const h of [...channel.get(m.type) ?? []]) h(d);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const disposers = [];
|
|
203
|
+
const cleanup = () => {
|
|
204
|
+
removeEventListener("message", onMessage);
|
|
205
|
+
for (const off of subs.values()) off();
|
|
206
|
+
for (const off of disposers) off();
|
|
207
|
+
subs.clear();
|
|
208
|
+
frame.remove();
|
|
209
|
+
};
|
|
210
|
+
addEventListener("message", onMessage);
|
|
211
|
+
for (const bridge of bridges) {
|
|
212
|
+
const off = bridge(port, { name });
|
|
213
|
+
if (off) disposers.push(off);
|
|
214
|
+
}
|
|
215
|
+
target.appendChild(frame);
|
|
216
|
+
try {
|
|
217
|
+
await withTimeout(timeout, name, ready);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
cleanup();
|
|
220
|
+
throw e;
|
|
221
|
+
}
|
|
222
|
+
disposers.push(track(name, resolved));
|
|
223
|
+
let alive = true;
|
|
224
|
+
return {
|
|
225
|
+
update(next) {
|
|
226
|
+
if (alive) post("update", next);
|
|
227
|
+
},
|
|
228
|
+
unmount() {
|
|
229
|
+
if (!alive) return;
|
|
230
|
+
alive = false;
|
|
231
|
+
post("unmount");
|
|
232
|
+
cleanup();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
export {
|
|
237
|
+
FRAME_SCRIPT,
|
|
238
|
+
enableIframe
|
|
239
|
+
};
|