vitral 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +357 -2
- package/dist/angular.d.ts +38 -0
- package/dist/angular.js +90 -0
- package/dist/auto.d.ts +2 -0
- package/dist/auto.js +12 -0
- package/dist/chunk-2XF5BKV3.js +190 -0
- package/dist/chunk-6Y3VMWHH.js +181 -0
- package/dist/chunk-DXORSF4X.js +42 -0
- package/dist/chunk-GNPFWISJ.js +80 -0
- package/dist/chunk-HP6QWU6K.js +51 -0
- package/dist/chunk-NCONW6WS.js +606 -0
- package/dist/chunk-PX7PBCCT.js +89 -0
- package/dist/chunk-S5MZT5IT.js +20 -0
- package/dist/chunk-SFL6LTP3.js +56 -0
- package/dist/chunk-UTYAX6MU.js +152 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +88 -0
- package/dist/dedupe-IWFIXRWG.js +81 -0
- package/dist/devtools.d.ts +12 -0
- package/dist/devtools.js +86 -0
- package/dist/element.d.ts +88 -0
- package/dist/element.js +18 -0
- package/dist/errors-KKB8FGCz.d.ts +11 -0
- package/dist/events-DUdjhfps.d.ts +6 -0
- package/dist/expose-53BEJB34.js +14 -0
- package/dist/host-slot-Cz6EiGsB.d.ts +6 -0
- package/dist/iframe.d.ts +28 -0
- package/dist/iframe.js +236 -0
- package/dist/index.d.ts +186 -0
- package/dist/index.js +65 -0
- package/dist/navigate-BRG1WJUM.d.ts +9 -0
- package/dist/overrides-NZAIUGK5.js +15 -0
- package/dist/preact.d.ts +45 -0
- package/dist/preact.js +71 -0
- package/dist/react.d.ts +63 -0
- package/dist/react.js +167 -0
- package/dist/routes.d.ts +56 -0
- package/dist/routes.js +143 -0
- package/dist/shadow-MYRJQLLY.js +72 -0
- package/dist/slot-pB276oPt.d.ts +36 -0
- package/dist/solid.d.ts +47 -0
- package/dist/solid.js +83 -0
- package/dist/sri-BVKZNX5Q.js +114 -0
- package/dist/state-BDdCwc21.d.ts +4 -0
- package/dist/svelte.d.ts +43 -0
- package/dist/svelte.js +42 -0
- package/dist/testing.d.ts +28 -0
- package/dist/testing.js +52 -0
- package/dist/types-BV-hUrms.d.ts +265 -0
- package/dist/vite.d.ts +78 -0
- package/dist/vite.js +786 -0
- package/dist/vue.d.ts +133 -0
- package/dist/vue.js +148 -0
- package/package.json +331 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 João (joaogabriel15)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,358 @@
|
|
|
1
|
-
|
|
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
|
+
|
|
5
|
+
<p align="center"><strong>Feito de partes. Visto como um todo.</strong></p>
|
|
6
|
+
|
|
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>
|
|
13
|
+
|
|
14
|
+
Junte micro-frontends de qualquer framework numa página só, sem configuração de build no host.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install vitral
|
|
18
|
+
```
|
|
19
|
+
|
|
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.
|
|
23
|
+
|
|
24
|
+
## Começo rápido
|
|
25
|
+
|
|
26
|
+
Para gerar host e remote prontos: `npm create vitral@latest`.
|
|
27
|
+
|
|
28
|
+
**1. No remote**, exporte o componente e use o plugin:
|
|
29
|
+
|
|
30
|
+
```jsx
|
|
31
|
+
// src/App.jsx (ou .vue, .svelte, .tsx…)
|
|
32
|
+
export default function Carrinho({ userId }) { … }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
// vite.config.js
|
|
37
|
+
import react from '@vitejs/plugin-react'
|
|
38
|
+
import vitral from 'vitral/vite'
|
|
39
|
+
|
|
40
|
+
export default { plugins: [react(), vitral()] }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
O plugin descobre o framework e embrulha o `export default` com o adaptador certo. A entrada padrão é `src/remote.*` ou `src/App.*`.
|
|
44
|
+
|
|
45
|
+
`vite build` gera o JS, o CSS e um `vitral.json`. Publique a pasta `dist` em qualquer CDN com CORS.
|
|
46
|
+
|
|
47
|
+
**2. No host**, registre a URL e use o componente:
|
|
48
|
+
|
|
49
|
+
```jsx
|
|
50
|
+
import { configure } from 'vitral'
|
|
51
|
+
import { Remote } from 'vitral/react'
|
|
52
|
+
|
|
53
|
+
configure({
|
|
54
|
+
remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export function App() {
|
|
58
|
+
return (
|
|
59
|
+
<Remote
|
|
60
|
+
name="carrinho"
|
|
61
|
+
props={{ userId: 42 }}
|
|
62
|
+
fallback={<p>Carregando…</p>}
|
|
63
|
+
error={(erro, tentarDeNovo) => <button onClick={tentarDeNovo}>Tentar de novo</button>}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
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.
|
|
96
|
+
|
|
97
|
+
### Host em Vue
|
|
98
|
+
|
|
99
|
+
```vue
|
|
100
|
+
<script setup>
|
|
101
|
+
import { VitralRemote } from 'vitral/vue'
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<VitralRemote name="carrinho" :props="{ userId: 42 }">
|
|
106
|
+
<template #fallback>Carregando…</template>
|
|
107
|
+
<template #error="{ retry }"><button @click="retry">Tentar de novo</button></template>
|
|
108
|
+
</VitralRemote>
|
|
109
|
+
</template>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Host em qualquer outro lugar
|
|
113
|
+
|
|
114
|
+
`<vitral-remote>` funciona em HTML puro, Angular, Svelte, Astro ou HTML gerado no servidor:
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<script type="module">
|
|
118
|
+
import { configure } from 'vitral'
|
|
119
|
+
import { defineVitralElement } from 'vitral/element'
|
|
120
|
+
|
|
121
|
+
configure({ remotes: { carrinho: 'https://cdn.empresa.com/carrinho/vitral.json' } })
|
|
122
|
+
defineVitralElement()
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<vitral-remote name="carrinho" props='{"userId": 42}'>
|
|
126
|
+
<p slot="fallback">Carregando…</p>
|
|
127
|
+
<p slot="error">Indisponível</p>
|
|
128
|
+
</vitral-remote>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Remote sem framework
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { component, html } from 'vitral'
|
|
135
|
+
|
|
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
|
+
`,
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
O que entra em `${}` é escapado. Mudar `state` ou as props atualiza a tela.
|
|
146
|
+
|
|
147
|
+
## Desenvolvimento
|
|
148
|
+
|
|
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.
|
|
152
|
+
|
|
153
|
+
## Comunicação entre remotes
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import { events } from 'vitral'
|
|
157
|
+
|
|
158
|
+
events.emit('carrinho:total', 120)
|
|
159
|
+
const parar = events.on('carrinho:total', (total) => console.log(total))
|
|
160
|
+
events.last('carrinho:total') // 120
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Todos os remotes usam o mesmo barramento. O que um remote assina é cancelado quando ele desmonta.
|
|
164
|
+
|
|
165
|
+
Para estado comum (usuário, tema, carrinho), use o contexto compartilhado:
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
import { provide, shared } from 'vitral'
|
|
169
|
+
|
|
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
|
+
```
|
|
175
|
+
|
|
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
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
configure({
|
|
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'
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Cada remote também aceita um objeto:
|
|
214
|
+
|
|
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 |
|
|
238
|
+
|
|
239
|
+
## Performance
|
|
240
|
+
|
|
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.
|
|
242
|
+
|
|
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">`).
|
|
249
|
+
|
|
250
|
+
```js
|
|
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 })
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Nos componentes de host e no `<vitral-remote>`:
|
|
257
|
+
|
|
258
|
+
```html
|
|
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 />
|
|
269
|
+
```
|
|
270
|
+
|
|
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
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import { mockRemote, failingRemote } from 'vitral/testing'
|
|
335
|
+
|
|
336
|
+
const carrinho = mockRemote('carrinho', (el, props) => (el.textContent = `itens: ${props.itens}`))
|
|
337
|
+
// renderize o host
|
|
338
|
+
expect(carrinho.lastProps).toEqual({ itens: 2 })
|
|
339
|
+
|
|
340
|
+
failingRemote('recomendacoes') // testa a tela de erro
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Comparado com Module Federation e single-spa
|
|
344
|
+
|
|
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
|
|
355
|
+
|
|
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)
|
|
@@ -0,0 +1,38 @@
|
|
|
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';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Adaptador Angular (standalone, zoneless ou com zone.js).
|
|
8
|
+
*
|
|
9
|
+
* Remote: export default angularRemote(AppComponent, { providers: [provideHttpClient()] })
|
|
10
|
+
* Props viram inputs (`input()` ou `@Input()`); o contexto vem de `inject(VITRAL_CONTEXT)`.
|
|
11
|
+
*
|
|
12
|
+
* Host: providers: [provideVitral({ remotes: '/remotes.json' })]
|
|
13
|
+
* schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
14
|
+
* <vitral-remote name="carrinho" [props]="{ userId }" isolate="shadow"></vitral-remote>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
declare const VITRAL_CONTEXT: InjectionToken<RemoteContext>;
|
|
18
|
+
/** Transforma um componente standalone em um remote. */
|
|
19
|
+
declare function angularRemote<P extends Props>(component: Type<unknown>, config?: Partial<ApplicationConfig>): RemoteModule<P>;
|
|
20
|
+
/** Dentro de um remote Angular: `const vitral = injectVitral()`. */
|
|
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>;
|
|
32
|
+
/**
|
|
33
|
+
* Host Angular: configura o vitral e registra `<vitral-remote>`. Use junto com
|
|
34
|
+
* `CUSTOM_ELEMENTS_SCHEMA` no componente que renderiza o remote.
|
|
35
|
+
*/
|
|
36
|
+
declare function provideVitral(config?: VitralConfig): (Provider | EnvironmentProviders)[];
|
|
37
|
+
|
|
38
|
+
export { VITRAL_CONTEXT, angularRemote, injectShared, injectVitral, provideVitral };
|
package/dist/angular.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineVitralElement
|
|
3
|
+
} from "./chunk-6Y3VMWHH.js";
|
|
4
|
+
import "./chunk-UTYAX6MU.js";
|
|
5
|
+
import {
|
|
6
|
+
defineRemote,
|
|
7
|
+
events,
|
|
8
|
+
memoSelect,
|
|
9
|
+
navigate,
|
|
10
|
+
same,
|
|
11
|
+
shared
|
|
12
|
+
} from "./chunk-NCONW6WS.js";
|
|
13
|
+
import {
|
|
14
|
+
configure
|
|
15
|
+
} from "./chunk-2XF5BKV3.js";
|
|
16
|
+
import "./chunk-SFL6LTP3.js";
|
|
17
|
+
import "./chunk-PX7PBCCT.js";
|
|
18
|
+
import "./chunk-GNPFWISJ.js";
|
|
19
|
+
|
|
20
|
+
// src/angular.ts
|
|
21
|
+
import {
|
|
22
|
+
DestroyRef,
|
|
23
|
+
InjectionToken,
|
|
24
|
+
createComponent,
|
|
25
|
+
inject,
|
|
26
|
+
provideAppInitializer,
|
|
27
|
+
provideZonelessChangeDetection,
|
|
28
|
+
reflectComponentType,
|
|
29
|
+
signal
|
|
30
|
+
} from "@angular/core";
|
|
31
|
+
import { createApplication } from "@angular/platform-browser";
|
|
32
|
+
var VITRAL_CONTEXT = new InjectionToken("vitral.context");
|
|
33
|
+
function angularRemote(component, config = {}) {
|
|
34
|
+
const inputs = new Set((reflectComponentType(component)?.inputs ?? []).map((i) => i.templateName));
|
|
35
|
+
return defineRemote({
|
|
36
|
+
async mount(el, props, ctx) {
|
|
37
|
+
const app = await createApplication({
|
|
38
|
+
providers: [provideZonelessChangeDetection(), { provide: VITRAL_CONTEXT, useValue: ctx }, ...config.providers ?? []]
|
|
39
|
+
});
|
|
40
|
+
const hostElement = document.createElement("div");
|
|
41
|
+
el.appendChild(hostElement);
|
|
42
|
+
const ref = createComponent(component, { environmentInjector: app.injector, hostElement });
|
|
43
|
+
const apply = (p) => {
|
|
44
|
+
for (const [k, v] of Object.entries(p)) if (inputs.has(k)) ref.setInput(k, v);
|
|
45
|
+
};
|
|
46
|
+
apply(props);
|
|
47
|
+
app.attachView(ref.hostView);
|
|
48
|
+
ref.changeDetectorRef.detectChanges();
|
|
49
|
+
return {
|
|
50
|
+
update(next) {
|
|
51
|
+
apply(next);
|
|
52
|
+
app.tick();
|
|
53
|
+
},
|
|
54
|
+
unmount() {
|
|
55
|
+
app.destroy();
|
|
56
|
+
hostElement.remove();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function injectVitral() {
|
|
63
|
+
return inject(VITRAL_CONTEXT, { optional: true });
|
|
64
|
+
}
|
|
65
|
+
function injectShared(key, select = same) {
|
|
66
|
+
const destroy = inject(DestroyRef);
|
|
67
|
+
const handle = shared(key);
|
|
68
|
+
const pick = memoSelect();
|
|
69
|
+
const value = signal(pick(handle.get(), select), { equal: Object.is });
|
|
70
|
+
destroy.onDestroy(handle.subscribe((v) => value.set(pick(v, select))));
|
|
71
|
+
return value.asReadonly();
|
|
72
|
+
}
|
|
73
|
+
function provideVitral(config = {}) {
|
|
74
|
+
return [
|
|
75
|
+
provideAppInitializer(() => {
|
|
76
|
+
if (typeof window === "undefined") return;
|
|
77
|
+
configure(config);
|
|
78
|
+
defineVitralElement();
|
|
79
|
+
})
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
VITRAL_CONTEXT,
|
|
84
|
+
angularRemote,
|
|
85
|
+
events,
|
|
86
|
+
injectShared,
|
|
87
|
+
injectVitral,
|
|
88
|
+
navigate,
|
|
89
|
+
provideVitral
|
|
90
|
+
};
|
package/dist/auto.d.ts
ADDED
package/dist/auto.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineVitralElement
|
|
3
|
+
} from "./chunk-6Y3VMWHH.js";
|
|
4
|
+
import "./chunk-UTYAX6MU.js";
|
|
5
|
+
import "./chunk-NCONW6WS.js";
|
|
6
|
+
import "./chunk-2XF5BKV3.js";
|
|
7
|
+
import "./chunk-SFL6LTP3.js";
|
|
8
|
+
import "./chunk-PX7PBCCT.js";
|
|
9
|
+
import "./chunk-GNPFWISJ.js";
|
|
10
|
+
|
|
11
|
+
// src/auto.ts
|
|
12
|
+
defineVitralElement();
|