vitral 0.4.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 +5 -5
- package/dist/auto.js +5 -5
- package/dist/{chunk-W6322KGP.js → chunk-4Z77XNBR.js} +2 -2
- package/dist/{chunk-EMSB2NVX.js → chunk-5JNV45BB.js} +4 -4
- package/dist/{chunk-IH3ZT3QU.js → chunk-C3QOBAUE.js} +1 -1
- package/dist/chunk-DAESKNQ6.js +207 -0
- package/dist/{chunk-BCC25ANK.js → chunk-K6EG7DMO.js} +8 -5
- package/dist/{chunk-6EX5KYT5.js → chunk-KU6HF2EW.js} +2 -2
- package/dist/{chunk-WD4CDGDZ.js → chunk-KZBH5MPD.js} +12 -11
- package/dist/chunk-NZEQGRBL.js +93 -0
- package/dist/chunk-PY7P5BF4.js +404 -0
- package/dist/cli.js +414 -44
- 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 +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-IAKYHJXF.js} +3 -3
- package/dist/federation.d.ts +1 -1
- package/dist/hydrate-YVZDJ2V7.js +168 -0
- package/dist/iframe.js +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- 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.d.ts +3 -3
- package/dist/react.js +4 -4
- package/dist/reframe-LQL4DZYP.js +251 -0
- package/dist/routes.d.ts +2 -2
- package/dist/routes.js +3 -3
- 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 +107 -16
package/dist/dict.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vitral/dict`: serve os remotes com dicionários de compressão
|
|
3
|
+
* (Compression Dictionary Transport, RFC 9842).
|
|
4
|
+
*
|
|
5
|
+
* Cada `.js` e `.css` do dist sai com `Use-As-Dictionary`. No deploy seguinte,
|
|
6
|
+
* o navegador pede o arquivo novo com `Available-Dictionary` (o hash do arquivo
|
|
7
|
+
* antigo que ele tem em cache) e recebe só a diferença, pré-comprimida por
|
|
8
|
+
* `vitral dict` em `.dcz` (zstd) ou `.dcb` (brotli).
|
|
9
|
+
*
|
|
10
|
+
* Só usa `Request`, `Response` e `Headers` da Web: roda em Node, Deno, Bun,
|
|
11
|
+
* Cloudflare Workers e outros edges. O navegador descomprime sozinho.
|
|
12
|
+
*/
|
|
13
|
+
/** Variantes pré-comprimidas de um arquivo para um dicionário. Caminhos relativos ao dist. */
|
|
14
|
+
interface DictVariant {
|
|
15
|
+
dcz?: string;
|
|
16
|
+
dcb?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Um arquivo do dist no índice. */
|
|
19
|
+
interface DictFile {
|
|
20
|
+
/** `Content-Type` do arquivo. */
|
|
21
|
+
type: string;
|
|
22
|
+
/** Variantes por hash do dicionário, no formato de `Available-Dictionary` (`:base64:`). */
|
|
23
|
+
deltas: Record<string, DictVariant>;
|
|
24
|
+
}
|
|
25
|
+
/** O `vitral-dict.json` que `vitral dict` grava no dist. */
|
|
26
|
+
interface DictIndex {
|
|
27
|
+
version: 1;
|
|
28
|
+
/** Por caminho relativo ao dist (`remote-a1b2c3d4.js`, `split/chunk-x-a1b2c3d4.js`). */
|
|
29
|
+
files: Record<string, DictFile>;
|
|
30
|
+
}
|
|
31
|
+
type Body = Uint8Array | ArrayBuffer | ReadableStream<Uint8Array> | string;
|
|
32
|
+
interface DictOptions {
|
|
33
|
+
/** Conteúdo do `vitral-dict.json`. */
|
|
34
|
+
index: DictIndex;
|
|
35
|
+
/** Lê um arquivo do dist pelo caminho relativo. `null` se não existir. */
|
|
36
|
+
read: (path: string) => Body | null | undefined | Promise<Body | null | undefined>;
|
|
37
|
+
/** Caminho em que o dist é servido. Padrão: `/`. */
|
|
38
|
+
base?: string;
|
|
39
|
+
/** `Cache-Control` das respostas com dicionário. Padrão: um ano, `immutable` (os nomes têm hash). */
|
|
40
|
+
cacheControl?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Request do Node (`http.IncomingMessage`), só o que o middleware usa. */
|
|
43
|
+
interface NodeRequest {
|
|
44
|
+
method?: string;
|
|
45
|
+
url?: string;
|
|
46
|
+
headers: Record<string, string | string[] | undefined>;
|
|
47
|
+
}
|
|
48
|
+
/** Response do Node (`http.ServerResponse`), só o que o middleware usa. */
|
|
49
|
+
interface NodeResponse {
|
|
50
|
+
setHeader(name: string, value: string): unknown;
|
|
51
|
+
writeHead(status: number, headers: Record<string, string>): unknown;
|
|
52
|
+
end(body?: Uint8Array | string): unknown;
|
|
53
|
+
}
|
|
54
|
+
interface Dict {
|
|
55
|
+
/**
|
|
56
|
+
* Responde com a variante `dcz`/`dcb` quando o navegador tem o dicionário certo.
|
|
57
|
+
* `undefined`: sirva o arquivo como sempre, com os cabeçalhos de `headers()`.
|
|
58
|
+
*/
|
|
59
|
+
fetch(request: Request): Promise<Response | undefined>;
|
|
60
|
+
/** `Use-As-Dictionary` e `Vary` para a resposta normal de um caminho. `undefined` se o caminho não está no índice. */
|
|
61
|
+
headers(pathname: string): Record<string, string> | undefined;
|
|
62
|
+
/** Middleware para Node, Express e Connect: responde a variante ou põe os cabeçalhos e chama `next`. */
|
|
63
|
+
node(req: NodeRequest, res: NodeResponse, next: (err?: unknown) => void): void;
|
|
64
|
+
}
|
|
65
|
+
/** Padrão de `match` para um caminho: o hash do nome vira `*`. */
|
|
66
|
+
declare function dictMatch(pathname: string): string;
|
|
67
|
+
declare function createDict(o: DictOptions): Dict;
|
|
68
|
+
|
|
69
|
+
export { type Dict, type DictFile, type DictIndex, type DictOptions, type DictVariant, type NodeRequest, type NodeResponse, createDict, dictMatch };
|
package/dist/dict.js
ADDED
package/dist/edge.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { P as Props } from './types-DUtcF3HO.js';
|
|
2
|
+
import './errors-kjBv6IH_.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rewriter de HTML em streaming, para runtimes sem `HTMLRewriter` (Node, Deno,
|
|
6
|
+
* Bun). Faz só o que o gateway precisa: acha as tags de abertura de um
|
|
7
|
+
* elemento (`<vitral-remote>`) e dos filhos diretos dele, deixa ler e trocar
|
|
8
|
+
* atributos e inserir HTML logo depois da tag. O resto passa como veio.
|
|
9
|
+
*
|
|
10
|
+
* Ignora comentários e o conteúdo de `<script>`, `<style>`, `<textarea>` e
|
|
11
|
+
* `<title>`. Não monta árvore: dentro do elemento, conta a profundidade pelas
|
|
12
|
+
* tags de abertura e fechamento (elementos vazios, como `<img>`, não contam).
|
|
13
|
+
*/
|
|
14
|
+
/** O subconjunto do `Element` do `HTMLRewriter` que o gateway usa. */
|
|
15
|
+
interface EdgeElement {
|
|
16
|
+
readonly tagName: string;
|
|
17
|
+
getAttribute(name: string): string | null;
|
|
18
|
+
hasAttribute(name: string): boolean;
|
|
19
|
+
setAttribute(name: string, value: string): void;
|
|
20
|
+
/** Insere conteúdo logo depois da tag de abertura. */
|
|
21
|
+
prepend(content: string, options?: {
|
|
22
|
+
html?: boolean;
|
|
23
|
+
}): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gateway de edge: host e remotes servidos de uma origem só, com Early Hints
|
|
28
|
+
* e SSR opcional dos remotes em Declarative Shadow DOM.
|
|
29
|
+
*
|
|
30
|
+
* import { gateway } from 'vitral/edge'
|
|
31
|
+
*
|
|
32
|
+
* const handler = gateway({
|
|
33
|
+
* origin: 'https://app.empresa.com',
|
|
34
|
+
* remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' },
|
|
35
|
+
* })
|
|
36
|
+
* export default { fetch: handler } // Cloudflare Workers
|
|
37
|
+
*
|
|
38
|
+
* - `/_vitral/<nome>/<caminho>` vira `<pasta do remote>/<caminho>`. O
|
|
39
|
+
* `vitral.json` sai com as URLs absolutas do CDN trocadas por caminhos desta
|
|
40
|
+
* origem; os hashes de SRI ficam iguais, porque os bytes são os mesmos.
|
|
41
|
+
* - O resto vai para `origin`. No HTML, `<vitral-remote src>` do CDN passa a
|
|
42
|
+
* apontar para cá, e a resposta ganha `Link: rel=modulepreload` (ou
|
|
43
|
+
* `preload`) para os remotes que a página usou da última vez.
|
|
44
|
+
* - Remote com `render` sai renderizado: um `<template shadowrootmode>` com o
|
|
45
|
+
* HTML e o CSS dele dentro do `<vitral-remote>`. No cliente, o remote que
|
|
46
|
+
* exporta `hydrate` hidrata esse HTML em vez de montar de novo.
|
|
47
|
+
*
|
|
48
|
+
* Só usa APIs Web (`fetch`, `Request`, `Response`, streams). Usa o
|
|
49
|
+
* `HTMLRewriter` quando o runtime tem (Cloudflare Workers); senão, um
|
|
50
|
+
* rewriter próprio em streaming. No Node, `toNodeListener(handler)`.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** HTML do remote para estas props, no servidor. */
|
|
54
|
+
type ServerRender = (props: Props) => string | Promise<string>;
|
|
55
|
+
/** Isolamento do remote na página (o mesmo de `isolate` no cliente). */
|
|
56
|
+
type EdgeIsolation = 'shadow' | 'none';
|
|
57
|
+
interface EdgeRemote {
|
|
58
|
+
/** URL do `vitral.json` (ou do JS) do remote no CDN. */
|
|
59
|
+
url: string;
|
|
60
|
+
/**
|
|
61
|
+
* SSR: devolve o HTML do remote. O mesmo `render` que o remote exporta
|
|
62
|
+
* para o servidor (ex.: `import { render } from 'carrinho/server'`).
|
|
63
|
+
*/
|
|
64
|
+
render?: ServerRender;
|
|
65
|
+
/** Isolamento usado no SSR quando a tag não tem `isolate`. */
|
|
66
|
+
isolate?: EdgeIsolation;
|
|
67
|
+
}
|
|
68
|
+
interface GatewayOptions {
|
|
69
|
+
/** Origem do host (o app que serve as páginas). */
|
|
70
|
+
origin: string;
|
|
71
|
+
/** Remotes por nome: a URL do `vitral.json` ou a entrada completa. */
|
|
72
|
+
remotes: Record<string, string | EdgeRemote>;
|
|
73
|
+
/** Prefixo dos caminhos dos remotes. Padrão `/_vitral/`. */
|
|
74
|
+
prefix?: string;
|
|
75
|
+
/** Isolamento padrão do SSR (o `isolate` do `configure` do host). Padrão `'none'`. */
|
|
76
|
+
isolate?: EdgeIsolation;
|
|
77
|
+
/** Remotes que esta URL usa, para os hints da primeira visita. */
|
|
78
|
+
preload?: (url: URL) => string[];
|
|
79
|
+
/** `fetch` usado para falar com o host e os CDNs. Padrão: o global. */
|
|
80
|
+
fetch?: (request: Request) => Promise<Response>;
|
|
81
|
+
/** Por quanto tempo o gateway guarda manifestos e CSS, em ms. Padrão 60000. */
|
|
82
|
+
ttl?: number;
|
|
83
|
+
/** Erro no SSR de um remote (a página sai sem o SSR dele). Padrão: `console.error`. */
|
|
84
|
+
onError?: (error: unknown, name: string) => void;
|
|
85
|
+
}
|
|
86
|
+
/** Handler do gateway: `(request) => Response`. */
|
|
87
|
+
interface Gateway {
|
|
88
|
+
(request: Request): Promise<Response>;
|
|
89
|
+
/** Valores de `Link` para esta requisição (para 103 Early Hints). */
|
|
90
|
+
hints(request: Request): Promise<string[]>;
|
|
91
|
+
}
|
|
92
|
+
declare function gateway(options: GatewayOptions): Gateway;
|
|
93
|
+
/** O que `toNodeListener` usa do `IncomingMessage`. */
|
|
94
|
+
interface NodeRequestLike extends AsyncIterable<Uint8Array> {
|
|
95
|
+
method?: string;
|
|
96
|
+
url?: string;
|
|
97
|
+
headers: Record<string, string | string[] | undefined>;
|
|
98
|
+
socket?: {
|
|
99
|
+
encrypted?: boolean;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** O que `toNodeListener` usa do `ServerResponse`. */
|
|
103
|
+
interface NodeResponseLike {
|
|
104
|
+
writeHead(status: number, headers: Record<string, string | string[]>): unknown;
|
|
105
|
+
write(chunk: Uint8Array): unknown;
|
|
106
|
+
end(chunk?: string): unknown;
|
|
107
|
+
writeEarlyHints?(hints: Record<string, string | string[]>): void;
|
|
108
|
+
headersSent?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Listener de `http.createServer` para um handler `(request) => Response`.
|
|
112
|
+
* Com o gateway, manda 103 Early Hints antes de buscar a página.
|
|
113
|
+
*
|
|
114
|
+
* createServer(toNodeListener(gateway({ origin, remotes }))).listen(8080)
|
|
115
|
+
*/
|
|
116
|
+
declare function toNodeListener(handler: ((request: Request) => Promise<Response>) | Gateway): (req: NodeRequestLike, res: NodeResponseLike) => Promise<void>;
|
|
117
|
+
|
|
118
|
+
export { type EdgeElement, type EdgeIsolation, type EdgeRemote, type Gateway, type GatewayOptions, type NodeRequestLike, type NodeResponseLike, type ServerRender, gateway, toNodeListener };
|
package/dist/edge.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
// src/edge/rewriter.ts
|
|
2
|
+
var RAW = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
|
|
3
|
+
var VOID = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
|
|
4
|
+
var ATTR = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
5
|
+
function decodeEntities(s) {
|
|
6
|
+
return s.replace(/&(#x[\da-f]+|#\d+|quot|amp|lt|gt|apos|#39);/gi, (_, e) => {
|
|
7
|
+
const k = e.toLowerCase();
|
|
8
|
+
if (k[0] === "#") return String.fromCodePoint(k[1] === "x" ? parseInt(k.slice(2), 16) : Number(k.slice(1)));
|
|
9
|
+
return { quot: '"', amp: "&", lt: "<", gt: ">", apos: "'" }[k];
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
var escapeAttr = (s) => s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
13
|
+
var Tag = class {
|
|
14
|
+
constructor(tagName, raw) {
|
|
15
|
+
this.tagName = tagName;
|
|
16
|
+
this.raw = raw;
|
|
17
|
+
const inner = raw.slice(1 + tagName.length, raw.endsWith("/>") ? -2 : -1);
|
|
18
|
+
for (const m of inner.matchAll(ATTR)) this.#attrs.push([m[1].toLowerCase(), m[2] ?? m[3] ?? m[4] ?? null]);
|
|
19
|
+
}
|
|
20
|
+
// Valor cru (como veio, igual ao `HTMLRewriter`) e se foi trocado.
|
|
21
|
+
#attrs = [];
|
|
22
|
+
#changed = false;
|
|
23
|
+
#after = "";
|
|
24
|
+
getAttribute(name) {
|
|
25
|
+
const a = this.#attrs.find(([k]) => k === name);
|
|
26
|
+
return a ? a[1] ?? "" : null;
|
|
27
|
+
}
|
|
28
|
+
hasAttribute(name) {
|
|
29
|
+
return this.getAttribute(name) !== null;
|
|
30
|
+
}
|
|
31
|
+
setAttribute(name, value) {
|
|
32
|
+
this.#changed = true;
|
|
33
|
+
const a = this.#attrs.find(([k]) => k === name);
|
|
34
|
+
if (a) a[1] = value, a[2] = true;
|
|
35
|
+
else this.#attrs.push([name, value, true]);
|
|
36
|
+
}
|
|
37
|
+
prepend(content, options) {
|
|
38
|
+
this.#after = (options?.html ? content : escapeAttr(content).replace(/>/g, ">")) + this.#after;
|
|
39
|
+
}
|
|
40
|
+
toString() {
|
|
41
|
+
const tag = this.#changed ? `<${this.tagName}${this.#attrs.map(([k, v, set]) => v === null ? ` ${k}` : ` ${k}="${set ? escapeAttr(v) : v.replace(/"/g, """)}"`).join("")}${this.raw.endsWith("/>") ? " /" : ""}>` : this.raw;
|
|
42
|
+
return tag + this.#after;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function rewrite(body, tag, handlers) {
|
|
46
|
+
const decoder = new TextDecoder();
|
|
47
|
+
const encoder = new TextEncoder();
|
|
48
|
+
let buf = "";
|
|
49
|
+
let raw = null;
|
|
50
|
+
let depth = -1;
|
|
51
|
+
async function process(out, end) {
|
|
52
|
+
let text = "";
|
|
53
|
+
let i = 0;
|
|
54
|
+
const flush = () => text && (out.enqueue(encoder.encode(text)), text = "");
|
|
55
|
+
for (; ; ) {
|
|
56
|
+
const lt = buf.indexOf("<", i);
|
|
57
|
+
if (lt < 0) {
|
|
58
|
+
text += buf.slice(i);
|
|
59
|
+
i = buf.length;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
text += buf.slice(i, lt);
|
|
63
|
+
i = lt;
|
|
64
|
+
if (raw) {
|
|
65
|
+
const close2 = buf.slice(lt, lt + raw.length + 2).toLowerCase();
|
|
66
|
+
if (close2.length < raw.length + 2 && !end) break;
|
|
67
|
+
if (close2 === `</${raw}`) raw = null;
|
|
68
|
+
else {
|
|
69
|
+
text += "<";
|
|
70
|
+
i = lt + 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!end && buf.length - lt < 4 && "<!--".startsWith(buf.slice(lt))) break;
|
|
75
|
+
if (buf.startsWith("<!--", lt)) {
|
|
76
|
+
const stop = buf.indexOf("-->", lt + 4);
|
|
77
|
+
if (stop < 0) {
|
|
78
|
+
if (!end) break;
|
|
79
|
+
text += buf.slice(lt);
|
|
80
|
+
i = buf.length;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
text += buf.slice(lt, stop + 3);
|
|
84
|
+
i = stop + 3;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const m = /^<(\/?)([a-zA-Z][^\s/>]*)/.exec(buf.slice(lt, lt + 64));
|
|
88
|
+
if (!m) {
|
|
89
|
+
if (buf.length - lt < 3 && !end) break;
|
|
90
|
+
text += "<";
|
|
91
|
+
i = lt + 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const close = findTagEnd(buf, lt);
|
|
95
|
+
if (close < 0) {
|
|
96
|
+
if (!end) break;
|
|
97
|
+
text += buf.slice(lt);
|
|
98
|
+
i = buf.length;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
const source = buf.slice(lt, close + 1);
|
|
102
|
+
i = close + 1;
|
|
103
|
+
const name = m[2].toLowerCase();
|
|
104
|
+
if (m[1]) {
|
|
105
|
+
if (depth > 0 && !VOID.has(name)) depth--;
|
|
106
|
+
else if (depth === 0 && name === tag) depth = -1;
|
|
107
|
+
text += source;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (RAW.has(name)) raw = name;
|
|
111
|
+
const selfClosing = source.endsWith("/>") || VOID.has(name);
|
|
112
|
+
if (name === tag && depth < 0) {
|
|
113
|
+
const el = new Tag(name, source);
|
|
114
|
+
const r = handlers.element(el);
|
|
115
|
+
if (r) flush(), await r;
|
|
116
|
+
text += el;
|
|
117
|
+
if (!selfClosing) depth = 0;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (depth === 0 && handlers.child) {
|
|
121
|
+
const el = new Tag(name, source);
|
|
122
|
+
handlers.child(el);
|
|
123
|
+
text += el;
|
|
124
|
+
} else text += source;
|
|
125
|
+
if (depth >= 0 && !selfClosing) depth++;
|
|
126
|
+
}
|
|
127
|
+
buf = buf.slice(i);
|
|
128
|
+
flush();
|
|
129
|
+
}
|
|
130
|
+
return body.pipeThrough(
|
|
131
|
+
new TransformStream({
|
|
132
|
+
async transform(chunk, out) {
|
|
133
|
+
buf += decoder.decode(chunk, { stream: true });
|
|
134
|
+
await process(out, false);
|
|
135
|
+
},
|
|
136
|
+
async flush(out) {
|
|
137
|
+
buf += decoder.decode();
|
|
138
|
+
await process(out, true);
|
|
139
|
+
if (buf) out.enqueue(encoder.encode(buf));
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
function findTagEnd(s, start) {
|
|
145
|
+
let quote = "";
|
|
146
|
+
for (let i = start + 1; i < s.length; i++) {
|
|
147
|
+
const c = s[i];
|
|
148
|
+
if (quote) {
|
|
149
|
+
if (c === quote) quote = "";
|
|
150
|
+
} else if (c === '"' || c === "'") quote = c;
|
|
151
|
+
else if (c === ">") return i;
|
|
152
|
+
}
|
|
153
|
+
return -1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/edge.ts
|
|
157
|
+
var STALE = ["content-encoding", "content-length", "transfer-encoding", "connection", "keep-alive"];
|
|
158
|
+
var MAX_PAGES = 1e3;
|
|
159
|
+
function gateway(options) {
|
|
160
|
+
const prefix = options.prefix ?? "/_vitral/";
|
|
161
|
+
const origin = new URL(options.origin);
|
|
162
|
+
const send = options.fetch ?? ((r) => fetch(r));
|
|
163
|
+
const ttl = options.ttl ?? 6e4;
|
|
164
|
+
const onError = options.onError ?? ((e, name) => console.error(`[vitral] SSR de ${name} falhou:`, e));
|
|
165
|
+
const remotes = /* @__PURE__ */ new Map();
|
|
166
|
+
for (const [name, value] of Object.entries(options.remotes)) {
|
|
167
|
+
const r = typeof value === "string" ? { url: value } : value;
|
|
168
|
+
const url = new URL(r.url).href;
|
|
169
|
+
remotes.set(name, { ...r, name, url, base: new URL(".", url).href });
|
|
170
|
+
}
|
|
171
|
+
const local = (r, abs) => abs.startsWith(r.base) ? `${prefix}${encodeURIComponent(r.name)}/${abs.slice(r.base.length)}` : abs;
|
|
172
|
+
const cache = /* @__PURE__ */ new Map();
|
|
173
|
+
function cached(key, load) {
|
|
174
|
+
const hit = cache.get(key);
|
|
175
|
+
if (hit && Date.now() - hit.at < ttl) return hit.value;
|
|
176
|
+
const value = load();
|
|
177
|
+
value.catch(() => cache.get(key)?.value === value && cache.delete(key));
|
|
178
|
+
cache.set(key, { at: Date.now(), value });
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
const get = async (url) => {
|
|
182
|
+
const res = await send(new Request(url));
|
|
183
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} em ${url}`);
|
|
184
|
+
return res;
|
|
185
|
+
};
|
|
186
|
+
const manifest = (r) => cached(`m ${r.url}`, async () => {
|
|
187
|
+
if (!new URL(r.url).pathname.endsWith(".json")) return { name: r.name, entry: r.url };
|
|
188
|
+
const m = await (await get(r.url)).json();
|
|
189
|
+
return m.stable || typeof m.entry !== "string" ? null : m;
|
|
190
|
+
});
|
|
191
|
+
const cssText = (url, integrity) => cached(`c ${url} ${integrity ?? ""}`, async () => {
|
|
192
|
+
const bytes = await (await get(url)).arrayBuffer();
|
|
193
|
+
if (integrity && !await matches(bytes, integrity)) throw new Error(`integrity n\xE3o confere em ${url}`);
|
|
194
|
+
return new TextDecoder().decode(bytes);
|
|
195
|
+
});
|
|
196
|
+
const seen = /* @__PURE__ */ new Map();
|
|
197
|
+
const remember = (path, found) => {
|
|
198
|
+
seen.delete(path);
|
|
199
|
+
seen.set(path, found);
|
|
200
|
+
if (seen.size > MAX_PAGES) seen.delete(seen.keys().next().value);
|
|
201
|
+
};
|
|
202
|
+
async function hints(request) {
|
|
203
|
+
const url = new URL(request.url);
|
|
204
|
+
if (url.pathname.startsWith(prefix)) return [];
|
|
205
|
+
const names = new Map(seen.get(url.pathname));
|
|
206
|
+
for (const name of options.preload?.(url) ?? []) if (!names.has(name)) names.set(name, remotes.get(name)?.isolate ?? options.isolate ?? "none");
|
|
207
|
+
const links = await Promise.all([...names].map(([name, isolate]) => linksFor(name, isolate)));
|
|
208
|
+
return links.flat();
|
|
209
|
+
}
|
|
210
|
+
async function linksFor(name, isolate) {
|
|
211
|
+
const r = remotes.get(name);
|
|
212
|
+
const m = r && await manifest(r).catch(() => null);
|
|
213
|
+
if (!r || !m) return [];
|
|
214
|
+
const { abs } = paths(r, m);
|
|
215
|
+
const js = m.split ? [m.split.entry, ...m.split.chunks ?? []] : [m.entry];
|
|
216
|
+
const hashed = !!m.integrity || Object.keys(m.files ?? {}).length > 0;
|
|
217
|
+
const out = js.map((u) => `<${local(r, abs(u))}>; ${hashed ? "rel=preload; as=fetch; crossorigin=anonymous" : "rel=modulepreload"}`);
|
|
218
|
+
for (const u of m.css ?? []) out.push(`<${local(r, abs(u))}>; rel=preload; as=${isolate === "shadow" ? "fetch" : "style"}; crossorigin=anonymous`);
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
async function asset(request, url) {
|
|
222
|
+
const rest = url.pathname.slice(prefix.length);
|
|
223
|
+
const slash = rest.indexOf("/");
|
|
224
|
+
const r = slash > 0 ? remotes.get(decodeURIComponent(rest.slice(0, slash))) : void 0;
|
|
225
|
+
const target = r && new URL(rest.slice(slash + 1) + url.search, r.base);
|
|
226
|
+
if (!r || !target.href.startsWith(r.base)) return new Response("remote n\xE3o encontrado", { status: 404 });
|
|
227
|
+
const res = await send(forward(request, target));
|
|
228
|
+
if (res.status !== 200 || !target.pathname.endsWith(".json")) return pass(res);
|
|
229
|
+
const text = await res.text();
|
|
230
|
+
const headers = clean(res.headers);
|
|
231
|
+
let json;
|
|
232
|
+
try {
|
|
233
|
+
json = relocate(JSON.parse(text), (s) => /^https?:\/\//.test(s) ? local(r, s) : s);
|
|
234
|
+
} catch {
|
|
235
|
+
return new Response(text, { status: 200, headers });
|
|
236
|
+
}
|
|
237
|
+
headers.set("content-type", "application/json");
|
|
238
|
+
headers.delete("etag");
|
|
239
|
+
return new Response(JSON.stringify(json), { status: 200, headers });
|
|
240
|
+
}
|
|
241
|
+
async function page(request, url, res) {
|
|
242
|
+
const headers = clean(res.headers);
|
|
243
|
+
for (const link of await hints(request).catch(() => [])) headers.append("link", link);
|
|
244
|
+
const found = /* @__PURE__ */ new Map();
|
|
245
|
+
let ssr = false;
|
|
246
|
+
const element = async (el) => {
|
|
247
|
+
ssr = false;
|
|
248
|
+
const src = el.getAttribute("src");
|
|
249
|
+
let r;
|
|
250
|
+
if (src) {
|
|
251
|
+
const abs = new URL(decodeEntities(src), url).href;
|
|
252
|
+
for (const x of remotes.values()) if (x.url === abs || abs === new URL(local(x, x.url), url).href) r = x;
|
|
253
|
+
if (r && abs === r.url) el.setAttribute("src", local(r, r.url));
|
|
254
|
+
} else r = remotes.get(el.getAttribute("name") ?? "");
|
|
255
|
+
if (!r) return;
|
|
256
|
+
const isolate = el.getAttribute("isolate") || r.isolate || options.isolate || "none";
|
|
257
|
+
if (isolate === "iframe") return;
|
|
258
|
+
found.set(r.name, isolate);
|
|
259
|
+
if (!r.render) return;
|
|
260
|
+
try {
|
|
261
|
+
const m = await manifest(r);
|
|
262
|
+
if (!m) return;
|
|
263
|
+
const props = parseProps(decodeEntities(el.getAttribute("props") ?? ""));
|
|
264
|
+
const label = src ? m.name : r.name;
|
|
265
|
+
el.prepend(await markup(r, m, label, await r.render(props), isolate), { html: true });
|
|
266
|
+
ssr = true;
|
|
267
|
+
} catch (e) {
|
|
268
|
+
onError(e, r.name);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const child = (el) => {
|
|
272
|
+
if (ssr && el.getAttribute("slot") === "fallback") el.setAttribute("hidden", "");
|
|
273
|
+
};
|
|
274
|
+
const body = transform(res.body, element, child).pipeThrough(
|
|
275
|
+
new TransformStream({
|
|
276
|
+
flush: () => remember(url.pathname, found)
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
return new Response(body, { status: res.status, statusText: res.statusText, headers });
|
|
280
|
+
}
|
|
281
|
+
async function markup(r, m, label, html, isolate) {
|
|
282
|
+
const { abs, sri } = paths(r, m);
|
|
283
|
+
const name = escapeAttr(label);
|
|
284
|
+
const scope = m.scope ? ` data-vitral-scope="${escapeAttr(m.scope)}"` : "";
|
|
285
|
+
const root = `<div data-vitral-root="${name}"${scope} style="display:contents">${html}</div>`;
|
|
286
|
+
const css = (m.css ?? []).map(abs);
|
|
287
|
+
const link = (u) => `<link rel="stylesheet" href="${escapeAttr(local(r, u))}" crossorigin="anonymous"${sri[u] ? ` integrity="${escapeAttr(sri[u])}"` : ""}>`;
|
|
288
|
+
if (isolate === "none") return `<div data-vitral-ssr data-vitral-slot="${name}">${css.map(link).join("")}${root}</div>`;
|
|
289
|
+
const s = m.shadow;
|
|
290
|
+
const files = s && css[0] ? s.css.map((u) => new URL(u, css[0]).href) : css;
|
|
291
|
+
const global = s && css[0] ? (s.global ?? []).map((u) => new URL(u, css[0]).href) : [];
|
|
292
|
+
const texts = await Promise.all(
|
|
293
|
+
files.map(async (u) => {
|
|
294
|
+
const text = await cssText(u, sri[u]);
|
|
295
|
+
return s && !s.rebase ? text : text.replace(RELATIVE_URL, (_, q, p) => `url(${q}${local(r, new URL(p, u).href)}${q})`);
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
const style = texts.length ? `<style data-vitral-ssr>${texts.join("\n").replace(/<\/style/gi, "<\\/style")}</style>` : "";
|
|
299
|
+
return `<div data-vitral-ssr data-vitral-slot="${name}">${global.map(link).join("")}<template shadowrootmode="open">${style}${root}</template></div>`;
|
|
300
|
+
}
|
|
301
|
+
const handler = (async (request) => {
|
|
302
|
+
const url = new URL(request.url);
|
|
303
|
+
if (url.pathname.startsWith(prefix)) return asset(request, url);
|
|
304
|
+
const res = await send(forward(request, new URL(url.pathname + url.search, origin)));
|
|
305
|
+
if (!res.body || !/text\/html/i.test(res.headers.get("content-type") ?? "")) return pass(res);
|
|
306
|
+
return page(request, url, res);
|
|
307
|
+
});
|
|
308
|
+
handler.hints = hints;
|
|
309
|
+
return handler;
|
|
310
|
+
}
|
|
311
|
+
var RELATIVE_URL = /url\(\s*(['"]?)(?![a-z]+:|\/\/|#)([^'")]+)\1\s*\)/gi;
|
|
312
|
+
function paths(r, m) {
|
|
313
|
+
const abs = (u) => new URL(u, r.url).href;
|
|
314
|
+
const sri = {};
|
|
315
|
+
for (const file in m.files ?? {}) sri[abs(file)] = m.files[file];
|
|
316
|
+
if (m.integrity) sri[abs(m.entry)] ??= m.integrity;
|
|
317
|
+
return { abs, sri };
|
|
318
|
+
}
|
|
319
|
+
function relocate(value, fn) {
|
|
320
|
+
if (typeof value === "string") return fn(value);
|
|
321
|
+
if (Array.isArray(value)) return value.map((v) => relocate(v, fn));
|
|
322
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [fn(k), relocate(v, fn)]));
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
function parseProps(text) {
|
|
326
|
+
try {
|
|
327
|
+
return text ? JSON.parse(text) : {};
|
|
328
|
+
} catch {
|
|
329
|
+
return {};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function forward(request, target) {
|
|
333
|
+
const headers = new Headers(request.headers);
|
|
334
|
+
headers.delete("host");
|
|
335
|
+
const from = new URL(request.url);
|
|
336
|
+
headers.set("x-forwarded-host", from.host);
|
|
337
|
+
headers.set("x-forwarded-proto", from.protocol.slice(0, -1));
|
|
338
|
+
const body = request.method === "GET" || request.method === "HEAD" ? void 0 : request.body;
|
|
339
|
+
return new Request(target, { method: request.method, headers, body, redirect: "manual", ...body ? { duplex: "half" } : {} });
|
|
340
|
+
}
|
|
341
|
+
function clean(from) {
|
|
342
|
+
const headers = new Headers(from);
|
|
343
|
+
for (const h of STALE) headers.delete(h);
|
|
344
|
+
return headers;
|
|
345
|
+
}
|
|
346
|
+
var pass = (res) => new Response(res.body, { status: res.status, statusText: res.statusText, headers: clean(res.headers) });
|
|
347
|
+
function transform(body, element, child) {
|
|
348
|
+
if (typeof HTMLRewriter !== "undefined")
|
|
349
|
+
return new HTMLRewriter().on("vitral-remote", { element }).on('vitral-remote > [slot="fallback"]', { element: child }).transform(new Response(body, { headers: { "content-type": "text/html" } })).body;
|
|
350
|
+
return rewrite(body, "vitral-remote", { element, child });
|
|
351
|
+
}
|
|
352
|
+
async function matches(bytes, integrity) {
|
|
353
|
+
for (const token of integrity.trim().split(/\s+/)) {
|
|
354
|
+
const [, alg, hash] = /^sha(256|384|512)-(.+)$/.exec(token) ?? [];
|
|
355
|
+
if (!alg) continue;
|
|
356
|
+
const digest = new Uint8Array(await crypto.subtle.digest(`SHA-${alg}`, bytes));
|
|
357
|
+
if (btoa(String.fromCharCode(...digest)) === hash) return true;
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
function toNodeListener(handler) {
|
|
362
|
+
return async (req, res) => {
|
|
363
|
+
try {
|
|
364
|
+
const proto = req.headers["x-forwarded-proto"] ?? (req.socket?.encrypted ? "https" : "http");
|
|
365
|
+
const url = new URL(req.url ?? "/", `${proto}://${req.headers.host ?? "localhost"}`);
|
|
366
|
+
const headers = new Headers();
|
|
367
|
+
for (const [k, v] of Object.entries(req.headers)) if (v !== void 0 && !k.startsWith(":")) for (const x of [v].flat()) headers.append(k, x);
|
|
368
|
+
const method = req.method ?? "GET";
|
|
369
|
+
const body = method === "GET" || method === "HEAD" ? void 0 : new ReadableStream({
|
|
370
|
+
async start(c) {
|
|
371
|
+
for await (const chunk of req) c.enqueue(chunk);
|
|
372
|
+
c.close();
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
const request = new Request(url, { method, headers, body, ...body ? { duplex: "half" } : {} });
|
|
376
|
+
if ("hints" in handler && res.writeEarlyHints) {
|
|
377
|
+
const link = await handler.hints(request).catch(() => []);
|
|
378
|
+
if (link.length) res.writeEarlyHints({ link });
|
|
379
|
+
}
|
|
380
|
+
const response = await handler(request);
|
|
381
|
+
const out = {};
|
|
382
|
+
response.headers.forEach((v, k) => out[k] = v);
|
|
383
|
+
const cookies = response.headers.getSetCookie?.() ?? [];
|
|
384
|
+
if (cookies.length) out["set-cookie"] = cookies;
|
|
385
|
+
res.writeHead(response.status, out);
|
|
386
|
+
if (response.body && method !== "HEAD") for await (const chunk of response.body) res.write(chunk);
|
|
387
|
+
res.end();
|
|
388
|
+
} catch (e) {
|
|
389
|
+
console.error("[vitral] gateway:", e);
|
|
390
|
+
if (!res.headersSent) res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
391
|
+
res.end("gateway: falha ao buscar a resposta");
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
export {
|
|
396
|
+
gateway,
|
|
397
|
+
toNodeListener
|
|
398
|
+
};
|
package/dist/element.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as Props, S as Shared, a as RemoteModule } from './types-
|
|
2
|
-
import { S as SlotStatus } from './slot-
|
|
1
|
+
import { P as Props, S as Shared, a as RemoteModule } from './types-DUtcF3HO.js';
|
|
2
|
+
import { S as SlotStatus } from './slot-D49L1rdU.js';
|
|
3
3
|
import './errors-kjBv6IH_.js';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/element.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
VitralRemoteElement,
|
|
4
4
|
defineVitralElement,
|
|
5
5
|
elementRemote
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-K6EG7DMO.js";
|
|
7
|
+
import "./chunk-4Z77XNBR.js";
|
|
8
|
+
import "./chunk-KZBH5MPD.js";
|
|
9
|
+
import "./chunk-5JNV45BB.js";
|
|
10
|
+
import "./chunk-KU6HF2EW.js";
|
|
11
11
|
import "./chunk-TW7KYJQ6.js";
|
|
12
12
|
import "./chunk-463H6B56.js";
|
|
13
13
|
import "./chunk-GNPFWISJ.js";
|