vitral 0.0.0-stage → 0.1.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 +324 -2
- package/dist/angular.d.ts +27 -0
- package/dist/angular.js +70 -0
- package/dist/chunk-IZK6ITIJ.js +96 -0
- package/dist/chunk-TTL52BF2.js +547 -0
- package/dist/element.d.ts +30 -0
- package/dist/element.js +11 -0
- package/dist/index.d.ts +191 -0
- package/dist/index.js +36 -0
- package/dist/preact.d.ts +22 -0
- package/dist/preact.js +43 -0
- package/dist/react.d.ts +32 -0
- package/dist/react.js +117 -0
- package/dist/solid.d.ts +24 -0
- package/dist/solid.js +60 -0
- package/dist/svelte.d.ts +27 -0
- package/dist/svelte.js +36 -0
- package/dist/testing.d.ts +28 -0
- package/dist/testing.js +47 -0
- package/dist/vite.d.ts +24 -0
- package/dist/vite.js +96 -0
- package/dist/vue.d.ts +61 -0
- package/dist/vue.js +125 -0
- package/package.json +195 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote em JS/TS puro sem escrever mount/update/unmount na mão.
|
|
3
|
+
*
|
|
4
|
+
* export default component<{ mensagem?: string }>({
|
|
5
|
+
* state: { cliques: 0 },
|
|
6
|
+
* render: ({ mensagem }, state, ctx) => html`
|
|
7
|
+
* <p>${mensagem}</p>
|
|
8
|
+
* <button onclick=${() => state.cliques++}>ping ${state.cliques}</button>
|
|
9
|
+
* `,
|
|
10
|
+
* })
|
|
11
|
+
*
|
|
12
|
+
* Valores interpolados são escapados (sem XSS). Mudar `state` ou receber
|
|
13
|
+
* props novas renderiza de novo. Funções em `on<evento>=${fn}` viram listeners.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
declare const TEMPLATE: unique symbol;
|
|
17
|
+
interface Template {
|
|
18
|
+
readonly [TEMPLATE]: true;
|
|
19
|
+
readonly strings: readonly string[];
|
|
20
|
+
readonly values: readonly unknown[];
|
|
21
|
+
}
|
|
22
|
+
/** Template HTML seguro: tudo que é interpolado é escapado, menos outro `html`. */
|
|
23
|
+
declare function html(strings: TemplateStringsArray, ...values: unknown[]): Template;
|
|
24
|
+
/** Marca uma string como HTML confiável (não é escapada). Use com cuidado. */
|
|
25
|
+
declare function unsafeHTML(markup: string): Template;
|
|
26
|
+
/** Renderiza `view` dentro de `el`, ligando os listeners declarados com `on<evento>=${fn}`. */
|
|
27
|
+
declare function render(view: Template | string | Node, el: Element): void;
|
|
28
|
+
type View = Template | string | Node | null | undefined;
|
|
29
|
+
type RenderFn<P extends Props, S extends object> = (props: P, state: S, ctx: RemoteContext) => View;
|
|
30
|
+
interface ComponentOptions<P extends Props, S extends object> {
|
|
31
|
+
/** Estado local. Alterar qualquer campo renderiza de novo. */
|
|
32
|
+
state?: S | (() => S);
|
|
33
|
+
render: RenderFn<P, S>;
|
|
34
|
+
/** Roda depois do primeiro render. Pode devolver uma função de limpeza. */
|
|
35
|
+
mounted?(el: HTMLElement, state: S, ctx: RemoteContext): void | (() => void);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Cria um remote a partir de uma função que devolve HTML. Aceita só a função
|
|
39
|
+
* (`component((props) => html\`...\`)`) ou `{ state, render, mounted }`.
|
|
40
|
+
*/
|
|
41
|
+
declare function component<P extends Props = Props, S extends object = Record<string, never>>(options: RenderFn<P, S> | ComponentOptions<P, S>): RemoteModule<P>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* vitral: micro-frontends com um único contrato.
|
|
45
|
+
*
|
|
46
|
+
* Um remote é um módulo ES que exporta `mount(el, props, ctx)` e devolve
|
|
47
|
+
* `{ update?, unmount }`. O host só precisa saber a URL. Nada de config
|
|
48
|
+
* compartilhada em tempo de build, nada de container global de dependências.
|
|
49
|
+
*/
|
|
50
|
+
type Props = Record<string, unknown>;
|
|
51
|
+
interface RemoteContext {
|
|
52
|
+
/** Nome com que o host registrou este remote. */
|
|
53
|
+
name: string;
|
|
54
|
+
/** URL base de onde o remote foi carregado (útil para assets). */
|
|
55
|
+
baseUrl: string;
|
|
56
|
+
/**
|
|
57
|
+
* Barramento de eventos compartilhado entre host e todos os remotes. As
|
|
58
|
+
* assinaturas feitas por aqui são canceladas sozinhas no unmount.
|
|
59
|
+
*/
|
|
60
|
+
events: EventBus;
|
|
61
|
+
/** Shadow root quando o host pediu isolamento, senão `null`. */
|
|
62
|
+
shadowRoot: ShadowRoot | null;
|
|
63
|
+
/** Avisa o host de um erro em tempo de execução (os adaptadores já chamam). */
|
|
64
|
+
reportError(error: unknown): void;
|
|
65
|
+
/** Navega a página inteira (host e todos os roteadores) para `url`. */
|
|
66
|
+
navigate(url: string, options?: {
|
|
67
|
+
replace?: boolean;
|
|
68
|
+
}): void;
|
|
69
|
+
}
|
|
70
|
+
interface MountedRemote<P extends Props = Props> {
|
|
71
|
+
update?(props: P): void;
|
|
72
|
+
unmount(): void;
|
|
73
|
+
}
|
|
74
|
+
interface RemoteModule<P extends Props = Props> {
|
|
75
|
+
mount(el: HTMLElement, props: P, ctx: RemoteContext): MountedRemote<P> | Promise<MountedRemote<P>>;
|
|
76
|
+
}
|
|
77
|
+
interface RemoteEntry {
|
|
78
|
+
/** URL do módulo JS do remote, ou do `vitral.json` gerado pelo plugin. */
|
|
79
|
+
url?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Remote local, sem rede: o próprio módulo ou uma função que o importa
|
|
82
|
+
* (ex.: `() => import('./carrinho')`). Útil em monorepos e testes.
|
|
83
|
+
*/
|
|
84
|
+
module?: RemoteModule<any> | (() => Promise<RemoteModule<any> | {
|
|
85
|
+
default: RemoteModule<any>;
|
|
86
|
+
}>);
|
|
87
|
+
/** Hash SRI (`sha256-…`, `sha384-…`, `sha512-…`) do JS. Verificado antes de executar. */
|
|
88
|
+
integrity?: string;
|
|
89
|
+
/** CSS extra para carregar junto. */
|
|
90
|
+
css?: string[];
|
|
91
|
+
/** Isolamento padrão para este remote. */
|
|
92
|
+
isolate?: Isolation;
|
|
93
|
+
}
|
|
94
|
+
type Isolation = 'shadow' | 'none';
|
|
95
|
+
interface VitralConfig {
|
|
96
|
+
/** Mapa nome → URL (ou entrada completa), ou a URL de um JSON com esse mapa. */
|
|
97
|
+
remotes?: Record<string, string | RemoteEntry> | string;
|
|
98
|
+
/**
|
|
99
|
+
* Origens de onde é permitido carregar código. Padrão: a própria origem da
|
|
100
|
+
* página mais as origens das URLs registradas. Use `['*']` para liberar tudo.
|
|
101
|
+
*/
|
|
102
|
+
allowedOrigins?: string[];
|
|
103
|
+
/** Tempo máximo para baixar um remote, em ms. Padrão 15000. */
|
|
104
|
+
timeout?: number;
|
|
105
|
+
/** Tentativas extras se o download falhar. Padrão 1. */
|
|
106
|
+
retries?: number;
|
|
107
|
+
/** Isolamento padrão para todos os remotes. Padrão `'none'`. */
|
|
108
|
+
isolate?: Isolation;
|
|
109
|
+
}
|
|
110
|
+
type RemoteErrorCode = 'NOT_REGISTERED' | 'ORIGIN_BLOCKED' | 'TIMEOUT' | 'INTEGRITY' | 'INVALID_MODULE' | 'LOAD_FAILED' | 'MOUNT_FAILED' | 'RENDER_FAILED';
|
|
111
|
+
declare class RemoteError extends Error {
|
|
112
|
+
code: RemoteErrorCode;
|
|
113
|
+
remote: string;
|
|
114
|
+
cause?: unknown | undefined;
|
|
115
|
+
constructor(code: RemoteErrorCode, remote: string, message: string, cause?: unknown | undefined);
|
|
116
|
+
}
|
|
117
|
+
/** Formato do `vitral.json` emitido pelo plugin de build. */
|
|
118
|
+
interface RemoteManifest {
|
|
119
|
+
name: string;
|
|
120
|
+
version?: string;
|
|
121
|
+
entry: string;
|
|
122
|
+
css?: string[];
|
|
123
|
+
integrity?: string;
|
|
124
|
+
/** Escopo usado pelo plugin para prefixar o CSS (`[data-vitral-scope]`). */
|
|
125
|
+
scope?: string;
|
|
126
|
+
}
|
|
127
|
+
/** Configura a lib. Pode ser chamada mais de uma vez; as opções são mescladas. */
|
|
128
|
+
declare function configure(config: VitralConfig): void;
|
|
129
|
+
/** Registra (ou substitui) um remote. */
|
|
130
|
+
declare function register(name: string, entry: string | RemoteEntry): void;
|
|
131
|
+
/** Baixa (uma vez só) e devolve o módulo de um remote. */
|
|
132
|
+
declare function loadRemote<P extends Props = Props>(name: string): Promise<RemoteModule<P>>;
|
|
133
|
+
/** Começa a baixar um remote sem montar (ex.: no hover de um link). */
|
|
134
|
+
declare function preload(name: string): void;
|
|
135
|
+
interface MountOptions {
|
|
136
|
+
isolate?: Isolation;
|
|
137
|
+
/** Chamado quando o remote quebra depois de montado (erro de render, handler etc.). */
|
|
138
|
+
onError?: (error: RemoteError) => void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Monta um remote dentro de `target`. Devolve um handle com `update` e
|
|
142
|
+
* `unmount` que funciona igual para qualquer framework.
|
|
143
|
+
*/
|
|
144
|
+
declare function mountRemote<P extends Props = Props>(target: HTMLElement, name: string, props: P, options?: MountOptions): Promise<MountedRemote<P>>;
|
|
145
|
+
type SlotStatus = 'loading' | 'ready' | 'error';
|
|
146
|
+
interface SlotOptions<P extends Props = Props> {
|
|
147
|
+
name: string;
|
|
148
|
+
props?: P;
|
|
149
|
+
isolate?: Isolation;
|
|
150
|
+
/** Chamado a cada mudança de estado (para mostrar fallback / erro). */
|
|
151
|
+
onStatus?: (status: SlotStatus, error: RemoteError | null) => void;
|
|
152
|
+
}
|
|
153
|
+
interface RemoteSlot<P extends Props = Props> {
|
|
154
|
+
/** Atualiza as props sem remontar. */
|
|
155
|
+
update(props: P): void;
|
|
156
|
+
/** Troca de remote ou de isolamento (remonta). */
|
|
157
|
+
set(options: Partial<Pick<SlotOptions<P>, 'name' | 'isolate'>>): void;
|
|
158
|
+
/** Remonta do zero (botão "tentar de novo"). */
|
|
159
|
+
retry(): void;
|
|
160
|
+
destroy(): void;
|
|
161
|
+
readonly status: SlotStatus;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Cuida do ciclo de vida de um remote dentro de `target`: monta, atualiza,
|
|
165
|
+
* remonta ao trocar de nome, ignora respostas atrasadas e limpa tudo no fim.
|
|
166
|
+
* Os adaptadores de host (Web Component, Svelte, Solid, Preact…) usam isto.
|
|
167
|
+
*/
|
|
168
|
+
declare function createSlot<P extends Props = Props>(target: HTMLElement, options: SlotOptions<P>): RemoteSlot<P>;
|
|
169
|
+
/** Declara um remote. É só uma função identidade com tipos. */
|
|
170
|
+
declare function defineRemote<P extends Props = Props>(remote: RemoteModule<P>): RemoteModule<P>;
|
|
171
|
+
type Handler<T = unknown> = (payload: T) => void;
|
|
172
|
+
interface EventBus {
|
|
173
|
+
emit<T = unknown>(type: string, payload?: T): void;
|
|
174
|
+
on<T = unknown>(type: string, handler: Handler<T>): () => void;
|
|
175
|
+
/** Último valor emitido em `type` (útil para estado como "usuário logado"). */
|
|
176
|
+
last<T = unknown>(type: string): T | undefined;
|
|
177
|
+
}
|
|
178
|
+
declare function createEventBus(): EventBus;
|
|
179
|
+
/** Barramento global. É o mesmo objeto que os remotes recebem em `ctx.events`. */
|
|
180
|
+
declare const events: EventBus;
|
|
181
|
+
/**
|
|
182
|
+
* Navega mantendo todos os roteadores em sincronia (React Router, Vue Router,
|
|
183
|
+
* Next, Nuxt…): atualiza o histórico e dispara `popstate`, que todos escutam.
|
|
184
|
+
*/
|
|
185
|
+
declare function navigate(url: string, options?: {
|
|
186
|
+
replace?: boolean;
|
|
187
|
+
}): void;
|
|
188
|
+
/** Só para testes: limpa todo o estado global. */
|
|
189
|
+
declare function __reset(): void;
|
|
190
|
+
|
|
191
|
+
export { type ComponentOptions, type EventBus, type Handler, type Isolation, type MountOptions, type MountedRemote, type Props, type RemoteContext, type RemoteEntry, RemoteError, type RemoteErrorCode, type RemoteManifest, type RemoteModule, type RemoteSlot, type RenderFn, type SlotOptions, type SlotStatus, type Template, type View, type VitralConfig, __reset, component, configure, createEventBus, createSlot, defineRemote, events, html, loadRemote, mountRemote, navigate, preload, register, render, unsafeHTML };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RemoteError,
|
|
3
|
+
__reset,
|
|
4
|
+
component,
|
|
5
|
+
configure,
|
|
6
|
+
createEventBus,
|
|
7
|
+
createSlot,
|
|
8
|
+
defineRemote,
|
|
9
|
+
events,
|
|
10
|
+
html,
|
|
11
|
+
loadRemote,
|
|
12
|
+
mountRemote,
|
|
13
|
+
navigate,
|
|
14
|
+
preload,
|
|
15
|
+
register,
|
|
16
|
+
render,
|
|
17
|
+
unsafeHTML
|
|
18
|
+
} from "./chunk-TTL52BF2.js";
|
|
19
|
+
export {
|
|
20
|
+
RemoteError,
|
|
21
|
+
__reset,
|
|
22
|
+
component,
|
|
23
|
+
configure,
|
|
24
|
+
createEventBus,
|
|
25
|
+
createSlot,
|
|
26
|
+
defineRemote,
|
|
27
|
+
events,
|
|
28
|
+
html,
|
|
29
|
+
loadRemote,
|
|
30
|
+
mountRemote,
|
|
31
|
+
navigate,
|
|
32
|
+
preload,
|
|
33
|
+
register,
|
|
34
|
+
render,
|
|
35
|
+
unsafeHTML
|
|
36
|
+
};
|
package/dist/preact.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as preact from 'preact';
|
|
2
|
+
import { VNode, ComponentType } from 'preact';
|
|
3
|
+
import { Props, Isolation, RemoteError, SlotStatus, RemoteModule } from './index.js';
|
|
4
|
+
export { RemoteContext, events, navigate } from './index.js';
|
|
5
|
+
|
|
6
|
+
/** Transforma um componente Preact em um remote. O contexto chega na prop `vitral`. */
|
|
7
|
+
declare function preactRemote<P extends Props>(Component: ComponentType<any>): RemoteModule<P>;
|
|
8
|
+
interface RemoteProps<P extends Props = Props> {
|
|
9
|
+
name: string;
|
|
10
|
+
props?: P;
|
|
11
|
+
isolate?: Isolation;
|
|
12
|
+
fallback?: VNode | string | null;
|
|
13
|
+
error?: VNode | string | ((error: RemoteError, retry: () => void) => VNode | string | null) | null;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
declare function Remote<P extends Props = Props>({ name, props, isolate, fallback, error, ...rest }: RemoteProps<P>): VNode<preact.ClassAttributes<HTMLElement> & {
|
|
17
|
+
'data-vitral': string;
|
|
18
|
+
'data-vitral-status': SlotStatus;
|
|
19
|
+
class: string | undefined;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
export { Remote, type RemoteProps, preactRemote };
|
package/dist/preact.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSlot,
|
|
3
|
+
defineRemote,
|
|
4
|
+
events,
|
|
5
|
+
navigate
|
|
6
|
+
} from "./chunk-TTL52BF2.js";
|
|
7
|
+
|
|
8
|
+
// src/preact.ts
|
|
9
|
+
import { h, render } from "preact";
|
|
10
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
11
|
+
function preactRemote(Component) {
|
|
12
|
+
return defineRemote({
|
|
13
|
+
mount(el, props, ctx) {
|
|
14
|
+
const draw = (p) => render(h(Component, { ...p, vitral: ctx }), el);
|
|
15
|
+
draw(props);
|
|
16
|
+
return { update: draw, unmount: () => render(null, el) };
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function Remote({ name, props, isolate, fallback = null, error = null, ...rest }) {
|
|
21
|
+
const ref = useRef(null);
|
|
22
|
+
const slot = useRef(null);
|
|
23
|
+
const [state, setState] = useState({ status: "loading", error: null });
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
slot.current = createSlot(ref.current, { name, props: props ?? {}, isolate, onStatus: (status, error2) => setState({ status, error: error2 }) });
|
|
26
|
+
return () => slot.current?.destroy();
|
|
27
|
+
}, []);
|
|
28
|
+
useEffect(() => slot.current?.set({ name, isolate }), [name, isolate]);
|
|
29
|
+
useEffect(() => slot.current?.update(props ?? {}), [props]);
|
|
30
|
+
const retry = () => slot.current?.retry();
|
|
31
|
+
return h("div", { "data-vitral": name, "data-vitral-status": state.status, class: rest.class }, [
|
|
32
|
+
state.status === "loading" ? fallback : null,
|
|
33
|
+
state.status === "error" && state.error ? typeof error === "function" ? error(state.error, retry) : error : null,
|
|
34
|
+
// O Preact nunca coloca filhos neste div: ele pertence ao remote.
|
|
35
|
+
h("div", { ref })
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
Remote,
|
|
40
|
+
events,
|
|
41
|
+
navigate,
|
|
42
|
+
preactRemote
|
|
43
|
+
};
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ComponentType } from 'react';
|
|
3
|
+
import { Props, Isolation, RemoteModule, RemoteContext } from './index.js';
|
|
4
|
+
export { EventBus, events } from './index.js';
|
|
5
|
+
|
|
6
|
+
/** Transforma um componente React em um remote. */
|
|
7
|
+
declare function reactRemote<P extends Props>(Component: ComponentType<P>): RemoteModule<P>;
|
|
8
|
+
/** Dentro de um remote: dá acesso ao contexto (nome, eventos, baseUrl). */
|
|
9
|
+
declare function useVitral(): RemoteContext | null;
|
|
10
|
+
/** Assina um evento do barramento global enquanto o componente estiver montado. */
|
|
11
|
+
declare function useEvent<T = unknown>(type: string, handler: (payload: T) => void): void;
|
|
12
|
+
/** Estado derivado do último valor emitido em `type`. */
|
|
13
|
+
declare function useEventValue<T = unknown>(type: string): T | undefined;
|
|
14
|
+
|
|
15
|
+
interface RemoteProps<P extends Props = Props> {
|
|
16
|
+
name: string;
|
|
17
|
+
props?: P;
|
|
18
|
+
isolate?: Isolation;
|
|
19
|
+
/** Mostrado enquanto o remote baixa. */
|
|
20
|
+
fallback?: ReactNode;
|
|
21
|
+
/** Mostrado se o remote falhar. Recebe o erro e uma função para tentar de novo. */
|
|
22
|
+
error?: ReactNode | ((error: Error, retry: () => void) => ReactNode);
|
|
23
|
+
onError?: (error: Error) => void;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Renderiza um remote. No servidor (Next SSR) renderiza só o fallback; o
|
|
28
|
+
* remote monta no cliente, então não há erro de hidratação.
|
|
29
|
+
*/
|
|
30
|
+
declare function Remote<P extends Props = Props>({ name, props, isolate, fallback, error, onError, className, }: RemoteProps<P>): react.JSX.Element;
|
|
31
|
+
|
|
32
|
+
export { Remote, type RemoteProps, reactRemote, useEvent, useEventValue, useVitral };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
defineRemote,
|
|
4
|
+
events,
|
|
5
|
+
mountRemote
|
|
6
|
+
} from "./chunk-TTL52BF2.js";
|
|
7
|
+
|
|
8
|
+
// src/react.tsx
|
|
9
|
+
import { Component as ReactComponent, createContext, useContext, useEffect, useRef, useState } from "react";
|
|
10
|
+
import { createRoot } from "react-dom/client";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var VitralContext = createContext(null);
|
|
13
|
+
var Boundary = class extends ReactComponent {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.state = { failed: false };
|
|
17
|
+
}
|
|
18
|
+
static getDerivedStateFromError() {
|
|
19
|
+
return { failed: true };
|
|
20
|
+
}
|
|
21
|
+
componentDidCatch(error) {
|
|
22
|
+
this.props.ctx.reportError(error);
|
|
23
|
+
}
|
|
24
|
+
render() {
|
|
25
|
+
return this.state.failed ? null : this.props.children;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function reactRemote(Component) {
|
|
29
|
+
return defineRemote({
|
|
30
|
+
mount(el, props, ctx) {
|
|
31
|
+
const root = createRoot(el, { onUncaughtError: (e) => ctx.reportError(e) });
|
|
32
|
+
const render = (p) => root.render(
|
|
33
|
+
/* @__PURE__ */ jsx(VitralContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(Boundary, { ctx, children: /* @__PURE__ */ jsx(Component, { ...p }) }) })
|
|
34
|
+
);
|
|
35
|
+
render(props);
|
|
36
|
+
return { update: render, unmount: () => root.unmount() };
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function useVitral() {
|
|
41
|
+
return useContext(VitralContext);
|
|
42
|
+
}
|
|
43
|
+
function useEvent(type, handler) {
|
|
44
|
+
const ref = useRef(handler);
|
|
45
|
+
ref.current = handler;
|
|
46
|
+
useEffect(() => events.on(type, (p) => ref.current(p)), [type]);
|
|
47
|
+
}
|
|
48
|
+
function useEventValue(type) {
|
|
49
|
+
const [value, setValue] = useState(() => events.last(type));
|
|
50
|
+
useEvent(type, setValue);
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
function Remote({
|
|
54
|
+
name,
|
|
55
|
+
props,
|
|
56
|
+
isolate,
|
|
57
|
+
fallback = null,
|
|
58
|
+
error,
|
|
59
|
+
onError,
|
|
60
|
+
className
|
|
61
|
+
}) {
|
|
62
|
+
const slot = useRef(null);
|
|
63
|
+
const handle = useRef(null);
|
|
64
|
+
const latestProps = useRef(props ?? {});
|
|
65
|
+
latestProps.current = props ?? {};
|
|
66
|
+
const [status, setStatus] = useState("loading");
|
|
67
|
+
const [err, setErr] = useState(null);
|
|
68
|
+
const [attempt, setAttempt] = useState(0);
|
|
69
|
+
const onErrorRef = useRef(onError);
|
|
70
|
+
onErrorRef.current = onError;
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const el = slot.current;
|
|
73
|
+
if (!el) return;
|
|
74
|
+
let cancelled = false;
|
|
75
|
+
setStatus("loading");
|
|
76
|
+
const fail = (e) => {
|
|
77
|
+
if (cancelled) return;
|
|
78
|
+
setErr(e);
|
|
79
|
+
setStatus("error");
|
|
80
|
+
if (onErrorRef.current) onErrorRef.current(e);
|
|
81
|
+
else console.error(e);
|
|
82
|
+
};
|
|
83
|
+
const initial = latestProps.current;
|
|
84
|
+
mountRemote(el, name, initial, { isolate, onError: fail }).then(
|
|
85
|
+
(h) => {
|
|
86
|
+
if (cancelled) return h.unmount();
|
|
87
|
+
handle.current = h;
|
|
88
|
+
if (latestProps.current !== initial) h.update?.(latestProps.current);
|
|
89
|
+
setStatus("ready");
|
|
90
|
+
},
|
|
91
|
+
fail
|
|
92
|
+
);
|
|
93
|
+
return () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
const h = handle.current;
|
|
96
|
+
handle.current = null;
|
|
97
|
+
if (h) queueMicrotask(() => h.unmount());
|
|
98
|
+
};
|
|
99
|
+
}, [name, isolate, attempt]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
handle.current?.update?.(latestProps.current);
|
|
102
|
+
}, [props]);
|
|
103
|
+
const retry = () => setAttempt((a) => a + 1);
|
|
104
|
+
return /* @__PURE__ */ jsxs("div", { className, "data-vitral": name, "data-vitral-status": status, children: [
|
|
105
|
+
status === "loading" && fallback,
|
|
106
|
+
status === "error" && err && (typeof error === "function" ? error(err, retry) : error ?? null),
|
|
107
|
+
/* @__PURE__ */ jsx("div", { ref: slot, style: status === "error" ? { display: "none" } : void 0 }, `${name}:${isolate}:${attempt}`)
|
|
108
|
+
] });
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
Remote,
|
|
112
|
+
events,
|
|
113
|
+
reactRemote,
|
|
114
|
+
useEvent,
|
|
115
|
+
useEventValue,
|
|
116
|
+
useVitral
|
|
117
|
+
};
|
package/dist/solid.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { JSX, Component } from 'solid-js';
|
|
2
|
+
import { Props, Isolation, RemoteError, RemoteModule } from './index.js';
|
|
3
|
+
export { RemoteContext, events, navigate } from './index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adaptador Solid (e SolidStart).
|
|
7
|
+
*
|
|
8
|
+
* Remote: export default solidRemote(App)
|
|
9
|
+
* Host: <Remote name="carrinho" props={{ userId: id() }} />
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Transforma um componente Solid em um remote. O contexto chega na prop `vitral`. */
|
|
13
|
+
declare function solidRemote<P extends Props>(Comp: Component<any>): RemoteModule<P>;
|
|
14
|
+
interface RemoteProps<P extends Props = Props> {
|
|
15
|
+
name: string;
|
|
16
|
+
props?: P;
|
|
17
|
+
isolate?: Isolation;
|
|
18
|
+
fallback?: JSX.Element;
|
|
19
|
+
error?: (error: RemoteError, retry: () => void) => JSX.Element;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
declare function Remote<P extends Props = Props>(p: RemoteProps<P>): JSX.Element;
|
|
23
|
+
|
|
24
|
+
export { Remote, type RemoteProps, solidRemote };
|
package/dist/solid.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSlot,
|
|
3
|
+
defineRemote,
|
|
4
|
+
events,
|
|
5
|
+
navigate
|
|
6
|
+
} from "./chunk-TTL52BF2.js";
|
|
7
|
+
|
|
8
|
+
// src/solid.ts
|
|
9
|
+
import { createComponent, createEffect, createSignal, mergeProps, on, onCleanup, onMount } from "solid-js";
|
|
10
|
+
import { createStore, reconcile } from "solid-js/store";
|
|
11
|
+
import { isServer, render } from "solid-js/web";
|
|
12
|
+
function solidRemote(Comp) {
|
|
13
|
+
return defineRemote({
|
|
14
|
+
mount(el, props, ctx) {
|
|
15
|
+
const [state, setState] = createStore({ ...props });
|
|
16
|
+
const dispose = render(() => createComponent(Comp, mergeProps(state, { vitral: ctx })), el);
|
|
17
|
+
return {
|
|
18
|
+
update: (next) => setState(reconcile({ ...next })),
|
|
19
|
+
unmount: dispose
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function Remote(p) {
|
|
25
|
+
if (isServer) return p.fallback ?? null;
|
|
26
|
+
const [status, setStatus] = createSignal("loading");
|
|
27
|
+
const [error, setError] = createSignal(null);
|
|
28
|
+
const host = document.createElement("div");
|
|
29
|
+
host.setAttribute("data-vitral", p.name);
|
|
30
|
+
if (p.class) host.className = p.class;
|
|
31
|
+
const target = document.createElement("div");
|
|
32
|
+
let slot = null;
|
|
33
|
+
onMount(() => {
|
|
34
|
+
slot = createSlot(target, {
|
|
35
|
+
name: p.name,
|
|
36
|
+
props: p.props ?? {},
|
|
37
|
+
isolate: p.isolate,
|
|
38
|
+
onStatus: (s, e) => {
|
|
39
|
+
host.setAttribute("data-vitral-status", s);
|
|
40
|
+
setError(() => e);
|
|
41
|
+
setStatus(s);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
createEffect(on(() => [p.name, p.isolate], ([name, isolate]) => slot?.set({ name, isolate }), { defer: true }));
|
|
46
|
+
createEffect(on(() => ({ ...p.props ?? {} }), (props) => slot?.update(props), { defer: true }));
|
|
47
|
+
onCleanup(() => slot?.destroy());
|
|
48
|
+
const retry = () => slot?.retry();
|
|
49
|
+
return [
|
|
50
|
+
() => status() === "loading" ? p.fallback : null,
|
|
51
|
+
() => status() === "error" && error() && p.error ? p.error(error(), retry) : null,
|
|
52
|
+
(host.appendChild(target), host)
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
Remote,
|
|
57
|
+
events,
|
|
58
|
+
navigate,
|
|
59
|
+
solidRemote
|
|
60
|
+
};
|
package/dist/svelte.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Component } from 'svelte';
|
|
2
|
+
import { Props, Isolation, SlotStatus, RemoteError, RemoteModule } from './index.js';
|
|
3
|
+
export { RemoteContext, events, navigate } from './index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adaptador Svelte 5 (e SvelteKit).
|
|
7
|
+
*
|
|
8
|
+
* Remote: export default svelteRemote(App)
|
|
9
|
+
* Host: <div use:remote={{ name: 'carrinho', props: { userId } }}></div>
|
|
10
|
+
* ou o Web Component <vitral-remote> de 'vitral/element'.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Transforma um componente Svelte em um remote. O contexto chega na prop `vitral`. */
|
|
14
|
+
declare function svelteRemote<P extends Props>(component: Component<any>): RemoteModule<P>;
|
|
15
|
+
interface RemoteActionParams<P extends Props = Props> {
|
|
16
|
+
name: string;
|
|
17
|
+
props?: P;
|
|
18
|
+
isolate?: Isolation;
|
|
19
|
+
onStatus?: (status: SlotStatus, error: RemoteError | null) => void;
|
|
20
|
+
}
|
|
21
|
+
/** Action Svelte: `<div use:remote={{ name, props }}>`. Funciona também em SvelteKit (só roda no cliente). */
|
|
22
|
+
declare function remote<P extends Props = Props>(node: HTMLElement, params: RemoteActionParams<P>): {
|
|
23
|
+
update(next: RemoteActionParams<P>): void;
|
|
24
|
+
destroy: () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { type RemoteActionParams, remote, svelteRemote };
|
package/dist/svelte.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSlot,
|
|
3
|
+
defineRemote,
|
|
4
|
+
events,
|
|
5
|
+
navigate
|
|
6
|
+
} from "./chunk-TTL52BF2.js";
|
|
7
|
+
|
|
8
|
+
// src/svelte.ts
|
|
9
|
+
import { createClassComponent } from "svelte/legacy";
|
|
10
|
+
function svelteRemote(component) {
|
|
11
|
+
return defineRemote({
|
|
12
|
+
mount(el, props, ctx) {
|
|
13
|
+
const instance = createClassComponent({ component, target: el, props: { ...props, vitral: ctx } });
|
|
14
|
+
return {
|
|
15
|
+
update: (next) => instance.$set({ ...next, vitral: ctx }),
|
|
16
|
+
unmount: () => instance.$destroy()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function remote(node, params) {
|
|
22
|
+
const slot = createSlot(node, params);
|
|
23
|
+
return {
|
|
24
|
+
update(next) {
|
|
25
|
+
slot.set({ name: next.name, isolate: next.isolate });
|
|
26
|
+
slot.update(next.props ?? {});
|
|
27
|
+
},
|
|
28
|
+
destroy: () => slot.destroy()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
events,
|
|
33
|
+
navigate,
|
|
34
|
+
remote,
|
|
35
|
+
svelteRemote
|
|
36
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Props, RemoteContext } from './index.js';
|
|
2
|
+
export { __reset as resetVitral } from './index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Utilidades de teste: monte o host sem subir nenhum remote de verdade.
|
|
6
|
+
*
|
|
7
|
+
* import { mockRemote } from 'vitral/testing'
|
|
8
|
+
* const carrinho = mockRemote('carrinho', (el, props) => (el.textContent = `itens: ${props.itens}`))
|
|
9
|
+
* render(<App />)
|
|
10
|
+
* expect(carrinho.lastProps).toEqual({ itens: 2 })
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface MockRemote<P extends Props = Props> {
|
|
14
|
+
/** Quantas vezes foi montado. */
|
|
15
|
+
readonly mounts: number;
|
|
16
|
+
/** Props mais recentes recebidas (mount ou update). */
|
|
17
|
+
readonly lastProps: P | undefined;
|
|
18
|
+
/** Contexto da última montagem (eventos, navigate…). */
|
|
19
|
+
readonly ctx: RemoteContext | undefined;
|
|
20
|
+
/** Está montado agora? */
|
|
21
|
+
readonly mounted: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Registra um remote falso. `render` é chamado no mount e a cada update. */
|
|
24
|
+
declare function mockRemote<P extends Props = Props>(name: string, render?: (el: HTMLElement, props: P, ctx: RemoteContext) => void): MockRemote<P>;
|
|
25
|
+
/** Remote que sempre falha ao montar, para testar telas de erro. */
|
|
26
|
+
declare function failingRemote(name: string, message?: string): void;
|
|
27
|
+
|
|
28
|
+
export { type MockRemote, failingRemote, mockRemote };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__reset,
|
|
3
|
+
register
|
|
4
|
+
} from "./chunk-TTL52BF2.js";
|
|
5
|
+
|
|
6
|
+
// src/testing.ts
|
|
7
|
+
function mockRemote(name, render = (el) => {
|
|
8
|
+
el.setAttribute("data-mock-remote", name);
|
|
9
|
+
}) {
|
|
10
|
+
const stats = { mounts: 0, lastProps: void 0, ctx: void 0, mounted: false };
|
|
11
|
+
register(name, {
|
|
12
|
+
module: {
|
|
13
|
+
mount(el, props, ctx) {
|
|
14
|
+
stats.mounts++;
|
|
15
|
+
stats.mounted = true;
|
|
16
|
+
stats.lastProps = props;
|
|
17
|
+
stats.ctx = ctx;
|
|
18
|
+
render(el, props, ctx);
|
|
19
|
+
return {
|
|
20
|
+
update(next) {
|
|
21
|
+
stats.lastProps = next;
|
|
22
|
+
render(el, next, ctx);
|
|
23
|
+
},
|
|
24
|
+
unmount() {
|
|
25
|
+
stats.mounted = false;
|
|
26
|
+
el.replaceChildren();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return stats;
|
|
33
|
+
}
|
|
34
|
+
function failingRemote(name, message = "falha simulada") {
|
|
35
|
+
register(name, {
|
|
36
|
+
module: {
|
|
37
|
+
mount() {
|
|
38
|
+
throw new Error(message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
failingRemote,
|
|
45
|
+
mockRemote,
|
|
46
|
+
__reset as resetVitral
|
|
47
|
+
};
|