vitral 0.1.0 → 0.2.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 (56) hide show
  1. package/README.md +250 -217
  2. package/dist/angular.d.ts +15 -4
  3. package/dist/angular.js +25 -5
  4. package/dist/auto.d.ts +2 -0
  5. package/dist/auto.js +12 -0
  6. package/dist/chunk-2XF5BKV3.js +190 -0
  7. package/dist/chunk-6Y3VMWHH.js +181 -0
  8. package/dist/chunk-DXORSF4X.js +42 -0
  9. package/dist/chunk-GNPFWISJ.js +80 -0
  10. package/dist/chunk-HP6QWU6K.js +51 -0
  11. package/dist/chunk-NCONW6WS.js +606 -0
  12. package/dist/chunk-PX7PBCCT.js +89 -0
  13. package/dist/chunk-S5MZT5IT.js +20 -0
  14. package/dist/chunk-SFL6LTP3.js +56 -0
  15. package/dist/chunk-UTYAX6MU.js +152 -0
  16. package/dist/cli.d.ts +1 -0
  17. package/dist/cli.js +88 -0
  18. package/dist/dedupe-IWFIXRWG.js +81 -0
  19. package/dist/devtools.d.ts +12 -0
  20. package/dist/devtools.js +86 -0
  21. package/dist/element.d.ts +60 -2
  22. package/dist/element.js +9 -2
  23. package/dist/errors-KKB8FGCz.d.ts +11 -0
  24. package/dist/events-DUdjhfps.d.ts +6 -0
  25. package/dist/expose-53BEJB34.js +14 -0
  26. package/dist/host-slot-Cz6EiGsB.d.ts +6 -0
  27. package/dist/iframe.d.ts +28 -0
  28. package/dist/iframe.js +236 -0
  29. package/dist/index.d.ts +142 -147
  30. package/dist/index.js +39 -10
  31. package/dist/navigate-BRG1WJUM.d.ts +9 -0
  32. package/dist/overrides-NZAIUGK5.js +15 -0
  33. package/dist/preact.d.ts +30 -7
  34. package/dist/preact.js +37 -9
  35. package/dist/react.d.ts +38 -7
  36. package/dist/react.js +92 -42
  37. package/dist/routes.d.ts +56 -0
  38. package/dist/routes.js +143 -0
  39. package/dist/shadow-MYRJQLLY.js +72 -0
  40. package/dist/slot-pB276oPt.d.ts +36 -0
  41. package/dist/solid.d.ts +29 -6
  42. package/dist/solid.js +31 -8
  43. package/dist/sri-BVKZNX5Q.js +114 -0
  44. package/dist/state-BDdCwc21.d.ts +4 -0
  45. package/dist/svelte.d.ts +19 -3
  46. package/dist/svelte.js +10 -4
  47. package/dist/testing.d.ts +2 -2
  48. package/dist/testing.js +8 -3
  49. package/dist/types-BV-hUrms.d.ts +265 -0
  50. package/dist/vite.d.ts +58 -4
  51. package/dist/vite.js +714 -24
  52. package/dist/vue.d.ts +79 -7
  53. package/dist/vue.js +69 -46
  54. package/package.json +144 -8
  55. package/dist/chunk-IZK6ITIJ.js +0 -96
  56. package/dist/chunk-TTL52BF2.js +0 -547
package/README.md CHANGED
@@ -1,162 +1,100 @@
1
- <p align="center"><img src="docs/logo.svg" width="96" alt="vitral"></p>
1
+ <p align="center"><img src="https://raw.githubusercontent.com/joaogabriel15/vitral/main/docs/logo.svg" width="96" alt="vitral"></p>
2
2
 
3
3
  <h1 align="center">vitral</h1>
4
4
 
5
5
  <p align="center"><strong>Feito de partes. Visto como um todo.</strong></p>
6
6
 
7
- [![CI](https://github.com/joaogabriel15/vitral/actions/workflows/ci.yml/badge.svg)](https://github.com/joaogabriel15/vitral/actions/workflows/ci.yml)
8
- [![npm](https://img.shields.io/npm/v/vitral)](https://www.npmjs.com/package/vitral)
9
- [![tamanho](https://img.shields.io/bundlejs/size/vitral)](https://bundlejs.com/?q=vitral)
10
- [![licença](https://img.shields.io/npm/l/vitral)](LICENSE)
11
-
12
- ```bash
13
- npm install vitral
14
- ```
7
+ <p align="center">
8
+ <a href="https://github.com/joaogabriel15/vitral/actions/workflows/ci.yml"><img src="https://github.com/joaogabriel15/vitral/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
9
+ <a href="https://www.npmjs.com/package/vitral"><img src="https://img.shields.io/npm/v/vitral" alt="npm"></a>
10
+ <a href="https://bundlejs.com/?q=vitral"><img src="https://img.shields.io/bundlejs/size/vitral" alt="tamanho"></a>
11
+ <a href="https://github.com/joaogabriel15/vitral/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/vitral" alt="licença MIT"></a>
12
+ </p>
15
13
 
16
14
  Junte micro-frontends de qualquer framework numa página só, sem configuração de build no host.
17
15
 
18
- - Cada app publica um módulo e o host só precisa da URL dele.
19
- - React, Vue, Angular, Svelte e os outros convivem na mesma tela.
20
- - CSS, erros e deploys ficam isolados por app.
21
-
22
- ```
23
- host (qualquer framework) remote (qualquer framework)
24
- ┌────────────────────────────┐ URL ┌──────────────────────────────┐
25
- │ configure({ remotes }) │ ─────────▶ │ vitral.json { entry, css, │
26
- │ <Remote name="carrinho" /> │ │ integrity } │
27
- │ │ ◀───────── │ remote.js mount/update/ │
28
- │ events (barramento global) │ import() │ unmount │
29
- └────────────────────────────┘ └──────────────────────────────┘
16
+ ```bash
17
+ npm install vitral
30
18
  ```
31
19
 
32
- ## Por que é diferente
33
-
34
- | Dor comum (Module Federation, single-spa) | Como o vitral resolve |
35
- |---|---|
36
- | Host e remote precisam de config de build combinada | Host não tem config de build nenhuma. Só `configure({ remotes })` em runtime. |
37
- | `shared` / singletons quebram com versões diferentes de React/Vue | Por padrão cada remote leva o próprio framework, isolado na sua própria root. Nenhum conflito de versão. Compartilhar é opcional (import map). |
38
- | Erro de um remote derruba a página | Cada remote tem estado de loading, erro e "tentar de novo" próprios. |
39
- | CSS de um vaza no outro; Tailwind do remote sem classes ou quebrando o host | O remote leva o próprio CSS, já com escopo automático. `isolate="shadow"` isola de vez, e o `@property` do Tailwind v4 continua funcionando. |
40
- | Cache velho depois do deploy (`ChunkLoadError`) | Arquivos com hash; só o `vitral.json` é revalidado. |
41
- | Roteadores brigando | `navigate(url)` mantém host e remotes na mesma URL. |
42
- | Executar JS de outra origem sem garantia | Allowlist de origens + verificação de hash SRI antes de executar. |
43
- | Muitos conceitos (containers, exposes, lifecycles, root config) | Um contrato: `mount(el, props, ctx) → { update, unmount }`. |
44
-
45
- ## Frameworks suportados
46
-
47
- | Framework | Remote (publicar) | Host (consumir) | Import |
48
- |---|---|---|---|
49
- | React 18/19 | `reactRemote(App)` | `<Remote>` | `vitral/react` |
50
- | Next.js (App e Pages Router) | igual ao React | `<Remote>` (já é `'use client'`, SSR seguro) | `vitral/react` |
51
- | Vue 3 | `vueRemote(App, { setup })` | `<VitralRemote>` | `vitral/vue` |
52
- | Nuxt 3/4 | igual ao Vue | `<VitralRemote>` (SSR seguro) | `vitral/vue` |
53
- | Angular 17+ (standalone, com ou sem zone.js) | `angularRemote(AppComponent, { providers })` | `<vitral-remote>` + `provideVitral()` | `vitral/angular` |
54
- | Svelte 5 / SvelteKit | `svelteRemote(App)` | `use:remote` ou `<vitral-remote>` | `vitral/svelte` |
55
- | Solid / SolidStart | `solidRemote(App)` | `<Remote>` | `vitral/solid` |
56
- | Preact | `preactRemote(App)` | `<Remote>` | `vitral/preact` |
57
- | Lit, Stencil, Web Components | `elementRemote('meu-widget')` | `<vitral-remote>` | `vitral/element` |
58
- | JS / TypeScript puro | `component(render)` | `<vitral-remote>` | `vitral`, `vitral/element` |
59
- | Qualquer outro (Astro, Qwik, jQuery, HTML do servidor…) | `defineRemote({ mount })` | `<vitral-remote>` | `vitral/element` |
60
-
61
- Todas as 72 combinações host × remote dos exemplos (9 hosts × 8 remotes) são
62
- testadas no navegador a cada execução do e2e.
20
+ - O host só precisa da URL de cada remote.
21
+ - React, Vue, Angular, Svelte, Solid, Preact, Lit e JS puro na mesma página.
22
+ - Cada remote tem o próprio CSS, os próprios erros e o próprio deploy.
63
23
 
64
- ## Uso
24
+ ## Começo rápido
65
25
 
66
- ### Publicar um remote
26
+ Para gerar host e remote prontos: `npm create vitral@latest`.
67
27
 
68
- ```js
69
- // React // Vue
70
- import { reactRemote } from 'vitral/react' import { vueRemote } from 'vitral/vue'
71
- export default reactRemote(Carrinho) export default vueRemote(Catalogo, { setup: (app) => app.use(pinia) })
72
-
73
- // Svelte (o contexto chega na prop `vitral`)
74
- import { svelteRemote } from 'vitral/svelte'
75
- export default svelteRemote(Cartao)
76
-
77
- // Solid / Preact (o contexto chega na prop `vitral`)
78
- export default solidRemote(Cartao) export default preactRemote(Cartao)
79
-
80
- // Angular: props viram inputs; contexto com injectVitral()
81
- import { angularRemote } from 'vitral/angular'
82
- export default angularRemote(CartaoComponent, { providers: [provideHttpClient()] })
28
+ **1. No remote**, exporte o componente e use o plugin:
83
29
 
84
- // Lit / Web Component: props viram propriedades; contexto em `this.vitral`
85
- import { elementRemote } from 'vitral/element'
86
- export default elementRemote('lit-cartao', () => customElements.define('lit-cartao', LitCartao))
87
- ```
88
-
89
- JS ou TypeScript puro, sem framework nenhum:
90
-
91
- ```ts
92
- import { component, html } from 'vitral'
93
-
94
- export default component<{ mensagem?: string }>({
95
- state: { cliques: 0 },
96
- render: ({ mensagem }, state, ctx) => html`
97
- <p>${mensagem}</p>
98
- <button onclick=${() => state.cliques++}>cliques: ${state.cliques}</button>
99
- `,
100
- })
30
+ ```jsx
31
+ // src/App.jsx (ou .vue, .svelte, .tsx…)
32
+ export default function Carrinho({ userId }) { … }
101
33
  ```
102
34
 
103
- Sem estado, basta a função: ``component(({ nome }) => html`<p>Olá, ${nome}</p>`)``.
104
- Tudo que entra no `${}` é escapado, então dá para colocar dado do usuário sem
105
- risco de XSS. Mudar o `state` ou receber props novas atualiza a tela sozinho.
106
- Se precisar de controle total, `defineRemote({ mount(el, props, ctx) { ... } })`
107
- continua disponível.
108
-
109
- Build com o plugin (o mesmo para todos os frameworks):
110
-
111
35
  ```js
112
36
  // vite.config.js
37
+ import react from '@vitejs/plugin-react'
113
38
  import vitral from 'vitral/vite'
114
- export default { plugins: [react() /* ou vue(), svelte(), solid(), angular()… */, vitral({ entry: 'src/remote.jsx' })] }
115
- ```
116
-
117
- `vite build` gera `dist/remote-[hash].js`, `dist/remote-[hash].css` e
118
- `dist/vitral.json` (com o hash `sha384` do JS). Suba a pasta em qualquer CDN com
119
- CORS. Para Angular use `@analogjs/vite-plugin-angular` (compila em AOT).
120
39
 
121
- ### CSS e Tailwind
40
+ export default { plugins: [react(), vitral()] }
41
+ ```
122
42
 
123
- Use Tailwind (ou qualquer CSS) no remote normalmente. O plugin prefixa o CSS
124
- com `[data-vitral-scope="nome"]` e transforma `:root`/`html`/`body` na raiz do
125
- remote, então o preflight e o tema não vazam para o host, e o host não precisa
126
- escanear os arquivos do remote. Com `isolate="shadow"`, as regras `@property`
127
- do Tailwind v4 sobem para o documento para que sombras, rings e gradientes
128
- funcionem. Veja [docs/PROBLEMAS.md](docs/PROBLEMAS.md).
43
+ O plugin descobre o framework e embrulha o `export default` com o adaptador certo. A entrada padrão é `src/remote.*` ou `src/App.*`.
129
44
 
130
- Sem Vite? Qualquer bundler serve (webpack, Rspack, esbuild, Rollup, Angular
131
- CLI): basta gerar um módulo ES cujo `default` tenha `mount(el, props, ctx)`.
45
+ `vite build` gera o JS, o CSS e um `vitral.json`. Publique a pasta `dist` em qualquer CDN com CORS.
132
46
 
133
- ### Consumir no host (React / Next)
47
+ **2. No host**, registre a URL e use o componente:
134
48
 
135
49
  ```jsx
136
50
  import { configure } from 'vitral'
137
- import { Remote, useEventValue } from 'vitral/react'
51
+ import { Remote } from 'vitral/react'
138
52
 
139
- configure({ remotes: '/remotes.json' }) // ou um objeto { nome: url }
53
+ configure({
54
+ remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' },
55
+ })
140
56
 
141
- function App() {
142
- const total = useEventValue('carrinho:total')
57
+ export function App() {
143
58
  return (
144
59
  <Remote
145
60
  name="carrinho"
146
61
  props={{ userId: 42 }}
147
- isolate="shadow"
148
- fallback={<Spinner />}
149
- error={(err, retry) => <button onClick={retry}>Tentar de novo</button>}
62
+ fallback={<p>Carregando…</p>}
63
+ error={(erro, tentarDeNovo) => <button onClick={tentarDeNovo}>Tentar de novo</button>}
150
64
  />
151
65
  )
152
66
  }
153
67
  ```
154
68
 
155
- `<Remote>` já é `'use client'`. No Next ele renderiza o `fallback` no servidor
156
- e monta o remote no cliente, então funciona no App Router e no Pages Router
157
- sem `dynamic(..., { ssr: false })`.
69
+ Pronto. Mudar `props` atualiza o remote sem remontar.
70
+
71
+ Sem build no host, numa página HTML qualquer:
72
+
73
+ ```html
74
+ <script type="module" src="https://cdn.jsdelivr.net/npm/vitral/dist/auto.js"></script>
75
+ <vitral-remote src="https://cdn.empresa.com/carrinho/vitral.json"></vitral-remote>
76
+ ```
77
+
78
+ Todos os componentes de host aceitam `src` no lugar de `name`.
79
+
80
+ ## Frameworks
81
+
82
+ | Framework | Remote | Host | Import |
83
+ |---|---|---|---|
84
+ | React 18/19 | `reactRemote(App)` | `<Remote>` | `vitral/react` |
85
+ | Next.js (App e Pages Router) | `reactRemote(App)` | `<Remote>` | `vitral/react` |
86
+ | Vue 3 | `vueRemote(App, { setup })` | `<VitralRemote>` | `vitral/vue` |
87
+ | Nuxt 3/4 | `vueRemote(App, { setup })` | `<VitralRemote>` | `vitral/vue` |
88
+ | Angular 17+ | `angularRemote(Component, { providers })` | `<vitral-remote>` + `provideVitral()` | `vitral/angular` |
89
+ | Svelte 5 | `svelteRemote(App)` | `use:remote` | `vitral/svelte` |
90
+ | Solid | `solidRemote(App)` | `<Remote>` | `vitral/solid` |
91
+ | Preact | `preactRemote(App)` | `<Remote>` | `vitral/preact` |
92
+ | Lit e Web Components | `elementRemote('meu-widget')` | `<vitral-remote>` | `vitral/element` |
93
+ | JS e TypeScript puro | `component({ render })` | `<vitral-remote>` | `vitral` |
94
+
95
+ No Next e no Nuxt o componente renderiza o `fallback` no servidor e monta o remote no cliente.
158
96
 
159
- ### Consumir no host (Vue / Nuxt)
97
+ ### Host em Vue
160
98
 
161
99
  ```vue
162
100
  <script setup>
@@ -164,162 +102,257 @@ import { VitralRemote } from 'vitral/vue'
164
102
  </script>
165
103
 
166
104
  <template>
167
- <VitralRemote name="carrinho" :props="{ userId: 42 }" isolate="shadow">
105
+ <VitralRemote name="carrinho" :props="{ userId: 42 }">
168
106
  <template #fallback>Carregando…</template>
169
- <template #error="{ error, retry }"><button @click="retry">Tentar de novo</button></template>
107
+ <template #error="{ retry }"><button @click="retry">Tentar de novo</button></template>
170
108
  </VitralRemote>
171
109
  </template>
172
110
  ```
173
111
 
174
- No Nuxt, chame `configure()` num plugin `.client.ts`. O componente renderiza o
175
- fallback no SSR e monta o remote no cliente.
112
+ ### Host em qualquer outro lugar
176
113
 
177
- ### Consumir no host (JS/TS puro, Angular, Svelte, qualquer coisa)
178
-
179
- O Web Component `<vitral-remote>` funciona em qualquer lugar que renderize HTML:
114
+ `<vitral-remote>` funciona em HTML puro, Angular, Svelte, Astro ou HTML gerado no servidor:
180
115
 
181
116
  ```html
182
117
  <script type="module">
183
118
  import { configure } from 'vitral'
184
119
  import { defineVitralElement } from 'vitral/element'
185
- configure({ remotes: '/remotes.json' })
120
+
121
+ configure({ remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' } })
186
122
  defineVitralElement()
187
123
  </script>
188
124
 
189
- <vitral-remote name="carrinho" props='{"userId": 42}' isolate="shadow">
125
+ <vitral-remote name="carrinho" props='{"userId": 42}'>
190
126
  <p slot="fallback">Carregando…</p>
191
127
  <p slot="error">Indisponível</p>
192
128
  </vitral-remote>
193
129
  ```
194
130
 
195
- `el.props = {...}` atualiza sem remontar, `el.retry()` remonta, e os eventos
196
- `vitral-ready` / `vitral-error` avisam o estado.
197
-
198
- **Angular:**
131
+ ### Remote sem framework
199
132
 
200
133
  ```ts
201
- bootstrapApplication(App, { providers: [provideVitral({ remotes: '/remotes.json' })] })
134
+ import { component, html } from 'vitral'
202
135
 
203
- @Component({
204
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
205
- template: `<vitral-remote name="carrinho" [props]="{ userId: id() }" (vitral-error)="falhou()"></vitral-remote>`,
136
+ export default component<{ nome: string }>({
137
+ state: { cliques: 0 },
138
+ render: ({ nome }, state) => html`
139
+ <p>Olá, ${nome}</p>
140
+ <button onclick=${() => state.cliques++}>cliques: ${state.cliques}</button>
141
+ `,
206
142
  })
207
143
  ```
208
144
 
209
- **Svelte:** `<div use:remote={{ name: 'carrinho', props: { userId } }}></div>`
210
- (com `import { remote } from 'vitral/svelte'`).
145
+ O que entra em `${}` é escapado. Mudar `state` ou as props atualiza a tela.
146
+
147
+ ## Desenvolvimento
211
148
 
212
- **Solid / Preact:** `<Remote name="carrinho" props={{ userId }} fallback={…} error={(e, retry) => …} />`.
149
+ - **HMR:** com o remote em `vite dev`, aponte o host para `http://localhost:5174/vitral.json`. Editar o remote atualiza o host sem recarregar a página.
150
+ - **Overrides:** `http://localhost:3000/?vitral=carrinho@http://localhost:5174` troca a URL de um remote sem mexer no host. Só vale em `localhost` ou com `configure({ devOverrides: true })`. Para guardar no navegador, use `devtools()` de `vitral/devtools`.
151
+ - **Tipos das props:** o build publica `vitral.d.ts`. No host, `npx vitral types` gera `vitral-env.d.ts`, e uma prop errada passa a não compilar.
213
152
 
214
- ### Comunicação
153
+ ## Comunicação entre remotes
215
154
 
216
155
  ```js
217
156
  import { events } from 'vitral'
218
- events.emit('carrinho:adicionar', produto)
219
- const off = events.on('carrinho:adicionar', (p) => …)
220
- events.last('usuario') // último valor emitido
157
+
158
+ events.emit('carrinho:total', 120)
159
+ const parar = events.on('carrinho:total', (total) => console.log(total))
160
+ events.last('carrinho:total') // 120
221
161
  ```
222
162
 
223
- Dentro de remotes: `useVitral()` (contexto), `useEvent(tipo, fn)` e
224
- `useEventValue(tipo)` existem em `vitral/react` e `vitral/vue`. O barramento é o
225
- mesmo para todos, mesmo que cada remote tenha sua cópia da lib, e o que o
226
- remote assina por `ctx.events` é cancelado sozinho no unmount.
163
+ Todos os remotes usam o mesmo barramento. O que um remote assina é cancelado quando ele desmonta.
227
164
 
228
- Para navegar de dentro de um remote, use `navigate('/pedidos/42')` (ou
229
- `ctx.navigate`). Ele atualiza o histórico e avisa todos os roteadores.
165
+ Para estado comum (usuário, tema, carrinho), use o contexto compartilhado:
230
166
 
231
- ### Erros
167
+ ```js
168
+ import { provide, shared } from 'vitral'
232
169
 
233
- Todo erro é um `RemoteError` com `code` e o nome do remote. Falhas de
234
- download e erros de render dentro do remote (React, Vue) aparecem no `error`
235
- do componente de host (ou no slot `error` do `<vitral-remote>`), com `retry`
236
- para remontar só aquele remote.
170
+ provide('usuario', { id: 42 }) // host: cria e é o dono
171
+ provide('tema', 'claro', { writable: false }) // só o dono altera
172
+ shared('tema').subscribe((tema) => console.log(tema))
173
+ const nome = useShared('usuario', (u) => u?.nome) // React, Preact, Vue e Solid
174
+ ```
237
175
 
238
- ### Configuração
176
+ Angular usa `injectShared`, Svelte usa `$tema` e Lit usa `SharedController`. Detalhes em [Contexto compartilhado](https://joaogabriel15.github.io/vitral/aprender/contexto).
177
+
178
+ Para mudar de página de dentro de um remote, use `navigate('/pedidos/42')`. Todos os roteadores da página recebem a mudança.
179
+
180
+ ## CSS e Tailwind
181
+
182
+ Use Tailwind ou qualquer CSS no remote. O plugin prefixa o CSS para valer só dentro do remote, então o CSS de um remote não muda o host nem os outros remotes.
183
+
184
+ Para isolar por completo, use Shadow DOM:
185
+
186
+ ```jsx
187
+ <Remote name="carrinho" isolate="shadow" />
188
+ ```
189
+
190
+ Com Shadow DOM, as regras `@property` e `@font-face` sobem para o documento, então sombras, rings, gradientes e fontes do Tailwind v4 funcionam.
191
+
192
+ O remote também pode usar o tema do host:
193
+
194
+ ```js
195
+ vitral({ theme: 'host' }) // var(--x) usa o valor do host, se existir
196
+ configure({ styles: { inherit: ['/assets/base.css'] } }) // CSS base do host dentro do Shadow DOM
197
+ ```
198
+
199
+ Tema com Tailwind v4, dark mode, CSS legado e Tailwind v3 no host: [Estilos](https://joaogabriel15.github.io/vitral/aprender/estilos).
200
+
201
+ ## Configuração
239
202
 
240
203
  ```js
241
204
  configure({
242
- remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' }, // ou URL de um JSON
243
- allowedOrigins: ['https://cdn.empresa.com'], // padrão: página + origens registradas
244
- timeout: 15000,
245
- retries: 1,
246
- isolate: 'none', // ou 'shadow' para todos
205
+ remotes: '/remotes.json', // ou { nome: url }
206
+ allowedOrigins: ['https://cdn.empresa.com'], // padrão: a página e as origens registradas
207
+ timeout: 15000, // ms por download
208
+ retries: 1, // novas tentativas em erro de rede
209
+ isolate: 'none', // ou 'shadow'
247
210
  })
248
211
  ```
249
212
 
250
- Entradas podem ser objetos: `{ url, integrity, css, isolate }`, ou
251
- `{ module: () => import('./carrinho') }` para um remote local (monorepo) sem
252
- rede. A `integrity`
253
- declarada pelo host vence a do manifesto (fixe o hash no host quando o remote
254
- for de terceiros).
213
+ Cada remote também aceita um objeto:
255
214
 
256
- ## Segurança
215
+ ```js
216
+ configure({
217
+ remotes: {
218
+ carrinho: { url: 'https://cdn.empresa.com/carrinho/vitral.json', integrity: 'sha384-…' },
219
+ menu: { module: () => import('./menu') }, // remote local, sem rede
220
+ },
221
+ })
222
+ ```
223
+
224
+ ## Erros
225
+
226
+ Todo erro é um `RemoteError` com `code`, o nome do remote e `docs`, o link da [página do erro](https://github.com/joaogabriel15/vitral/tree/main/docs/erros) com causas e correção. Um remote que falha mostra o próprio erro e não derruba a página. `retry` monta só aquele remote de novo.
227
+
228
+ | `code` | Quando |
229
+ |---|---|
230
+ | `NOT_REGISTERED` | Nome não registrado em `configure` |
231
+ | `ORIGIN_BLOCKED` | URL fora de `allowedOrigins` |
232
+ | `TIMEOUT` | Não respondeu dentro de `timeout` |
233
+ | `INTEGRITY` | O hash do JS não confere |
234
+ | `INVALID_MODULE` | O arquivo não exporta `mount()` |
235
+ | `LOAD_FAILED` | Erro de rede ou HTTP |
236
+ | `MOUNT_FAILED` | O remote lançou erro ao montar |
237
+ | `RENDER_FAILED` | O remote quebrou depois de montado |
257
238
 
258
- - **Allowlist de origens**: nenhum código é importado de origem não permitida,
259
- nem mesmo se um manifesto apontar para fora.
260
- - **SRI**: quando há `integrity`, a lib baixa o JS, confere o hash e executa
261
- exatamente os bytes verificados (via `blob:`). Com CSP, inclua `blob:` em
262
- `script-src`.
263
- - **Sem `eval`**, sem iframes, sem globais além de um `Symbol.for('vitral.state.v1')`.
264
- - Shadow DOM isola CSS, não JS: um remote ainda roda com os privilégios da
265
- página. Para código não confiável de verdade, use iframe.
239
+ ## Performance
266
240
 
267
- ## Dependências compartilhadas (opcional)
241
+ Remote com `import()` também gera `dist/split/`: o navegador baixa só o que o remote usa ao abrir, e o resto chega no `import()`, conferido pelo mesmo hash. Hosts antigos continuam usando o arquivo único.
268
242
 
269
- Para não baixar React/Vue duas vezes, marque-os como `shared` no remote e
270
- declare um import map no host:
243
+ No CDN, sirva `vitral.json` com `Cache-Control: no-cache` e o resto (nome com hash) com `public, max-age=31536000, immutable`.
244
+
245
+
246
+ Assim que o manifesto chega, o JS e o CSS do remote baixam juntos. A montagem
247
+ espera os dois, para o remote não aparecer sem estilo. `configure()` abre a
248
+ conexão com a origem de cada remote (`<link rel="preconnect">`).
271
249
 
272
250
  ```js
273
- vitral({ entry: 'src/remote.jsx', shared: ['react', 'react-dom', 'react-dom/client'] })
251
+ import { preload } from 'vitral'
252
+ // Baixa JS e CSS sem montar, por exemplo no hover de um link
253
+ link.addEventListener('pointerenter', () => preload('checkout'), { once: true })
274
254
  ```
275
255
 
256
+ Nos componentes de host e no `<vitral-remote>`:
257
+
276
258
  ```html
277
- <script type="importmap">
278
- { "imports": { "react": "https://esm.sh/react@19.2.0", "react-dom/client": "https://esm.sh/react-dom@19.2.0/client" } }
279
- </script>
259
+ <!-- monta perto de aparecer na tela; reserve o espaço para a página não pular -->
260
+ <vitral-remote name="comentarios" loading="lazy" style="display:block;min-height:400px"></vitral-remote>
261
+ <!-- monta com o navegador ocioso, mas já baixa no hover -->
262
+ <vitral-remote name="checkout" loading="idle" prefetch="hover"></vitral-remote>
263
+ <!-- remote que aparece logo ao abrir a página -->
264
+ <vitral-remote name="cabecalho" priority="high"></vitral-remote>
265
+ ```
266
+
267
+ ```jsx
268
+ <Remote name="checkout" loading="lazy" prefetch="visible" keepAlive />
280
269
  ```
281
270
 
282
- ## Testes
271
+ | Opção | Valores | O que faz |
272
+ | --- | --- | --- |
273
+ | `loading` | `lazy`, `idle` | Monta a 200px de aparecer na tela, ou com o navegador ocioso |
274
+ | `prefetch` | `hover`, `visible`, `idle` | Chama `preload()` antes de montar |
275
+ | `priority` | `high` | `fetchpriority` alto no manifesto, no JS e no CSS |
276
+ | `keepAlive` (`keep-alive`) | `true` ou o máximo | Ao sair da página, o remote vai para um contêiner oculto (LRU, padrão 5); na volta só recebe as props novas |
277
+
278
+ Com keep-alive, o remote pode pausar timers e assinaturas:
279
+
280
+ ```js
281
+ mount(el, props, ctx) {
282
+ ctx.onDeactivate?.(() => clearInterval(timer))
283
+ ctx.onActivate?.(() => (timer = setInterval(tick, 1000)))
284
+ }
285
+ ```
286
+
287
+ ### Métricas
288
+
289
+ Cada etapa do carregamento vira um `performance.measure` com o nome
290
+ `vitral:<remote>:<etapa>`. As etapas são `manifest`, `js`, `css` e `mount`.
291
+ Elas aparecem na aba Performance do DevTools. Para mandar ao seu RUM:
292
+
293
+ ```js
294
+ configure({
295
+ onMetric: ({ name, stage, duration }) => rum.send(`vitral.${stage}`, duration, { remote: name }),
296
+ })
297
+ ```
298
+
299
+ O evento `vitral-ready` (e o `ready` do componente Vue) traz os tempos em
300
+ `detail.timings`, por exemplo `{ manifest: 12.3, js: 40.1, css: 8, mount: 2.4 }`.
301
+ Um remote local (`module`) só mede `css` e `mount`. Erros dentro do
302
+ `onMetric` são ignorados.
303
+
304
+ ## Dependências compartilhadas
305
+
306
+ Para não baixar React ou Vue mais de uma vez, liste-os em `dedupe` no remote:
307
+
308
+ ```js
309
+ vitral({ dedupe: ['react'] })
310
+ ```
311
+
312
+ O remote usa a cópia que já está na página quando a versão serve (padrão: `^` da versão instalada no remote) e baixa a própria só quando não serve. Os componentes de host em React oferecem o React do host sem configuração.
313
+
314
+ ### Mais formas de usar
315
+
316
+ Vários componentes por remote (`<Remote name="carrinho/MiniCarrinho" />`),
317
+ módulos sem UI (`loadModule('carrinho/api')`), deploy novo sem recarregar a
318
+ página (`watchUpdates()` e `refresh('carrinho')`) e app shell por rota
319
+ (`routes()` e `<vitral-outlet>`, em `vitral/routes`). Veja
320
+ [Exposes](https://joaogabriel15.github.io/vitral/aprender/exposes), [Atualização sem reload](https://joaogabriel15.github.io/vitral/aprender/atualizacoes) e [Rotas](https://joaogabriel15.github.io/vitral/aprender/rotas).
321
+
322
+ ## Segurança
323
+
324
+ - Só importa JS de origens permitidas, mesmo que o `vitral.json` aponte para outra. O `src` de um componente de host entra na allowlist; o atributo `src` do `<vitral-remote>` não entra quando há `allowedOrigins`.
325
+ - Com `integrity`, nada roda sem o hash conferir. A lib usa o primeiro caminho que o navegador garante: import map com `integrity` (Chrome 133+, Safari 18.4+), `<link rel="modulepreload" integrity>` (Firefox 115+) ou, nos outros, baixa, confere e executa exatamente os bytes conferidos via `blob:`. Aceita `sha256`, `sha384` e `sha512`, e vários hashes separados por espaço (vale o mais forte). O CSS do manifesto também é conferido.
326
+ - Com CSP, a lib copia o `nonce` de um `<script nonce>` da página para o import map. Sem nonce, cai para o `modulepreload`.
327
+ - O hash só protege contra CDN comprometido se vier de outro lugar: fixe `integrity` no host ou embuta o manifesto (`{ url, manifest }`).
328
+ - Sem `eval` e sem iframes.
329
+ - Shadow DOM isola o CSS, não o JS. Para código de terceiros em que você não confia, use iframe.
330
+
331
+ ## Testes do host
283
332
 
284
333
  ```ts
285
- // Teste do host sem subir nenhum remote
286
334
  import { mockRemote, failingRemote } from 'vitral/testing'
287
335
 
288
336
  const carrinho = mockRemote('carrinho', (el, props) => (el.textContent = `itens: ${props.itens}`))
289
- // …renderize o host…
337
+ // renderize o host
290
338
  expect(carrinho.lastProps).toEqual({ itens: 2 })
291
- failingRemote('recomendacoes') // para testar a tela de erro
339
+
340
+ failingRemote('recomendacoes') // testa a tela de erro
292
341
  ```
293
342
 
294
- No repositório (veja [CONTRIBUTING.md](CONTRIBUTING.md)):
343
+ ## Comparado com Module Federation e single-spa
295
344
 
296
- ```bash
297
- pnpm install
298
- pnpm check # build, tipos, publint/attw, tamanho e testes unitários
299
- pnpm test:e2e # builda tudo e roda o Playwright: 9 hosts × 8 remotes + demo
300
- pnpm examples # sobe os exemplos (hosts em :4201-:4207, remotes em :4100)
301
- ```
345
+ | | Module Federation / single-spa | vitral |
346
+ |---|---|---|
347
+ | Build do host | Config combinada com cada remote | Nenhuma. Só `configure()` |
348
+ | Versões de React/Vue | `shared` e singletons, conflitos de versão | Cada remote leva a sua. Compartilhar é opcional |
349
+ | Remote fora do ar | Pode derrubar a página | Só aquele remote mostra erro |
350
+ | CSS | Vaza entre apps | Escopo automático ou Shadow DOM |
351
+ | Deploy novo | `ChunkLoadError` com cache velho | Arquivos com hash; só o `vitral.json` revalida |
352
+ | Conceitos | Containers, exposes, lifecycles, root config | `mount(el, props, ctx)` → `{ update, unmount }` |
353
+
354
+ ## Links
302
355
 
303
- O CI roda tudo isso em Node 20, 22 e 24 a cada push e PR. Versões novas saem
304
- pelo Changesets: `pnpm changeset`, merge do PR "Versionar pacote" e o GitHub
305
- Actions publica no npm com proveniência.
306
-
307
- ## Exemplos e demo
308
-
309
- - `examples/remotes/*`: o mesmo cartão em React, Vue, Svelte, Solid, Preact,
310
- Angular, Lit e TypeScript puro.
311
- - `examples/hosts/*`: hosts em JS puro (:4201), React (:4202), Vue (:4203),
312
- Svelte (:4204), Solid (:4205), Preact (:4206), Angular (:4207), Next (:4208,
313
- `pnpm --dir examples/hosts/next start`) e Nuxt (:4209,
314
- `pnpm --dir examples/hosts/nuxt start`), cada um carregando os 8 remotes.
315
- - `demo/`: catálogo Vue + carrinho React com Tailwind (`pnpm demo`, em
316
- http://localhost:5173 e :5174).
317
-
318
- ## Estado
319
-
320
- Protótipo 0.1. Testado (32 testes unitários e 43 e2e): carregamento via
321
- manifesto com SRI, Shadow DOM, props reativas sem remontar, props que mudam
322
- durante o download, desmontar/remontar, StrictMode do React, erros de download
323
- e de render, eventos entre frameworks, Tailwind v4 com e sem Shadow DOM, escopo
324
- de CSS e SSR em Next e Nuxt. O que ainda falta está em
325
- [docs/PROBLEMAS.md](docs/PROBLEMAS.md#ainda-não-resolvidos-próximos-passos-sugeridos).
356
+ - [Exemplos](https://github.com/joaogabriel15/vitral/tree/main/examples): o mesmo remote em 8 frameworks e 9 hosts.
357
+ - [Como contribuir](https://github.com/joaogabriel15/vitral/blob/main/CONTRIBUTING.md)
358
+ - Licença [MIT](https://github.com/joaogabriel15/vitral/blob/main/LICENSE)
package/dist/angular.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { InjectionToken, Type, ApplicationConfig, Provider, EnvironmentProviders } from '@angular/core';
2
- import { RemoteContext, Props, RemoteModule, VitralConfig } from './index.js';
3
- export { events, navigate } from './index.js';
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-BV-hUrms.js';
3
+ export { e as events } from './events-DUdjhfps.js';
4
+ export { n as navigate } from './navigate-BRG1WJUM.js';
4
5
 
5
6
  /**
6
7
  * Adaptador Angular (standalone, zoneless ou com zone.js).
@@ -18,10 +19,20 @@ declare const VITRAL_CONTEXT: InjectionToken<RemoteContext>;
18
19
  declare function angularRemote<P extends Props>(component: Type<unknown>, config?: Partial<ApplicationConfig>): RemoteModule<P>;
19
20
  /** Dentro de um remote Angular: `const vitral = injectVitral()`. */
20
21
  declare function injectVitral(): RemoteContext | null;
22
+ /**
23
+ * Signal de uma chave compartilhada (`provide`/`shared`). Com seletor, só
24
+ * muda quando o resultado do seletor muda (`Object.is`). Chame num contexto de
25
+ * injeção (construtor, inicializador de campo); a assinatura acaba no destroy.
26
+ *
27
+ * usuario = injectShared('usuario')
28
+ * nome = injectShared('usuario', (u) => u?.nome)
29
+ */
30
+ declare function injectShared<K extends keyof Shared, S = Shared[K] | undefined>(key: K, select?: (value: Shared[K] | undefined) => S): Signal<S>;
31
+ declare function injectShared<T = unknown, S = T | undefined>(key: string, select?: (value: T | undefined) => S): Signal<S>;
21
32
  /**
22
33
  * Host Angular: configura o vitral e registra `<vitral-remote>`. Use junto com
23
34
  * `CUSTOM_ELEMENTS_SCHEMA` no componente que renderiza o remote.
24
35
  */
25
36
  declare function provideVitral(config?: VitralConfig): (Provider | EnvironmentProviders)[];
26
37
 
27
- export { VITRAL_CONTEXT, angularRemote, injectVitral, provideVitral };
38
+ export { VITRAL_CONTEXT, angularRemote, injectShared, injectVitral, provideVitral };