Este artigo detalha uma abordagem para implementar o cache de páginas de microaplicativos Qiankun quando eles são exibidos em abas de navegação, com o objetivo de melhorar a experiência do usuário e o desempenho em aplicações web complexas.
Contexto e Desafios
Em projetos que utilizam frameworks como Jeecg-Boot, a crescente base de código em módulos individuais pode levar a tempos de compilação lentos e dificuldades no gerenciamento de branches. A adoção de uma arquitetura de microfrontends com Qiankun surge como uma solução para isolar e gerenciar esses módulos como aplicações independentes. O Jeecg-Boot já integra o Qiankun, facilitando a transição. Inicialmente, a configuração via registerMicroApps foi utilizada para o registro automático.
No entanto, com o tempo, surgiu a necessidade de um comportamento específico: quando um usuário alterna entre abas de navegação, as páginas dos microaplicativos devem ser mantidas em cache. Ao fechar uma aba, o cache associado a essa aba deve ser removido. Se o usuário acessar a página novamente a partir do menu, ela deve ser recarregada.
Soluções encontradas online para implementar cache com registerMicroApps, como as descritas em [referência 1] e [referência 2], apresentaram limitações, especialmente quando um único microaplicativo precisava gerenciar o cache de múltiplas páginas. Isso levou à exploração de uma abordagem alternativa utilizando loadMicroApp.
Implementação com loadMicroApp
Aplicação Principal
-
Modificação em
apps.js:A configuração das aplicações é ajustada para definir entradas e contêineres específicos para cada microaplicativo.
const microAppsConfig = [ { name: 'appOne', entry: process.env.NODE_ENV === 'development' ? 'http://localhost:8081/' : document.querySelector('html').dataset.appOneUrl, container: '#microAppContainerOne', activeRule: '/appOne', // Regra de roteamento para ativar o microapp sandbox: { strictStyleIsolation: true // Habilita isolamento de estilo } }, { name: 'appTwo', entry: process.env.env === 'development' ? 'http://localhost:8082/' : document.querySelector('html').dataset.appTwoUrl, container: '#microAppContainerTwo', activeRule: '/appTwo', // Regra de roteamento para ativar o microapp sandbox: { strictStyleIsolation: true // Habilita isolamento de estilo } } ]; -
Modificação em
index.js:A função para carregar os microaplicativos é adaptada para usar
loadMicroApp, permitindo um controle mais granular.import { loadMicroApp } from 'qiankun'; import { microAppsConfig } from './apps'; import { getSharedProps, initializeGlobalState } from './stateManager'; import { prefetchMicroApps } from 'qiankun'; // Pré-carrega os microaplicativos para otimização prefetchMicroApps(microAppsConfig); /** * Carrega e inicializa um microaplicativo. * @param {object} appConfig - Configuração do microaplicativo. * @returns {object} O objeto loader do microaplicativo. */ function initializeMicroApp(appConfig) { const microAppLoader = loadMicroApp({ ...appConfig, props: { ...getSharedProps(), } }, { sandbox: { experimentalStyleIsolation: true, // Modo sandbox experimental } }); // Inicializa o estado global para comunicação initializeGlobalState(); return microAppLoader; } export default initializeMicroApp; -
Modificação em
stateManager.js:Gerencia o estado global e a comunicação entre a aplicação principal e os microaplicativos.
import { initGlobalState } from 'qiankun'; import appStore from '../store'; // Assumindo que 'store' é o Vuex store import Vue from 'vue'; import { TOKEN_KEY, USERNAME_KEY } from '@/store/mutationTypes'; // Constantes de mutação // Dados públicos a serem passados para os microaplicativos export function getSharedProps() { return { publicData: { basePath: process.env.BASE_URL, authToken: Vue.ls.get(TOKEN_KEY) } }; } /** * Configura o estado global e os mecanismos de comunicação. */ export function initializeGlobalState() { const initialState = { currentUser: Vue.ls.get(USERNAME_KEY), isLoggedIn: true, closeCurrentTab: false, tabUpdateTrigger: 0, approvalDetails: '', }; const actions = initGlobalState(initialState); actions.setGlobalState(initialState); actions.onGlobalStateChange((newState, previousState) => { if (!newState.isLoggedIn) { appStore.dispatch('userLogout').then(() => { window.location.reload(); }); } if (newState.closeCurrentTab) { appStore.dispatch('setTabCloseAction', true); actions.setGlobalState({ closeCurrentTab: false }); } }); // Disponibiliza as ações de comunicação globalmente via $actions Vue.prototype.$actions = actions; } -
Modificação em
main.js:Controla o ciclo de vida dos microaplicativos com base nas mudanças de rota e gerencia o cache das abas.
import { unmountMicroApps } from 'qiankun'; // Importa a função de desmontar import initializeMicroApp from './index'; // Assumindo que initializeMicroApp está exportado de index.js import { microAppsConfig } from './apps'; import appStore from '../store'; import Vue from 'vue'; import { CACHED_ROUTES_KEY, SUB_APP_ROUTES_MAP_KEY } from '@/utils/cacheConstants'; // Constantes para cache let activeMicroApps = {}; // Objeto para rastrear os microaplicativos ativos router.beforeEach((to, from, next) => { const matchedAppConfig = microAppsConfig.find(app => to.path.startsWith(app.activeRule)); if (matchedAppConfig) { appStore.dispatch('setQiankunActiveState', true); const cachedMicroApp = activeMicroApps[matchedAppConfig.name]; if (cachedMicroApp) { // Microaplicativo já está carregado e em cache next(); return; } // Carrega o microaplicativo se não estiver em cache activeMicroApps[matchedAppConfig.name] = initializeMicroApp({ ...matchedAppConfig }); next(); } else { // Desmonta todos os microaplicativos se a rota não corresponder a nenhum performMicroAppUnmount(); appStore.dispatch('setQiankunActiveState', false); next(); } }); /** * Desmonta microaplicativos com base nas configurações de cache das abas. */ export function performMicroAppUnmount () { const cachedIncludedRoutes = Vue.ls.get(CACHED_ROUTES_KEY) || []; const subAppRoutesMap = Vue.ls.get(SUB_APP_ROUTES_MAP_KEY) || {}; for (const appName in activeMicroApps) { const routesForApp = subAppRoutesMap[appName] || []; let isAnyRouteCached = false; cachedIncludedRoutes.forEach(cachedRoute => { if (routesForApp.includes(cachedRoute)) { isAnyRouteCached = true; } }); // Se nenhuma rota deste subaplicativo estiver marcada para cache, desmonte-o if (!isAnyRouteCached) { activeMicroApps[appName].unmount(); delete activeMicroApps[appName]; } } } -
Gerenciamento de Fechamento de Abas:
Quando uma aba é fechada, a função
performMicroAppUnmountdeve ser invocada para remover o cache do microaplicativo correspondente, a menos que outras abas desse mesmo microaplicativo permaneçam abertas e em cache.
Microaplicativo
-
Resolução de Conflitos de Rota:
No ponto de entrada de cada microaplicativo (por exemplo,
main.jsdo subaplicativo), adicione código para garantir que o roteamento do microaplicativo não entre em conflito com o da aplicação principal. Isso geralmente envolve configurar o prefixo de roteamento do microaplicativo para corresponder à sua regra de ativação na aplicação principal.
Essas modificações permitem um cnotrole mais refinado sobre o carregamento e o descarregamento de microaplicativos, habilitando o cache seletivo de páginas em abas e a limpeza adequada quando as abas são fechadas.