up-cc 0.2.2 → 0.3.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/agents/up-analista-codigo.md +446 -0
- package/agents/up-auditor-modernidade.md +378 -0
- package/agents/up-auditor-performance.md +426 -0
- package/agents/up-auditor-ux.md +396 -0
- package/agents/up-consolidador-ideias.md +493 -0
- package/agents/up-pesquisador-mercado.md +350 -0
- package/agents/up-sintetizador-melhorias.md +407 -0
- package/bin/lib/core.cjs +3 -3
- package/bin/up-tools.cjs +490 -23
- package/commands/ajuda.md +20 -0
- package/commands/ideias.md +49 -0
- package/commands/melhorias.md +45 -0
- package/commands/resetar.md +27 -0
- package/package.json +1 -1
- package/references/audit-modernidade.md +1617 -0
- package/references/audit-performance.md +478 -0
- package/references/audit-ux.md +1544 -0
- package/templates/report.md +177 -0
- package/templates/suggestion.md +152 -0
- package/workflows/ideias.md +381 -0
- package/workflows/melhorias.md +409 -0
- package/workflows/resetar.md +186 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
<overview>
|
|
2
|
+
|
|
3
|
+
Catalogo de anti-padroes de performance para analise estatica de codigo. O agente auditor le este reference e compara contra o codebase para produzir sugestoes com Dimensao = `Performance` no formato `@up/templates/suggestion.md`.
|
|
4
|
+
|
|
5
|
+
**Como usar:** 1) Rode `<stack_detection>` para identificar a stack. 2) Filtre padroes pelo campo **Frameworks**. 3) Execute **Sinal de deteccao** via Grep. 4) Para cada match, produza sugestao.
|
|
6
|
+
|
|
7
|
+
**Impacto:** P = marginal, M = notavel em metricas, G = resolve gargalo real.
|
|
8
|
+
|
|
9
|
+
</overview>
|
|
10
|
+
|
|
11
|
+
<stack_detection>
|
|
12
|
+
|
|
13
|
+
## Deteccao de Stack
|
|
14
|
+
|
|
15
|
+
| Sinal (em package.json) | Detecta | Categorias |
|
|
16
|
+
|--------------------------|---------|-----------|
|
|
17
|
+
| `"react"` em dependencies | React | re-renders, bundle, assets, css, network, configs, deps |
|
|
18
|
+
| `"vue"` em dependencies | Vue | re-renders, bundle, assets, css, network, configs, deps |
|
|
19
|
+
| `"svelte"` em dependencies | Svelte | bundle, assets, css, network, configs, deps |
|
|
20
|
+
| `"next"` em dependencies | Next.js | + SSR, Image, dynamic imports |
|
|
21
|
+
| `"nuxt"` em dependencies | Nuxt | + SSR, NuxtImage |
|
|
22
|
+
| `tailwind.config.*` existe | Tailwind | Pular seletores CSS pesados |
|
|
23
|
+
| `"bootstrap"` em dependencies | Bootstrap | Verificar import completo vs cherry-pick |
|
|
24
|
+
| `"@prisma/client"` em deps | Prisma | include/select, findMany sem take |
|
|
25
|
+
| `"drizzle-orm"` em deps | Drizzle | query builder patterns |
|
|
26
|
+
| `"sequelize"` em deps | Sequelize | eager loading, N+1 |
|
|
27
|
+
| `"typeorm"` em deps | TypeORM | relations, query builder |
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
grep -E '"(react|vue|svelte|next|nuxt|@prisma/client|drizzle-orm|sequelize|typeorm|tailwindcss|bootstrap)"' package.json 2>/dev/null
|
|
31
|
+
ls tailwind.config.* 2>/dev/null
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
</stack_detection>
|
|
35
|
+
|
|
36
|
+
<category name="re-renders">
|
|
37
|
+
|
|
38
|
+
## Re-renders Desnecessarios
|
|
39
|
+
|
|
40
|
+
### INLINE-OBJECT-PROPS
|
|
41
|
+
**Frameworks:** React, Vue | **Impacto:** M
|
|
42
|
+
**Sinal de deteccao:**
|
|
43
|
+
```bash
|
|
44
|
+
grep -rn 'style={{\|={\[' src/ --include="*.tsx" --include="*.jsx"
|
|
45
|
+
```
|
|
46
|
+
**Exemplo ruim:**
|
|
47
|
+
```tsx
|
|
48
|
+
<Card style={{ padding: 16 }}><Badge options={['new']} /></Card> // novo objeto a cada render
|
|
49
|
+
```
|
|
50
|
+
**Solucao:**
|
|
51
|
+
```tsx
|
|
52
|
+
const cardStyle = { padding: 16 }; const opts = ['new'];
|
|
53
|
+
<Card style={cardStyle}><Badge options={opts} /></Card>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### ANONYMOUS-FN-PROPS
|
|
57
|
+
**Frameworks:** React, Vue | **Impacto:** M
|
|
58
|
+
**Sinal de deteccao:**
|
|
59
|
+
```bash
|
|
60
|
+
grep -rn 'onClick={() =>\|onChange={() =>' src/ --include="*.tsx" --include="*.jsx"
|
|
61
|
+
```
|
|
62
|
+
**Exemplo ruim:**
|
|
63
|
+
```tsx
|
|
64
|
+
{products.map(p => <ProductCard onClick={() => onSelect(p.id)} />)} // nova funcao a cada render
|
|
65
|
+
```
|
|
66
|
+
**Solucao:**
|
|
67
|
+
```tsx
|
|
68
|
+
const handleSelect = useCallback((id) => onSelect(id), [onSelect]);
|
|
69
|
+
{products.map(p => <ProductCard id={p.id} onClick={handleSelect} />)}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### MISSING-MEMO-LIST
|
|
73
|
+
**Frameworks:** React | **Impacto:** G
|
|
74
|
+
**Sinal de deteccao:**
|
|
75
|
+
```bash
|
|
76
|
+
grep -rn '\.map(' src/ --include="*.tsx" --include="*.jsx" | grep -v 'memo'
|
|
77
|
+
```
|
|
78
|
+
**Exemplo ruim:**
|
|
79
|
+
```tsx
|
|
80
|
+
// Cada OrderRow re-renderiza quando qualquer estado do pai muda
|
|
81
|
+
{orders.map(o => <OrderRow key={o.id} order={o} />)}
|
|
82
|
+
```
|
|
83
|
+
**Solucao:**
|
|
84
|
+
```tsx
|
|
85
|
+
const OrderRow = React.memo(({ order }) => <tr><td>{order.id}</td></tr>);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### PARENT-STATE-CASCADE
|
|
89
|
+
**Frameworks:** React, Vue | **Impacto:** G
|
|
90
|
+
**Sinal de deteccao:**
|
|
91
|
+
```bash
|
|
92
|
+
grep -rn 'setInterval\|addEventListener' src/ --include="*.tsx" --include="*.jsx"
|
|
93
|
+
```
|
|
94
|
+
**Exemplo ruim:**
|
|
95
|
+
```tsx
|
|
96
|
+
function Dashboard() {
|
|
97
|
+
const [time, setTime] = useState(Date.now()); // atualiza todo segundo
|
|
98
|
+
useEffect(() => { const id = setInterval(() => setTime(Date.now()), 1000); return () => clearInterval(id); }, []);
|
|
99
|
+
return <><Clock time={time} />{products.map(p => <Card key={p.id} product={p} />)}</>;
|
|
100
|
+
// todos os Cards re-renderizam a cada segundo
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
**Solucao:** Isolar estado volatil em componente separado (`<LiveClock />`) para nao cascatear re-renders.
|
|
104
|
+
|
|
105
|
+
### MISSING-USEMEMO-EXPENSIVE
|
|
106
|
+
**Frameworks:** React | **Impacto:** M
|
|
107
|
+
**Sinal de deteccao:**
|
|
108
|
+
```bash
|
|
109
|
+
grep -rn '\.filter(\|\.sort(\|\.reduce(' src/ --include="*.tsx" --include="*.jsx" | grep -v 'useMemo'
|
|
110
|
+
```
|
|
111
|
+
**Exemplo ruim:**
|
|
112
|
+
```tsx
|
|
113
|
+
const results = items.filter(i => i.name.includes(query)).sort((a, b) => b.score - a.score); // recalcula a cada render
|
|
114
|
+
```
|
|
115
|
+
**Solucao:**
|
|
116
|
+
```tsx
|
|
117
|
+
const results = useMemo(() => items.filter(i => i.name.includes(query)).sort((a, b) => b.score - a.score), [items, query]);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### MISSING-KEY-STABLE
|
|
121
|
+
**Frameworks:** React, Vue | **Impacto:** M
|
|
122
|
+
**Sinal de deteccao:**
|
|
123
|
+
```bash
|
|
124
|
+
grep -rn 'key={i}\|key={index}\|key={idx}' src/ --include="*.tsx" --include="*.jsx"
|
|
125
|
+
```
|
|
126
|
+
**Exemplo ruim:**
|
|
127
|
+
```tsx
|
|
128
|
+
{items.map((item, index) => <ListItem key={index} item={item} />)} // reordenacao causa reconciliacao completa
|
|
129
|
+
```
|
|
130
|
+
**Solucao:** Usar ID estavel: `key={item.id}`.
|
|
131
|
+
|
|
132
|
+
</category>
|
|
133
|
+
|
|
134
|
+
<category name="bundle">
|
|
135
|
+
|
|
136
|
+
## Bundle Size
|
|
137
|
+
|
|
138
|
+
### FULL-LIBRARY-IMPORT
|
|
139
|
+
**Frameworks:** All | **Impacto:** G
|
|
140
|
+
**Sinal de deteccao:**
|
|
141
|
+
```bash
|
|
142
|
+
grep -rn "from 'lodash'" src/ --include="*.ts" --include="*.tsx" | grep -v 'lodash/'
|
|
143
|
+
```
|
|
144
|
+
**Exemplo ruim:**
|
|
145
|
+
```typescript
|
|
146
|
+
import _ from 'lodash'; // 70KB para usar 1 funcao
|
|
147
|
+
```
|
|
148
|
+
**Solucao:**
|
|
149
|
+
```typescript
|
|
150
|
+
import debounce from 'lodash/debounce'; // ~2KB
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### HEAVY-DEP-WITH-ALTERNATIVE
|
|
154
|
+
**Frameworks:** All | **Impacto:** G
|
|
155
|
+
**Sinal de deteccao:**
|
|
156
|
+
```bash
|
|
157
|
+
grep -E '"(moment|lodash|jquery|axios|classnames|uuid)"' package.json
|
|
158
|
+
```
|
|
159
|
+
**Tabela:** moment(67KB)->dayjs(2KB), lodash(70KB)->lodash-es, jquery(87KB)->DOM nativo, axios(13KB)->fetch, classnames(1KB)->clsx(0.5KB), uuid(3KB)->crypto.randomUUID().
|
|
160
|
+
|
|
161
|
+
### MISSING-CODE-SPLITTING
|
|
162
|
+
**Frameworks:** React, Vue, Svelte | **Impacto:** G
|
|
163
|
+
**Sinal de deteccao:**
|
|
164
|
+
```bash
|
|
165
|
+
grep -rn "import .* from '.*/pages/" src/ --include="*.tsx" --include="*.ts" | grep -v 'lazy\|dynamic\|import('
|
|
166
|
+
```
|
|
167
|
+
**Exemplo ruim:**
|
|
168
|
+
```typescript
|
|
169
|
+
import Dashboard from './pages/Dashboard'; // todas as paginas no bundle inicial
|
|
170
|
+
```
|
|
171
|
+
**Solucao:**
|
|
172
|
+
```typescript
|
|
173
|
+
const Dashboard = lazy(() => import('./pages/Dashboard')); // chunk separado por rota
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### DEV-DEP-IN-DEPENDENCIES
|
|
177
|
+
**Frameworks:** All | **Impacto:** M
|
|
178
|
+
**Sinal de deteccao:**
|
|
179
|
+
```bash
|
|
180
|
+
node -e "const p=require('./package.json'),d=Object.keys(p.dependencies||{}),t=['eslint','prettier','jest','vitest','typescript','@types/','storybook','husky','nodemon'];d.filter(x=>t.some(k=>x.includes(k))).forEach(x=>console.log('DEV em deps:',x))"
|
|
181
|
+
```
|
|
182
|
+
Ferramentas de desenvolvimento devem estar em `devDependencies`, nao `dependencies`.
|
|
183
|
+
|
|
184
|
+
### UNNECESSARY-POLYFILLS
|
|
185
|
+
**Frameworks:** All | **Impacto:** M
|
|
186
|
+
**Sinal de deteccao:**
|
|
187
|
+
```bash
|
|
188
|
+
grep -rn "core-js\|@babel/polyfill\|regenerator-runtime" src/ --include="*.ts" --include="*.js"
|
|
189
|
+
```
|
|
190
|
+
Remover polyfills se browserslist target >= ES2020. Usar `useBuiltIns: 'usage'` para incluir apenas o necessario.
|
|
191
|
+
|
|
192
|
+
### BARREL-FILE-BLOAT
|
|
193
|
+
**Frameworks:** All | **Impacto:** M
|
|
194
|
+
**Sinal de deteccao:**
|
|
195
|
+
```bash
|
|
196
|
+
grep -rn "export \* from" src/ --include="index.ts" --include="index.tsx"
|
|
197
|
+
```
|
|
198
|
+
**Exemplo ruim:**
|
|
199
|
+
```typescript
|
|
200
|
+
export * from './Button'; export * from './DataGrid'; // barrel re-exporta tudo
|
|
201
|
+
import { Button } from '../components'; // bundler pode incluir DataGrid tambem
|
|
202
|
+
```
|
|
203
|
+
**Solucao:** Import direto: `import { Button } from '../components/Button'`.
|
|
204
|
+
|
|
205
|
+
</category>
|
|
206
|
+
|
|
207
|
+
<category name="queries">
|
|
208
|
+
|
|
209
|
+
## Queries e Acesso a Dados
|
|
210
|
+
|
|
211
|
+
### N-PLUS-ONE
|
|
212
|
+
**Frameworks:** All | **Impacto:** G
|
|
213
|
+
**Sinal de deteccao:**
|
|
214
|
+
```bash
|
|
215
|
+
grep -rn 'for.*await.*find\|forEach.*await.*find\|\.map(.*await.*find' src/ --include="*.ts" --include="*.js"
|
|
216
|
+
```
|
|
217
|
+
**Exemplo ruim:**
|
|
218
|
+
```typescript
|
|
219
|
+
const orders = await prisma.order.findMany();
|
|
220
|
+
for (const o of orders) { o.user = await prisma.user.findUnique({ where: { id: o.userId } }); } // N queries extras
|
|
221
|
+
```
|
|
222
|
+
**Solucao:**
|
|
223
|
+
```typescript
|
|
224
|
+
const orders = await prisma.order.findMany({ include: { user: { select: { name: true } } } }); // 1 query
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### MISSING-PAGINATION
|
|
228
|
+
**Frameworks:** All | **Impacto:** G
|
|
229
|
+
**Sinal de deteccao:**
|
|
230
|
+
```bash
|
|
231
|
+
grep -rn 'findMany(\s*)' src/ --include="*.ts" | grep -v 'take\|skip\|cursor'
|
|
232
|
+
grep -rn 'SELECT.*FROM' src/ --include="*.ts" | grep -vi 'LIMIT'
|
|
233
|
+
```
|
|
234
|
+
Endpoints que retornam listas sem `take`/`limit` podem causar OOM com dados grandes. Adicionar paginacao com cursor ou offset.
|
|
235
|
+
|
|
236
|
+
### SELECT-ALL-FIELDS
|
|
237
|
+
**Frameworks:** All | **Impacto:** M
|
|
238
|
+
**Sinal de deteccao:**
|
|
239
|
+
```bash
|
|
240
|
+
grep -rn 'findMany()' src/ --include="*.ts"
|
|
241
|
+
grep -rn 'SELECT \*' src/ --include="*.ts" --include="*.sql"
|
|
242
|
+
```
|
|
243
|
+
Retornar apenas campos necessarios via `select`. Evitar `SELECT *` e `findMany()` sem select.
|
|
244
|
+
|
|
245
|
+
### MISSING-INDEX-HINT
|
|
246
|
+
**Frameworks:** All | **Impacto:** G
|
|
247
|
+
**Sinal de deteccao:**
|
|
248
|
+
```bash
|
|
249
|
+
grep -rn 'where:' src/ --include="*.ts" -A 5 | grep -v 'id:'
|
|
250
|
+
grep -rn '@@index\|@unique' prisma/schema.prisma 2>/dev/null
|
|
251
|
+
```
|
|
252
|
+
Campos usados em WHERE/ORDER BY devem ter indice. Verificar schema Prisma por `@@index` nos campos filtrados.
|
|
253
|
+
|
|
254
|
+
### MISSING-EAGER-LOADING
|
|
255
|
+
**Frameworks:** Sequelize, TypeORM | **Impacto:** G
|
|
256
|
+
**Sinal de deteccao:**
|
|
257
|
+
```bash
|
|
258
|
+
grep -rn '\.getUser\|\.getOrders\|\.getPosts' src/ --include="*.ts" --include="*.js"
|
|
259
|
+
```
|
|
260
|
+
Getters de associacao em loop causam N+1. Usar `include` para eager loading.
|
|
261
|
+
|
|
262
|
+
</category>
|
|
263
|
+
|
|
264
|
+
<category name="assets">
|
|
265
|
+
|
|
266
|
+
## Assets e Midia
|
|
267
|
+
|
|
268
|
+
### IMG-MISSING-DIMENSIONS
|
|
269
|
+
**Frameworks:** All | **Impacto:** G
|
|
270
|
+
**Sinal de deteccao:**
|
|
271
|
+
```bash
|
|
272
|
+
grep -rn '<img' src/ --include="*.tsx" --include="*.jsx" --include="*.html" | grep -v 'width\|height\|fill'
|
|
273
|
+
```
|
|
274
|
+
Imagens sem width/height causam CLS (Cumulative Layout Shift). Adicionar dimensoes explicitas ou `fill` (Next.js Image).
|
|
275
|
+
|
|
276
|
+
### IMG-MISSING-LAZY
|
|
277
|
+
**Frameworks:** All | **Impacto:** M
|
|
278
|
+
**Sinal de deteccao:**
|
|
279
|
+
```bash
|
|
280
|
+
grep -rn '<img' src/ --include="*.tsx" --include="*.jsx" | grep -v 'loading=\|priority'
|
|
281
|
+
```
|
|
282
|
+
Imagens abaixo do fold devem ter `loading="lazy"`. Imagens acima do fold (hero) devem ser `eager`/`priority`.
|
|
283
|
+
|
|
284
|
+
### FONT-MISSING-DISPLAY
|
|
285
|
+
**Frameworks:** All | **Impacto:** M
|
|
286
|
+
**Sinal de deteccao:**
|
|
287
|
+
```bash
|
|
288
|
+
grep -rn '@font-face' src/ --include="*.css" --include="*.scss" -A 5 | grep -v 'font-display'
|
|
289
|
+
grep -rn 'fonts.googleapis.com' src/ --include="*.html" --include="*.tsx" | grep -v 'display='
|
|
290
|
+
```
|
|
291
|
+
`@font-face` sem `font-display: swap` causa FOIT (texto invisivel). Google Fonts deve incluir `&display=swap`.
|
|
292
|
+
|
|
293
|
+
### LARGE-INLINE-SVG
|
|
294
|
+
**Frameworks:** React, Vue, Svelte | **Impacto:** P
|
|
295
|
+
**Sinal de deteccao:**
|
|
296
|
+
```bash
|
|
297
|
+
grep -rn '<svg' src/ --include="*.tsx" --include="*.jsx"
|
|
298
|
+
```
|
|
299
|
+
SVGs inline grandes (>50 linhas) devem ser importados como componentes ou usar sprite sheet.
|
|
300
|
+
|
|
301
|
+
### UNOPTIMIZED-IMG-FORMAT
|
|
302
|
+
**Frameworks:** All | **Impacto:** M
|
|
303
|
+
**Sinal de deteccao:**
|
|
304
|
+
```bash
|
|
305
|
+
grep -rn '\.png"\|\.jpg"\|\.jpeg"' src/ --include="*.tsx" --include="*.jsx" --include="*.html" | grep -v 'favicon'
|
|
306
|
+
```
|
|
307
|
+
Usar `<picture>` com WebP/AVIF e fallback, ou Next.js `<Image>` que otimiza automaticamente.
|
|
308
|
+
|
|
309
|
+
</category>
|
|
310
|
+
|
|
311
|
+
<category name="css">
|
|
312
|
+
|
|
313
|
+
## CSS e Layout
|
|
314
|
+
|
|
315
|
+
### EXPENSIVE-SELECTORS
|
|
316
|
+
**Frameworks:** All (menos relevante com Tailwind) | **Impacto:** P
|
|
317
|
+
**Sinal de deteccao:**
|
|
318
|
+
```bash
|
|
319
|
+
grep -rn '\* {' src/ --include="*.css" --include="*.scss" | grep -v ':root\|html'
|
|
320
|
+
```
|
|
321
|
+
Seletores universais (`*`) e descendentes profundos (`.a .b .c .d .e`) sao custosos. Preferir classes diretas.
|
|
322
|
+
|
|
323
|
+
### LAYOUT-THRASHING
|
|
324
|
+
**Frameworks:** All | **Impacto:** G
|
|
325
|
+
**Sinal de deteccao:**
|
|
326
|
+
```bash
|
|
327
|
+
grep -rn 'offsetHeight\|offsetWidth\|clientHeight\|getBoundingClientRect' src/ --include="*.ts" --include="*.tsx"
|
|
328
|
+
```
|
|
329
|
+
**Exemplo ruim:**
|
|
330
|
+
```typescript
|
|
331
|
+
cards.forEach(c => { const h = c.offsetHeight; c.style.height = h + 10 + 'px'; }); // leitura+escrita alternada
|
|
332
|
+
```
|
|
333
|
+
**Solucao:** Batch: todas as leituras primeiro, depois todas as escritas.
|
|
334
|
+
|
|
335
|
+
### NON-COMPOSITED-ANIMATIONS
|
|
336
|
+
**Frameworks:** All | **Impacto:** M
|
|
337
|
+
**Sinal de deteccao:**
|
|
338
|
+
```bash
|
|
339
|
+
grep -rn 'transition\|animation' src/ --include="*.css" --include="*.scss" | grep -i 'width\|height\|top\|left\|margin'
|
|
340
|
+
```
|
|
341
|
+
Animar `width/height/top/left` causa layout a cada frame. Usar `transform` e `opacity` (GPU-composited, 60fps).
|
|
342
|
+
|
|
343
|
+
### UNUSED-CSS-LARGE
|
|
344
|
+
**Frameworks:** All (menos relevante com Tailwind purge) | **Impacto:** M
|
|
345
|
+
**Sinal de deteccao:**
|
|
346
|
+
```bash
|
|
347
|
+
grep -rn "import 'bootstrap'" src/ --include="*.ts" --include="*.tsx" --include="*.js"
|
|
348
|
+
```
|
|
349
|
+
Import de CSS inteiro de frameworks (Bootstrap 250KB) quando so 10% e usado. Importar modulos especificos ou usar PurgeCSS.
|
|
350
|
+
|
|
351
|
+
</category>
|
|
352
|
+
|
|
353
|
+
<category name="network">
|
|
354
|
+
|
|
355
|
+
## Rede e Comunicacao
|
|
356
|
+
|
|
357
|
+
### FETCH-WATERFALL
|
|
358
|
+
**Frameworks:** All | **Impacto:** G
|
|
359
|
+
**Sinal de deteccao:**
|
|
360
|
+
```bash
|
|
361
|
+
grep -rn 'await.*fetch\|await.*axios' src/ --include="*.ts" --include="*.tsx" -A 1 | grep -B 1 'await.*fetch\|await.*axios'
|
|
362
|
+
```
|
|
363
|
+
**Exemplo ruim:**
|
|
364
|
+
```typescript
|
|
365
|
+
const user = await fetch('/api/user').then(r => r.json());
|
|
366
|
+
const orders = await fetch('/api/orders').then(r => r.json()); // espera user terminar
|
|
367
|
+
```
|
|
368
|
+
**Solucao:**
|
|
369
|
+
```typescript
|
|
370
|
+
const [user, orders] = await Promise.all([fetch('/api/user').then(r=>r.json()), fetch('/api/orders').then(r=>r.json())]);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### MISSING-CACHE-HEADERS
|
|
374
|
+
**Frameworks:** All (backend) | **Impacto:** M
|
|
375
|
+
**Sinal de deteccao:**
|
|
376
|
+
```bash
|
|
377
|
+
grep -rn 'Cache-Control\|max-age\|stale-while-revalidate' src/ --include="*.ts" --include="*.js"
|
|
378
|
+
```
|
|
379
|
+
Respostas de dados que mudam raramente devem ter `Cache-Control: public, max-age=3600, stale-while-revalidate=86400`.
|
|
380
|
+
|
|
381
|
+
### LARGE-JSON-PAYLOAD
|
|
382
|
+
**Frameworks:** All | **Impacto:** M
|
|
383
|
+
**Sinal de deteccao:**
|
|
384
|
+
```bash
|
|
385
|
+
grep -rn 'include:' src/ --include="*.ts" -A 10 | grep -c 'include:'
|
|
386
|
+
```
|
|
387
|
+
Includes aninhados profundos geram payloads de MB. Usar `select` para limitar campos e paginar resultados.
|
|
388
|
+
|
|
389
|
+
### MISSING-COMPRESSION
|
|
390
|
+
**Frameworks:** All (backend) | **Impacto:** M
|
|
391
|
+
**Sinal de deteccao:**
|
|
392
|
+
```bash
|
|
393
|
+
grep -E '"compression"' package.json
|
|
394
|
+
grep -rn "from 'compression'" src/ --include="*.ts" --include="*.js"
|
|
395
|
+
```
|
|
396
|
+
Express sem middleware `compression` envia JSON sem gzip. Compressao reduz payloads em 70-90%.
|
|
397
|
+
|
|
398
|
+
</category>
|
|
399
|
+
|
|
400
|
+
<category name="configs">
|
|
401
|
+
|
|
402
|
+
## Configuracao e Build
|
|
403
|
+
|
|
404
|
+
### SOURCEMAPS-IN-PROD
|
|
405
|
+
**Frameworks:** All | **Impacto:** M
|
|
406
|
+
**Sinal de deteccao:**
|
|
407
|
+
```bash
|
|
408
|
+
grep -rn 'sourcemap\|sourceMap\|devtool.*source-map' webpack.config.* vite.config.* next.config.* tsconfig.json 2>/dev/null
|
|
409
|
+
```
|
|
410
|
+
Source maps em producao expoe codigo-fonte. Usar condicional: `devtool: process.env.NODE_ENV === 'production' ? false : 'source-map'`.
|
|
411
|
+
|
|
412
|
+
### CONSOLE-LOGS-IN-PROD
|
|
413
|
+
**Frameworks:** All | **Impacto:** P
|
|
414
|
+
**Sinal de deteccao:**
|
|
415
|
+
```bash
|
|
416
|
+
grep -rn 'console\.log\|console\.debug' src/ --include="*.ts" --include="*.tsx" | grep -v '\.test\.\|\.spec\.'
|
|
417
|
+
```
|
|
418
|
+
Console.log em producao polui o console e pode vazar dados sensiveis. Usar logger estruturado que respeita nivel por ambiente.
|
|
419
|
+
|
|
420
|
+
### HARDCODED-DEV-VALUES
|
|
421
|
+
**Frameworks:** All | **Impacto:** G
|
|
422
|
+
**Sinal de deteccao:**
|
|
423
|
+
```bash
|
|
424
|
+
grep -rn 'localhost\|127\.0\.0\.1' src/ --include="*.ts" --include="*.tsx" | grep -v '\.test\.\|\.spec\.'
|
|
425
|
+
```
|
|
426
|
+
URLs de localhost hardcoded quebram em producao. Usar `process.env.API_URL` com fallback para dev.
|
|
427
|
+
|
|
428
|
+
</category>
|
|
429
|
+
|
|
430
|
+
<category name="deps">
|
|
431
|
+
|
|
432
|
+
## Dependencias
|
|
433
|
+
|
|
434
|
+
### HEAVY-DEPS-TABLE
|
|
435
|
+
**Frameworks:** All | **Impacto:** G
|
|
436
|
+
**Sinal de deteccao:**
|
|
437
|
+
```bash
|
|
438
|
+
grep -E '"(moment|lodash|jquery|axios|classnames|uuid|underscore|request|bluebird|node-fetch)"' package.json
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
| Dependencia | Tamanho (gz) | Alternativa | Tamanho |
|
|
442
|
+
|-------------|-------------|-------------|---------|
|
|
443
|
+
| moment | 67KB | dayjs | 2KB |
|
|
444
|
+
| lodash | 70KB | lodash-es + cherry-pick | 2-5KB |
|
|
445
|
+
| jquery | 87KB | DOM nativo | 0KB |
|
|
446
|
+
| axios | 13KB | fetch nativo | 0KB |
|
|
447
|
+
| classnames | 1KB | clsx | 0.5KB |
|
|
448
|
+
| uuid | 3KB | crypto.randomUUID() | 0KB |
|
|
449
|
+
| underscore | 18KB | ES2020+ nativos | 0KB |
|
|
450
|
+
| request | 48KB | fetch/undici | 0KB |
|
|
451
|
+
| bluebird | 18KB | Promise nativo | 0KB |
|
|
452
|
+
| node-fetch | 8KB | fetch nativo (Node 18+) | 0KB |
|
|
453
|
+
|
|
454
|
+
### ABANDONED-DEPS
|
|
455
|
+
**Frameworks:** All | **Impacto:** M
|
|
456
|
+
**Sinal de deteccao:**
|
|
457
|
+
```bash
|
|
458
|
+
npm view $(node -e "Object.keys(require('./package.json').dependencies||{}).forEach(d=>process.stdout.write(d+' '))") time.modified 2>/dev/null
|
|
459
|
+
```
|
|
460
|
+
Dependencias com ultimo publish > 2 anos sao candidatas a substituicao. Verificar se README menciona "deprecated".
|
|
461
|
+
|
|
462
|
+
### DUPLICATE-PURPOSE-DEPS
|
|
463
|
+
**Frameworks:** All | **Impacto:** M
|
|
464
|
+
**Sinal de deteccao:**
|
|
465
|
+
```bash
|
|
466
|
+
node -e "const d=Object.keys({...require('./package.json').dependencies,...require('./package.json').devDependencies}),g={datas:['moment','dayjs','date-fns','luxon'],http:['axios','got','node-fetch','request','undici'],utils:['lodash','underscore','ramda'],css:['styled-components','@emotion/react','styled-jsx'],state:['redux','mobx','zustand','jotai','recoil']};Object.entries(g).forEach(([c,l])=>{const f=l.filter(x=>d.includes(x));if(f.length>1)console.log(c+': '+f.join(', '))})"
|
|
467
|
+
```
|
|
468
|
+
Multiplas libs para o mesmo proposito (ex: moment + date-fns) inflam bundle. Consolidar em uma.
|
|
469
|
+
|
|
470
|
+
### KNOWN-VULNERABILITIES
|
|
471
|
+
**Frameworks:** All | **Impacto:** G
|
|
472
|
+
**Sinal de deteccao:**
|
|
473
|
+
```bash
|
|
474
|
+
npm audit --json 2>/dev/null | node -e "try{const a=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')),v=a.metadata?.vulnerabilities||{};if(v.high||v.critical)console.log('CRITICO:',v.critical||0,'criticas,',v.high||0,'altas')}catch(e){}"
|
|
475
|
+
```
|
|
476
|
+
Dependencias com vulnerabilidades high/critical devem ser atualizadas via `npm audit fix`.
|
|
477
|
+
|
|
478
|
+
</category>
|