Pular para o conteúdo principal

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:

ExportAssinaturaPapel
pagesRecord<string, () => Promise<unknown>>Mapa de páginas Inertia, com chave igual ao nome que o backend passa em Inertia::render
registerSlots?() => voidChamado uma vez por módulo após o load; registra componentes em slots do core
registerLocales?() => voidRegistra bundles de tradução i18n no runtime

O loader expõe:

  • prefetchInstalledModules(modules) — carrega cada módulo instalado e chama registerLocales() e depois registerSlots().
  • 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/... = identifier do módulo).
Nomenclatura de páginas

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.

Versionamento do Host API

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