Frontend Modular
Módulos do ION Guard trazem seu próprio frontend (React + Inertia). Cada módulo é buildado de forma independente e distribuído como um bundle ESM; o core carrega esse bundle em runtime e integra suas páginas, traduções e componentes de slot — sem recompilar o core.
Como o core carrega um módulo
Ao instalar, o dist/ do módulo é copiado para public/module-bundles/{identifier}/. Em runtime, o resources/js/runtime/module-loader.ts do core busca o entry.js do bundle sob demanda e usa seus exports.
Um bundle de módulo pode exportar:
| Export | Assinatura | Papel |
|---|---|---|
pages | Record<string, () => Promise<unknown>> | Mapa de páginas Inertia, com chave igual ao nome que o backend passa em Inertia::render |
registerSlots? | () => void | Chamado uma vez por módulo após o load; registra componentes em slots do core |
registerLocales? | () => void | Registra bundles de tradução i18n no runtime |
O loader expõe:
prefetchInstalledModules(modules)— carrega cada módulo instalado e chamaregisterLocales()e depoisregisterSlots().loadModulePage(module, pageName)— resolve e devolve o componente da página (entry.pages[pageName]), com erro claro se o mapa de páginas ou a página não existir.findInstalledModuleForPage(pageName, modules)— descobre a qual módulo pertence uma página pelo prefixo do nome (prefixo/...=identifierdo módulo).
O nome da página começa pelo identifier do módulo (kebab-case). Ex.: indoor-location/dashboard. O findInstalledModuleForPage usa exatamente esse prefixo para rotear a página ao bundle certo.
O entry.ts do módulo
Cada módulo define um resources/js/entry.ts que monta o mapa de páginas via import.meta.glob e expõe os hooks de registro:
// Auto-descoberta de páginas Inertia
const rawPages = import.meta.glob('./pages/**/*.tsx');
export const pages: Record<string, () => Promise<unknown>> = {};
for (const [filePath, resolver] of Object.entries(rawPages)) {
const name = filePath.replace(/^\.\/pages\//, '').replace(/\.tsx$/, '');
pages[name] = resolver;
}
export function registerLocales(): void {
// Importar e registrar bundles de tradução aqui
}
export function registerSlots(): void {
// Registrar componentes em slots do core aqui
}
Host API — dependências compartilhadas
O módulo é buildado externalizando React, Inertia, Lucide e o surface @/... do core. Em runtime, essas dependências são resolvidas a partir de window.__host__, publicado pelo core em resources/js/runtime/host-api.ts:
react → window.__host__.react
react/jsx-runtime → window.__host__.reactJsxRuntime
@inertiajs/react → window.__host__.inertia
lucide-react → window.__host__.lucide
@/<path> → window.__host__.modules['@/<path>']
Isso garante que o módulo use a mesma instância de React, do i18n e dos componentes do core (botões, cards, tabelas, charts, layouts etc.), em vez de embutir cópias próprias.
O host-api.ts exporta HOST_API_VERSION (semver; 1.0.0 em v0.8.7). Adicionar um símbolo ao surface é o contrato. Uma mudança incompatível exige subir HOST_API_VERSION e o campo compatibleHostApi de cada módulo distribuído — o module-loader carrega esse campo junto com o bundle.
Slots — pontos de extensão de UI
Slots permitem que um módulo injete componentes em pontos do core sem alterá-lo. O registro é feito em resources/js/lib/slot-registry.ts:
registerSlotComponent(key, component); // registra
getSlotComponent(key); // recupera
No core, o componente <Slot name="..." /> renderiza todos os componentes registrados para aquele slot. Ele lê as entradas de usePage().props.slots[name] e resolve cada uma via getSlotComponent(entry.component) — silenciosamente ignorando slots vazios ou componentes não registrados. É dentro do registerSlots() do entry.ts que o módulo chama registerSlotComponent().
Próximos passos
- Sistema de Módulos — Lifecycle completo (backend + frontend)
- Criando um Módulo — Onde o
entry.tsse encaixa