Skip to content

Developer Experience

O Slash oferece ferramentas de desenvolvimento para ajudar a detectar anti-patterns, problemas de performance e uso incorreto da API durante o desenvolvimento.

O modo de desenvolvimento (DEV_MODE) é ativado automaticamente quando process.env.NODE_ENV !== 'production'. Neste modo, o Slash executa validações extras e emite warnings/errors no console.

import { setDevMode, isDevMode } from '@_bashell/slash';
// Desabilitar modo dev (não recomendado)
setDevMode(false);
// Verificar se está em modo dev
if (isDevMode()) {
console.log('Rodando em modo de desenvolvimento');
}
// Em produção, o dev mode é desabilitado automaticamente

O pacote publica dois builds de cada entrada (core, router, forms, ssr). O build de desenvolvimento (dist/dev) traz os avisos de segurança e de uso; o de produção (dist) não os traz. A escolha é feita pelas condições de exportação do package.json:

  • Vite em dev e webpack em mode: "development" resolvem a condição development e já usam o build com avisos, sem configuração.
  • Em produção (e no Node puro) vale o build sem avisos. Os erros de verdade (ErrorBoundary, guards do roteador, batch) continuam indo para console.error.
  • Para forçar os avisos fora de um bundler: node --conditions=development app.mjs.

As mensagens de aviso e de erro do runtime são todas em inglês.

Warnings ajudam a detectar potenciais problemas e anti-patterns no código.

import { setWarningsEnabled, isWarningsEnabled } from '@_bashell/slash';
// Desabilitar warnings (não recomendado)
setWarningsEnabled(false);
// Verificar se warnings estão habilitados
if (isWarningsEnabled()) {
console.log('Warnings habilitados');
}

O Slash detecta os seguintes anti-patterns:

import { createState } from '@_bashell/slash';
const state = createState({ count: 0 });
// ❌ Errado - mutação direta
// Emite erro: "Direct state mutation detected"
state.get().count = 5;
// ✅ Correto - usar .set()
state.set({ count: 5 });

Mensagem:

❌ [SLASH ERROR] [STATE_MUTATION] Direct state mutation detected.
Use state.set() instead of mutating state directly.
💡 Hint:
Replace: state.myProp = value
With: state.set({ ...state.get(), myProp: value })
import { createState } from '@_bashell/slash';
const state = createState({ count: 0 });
// ❌ Errado - não limpa o watcher
function setupWatcher() {
state.watch((newVal) => {
console.log('Changed:', newVal);
});
// Se setupWatcher() for chamado múltiplas vezes,
// acumula watchers sem cleanup
}
// ✅ Correto - sempre fazer cleanup
function setupWatcher() {
const unwatch = state.watch((newVal) => {
console.log('Changed:', newVal);
});
// Cleanup quando componente for destruído
return unwatch;
}

Mensagem (quando excede 1000 watchers):

⚠️ [SLASH WARNING] [MEMORY_LEAK] High number of watchers detected (1234).
This may indicate missing cleanup.
💡 Hint:
Make sure to call the cleanup function returned by state.watch():
const unwatch = state.watch(callback);
// Later: unwatch();
import { createState } from '@_bashell/slash';
// ❌ Errado - objeto muito grande (>1000 chaves)
const hugeState = createState({
user1: {...},
user2: {...},
// ... 1000+ propriedades
});
// ✅ Correto - dividir em múltiplos estados
const usersState = createState({});
const settingsState = createState({});
const cacheState = createState({});

Mensagem:

⚠️ [SLASH WARNING] [PERFORMANCE] Large object detected in state
comparison (1234 keys). Consider breaking into smaller states.
💡 Hint:
Split large states into multiple smaller states for better performance:
const userState = createState({ ... });
const settingsState = createState({ ... });
import { html, createState } from '@_bashell/slash';
// ❌ Errado - estado criado dentro do componente
function Counter() {
const count = createState(0); // Warning!
return html`
<button onClick=${() => count.set(count.get() + 1)}>
Count: ${count.get()}
</button>
`;
}
// ✅ Correto - estado criado fora
const count = createState(0);
function Counter() {
return html`
<button onClick=${() => count.set(count.get() + 1)}>
Count: ${count.get()}
</button>
`;
}

Mensagem:

⚠️ [SLASH WARNING] [REACTIVITY_VIOLATION] State created inside component.
This may cause memory leaks and unexpected behavior.
💡 Hint:
Move state creation outside of component:
// ❌ Bad:
function MyComponent() {
const state = createState({});
}
// ✅ Good:
const myState = createState({});
function MyComponent() { ... }

Batches aninhados funcionam (as notificações ocorrem no fim do batch mais externo); o aviso existe porque o batch interno é redundante.

import { batch, createState } from '@_bashell/slash';
const state = createState({ a: 0, b: 0 });
// ❌ Errado - batch aninhado (redundante)
batch(() => {
batch(() => { // Warning!
state.set({ a: 1, b: 2 });
});
});
// ✅ Correto - um único batch
batch(() => {
state.set({ a: 1, b: 2 });
});

Mensagem:

⚠️ [SLASH WARNING] [API_MISUSE] Nested batch detected (depth: 2).
Nested batches are redundant.
💡 Hint:
Remove nested batch() calls:
// ❌ Bad:
batch(() => {
batch(() => { ... });
});
// ✅ Good:
batch(() => { ... });
import { createState } from '@_bashell/slash';
const state = createState({ count: 0 });
// ❌ Errado - null não é permitido
state.set(null); // Error!
// ❌ Errado - primitivos não são permitidos
state.set(42); // Error!
// ✅ Correto - sempre passar objeto
state.set({ count: 42 });

Mensagem:

❌ [SLASH ERROR] [TYPE_MISMATCH] State payload must be an object,
received number.
💡 Hint:
Pass an object to state.set():
state.set({ key: value });

Por padrão, erros são apenas logados no console. Para ambientes de teste, você pode configurar o Slash para lançar exceções:

import { setErrorsThrow } from '@_bashell/slash';
// Útil em testes - lança exceção ao invés de console.error
setErrorsThrow(true);
// Agora erros lançam exceção
const state = createState({ count: 0 });
state.set(null); // Throws Error!

Todas as mensagens de desenvolvimento seguem um formato consistente:

interface DevMessage {
type: 'error' | 'warning' | 'info';
category:
| 'STATE_MUTATION'
| 'REACTIVITY_VIOLATION'
| 'MEMORY_LEAK'
| 'PERFORMANCE'
| 'API_MISUSE'
| 'TYPE_MISMATCH';
message: string;
hint?: string;
context?: Record<string, unknown>;
timestamp: number;
}
CategoriaDescrição
STATE_MUTATIONMutação direta do estado detectada
REACTIVITY_VIOLATIONViolação de regras de reatividade
MEMORY_LEAKPotencial vazamento de memória
PERFORMANCEProblemas de performance
API_MISUSEUso incorreto da API
TYPE_MISMATCHTipo incorreto de valor

Em produção, todas as validações e warnings são desabilitados automaticamente para melhor performance. Se você precisa debugar em produção:

// Habilitar temporariamente em produção
import { setDevMode, setWarningsEnabled } from '@_bashell/slash';
if (window.location.search.includes('debug=true')) {
setDevMode(true);
setWarningsEnabled(true);
}
  1. Sempre rode em dev mode durante desenvolvimento

    • Detecta problemas cedo
    • Mensagens de erro claras e úteis
  2. Corrija todos os warnings

    • Warnings indicam potenciais bugs
    • Não ignore - investigue e corrija
  3. Use setErrorsThrow(true) em testes

    • Captura erros com expect().toThrow()
    • Garante que o código lida corretamente com erros
  4. Desabilite em produção

    • Dev mode é automaticamente desabilitado
    • Não force habilitação em produção
  5. Monitore watchers

    • Sempre faça cleanup de watchers
    • Use o warning de memory leak como alerta
import {
createState,
batch,
setDevMode,
setWarningsEnabled,
setErrorsThrow,
isDevMode
} from '@_bashell/slash';
// Configuração para testes
if (process.env.NODE_ENV === 'test') {
setErrorsThrow(true);
}
// Configuração para desenvolvimento
if (process.env.NODE_ENV === 'development') {
console.log('Dev mode:', isDevMode()); // true
}
// Estado fora do componente ✅
const appState = createState({
users: [],
loading: false,
error: null
});
// Componente com boas práticas
function UserList() {
// Cleanup de watcher ✅
const unwatch = appState.watch((state) => {
console.log('State changed:', state);
});
// Batch para múltiplas atualizações ✅
const loadUsers = async () => {
batch(() => {
appState.set({
...appState.get(),
loading: true,
error: null
});
});
try {
const users = await fetchUsers();
appState.set({
...appState.get(),
users,
loading: false
});
} catch (error) {
appState.set({
...appState.get(),
error: error.message,
loading: false
});
}
};
// Retornar cleanup
return {
destroy: unwatch
};
}