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.
- package/README.md +250 -217
- package/dist/angular.d.ts +15 -4
- package/dist/angular.js +25 -5
- 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 +60 -2
- package/dist/element.js +9 -2
- 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 +142 -147
- package/dist/index.js +39 -10
- package/dist/navigate-BRG1WJUM.d.ts +9 -0
- package/dist/overrides-NZAIUGK5.js +15 -0
- package/dist/preact.d.ts +30 -7
- package/dist/preact.js +37 -9
- package/dist/react.d.ts +38 -7
- package/dist/react.js +92 -42
- 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 +29 -6
- package/dist/solid.js +31 -8
- package/dist/sri-BVKZNX5Q.js +114 -0
- package/dist/state-BDdCwc21.d.ts +4 -0
- package/dist/svelte.d.ts +19 -3
- package/dist/svelte.js +10 -4
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +8 -3
- package/dist/types-BV-hUrms.d.ts +265 -0
- package/dist/vite.d.ts +58 -4
- package/dist/vite.js +714 -24
- package/dist/vue.d.ts +79 -7
- package/dist/vue.js +69 -46
- package/package.json +144 -8
- package/dist/chunk-IZK6ITIJ.js +0 -96
- 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
##
|
|
24
|
+
## Começo rápido
|
|
65
25
|
|
|
66
|
-
|
|
26
|
+
Para gerar host e remote prontos: `npm create vitral@latest`.
|
|
67
27
|
|
|
68
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
export default
|
|
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
|
-
|
|
40
|
+
export default { plugins: [react(), vitral()] }
|
|
41
|
+
```
|
|
122
42
|
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
51
|
+
import { Remote } from 'vitral/react'
|
|
138
52
|
|
|
139
|
-
configure({
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
###
|
|
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 }"
|
|
105
|
+
<VitralRemote name="carrinho" :props="{ userId: 42 }">
|
|
168
106
|
<template #fallback>Carregando…</template>
|
|
169
|
-
<template #error="{
|
|
107
|
+
<template #error="{ retry }"><button @click="retry">Tentar de novo</button></template>
|
|
170
108
|
</VitralRemote>
|
|
171
109
|
</template>
|
|
172
110
|
```
|
|
173
111
|
|
|
174
|
-
|
|
175
|
-
fallback no SSR e monta o remote no cliente.
|
|
112
|
+
### Host em qualquer outro lugar
|
|
176
113
|
|
|
177
|
-
|
|
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
|
-
|
|
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}'
|
|
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
|
-
|
|
196
|
-
`vitral-ready` / `vitral-error` avisam o estado.
|
|
197
|
-
|
|
198
|
-
**Angular:**
|
|
131
|
+
### Remote sem framework
|
|
199
132
|
|
|
200
133
|
```ts
|
|
201
|
-
|
|
134
|
+
import { component, html } from 'vitral'
|
|
202
135
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
145
|
+
O que entra em `${}` é escapado. Mudar `state` ou as props atualiza a tela.
|
|
146
|
+
|
|
147
|
+
## Desenvolvimento
|
|
211
148
|
|
|
212
|
-
**
|
|
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
|
-
|
|
153
|
+
## Comunicação entre remotes
|
|
215
154
|
|
|
216
155
|
```js
|
|
217
156
|
import { events } from 'vitral'
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
events.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
167
|
+
```js
|
|
168
|
+
import { provide, shared } from 'vitral'
|
|
232
169
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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:
|
|
243
|
-
allowedOrigins: ['https://cdn.empresa.com'], // padrão: página
|
|
244
|
-
timeout: 15000,
|
|
245
|
-
retries: 1,
|
|
246
|
-
isolate: 'none',
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
337
|
+
// renderize o host
|
|
290
338
|
expect(carrinho.lastProps).toEqual({ itens: 2 })
|
|
291
|
-
|
|
339
|
+
|
|
340
|
+
failingRemote('recomendacoes') // testa a tela de erro
|
|
292
341
|
```
|
|
293
342
|
|
|
294
|
-
|
|
343
|
+
## Comparado com Module Federation e single-spa
|
|
295
344
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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 './
|
|
3
|
-
export { events
|
|
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 };
|