Análise de Tamanho do Bundle
Visualizador de bundle Metro e divisão de rotas lazy - descubra o que é enviado no bundle principal do Hermes e adie telas pesadas para depois do cold start.
Busque em todas as páginas da documentação
Visualizador de bundle Metro e divisão de rotas lazy - descubra o que é enviado no bundle principal do Hermes e adie telas pesadas para depois do cold start.
Cartão de receita de referência rápida - pronto para copiar e colar.
1. Gere o bundle de produção + source map
npx react-native bundle \
--platform android \
--dev false \
--entry-file node_modules/expo-router/entry.js \
--bundle-output ./.perf/index.android.bundle \
--sourcemap-output ./.perf/index.android.bundle.map \
--assets-dest ./.perf/assets2. Abra o treemap
npx react-native-bundle-visualizer \
--bundle ./.perf/index.android.bundle \
--sourcemap ./.perf/index.android.bundle.map3. Triagem dos principais infratores
| Bloco do Treemap | Correção Comum |
|---|---|
moment / date-fns/locale/* | dayjs ou imports mais específicos |
lodash completo | lodash/es/map ou helpers nativos |
Conjunto completo de @expo/vector-icons | Importação por família ou fonte subset |
SDKs de charting / maps / editor | Rota lazy + import() dinâmico |
Cópias duplicadas de react / aninhadas | Alinhe metro.config.js do monorepo |
4. Carregamento lazy de telas pesadas do Expo Router
// app/(tabs)/analytics.tsx - tela de aba lazy
import { Suspense, lazy } from "react";
import { ActivityIndicator, View } from "react-native";
const AnalyticsScreen = lazy(() => import("../../src/screens/AnalyticsScreen"));
export default function AnalyticsRoute() {
return (
<Suspense
fallback={
<View style={{ flex: 1, justifyContent: "center" }}>
<ActivityIndicator />
</View>
}
>
<AnalyticsScreen />
</Suspense>
);
}5. Gate de tamanho em CI (esboço)
#!/usr/bin/env bash
# scripts/check-bundle-size.sh
BUNDLE="./.perf/index.android.bundle"
MAX_BYTES=4500000 # ~4.3 MB raw - ajuste ao seu orçamento de gzip
SIZE=$(wc -c < "$BUNDLE")
if [ "$SIZE" -gt "$MAX_BYTES" ]; then
echo "Bundle $SIZE excede $MAX_BYTES"
exit 1
fiQuando usar isso:
import de um SDK pesado no caminho do layout raiz.Audite um import raiz inchado, divida uma tela pesada e verifique se o bloco do treemap se move para fora do caminho principal.
// app/_layout.tsx - mantenha a raiz enxuta
import { Stack } from "expo-router";
import { QueryClientProvider } from "@tanstack/react-query";
import { queryClient } from "../src/lib/queryClient";
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<Stack screenOptions={{ headerShown: false }} />
</QueryClientProvider>
);
}// app/settings/reports.tsx - tela pesada NÃO no caminho de TTI
import { Suspense, lazy } from "react";
import { ActivityIndicator, StyleSheet, View } from "react-native";
const ReportsScreen = lazy(() =>
import("../../src/screens/ReportsScreen").then((m) => ({ default: m.ReportsScreen })),
);
export default function ReportsRoute() {
return (
<Suspense fallback={<View style={styles.fallback}><ActivityIndicator /></View>}>
<ReportsScreen />
</Suspense>
);
}
const styles = StyleSheet.create({
fallback: { flex: 1, alignItems: "center", justifyContent: "center" },
});// src/screens/ReportsScreen.tsx - dependências pesadas isoladas em chunk lazy
import { StyleSheet, Text, View } from "react-native";
// Exemplo de dep pesada - só carrega quando o usuário abre Relatórios
import { VictoryPie } from "victory-native";
export function ReportsScreen() {
const data = [
{ x: "A", y: 35 },
{ x: "B", y: 25 },
{ x: "C", y: 40 },
];
return (
<View style={styles.screen}>
<Text style={styles.title}>Relatórios</Text>
<VictoryPie data={data} width={280} height={280} />
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16, alignItems: "center" },
title: { fontSize: 20, fontWeight: "700", marginBottom: 16 },
});Checklist de verificação
victory-native (ou seu SDK) no bundle principal.lazy() - re-bundle; o bloco pesado encolhe no treemap principal.importa o módulo pesado transitivamente através de barrels.// RUIM - carrega a biblioteca inteira
import _ from "lodash";
import * as Icons from "@expo/vector-icons";
// MELHOR
import debounce from "lodash/debounce";
import Ionicons from "@expo/vector-icons/Ionicons";// packages/ui/src/index.ts - reexporta tudo
export * from "./charts"; // ← arrasta charts para qualquer import `from "@acme/ui"`
// MELHOR - subpaths explícitos em exports do package.json
// import { Button } from "@acme/ui/button";Pacotes compartilhados em monorepos duplicam dependências facilmente - alinhe watchFolders e resolver em metro.config.js. Veja Pacotes Compartilhados e Resolução Metro.
| Padrão | Impacto no TTI |
|---|---|
Módulo de tela lazy() | Chunk carrega na primeira navegação - bom para configurações/relatórios |
Aba lazy: true (padrão) | Adia a montagem inicial até o foco da aba - bom para cold start |
Link prefetch | Carrega o chunk cedo - use apenas para a próxima tela provável |
Imports da raiz _layout | Sempre no caminho crítico - mantenha o mínimo |
// Pré-carrega rota de detalhes pesada quando o usuário passa o mouse sobre o card - trade-off opcional
import { Link } from "expo-router";
<Link href="/reports" prefetch>
Abrir relatórios
</Link>Por Regras de Orçamento de Performance:
Assets (imagens, fontes) são separados, mas afetam o download - mídia empacotada pelo expo-asset aparece na saída da exportação.
dev: true inclui ferramentas de desenvolvimento; sempre use --dev false.lazy().Ambos mapeiam bytes para módulos. react-native-bundle-visualizer é otimizado para saída Metro do RN - comece por aí. Use source-map-explorer se você já tiver ferramentas web em CI.
CI frequentemente faz o gate do tamanho bruto ou gzip - escolha um e documente-o. Gzip ≈ 30–40% do bruto para bundles típicos de RN; alinhe com sua definição de orçamento de 4 MB.
Metro suporta import() para chunks assíncronos. Módulos nativos ainda são autolinkados no tempo de compilação - remover o import JS não remove o código nativo a menos que você desinstale o pacote.
Não - apenas imports alcançáveis da entrada de produção contam. Eles ainda afetam o tempo de instalação e CI - veja Código Morto e Análise de Dependência.
Arquivos .ttf empacotados vão para os assets. Subset de fontes de ícones e carregue fontes de exibição após o splash quando possível - Otimização do Tempo de Inicialização.
TTI inclui inicialização nativa, carregamento de bytecode e trabalho da primeira tela - não apenas bytes JS. Perfis holísticos com Noções Básicas de Performance.
Versões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026