vitral 0.7.1 → 0.8.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.
Files changed (59) hide show
  1. package/README.md +74 -14
  2. package/dist/angular.d.ts +2 -2
  3. package/dist/angular.js +8 -6
  4. package/dist/auto.js +5 -4
  5. package/dist/chunk-4IWZIFAL.js +165 -0
  6. package/dist/chunk-6AFVJCQG.js +311 -0
  7. package/dist/{chunk-72H4FXMD.js → chunk-6T25WKNK.js} +44 -13
  8. package/dist/chunk-EQZOJU3B.js +48 -0
  9. package/dist/{chunk-O7DV3EVB.js → chunk-NTXMEZIZ.js} +9 -46
  10. package/dist/{chunk-4NSPG6NK.js → chunk-PQXS2UDM.js} +1 -1
  11. package/dist/{chunk-TXSHK3SK.js → chunk-ZJ4IM2PZ.js} +2 -2
  12. package/dist/{chunk-V24ZNHUF.js → chunk-ZRAGLT3I.js} +4 -4
  13. package/dist/cli.js +1 -1
  14. package/dist/{contract-46EZFQPQ.js → contract-OMHCH6LI.js} +1 -1
  15. package/dist/{dedupe-S7BHQMOZ.js → dedupe-PZCSKL3L.js} +1 -1
  16. package/dist/edge.d.ts +1 -1
  17. package/dist/element.d.ts +2 -2
  18. package/dist/element.js +5 -4
  19. package/dist/embed.js +2 -2
  20. package/dist/{events-DLdbFqBM.d.ts → events-Cieg_far.d.ts} +1 -1
  21. package/dist/{expose-ZLYQZUP2.js → expose-V5ASGX4E.js} +2 -2
  22. package/dist/federation.d.ts +1 -1
  23. package/dist/{hydrate-NFZZMYWT.js → hydrate-6NE4MYOD.js} +7 -5
  24. package/dist/iframe.js +9 -7
  25. package/dist/index.d.ts +6 -19
  26. package/dist/index.js +6 -4
  27. package/dist/loader-BIXjuvvp.d.ts +25 -0
  28. package/dist/next-config.d.ts +95 -0
  29. package/dist/next-config.js +149 -0
  30. package/dist/next-host.d.ts +26 -0
  31. package/dist/next-host.js +92 -0
  32. package/dist/next.d.ts +190 -0
  33. package/dist/next.js +287 -0
  34. package/dist/preact.d.ts +4 -4
  35. package/dist/preact.js +7 -5
  36. package/dist/prefetch.js +2 -2
  37. package/dist/react-server.d.ts +1 -1
  38. package/dist/react.d.ts +11 -4
  39. package/dist/react.js +17 -144
  40. package/dist/{reframe-7K5S6JC6.js → reframe-3ZN5CHEG.js} +4 -4
  41. package/dist/routes.d.ts +2 -2
  42. package/dist/routes.js +6 -4
  43. package/dist/single-spa.d.ts +1 -1
  44. package/dist/{slot-Ddzp7AgL.d.ts → slot-Dhczjs1E.d.ts} +1 -1
  45. package/dist/solid.d.ts +3 -3
  46. package/dist/solid.js +7 -5
  47. package/dist/{sri-576GTDIA.js → sri-TBZ6PYYY.js} +39 -24
  48. package/dist/svelte.d.ts +4 -4
  49. package/dist/svelte.js +6 -4
  50. package/dist/sw.d.ts +8 -0
  51. package/dist/sw.js +21 -0
  52. package/dist/testing.d.ts +1 -1
  53. package/dist/testing.js +3 -2
  54. package/dist/{types-EOz1rOHs.d.ts → types-wsKR1gfx.d.ts} +19 -3
  55. package/dist/vite.d.ts +33 -1
  56. package/dist/vite.js +353 -158
  57. package/dist/vue.d.ts +3 -3
  58. package/dist/vue.js +6 -4
  59. package/package.json +28 -12
package/README.md CHANGED
@@ -22,7 +22,16 @@
22
22
 
23
23
  Junte micro-frontends de qualquer framework numa página só, sem configuração de build no host.
24
24
 
25
- Cada equipe publica o próprio pedaço da página (um remote) com `vite build`. O host recebe a URL de cada remote e monta tudo no navegador. Não há plugin no host, não há versão de React para combinar entre equipes e um remote fora do ar não derruba a página.
25
+ Cada equipe publica o próprio pedaço da página (um remote) com `vite build`. O host recebe a URL de cada remote e monta tudo no navegador.
26
+
27
+ **Por que usar:**
28
+
29
+ - **Zero configuração no host.** Sem plugin de build: só `configure({ remotes })` com a URL de cada remote.
30
+ - **Qualquer framework.** React, Next, Vue, Nuxt, Angular, Svelte, Solid, Preact, Lit e JS puro na mesma página. Sem combinar versão de React entre equipes.
31
+ - **Deploy independente.** Cada equipe publica quando quiser. O host vê a versão nova sem novo build.
32
+ - **CSS e erros isolados.** O CSS de um remote não muda o resto da página. Um remote fora do ar mostra o próprio erro, e o resto continua funcionando.
33
+ - **Seguro.** O JS só roda se vier de uma origem permitida e se o hash SHA-384 bater.
34
+ - **Leve e rápido.** 3,4 kB de host. Primeira visita 57% mais rápida que o Module Federation e troca de área 4,6 vezes mais rápida que o Next.js Multi-Zones.
26
35
 
27
36
  ```bash
28
37
  npm install vitral
@@ -35,25 +44,25 @@ npm install vitral
35
44
  </picture>
36
45
  </p>
37
46
 
38
- ## Por que o vitral
47
+ ## Comparação
39
48
 
40
- | | Module Federation / single-spa | vitral |
41
- |---|---|---|
42
- | Build do host | Config combinada com cada remote | Nenhuma. Só `configure()` |
43
- | Versões de React/Vue | `shared` e singletons, conflitos de versão | Cada remote leva a sua. Compartilhar é opcional |
44
- | Remote fora do ar | Pode derrubar a página | Só aquele remote mostra erro |
45
- | CSS | Vaza entre apps | Escopo automático ou Shadow DOM |
46
- | Deploy novo | `ChunkLoadError` com cache velho | Arquivos com hash; só o `vitral.json` revalida |
47
- | Conceitos | Containers, exposes, lifecycles, root config | `mount(el, props, ctx)` → `{ update, unmount }` |
49
+ | | Module Federation | single-spa | Next.js Multi-Zones | vitral |
50
+ |---|---|---|---|---|
51
+ | Configuração no host | Plugin no build, combinado com cada remote | Root config e import map | Um app Next por zona e rewrites | Nenhuma. Só `configure()` |
52
+ | Frameworks | Combinar versões em `shared` | Um adaptador e um ciclo de vida por framework | Só Next | Qualquer um, sem combinar versão |
53
+ | Divide a página em | Pedaços | Apps por rota | Rotas (cada zona é uma página) | Pedaços ou rotas |
54
+ | Trocar de área | Sem recarregar | Sem recarregar | Recarrega a página e baixa Next e React de novo | Sem recarregar |
55
+ | Isolamento de CSS | Nenhum | Nenhum | Por página | Escopo automático ou Shadow DOM |
56
+ | Remote fora do ar | Erro sem tratamento derruba a página | A tela de erro é com você | A zona inteira fica fora | Só aquele remote mostra erro, com `retry` |
57
+ | Código de host (brotli) | 19,4 kB | 5,8 kB | Runtime do Next em cada zona | 3,4 kB |
48
58
 
49
59
  Medido no [benchmark público](https://joaogabriel15.github.io/vitral/aprender/benchmark) (Chromium, rede Slow 4G, mediana de 7 visitas):
50
60
 
51
- - **3,4 kB** de código de host mínimo, com brotli. O host do Module Federation tem 19,4 kB.
52
61
  - **57% menos tempo** na primeira visita que o Module Federation com React compartilhado (1157 ms contra 2673 ms, 1 remote).
53
- - **4,6 vezes mais rápido** para trocar de área que o Next.js Multi-Zones (184 ms contra 850 ms).
62
+ - **4,6 vezes mais rápido** para trocar de área que o Next.js Multi-Zones (184 ms contra 850 ms), com 63% menos bytes para abrir duas áreas.
54
63
  - **10 frameworks** cobertos por testes de ponta a ponta no CI, com host e remote de frameworks diferentes, em Chromium, Firefox, WebKit e mobile.
55
64
 
56
- A página do benchmark também lista onde o vitral perde.
65
+ O método e as versões usadas estão na página do benchmark.
57
66
 
58
67
  ## Começo rápido
59
68
 
@@ -111,6 +120,55 @@ Sem build no host, numa página HTML qualquer:
111
120
 
112
121
  Todos os componentes de host aceitam `src` no lugar de `name`.
113
122
 
123
+ ## Rodar local
124
+
125
+ Cada remote roda no próprio `vite dev`, numa porta. O host aponta para essas portas:
126
+
127
+ ```bash
128
+ cd carrinho && npm run dev # vite --port 5174
129
+ cd busca && npm run dev # vite --port 5175
130
+ cd loja && npm run dev # vite --port 3000
131
+ ```
132
+
133
+ ```json
134
+ // loja/public/remotes.json
135
+ {
136
+ "carrinho": "http://localhost:5174/vitral.json",
137
+ "busca": "http://localhost:5175/vitral.json"
138
+ }
139
+ ```
140
+
141
+ ```js
142
+ // loja/src/main.jsx
143
+ configure({ remotes: '/remotes.json' })
144
+ ```
145
+
146
+ Editar o remote atualiza o host sem recarregar a página (HMR). Para rodar só um remote local e pegar os outros de homologação, abra o host com `?vitral=carrinho@http://localhost:5174`.
147
+
148
+ ## Docker e Kubernetes
149
+
150
+ Cada remote vira uma imagem nginx com o `dist`. O host lê a lista de remotes de uma variável de ambiente quando o contêiner sobe, então a mesma imagem serve em qualquer ambiente.
151
+
152
+ ```yaml
153
+ # compose.yaml
154
+ services:
155
+ loja: # host: http://localhost:8080
156
+ build: ./loja
157
+ ports: ["8080:80"]
158
+ environment:
159
+ VITRAL_REMOTES: '{"carrinho":"/mf/carrinho/vitral.json","busca":"/mf/busca/vitral.json"}'
160
+ carrinho: # remote React
161
+ build: ./carrinho
162
+ busca: # remote Vue
163
+ build: ./busca
164
+ ```
165
+
166
+ O nginx do host repassa `/mf/carrinho/` para o contêiner `carrinho`. Tudo fica na mesma origem: sem CORS e com uma porta só. Parar o contêiner `carrinho` mostra o erro só no lugar do carrinho.
167
+
168
+ No Kubernetes, as mesmas imagens funcionam com um Service por remote; o Ingress pode fazer o papel do nginx do host. Se os remotes ficam num CDN, troque `VITRAL_REMOTES` pelas URLs do CDN.
169
+
170
+ Exemplo completo, com Dockerfiles e configuração do nginx: [`examples/docker`](https://github.com/joaogabriel15/vitral/tree/main/examples/docker).
171
+
114
172
  ## Frameworks
115
173
 
116
174
  | Framework | Remote | Host | Import |
@@ -128,6 +186,8 @@ Todos os componentes de host aceitam `src` no lugar de `name`.
128
186
 
129
187
  No Next e no Nuxt o componente renderiza o `fallback` no servidor e monta o remote no cliente.
130
188
 
189
+ Uma zona do Multi-Zones vira remote sem reescrever o código Next (`vitral({ next: true })`), e o app Next principal vira host com `withVitral` e `<NextRemote>`, trocando de área sem recarregar. Veja [Next.js](https://joaogabriel15.github.io/vitral/frameworks/next).
190
+
131
191
  ### Host em Vue
132
192
 
133
193
  ```vue
@@ -378,4 +438,4 @@ failingRemote('recomendacoes') // testa a tela de erro
378
438
 
379
439
  - [Exemplos](https://github.com/joaogabriel15/vitral/tree/main/examples): o mesmo remote em 8 frameworks e 9 hosts.
380
440
  - [Como contribuir](https://github.com/joaogabriel15/vitral/blob/main/CONTRIBUTING.md)
381
- - Licença [MIT](https://github.com/joaogabriel15/vitral/blob/main/LICENSE)
441
+ - Licença [MIT](https://github.com/joaogabriel15/vitral/blob/main/LICENSE). Feito por [Pacheco Codes](https://pacheco.dev.br/).
package/dist/angular.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { InjectionToken, Type, ApplicationConfig, Signal, Provider, EnvironmentProviders } from '@angular/core';
2
- import { R as RemoteContext, P as Props, a as RemoteModule, S as Shared, V as VitralConfig } from './types-EOz1rOHs.js';
3
- export { e as events } from './events-DLdbFqBM.js';
2
+ import { R as RemoteContext, P as Props, a as RemoteModule, S as Shared, V as VitralConfig } from './types-wsKR1gfx.js';
3
+ export { e as events } from './events-Cieg_far.js';
4
4
  export { n as navigate } from './navigate-BRG1WJUM.js';
5
5
  import './errors-kjBv6IH_.js';
6
6
 
package/dist/angular.js CHANGED
@@ -1,18 +1,20 @@
1
1
  import {
2
2
  defineVitralElement
3
- } from "./chunk-V24ZNHUF.js";
4
- import "./chunk-TXSHK3SK.js";
3
+ } from "./chunk-ZRAGLT3I.js";
4
+ import "./chunk-ZJ4IM2PZ.js";
5
5
  import {
6
6
  defineRemote,
7
- events,
8
7
  memoSelect,
9
- navigate,
10
8
  same,
11
9
  shared
12
- } from "./chunk-O7DV3EVB.js";
10
+ } from "./chunk-NTXMEZIZ.js";
11
+ import {
12
+ events,
13
+ navigate
14
+ } from "./chunk-EQZOJU3B.js";
13
15
  import {
14
16
  configure
15
- } from "./chunk-72H4FXMD.js";
17
+ } from "./chunk-6T25WKNK.js";
16
18
  import "./chunk-KU6HF2EW.js";
17
19
  import "./chunk-TW7KYJQ6.js";
18
20
  import "./chunk-463H6B56.js";
package/dist/auto.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  defineVitralElement
3
- } from "./chunk-V24ZNHUF.js";
4
- import "./chunk-TXSHK3SK.js";
5
- import "./chunk-O7DV3EVB.js";
6
- import "./chunk-72H4FXMD.js";
3
+ } from "./chunk-ZRAGLT3I.js";
4
+ import "./chunk-ZJ4IM2PZ.js";
5
+ import "./chunk-NTXMEZIZ.js";
6
+ import "./chunk-EQZOJU3B.js";
7
+ import "./chunk-6T25WKNK.js";
7
8
  import "./chunk-KU6HF2EW.js";
8
9
  import "./chunk-TW7KYJQ6.js";
9
10
  import "./chunk-463H6B56.js";
@@ -0,0 +1,165 @@
1
+ import {
2
+ RemoteTree,
3
+ VitralContext
4
+ } from "./chunk-B2MPYF4M.js";
5
+ import {
6
+ hostSlot
7
+ } from "./chunk-ZJ4IM2PZ.js";
8
+ import {
9
+ defineRemote,
10
+ memoSelect,
11
+ same,
12
+ shared
13
+ } from "./chunk-NTXMEZIZ.js";
14
+ import {
15
+ events
16
+ } from "./chunk-EQZOJU3B.js";
17
+ import {
18
+ preload
19
+ } from "./chunk-6T25WKNK.js";
20
+ import {
21
+ protocol
22
+ } from "./chunk-GNPFWISJ.js";
23
+
24
+ // src/react.tsx
25
+ import {
26
+ useContext,
27
+ useEffect,
28
+ useMemo,
29
+ useRef,
30
+ useState,
31
+ useSyncExternalStore
32
+ } from "react";
33
+ import { createRoot, hydrateRoot } from "react-dom/client";
34
+ import * as ReactAll from "react";
35
+ import * as ReactDOM from "react-dom";
36
+ import * as ReactDOMClient from "react-dom/client";
37
+ import * as JSXRuntime from "react/jsx-runtime";
38
+ import * as JSXDevRuntime from "react/jsx-dev-runtime";
39
+ import { jsx, jsxs } from "react/jsx-runtime";
40
+ function reactRemote(Component) {
41
+ const start = (el, props, ctx, hydrate) => {
42
+ const tree = (p) => /* @__PURE__ */ jsx(RemoteTree, { Component, props: p, ctx });
43
+ const options = { onUncaughtError: (e) => ctx.reportError(e) };
44
+ const root = hydrate ? hydrateRoot(el, tree(props), options) : createRoot(el, options);
45
+ const render = (p) => root.render(tree(p));
46
+ if (!hydrate) render(props);
47
+ return { update: render, unmount: () => root.unmount() };
48
+ };
49
+ return defineRemote({
50
+ mount: (el, props, ctx) => start(el, props, ctx, false),
51
+ hydrate: (el, props, ctx) => start(el, props, ctx, true)
52
+ });
53
+ }
54
+ function useVitral() {
55
+ return useContext(VitralContext);
56
+ }
57
+ function usePortalContainer() {
58
+ return useVitral()?.portalContainer;
59
+ }
60
+ function useEvent(type, handler) {
61
+ const ref = useRef(handler);
62
+ ref.current = handler;
63
+ useEffect(() => events.on(type, (p) => ref.current(p)), [type]);
64
+ }
65
+ function useEventValue(type) {
66
+ const [value, setValue] = useState(() => events.last(type));
67
+ useEvent(type, setValue);
68
+ return value;
69
+ }
70
+ function useShared(key, select = same) {
71
+ const handle = useMemo(() => shared(key), [key]);
72
+ const [pick] = useState(memoSelect);
73
+ return useSyncExternalStore(
74
+ handle.subscribe,
75
+ () => pick(handle.get(), select),
76
+ () => pick(void 0, select)
77
+ );
78
+ }
79
+ var offeredTo;
80
+ function offerReact() {
81
+ const p = protocol();
82
+ if (offeredTo === p) return;
83
+ offeredTo = p;
84
+ const compiler = ReactAll.__COMPILER_RUNTIME;
85
+ const modules = {
86
+ react: ReactAll,
87
+ "react-dom": ReactDOM,
88
+ "react-dom/client": ReactDOMClient,
89
+ "react/jsx-runtime": JSXRuntime,
90
+ "react/jsx-dev-runtime": JSXDevRuntime,
91
+ // O React 19 expõe o react/compiler-runtime pelo próprio react.
92
+ ...compiler && { "react/compiler-runtime": compiler }
93
+ };
94
+ p.pending(import("./dedupe-PZCSKL3L.js").then((m) => m.offer(p, { react: { version: ReactAll.version, modules } })).catch(() => {
95
+ }));
96
+ }
97
+ function preload2(name, options) {
98
+ offerReact();
99
+ preload(name, options);
100
+ }
101
+ function Remote({
102
+ name,
103
+ src,
104
+ props,
105
+ isolate,
106
+ fallback = null,
107
+ error,
108
+ onError,
109
+ className,
110
+ loading,
111
+ prefetch,
112
+ priority,
113
+ keepAlive
114
+ }) {
115
+ const target = useRef(null);
116
+ const slot = useRef(null);
117
+ const [state, setState] = useState({ status: "loading", error: null });
118
+ const onErrorRef = useRef(onError);
119
+ onErrorRef.current = onError;
120
+ useEffect(() => {
121
+ offerReact();
122
+ const s = hostSlot(target.current, {
123
+ name,
124
+ src,
125
+ props: props ?? {},
126
+ isolate,
127
+ loading,
128
+ prefetch,
129
+ priority,
130
+ keepAlive,
131
+ onStatus(status2, e) {
132
+ setState({ status: status2, error: e });
133
+ if (e) {
134
+ if (onErrorRef.current) onErrorRef.current(e);
135
+ else console.error(e);
136
+ }
137
+ }
138
+ });
139
+ slot.current = s;
140
+ return () => {
141
+ slot.current = null;
142
+ queueMicrotask(() => s.destroy());
143
+ };
144
+ }, []);
145
+ useEffect(() => slot.current?.set({ name, src, isolate }), [name, src, isolate]);
146
+ useEffect(() => slot.current?.update(props ?? {}), [props]);
147
+ const retry = () => slot.current?.retry();
148
+ const { status, error: err } = state;
149
+ return /* @__PURE__ */ jsxs("div", { className, "data-vitral": name ?? src, "data-vitral-status": status, children: [
150
+ status === "loading" && fallback,
151
+ status === "error" && err && (typeof error === "function" ? error(err, retry) : error ?? null),
152
+ /* @__PURE__ */ jsx("div", { ref: target })
153
+ ] });
154
+ }
155
+
156
+ export {
157
+ reactRemote,
158
+ useVitral,
159
+ usePortalContainer,
160
+ useEvent,
161
+ useEventValue,
162
+ useShared,
163
+ preload2 as preload,
164
+ Remote
165
+ };
@@ -0,0 +1,311 @@
1
+ // src/vite/dedupe.ts
2
+ import { existsSync, readFileSync, realpathSync } from "fs";
3
+ import { dirname, join, resolve } from "path";
4
+ import { pathToFileURL } from "url";
5
+
6
+ // src/vite/exports.ts
7
+ var nameOf = (n) => n.type === "Identifier" ? n.name : String(n.value);
8
+ function bindings(node, out = []) {
9
+ if (!node) return out;
10
+ if (node.type === "Identifier") out.push(node.name);
11
+ else if (node.type === "ObjectPattern") for (const p of node.properties) bindings(p.type === "RestElement" ? p.argument : p.value, out);
12
+ else if (node.type === "ArrayPattern") for (const e of node.elements) bindings(e?.type === "RestElement" ? e.argument : e, out);
13
+ else if (node.type === "AssignmentPattern") bindings(node.left, out);
14
+ else if (node.type === "RestElement") bindings(node.argument, out);
15
+ return out;
16
+ }
17
+ async function exportOrigins(ctx, file, cache) {
18
+ const parsed = /* @__PURE__ */ new Map();
19
+ const queue = [file];
20
+ while (queue.length) {
21
+ const f = queue.shift();
22
+ if (parsed.has(f)) continue;
23
+ let p = cache.get(f);
24
+ if (!p) cache.set(f, p = read(ctx, f));
25
+ const done = await p;
26
+ parsed.set(f, done);
27
+ queue.push(...done.star, ...done.named.flatMap((n) => n.source ? [n.source] : []));
28
+ }
29
+ return origins(file, parsed, /* @__PURE__ */ new Map(), []);
30
+ }
31
+ function origins(file, parsed, memo, stack) {
32
+ if (stack.includes(file)) return /* @__PURE__ */ new Map();
33
+ const known = memo.get(file);
34
+ if (known) return known;
35
+ const p = parsed.get(file);
36
+ const next = [...stack, file];
37
+ const out = new Map(p.local);
38
+ for (const n of p.named) out.set(n.exported, n.source && origins(n.source, parsed, memo, next).get(n.name) || n.fallback);
39
+ for (const s of p.star) for (const [name, origin] of origins(s, parsed, memo, next)) if (name !== "default" && !out.has(name)) out.set(name, origin);
40
+ memo.set(file, out);
41
+ return out;
42
+ }
43
+ async function read(ctx, file) {
44
+ const out = { local: /* @__PURE__ */ new Map(), named: [], star: [] };
45
+ const info = await ctx.load({ id: file });
46
+ if (!info.code) return out;
47
+ const body = ctx.parse(info.code).body ?? [];
48
+ const target = async (source) => {
49
+ const r = await ctx.resolve(source, file, { skipSelf: false });
50
+ return r && !r.external ? r.id : void 0;
51
+ };
52
+ const imports = /* @__PURE__ */ new Map();
53
+ for (const n of body)
54
+ if (n.type === "ImportDeclaration") {
55
+ for (const s of n.specifiers)
56
+ if (s.type !== "ImportNamespaceSpecifier")
57
+ imports.set(s.local.name, { source: n.source.value, name: s.type === "ImportDefaultSpecifier" ? "default" : nameOf(s.imported) });
58
+ }
59
+ const fixed = /* @__PURE__ */ new Set();
60
+ for (const n of body) {
61
+ const d = n.type === "ExportNamedDeclaration" ? n.declaration : n;
62
+ if (d?.type === "FunctionDeclaration" || d?.type === "ClassDeclaration") d.id && fixed.add(d.id.name);
63
+ else if (d?.type === "VariableDeclaration" && d.kind === "const") for (const x of d.declarations) for (const name of bindings(x.id)) fixed.add(name);
64
+ }
65
+ for (const n of body) {
66
+ if (n.type === "ExportDefaultDeclaration") {
67
+ const id = n.declaration.type === "Identifier" ? n.declaration.name : void 0;
68
+ const via = id ? imports.get(id) : void 0;
69
+ if (via) out.named.push({ exported: "default", name: via.name, source: await target(via.source), fallback: `${file}#default` });
70
+ else out.local.set("default", id && fixed.has(id) ? `${file}#${id}` : `${file}#default`);
71
+ } else if (n.type === "ExportAllDeclaration") {
72
+ if (n.exported) out.local.set(nameOf(n.exported), `${file}#${nameOf(n.exported)}`);
73
+ else {
74
+ const t = await target(n.source.value);
75
+ if (t) out.star.push(t);
76
+ }
77
+ } else if (n.type === "ExportNamedDeclaration") {
78
+ if (n.exportKind === "type") continue;
79
+ if (n.declaration) {
80
+ const d = n.declaration;
81
+ const names = d.type === "VariableDeclaration" ? d.declarations.flatMap((x) => bindings(x.id)) : d.id ? [d.id.name] : [];
82
+ for (const name of names) out.local.set(name, `${file}#${name}`);
83
+ }
84
+ for (const s of n.specifiers ?? []) {
85
+ if (s.exportKind === "type") continue;
86
+ const exported = nameOf(s.exported);
87
+ const local = nameOf(s.local);
88
+ const via = n.source ? { source: n.source.value, name: local } : imports.get(local);
89
+ const fallback = `${file}#${n.source ? exported : local}`;
90
+ out.named.push({ exported, name: via?.name ?? local, source: via ? await target(via.source) : void 0, fallback });
91
+ }
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+
97
+ // src/vite/dedupe.ts
98
+ var REACT = ["react", "react-dom", "react-dom/client", "react/jsx-runtime", "react/jsx-dev-runtime", "react/compiler-runtime"];
99
+ var BOOT = "virtual:vitral-boot";
100
+ var DEPS = "\0vitral-deps";
101
+ var OWN = "\0vitral-own:";
102
+ var SHIM = "\0vitral-shim:";
103
+ var DEEP = "\0vitral-deep:";
104
+ var pkgOf = (spec) => spec.split("/").slice(0, spec.startsWith("@") ? 2 : 1).join("/");
105
+ var norm = (p) => p.replace(/\\/g, "/");
106
+ var groupOf = (spec) => REACT.includes(spec) || pkgOf(spec) === "scheduler" ? "react" : pkgOf(spec);
107
+ var ident = /^[A-Za-z_$][\w$]*$/;
108
+ function packageOf(file, pkg) {
109
+ for (let dir = dirname(file); dir !== dirname(dir); dir = dirname(dir)) {
110
+ const json = join(dir, "package.json");
111
+ if (!existsSync(json)) continue;
112
+ const data = JSON.parse(readFileSync(json, "utf8"));
113
+ if (data.name === pkg) return { dir, data };
114
+ }
115
+ throw new Error(`vitral: vers\xE3o de ${pkg} n\xE3o encontrada`);
116
+ }
117
+ function subpaths(pkg, data) {
118
+ const exp = data.exports;
119
+ if (!exp || typeof exp !== "object" || Array.isArray(exp) || !Object.keys(exp).some((k) => k.startsWith("."))) return [pkg];
120
+ const target = (v) => typeof v === "string" ? v : v && typeof v === "object" ? target(v.import ?? v.default ?? v.require ?? Object.values(v)[0]) : "";
121
+ return Object.keys(exp).filter((k) => !k.includes("*") && !/\.(json|css|scss)$/.test(target(exp[k]))).map((k) => k === "." ? pkg : pkg + k.slice(1));
122
+ }
123
+ async function groups(list, root, entry, resolveId) {
124
+ const importer = resolve(root, entry);
125
+ const byName = /* @__PURE__ */ new Map();
126
+ const all = /* @__PURE__ */ new Set();
127
+ for (const spec of list) {
128
+ const g = groupOf(spec);
129
+ if (spec.endsWith("/*")) all.add(g);
130
+ byName.set(g, [.../* @__PURE__ */ new Set([...byName.get(g) ?? [], ...g === "react" ? REACT : [spec.endsWith("/*") ? g : spec]])]);
131
+ }
132
+ const out = [];
133
+ for (const [name, list2] of byName) {
134
+ const group = { name, range: "", version: "", keys: [], names: {}, pkgs: name === "react" ? ["react", "react-dom", "scheduler"] : [name] };
135
+ let specs = list2;
136
+ for (let i = 0; i < specs.length; i++) {
137
+ const spec = specs[i];
138
+ const found = await resolveId(spec, importer);
139
+ if (!found) {
140
+ if (spec === name) throw new Error(`vitral: dedupe: ${spec} n\xE3o est\xE1 instalado`);
141
+ continue;
142
+ }
143
+ if (spec === name) {
144
+ const pkg = packageOf(found.id, name);
145
+ group.version = pkg.data.version;
146
+ group.dir = norm(realpathSync(pkg.dir));
147
+ if (all.has(name)) {
148
+ group.files = true;
149
+ specs = [.../* @__PURE__ */ new Set([...specs, ...subpaths(name, pkg.data)])];
150
+ }
151
+ }
152
+ try {
153
+ const mod = await import(pathToFileURL(found.id.split("?")[0]).href);
154
+ const extra = mod.default && typeof mod.default === "object" ? Object.keys(mod.default) : [];
155
+ group.names[spec] = [.../* @__PURE__ */ new Set([...Object.keys(mod), ...extra])].filter((n) => n !== "default" && ident.test(n));
156
+ } catch {
157
+ }
158
+ group.keys.push(spec);
159
+ }
160
+ group.range = name === "svelte" || name.startsWith("@angular/") ? group.version : `^${group.version}`;
161
+ out.push(group);
162
+ }
163
+ return out;
164
+ }
165
+ var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\0/g, "\\x00");
166
+ function dedupeIds(list) {
167
+ const specs = new Set(list.flatMap((s) => groupOf(s) === "react" ? REACT : [s.replace(/\/\*$/, "")]));
168
+ return new RegExp(`${escape(BOOT)}$|^(${[DEPS, ...specs].map(escape).join("|")})$|^(${escape(OWN)}|${escape(SHIM)}|${escape(DEEP)})`);
169
+ }
170
+ function dedupeHooks(entry, getGroups) {
171
+ const keyOf = (id) => getGroups().find((g) => g.keys.includes(id));
172
+ const inside = (g, file) => {
173
+ const f = norm(file);
174
+ return g.pkgs.some((p) => f.includes(`/node_modules/${p}/`)) || !!g.dir && f.startsWith(g.dir + "/");
175
+ };
176
+ let entries;
177
+ let origins2 = /* @__PURE__ */ new Map();
178
+ const exports = /* @__PURE__ */ new Map();
179
+ const entryFiles = (ctx) => entries ??= (async () => {
180
+ const map = /* @__PURE__ */ new Map();
181
+ for (const g of getGroups())
182
+ if (g.files)
183
+ for (const k of g.keys) {
184
+ const r = await ctx.resolve(k, void 0, { skipSelf: true });
185
+ if (r && !r.external) map.set(r.id, k);
186
+ }
187
+ return map;
188
+ })();
189
+ const originsOf = (ctx, g) => {
190
+ if (!origins2.has(g.name))
191
+ origins2.set(g.name, (async () => {
192
+ const files = await entryFiles(ctx);
193
+ const index = /* @__PURE__ */ new Map();
194
+ for (const [file, spec] of files) {
195
+ if (!g.keys.includes(spec)) continue;
196
+ for (const [name, origin] of await exportOrigins(ctx, file, exports)) if (!index.has(origin)) index.set(origin, { spec, name });
197
+ }
198
+ return index;
199
+ })());
200
+ return origins2.get(g.name);
201
+ };
202
+ return {
203
+ /** Nova build: esquece o que foi lido da anterior. */
204
+ reset() {
205
+ entries = void 0;
206
+ origins2 = /* @__PURE__ */ new Map();
207
+ exports.clear();
208
+ },
209
+ /** Algum grupo troca também os imports que chegam a arquivos do pacote? */
210
+ files: () => getGroups().some((g) => g.files),
211
+ resolveId: {
212
+ order: "pre",
213
+ handler(id, importer) {
214
+ if (id.endsWith(BOOT)) return `\0${BOOT}`;
215
+ if (id === DEPS || id.startsWith(OWN) || id.startsWith(SHIM) || id.startsWith(DEEP)) return id;
216
+ const g = keyOf(id);
217
+ if (!g || !importer || importer.startsWith(OWN)) return null;
218
+ if (inside(g, importer)) return null;
219
+ return SHIM + id;
220
+ }
221
+ },
222
+ /**
223
+ * Um import já resolvido que chega a um arquivo de um grupo com `files`
224
+ * (por alias, `paths` do tsconfig ou caminho direto): vira o shim do
225
+ * especificador daquele arquivo, ou de um arquivo interno que o pacote reexporta.
226
+ */
227
+ async resolved(ctx, file, importer) {
228
+ if (!importer || importer.startsWith("\0")) return null;
229
+ const g = getGroups().find((x) => x.files && inside(x, file));
230
+ if (!g || inside(g, importer)) return null;
231
+ const spec = (await entryFiles(ctx)).get(file);
232
+ return spec ? SHIM + spec : DEEP + file;
233
+ },
234
+ async load(ctx, id) {
235
+ if (id === `\0${BOOT}`) return boot(entry(), getGroups());
236
+ if (id === DEPS) return "export const deps = {}";
237
+ if (id.startsWith(OWN)) {
238
+ const g = getGroups().find((x) => x.name === id.slice(OWN.length));
239
+ return g.keys.map((k, i) => `import * as m${i} from ${JSON.stringify(k)}`).join("\n") + `
240
+ export default {${g.keys.map((k, i) => `${JSON.stringify(k)}: m${i}`).join(", ")}}
241
+ `;
242
+ }
243
+ if (id.startsWith(SHIM)) {
244
+ const spec = id.slice(SHIM.length);
245
+ const g = keyOf(spec);
246
+ let names = g.names[spec];
247
+ if (!names) {
248
+ const file = [...await entryFiles(ctx)].find(([, k]) => k === spec)?.[0];
249
+ names = file ? [...(await exportOrigins(ctx, file, exports)).keys()].filter((n) => n !== "default" && ident.test(n)) : [];
250
+ }
251
+ return `import { deps } from ${JSON.stringify(DEPS)}
252
+ const m = deps[${JSON.stringify(spec)}]
253
+ export default m.default ?? m
254
+ ` + (names.length ? `export const { ${names.join(", ")} } = m
255
+ ` : "");
256
+ }
257
+ if (id.startsWith(DEEP)) {
258
+ const file = id.slice(DEEP.length);
259
+ const g = getGroups().find((x) => x.files && inside(x, file));
260
+ const index = await originsOf(ctx, g);
261
+ const own = await exportOrigins(ctx, file, exports);
262
+ const via = [...own].map(([name, origin]) => [name, index.get(origin)]);
263
+ const f = JSON.stringify(file);
264
+ const shared = via.filter(([, v]) => v);
265
+ const rest = via.filter(([, v]) => !v).map(([name]) => name);
266
+ if (!shared.length) {
267
+ ctx.warn?.(`dedupe: ${file.slice(g.dir.length + 1)} n\xE3o sai por nenhum export de ${g.name}: o remote leva uma c\xF3pia pr\xF3pria deste arquivo`);
268
+ return `export * from ${f}
269
+ ` + (own.has("default") ? `export { default } from ${f}
270
+ ` : "");
271
+ }
272
+ if (rest.length)
273
+ ctx.warn?.(`dedupe: ${file.slice(g.dir.length + 1)}: ${rest.join(", ")} n\xE3o sai(em) por nenhum export de ${g.name}: esses nomes v\xEAm de uma c\xF3pia pr\xF3pria deste arquivo`);
274
+ return `import { deps } from ${JSON.stringify(DEPS)}
275
+ ` + shared.map(([name, v], i) => `const v${i} = deps[${JSON.stringify(v.spec)}][${JSON.stringify(v.name)}]
276
+ ` + (name === "default" ? `export default v${i}
277
+ ` : `export { v${i} as ${JSON.stringify(name)} }
278
+ `)).join("") + (rest.length ? `export { ${rest.map((n) => n === "default" ? "default" : JSON.stringify(n)).join(", ")} } from ${f}
279
+ ` : "");
280
+ }
281
+ return null;
282
+ }
283
+ };
284
+ }
285
+ function boot(entry, groups2) {
286
+ const calls = groups2.map(
287
+ (g) => `need(${JSON.stringify(g.name)}, ${JSON.stringify(g.range)}, ${JSON.stringify(g.version)}, ${JSON.stringify(g.keys)}, () => import(${JSON.stringify(OWN + g.name)}).then((m) => m.default))`
288
+ );
289
+ return `const { deps } = await import(${JSON.stringify(DEPS)})
290
+ const p = globalThis[Symbol.for("vitral")]
291
+ const shared = !!(p && p.dedupe) && /\\/split\\/[^/]*$/.test(import.meta.url)
292
+ async function need(name, range, version, keys, load) {
293
+ let m = shared && (await p.dedupe(name, range, { version, load }))
294
+ const missing = m ? keys.filter((k) => !(k in m)) : []
295
+ if (missing.length) console.warn("[vitral] dedupe: a oferta de " + name + " n\xE3o tem " + missing.join(", ") + "; o remote usa a c\xF3pia pr\xF3pria inteira")
296
+ if (!m || missing.length) m = await load()
297
+ Object.assign(deps, m)
298
+ }
299
+ await Promise.all([${calls.join(", ")}])
300
+ const remote = await import(${JSON.stringify(entry)})
301
+ export default remote.default
302
+ `;
303
+ }
304
+
305
+ export {
306
+ BOOT,
307
+ subpaths,
308
+ groups,
309
+ dedupeIds,
310
+ dedupeHooks
311
+ };