Voltar para o Blog

JavaScript Minimalista: Como Vencer o Framework Fatigue e Melhorar Performance em 2025

Olá HaWkers, você já se sentiu exausto com a quantidade absurda de frameworks, bibliotecas e ferramentas no ecossistema JavaScript? Não está sozinho. O framework fatigue é real, e uma nova tendência está surgindo em 2025: JavaScript minimalista.

Desenvolvedores e empresas estão percebendo que menos código = melhor performance = melhor SEO = mais dinheiro. Vamos explorar como simplificar seu stack sem perder produtividade.

O Problema: Bloated Code Está Matando Performance

Realidade em 2025: Bundle Size Out of Control

// Projeto JavaScript típico em 2023-2024
const typicalProject2024 = {
  react: "18.2.0", // 44kb gzipped
  reactDom: "18.2.0", // 132kb gzipped
  redux: "4.2.1", // 3kb
  reactRedux: "8.1.3", // 16kb
  reduxToolkit: "1.9.7", // 48kb
  reactRouter: "6.20.1", // 14kb
  axios: "1.6.2", // 13kb
  lodash: "4.17.21", // 24kb (se importar tudo)
  momentJs: "2.29.4", // 72kb (!) - deprecated mas ainda usado
  materialUI: "5.14.20", // 400kb+ (!)

  totalBundle: "~800kb gzipped", // 2.5-3MB uncompressed
  loadTime: "4-7s on 3G",
  lighthouseScore: "45-65 (poor)",
  seoImpact: "Penalizado pelo Google (Core Web Vitals)",
};

// Projeto minimalista em 2025
const minimalistProject2025 = {
  react: "18.3.0", // Still 44kb (necessário)
  reactDom: "18.3.0", // Still 132kb (necessário)
  zustand: "4.5.0", // 1.2kb (!!) - substitui Redux
  tinyRouter: "Custom", // 2kb - substitui React Router
  nativeFetch: "Built-in", // 0kb - substitui Axios
  nativeMethods: "Built-in", // 0kb - substitui Lodash
  dayjs: "1.11.10", // 2kb - substitui Moment.js
  tailwindCSS: "3.4.0", // 10kb runtime - substitui MUI

  totalBundle: "~200kb gzipped", // 600kb uncompressed
  loadTime: "1-2s on 3G",
  lighthouseScore: "90-100 (excellent)",
  seoImpact: "Favorecido pelo Google",

  savings: {
    bundleSize: "-75%",
    loadTime: "-60%",
    maintenanceCost: "-50%",
  },
};

Vanilla JS Renaissance: O Retorno aos Fundamentos

Quando Vanilla JS é Suficiente

// 1. Manipulação de DOM (não precisa de jQuery em 2025)

// ❌ Antes (jQuery): 30kb de overhead
$("#myButton").on("click", function () {
  $(this).addClass("active");
  $(".modal").fadeIn();
});

// ✅ Agora (Vanilla JS): 0kb overhead
document.getElementById("myButton").addEventListener("click", function () {
  this.classList.add("active");

  // Fade in com CSS + JS
  const modal = document.querySelector(".modal");
  modal.style.display = "block";
  modal.style.opacity = "0";

  requestAnimationFrame(() => {
    modal.style.transition = "opacity 0.3s";
    modal.style.opacity = "1";
  });
});

// 2. HTTP requests (Fetch API nativa)

// ❌ Antes (Axios): 13kb
import axios from "axios";
const { data } = await axios.get("/api/users", {
  params: { role: "admin" },
  timeout: 5000,
});

// ✅ Agora (Fetch nativo): 0kb
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

const response = await fetch("/api/users?role=admin", {
  signal: controller.signal,
});
clearTimeout(timeout);

const data = await response.json();

// Wrapper reutilizável
async function fetchWithTimeout(url, options = {}, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });

    clearTimeout(timeout);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === "AbortError") {
      throw new Error("Request timeout");
    }
    throw error;
  }
}

// 3. Array/Object manipulation (sem Lodash)

// ❌ Antes (Lodash): 24kb
import _ from "lodash";
const uniqueUsers = _.uniqBy(users, "id");
const grouped = _.groupBy(products, "category");
const debounced = _.debounce(handleSearch, 300);

// ✅ Agora (Native JS): 0kb
const uniqueUsers = [...new Map(users.map((u) => [u.id, u])).values()];

const grouped = products.reduce((acc, product) => {
  const category = product.category;
  if (!acc[category]) acc[category] = [];
  acc[category].push(product);
  return acc;
}, {});

function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), wait);
  };
}

const debouncedSearch = debounce(handleSearch, 300);

Web Components: Futuro do JavaScript Minimalista?

// Web Components nativos (zero dependencies)

class TodoList extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.todos = [];
  }

  connectedCallback() {
    this.render();
    this.attachEventListeners();
  }

  render() {
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
          font-family: system-ui;
        }

        .todo-item {
          display: flex;
          gap: 12px;
          padding: 12px;
          border-bottom: 1px solid #eee;
        }

        .todo-item.completed {
          opacity: 0.5;
          text-decoration: line-through;
        }

        button {
          background: #007bff;
          color: white;
          border: none;
          padding: 8px 16px;
          border-radius: 4px;
          cursor: pointer;
        }

        button:hover {
          background: #0056b3;
        }
      </style>

      <div class="todo-app">
        <input
          type="text"
          id="newTodo"
          placeholder="Add new todo..."
        />
        <button id="addBtn">Add</button>

        <div id="todoList">
          ${this.todos
            .map(
              (todo) => `
            <div class="todo-item ${todo.completed ? "completed" : ""}" data-id="${todo.id}">
              <input type="checkbox" ${todo.completed ? "checked" : ""} />
              <span>${todo.text}</span>
              <button class="delete-btn">Delete</button>
            </div>
          `
            )
            .join("")}
        </div>
      </div>
    `;
  }

  attachEventListeners() {
    const addBtn = this.shadowRoot.getElementById("addBtn");
    const input = this.shadowRoot.getElementById("newTodo");

    addBtn.addEventListener("click", () => {
      const text = input.value.trim();
      if (text) {
        this.addTodo(text);
        input.value = "";
      }
    });

    // Event delegation para checkboxes e delete buttons
    this.shadowRoot.addEventListener("change", (e) => {
      if (e.target.type === "checkbox") {
        const id = e.target.closest(".todo-item").dataset.id;
        this.toggleTodo(id);
      }
    });

    this.shadowRoot.addEventListener("click", (e) => {
      if (e.target.classList.contains("delete-btn")) {
        const id = e.target.closest(".todo-item").dataset.id;
        this.deleteTodo(id);
      }
    });
  }

  addTodo(text) {
    this.todos.push({
      id: Date.now().toString(),
      text,
      completed: false,
    });
    this.render();
    this.attachEventListeners();
  }

  toggleTodo(id) {
    const todo = this.todos.find((t) => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
      this.render();
      this.attachEventListeners();
    }
  }

  deleteTodo(id) {
    this.todos = this.todos.filter((t) => t.id !== id);
    this.render();
    this.attachEventListeners();
  }
}

// Registrar componente
customElements.define("todo-list", TodoList);

// Uso em HTML (zero build step!)
// <todo-list></todo-list>

// Bundle size: ~0kb (nativo do browser)
// Compatibilidade: Todos os browsers modernos (98%+ suporte)

Zustand vs Redux: A Revolução Minimalista

Por Que Zustand Está Dominando em 2025

// ❌ Redux Toolkit (ainda verboso mesmo "simplificado")
// Arquivos: store.ts, userSlice.ts, productSlice.ts, hooks.ts, etc.

// store.ts
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";
import productReducer from "./productSlice";

export const store = configureStore({
  reducer: {
    user: userReducer,
    products: productReducer,
  },
});

// userSlice.ts
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

export const fetchUser = createAsyncThunk("user/fetchUser", async (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
});

const userSlice = createSlice({
  name: "user",
  initialState: { data: null, loading: false, error: null },
  reducers: {
    setUser: (state, action) => {
      state.data = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export const { setUser } = userSlice.actions;
export default userSlice.reducer;

// Component
import { useDispatch, useSelector } from "react-redux";
function UserProfile() {
  const dispatch = useDispatch();
  const user = useSelector((state) => state.user.data);
  const loading = useSelector((state) => state.user.loading);

  useEffect(() => {
    dispatch(fetchUser("123"));
  }, []);

  // ...
}

// Total: ~100 linhas de código, múltiplos arquivos, 67kb bundle

// ✅ Zustand (minimalista e poderoso)
import { create } from "zustand";

interface UserState {
  user: User | null;
  loading: boolean;
  error: string | null;
  fetchUser: (id: string) => Promise<void>;
  setUser: (user: User) => void;
}

export const useUserStore = create<UserState>((set) => ({
  user: null,
  loading: false,
  error: null,

  fetchUser: async (id) => {
    set({ loading: true, error: null });
    try {
      const response = await fetch(`/api/users/${id}`);
      const user = await response.json();
      set({ user, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },

  setUser: (user) => set({ user }),
}));

// Component (MUITO mais simples)
function UserProfile() {
  const { user, loading, fetchUser } = useUserStore();

  useEffect(() => {
    fetchUser("123");
  }, []);

  if (loading) return <Spinner />;
  return <div>{user?.name}</div>;
}

// Total: ~30 linhas, 1 arquivo, 1.2kb bundle (-98%!)

Zustand: Casos de Uso Avançados

// Persistência automática
import { create } from "zustand";
import { persist } from "zustand/middleware";

export const useCartStore = create(
  persist(
    (set, get) => ({
      items: [],
      total: 0,

      addItem: (item) =>
        set((state) => {
          const existing = state.items.find((i) => i.id === item.id);
          if (existing) {
            return {
              items: state.items.map((i) =>
                i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
              ),
            };
          }
          return { items: [...state.items, { ...item, quantity: 1 }] };
        }),

      removeItem: (id) =>
        set((state) => ({
          items: state.items.filter((i) => i.id !== id),
        })),

      // Computed value (similar a getters)
      get total() {
        return get().items.reduce((sum, item) => sum + item.price * item.quantity, 0);
      },
    }),
    {
      name: "cart-storage", // localStorage key
      partialize: (state) => ({ items: state.items }), // Salva apenas items
    }
  )
);

// Slices pattern (organização avançada)
const createUserSlice = (set) => ({
  user: null,
  login: (credentials) => {
    /* ... */
  },
  logout: () => set({ user: null }),
});

const createProductSlice = (set) => ({
  products: [],
  fetchProducts: async () => {
    /* ... */
  },
});

export const useStore = create((...a) => ({
  ...createUserSlice(...a),
  ...createProductSlice(...a),
}));

// Middleware customizado (DevTools, logging, etc)
import { devtools, subscribeWithSelector } from "zustand/middleware";

export const useStore = create(
  devtools(
    subscribeWithSelector((set) => ({
      // State aqui
    })),
    { name: "MyAppStore" }
  )
);

// Subscribe a mudanças específicas
useStore.subscribe(
  (state) => state.user,
  (user, prevUser) => {
    console.log("User changed:", { user, prevUser });
    // Analytics, side effects, etc
  }
);

Jotai: O Minimalista Atômico

// Jotai: Ainda mais minimalista que Zustand (atoms approach)

import { atom, useAtom } from "jotai";

// 1. Atoms básicos
const countAtom = atom(0);
const userAtom = atom(null);

// Component
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  return (
    <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
  );
}

// 2. Derived atoms (computed values)
const todosAtom = atom([]);
const completedTodosAtom = atom((get) =>
  get(todosAtom).filter((t) => t.completed)
);

const todoStatsAtom = atom((get) => {
  const todos = get(todosAtom);
  return {
    total: todos.length,
    completed: get(completedTodosAtom).length,
    pending: todos.filter((t) => !t.completed).length,
  };
});

// Component
function TodoStats() {
  const [stats] = useAtom(todoStatsAtom);
  return (
    <div>
      Total: {stats.total} | Completed: {stats.completed} | Pending:{" "}
      {stats.pending}
    </div>
  );
}

// 3. Async atoms
const userIdAtom = atom("123");

const userAtom = atom(async (get) => {
  const userId = get(userIdAtom);
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
});

// Component (Suspense automático!)
function UserProfile() {
  const [user] = useAtom(userAtom);
  // user é Promise, mas Suspense handle isso
  return <div>{user.name}</div>;
}

// Wrapper com Suspense
function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile />
    </Suspense>
  );
}

// 4. Write-only atoms (actions)
const todosAtom = atom([]);

const addTodoAtom = atom(
  null, // read function (null = write-only)
  (get, set, newTodo) => {
    set(todosAtom, [...get(todosAtom), newTodo]);
  }
);

const removeTodoAtom = atom(null, (get, set, id) => {
  set(
    todosAtom,
    get(todosAtom).filter((t) => t.id !== id)
  );
});

// Component
function AddTodo() {
  const [, addTodo] = useAtom(addTodoAtom);
  return (
    <button onClick={() => addTodo({ id: Date.now(), text: "New todo" })}>
      Add
    </button>
  );
}

// Bundle: 3kb (!!!) - Menor que Zustand
// Performance: Excelente (re-renders apenas quando atom específico muda)

Zustand vs Jotai: Quando Usar Cada Um

const stateManagementGuide = {
  useZustand: {
    when: [
      "Store global centralizado",
      "Muitas actions complexas",
      "Familiaridade com Redux",
      "Persistência localStorage",
      "DevTools essencial",
    ],
    examples: ["E-commerce (cart)", "Auth state", "Theme/settings"],
    size: "1.2kb",
  },

  useJotai: {
    when: [
      "State granular (muitos atoms pequenos)",
      "Derived state complexo",
      "Suspense/async nativo",
      "Performance crítica (evitar re-renders)",
      "Bundle size crítico",
    ],
    examples: ["Formulários complexos", "Real-time dashboards", "Game state"],
    size: "3kb",
  },

  useReact: {
    when: ["State local simples", "Componente isolado", "Protótipos rápidos"],
    examples: ["Toggles", "Form inputs", "Modals"],
    size: "0kb (built-in)",
  },
};

Performance: Métricas Reais

// Benchmark: Aplicação e-commerce (10k produtos)

const performanceComparison = {
  heavyStack: {
    // React + Redux Toolkit + MUI + Lodash + Moment
    bundleSize: "850kb gzipped",
    fcp: "2.8s", // First Contentful Paint
    lcp: "4.5s", // Largest Contentful Paint
    tti: "5.2s", // Time to Interactive
    tbt: "890ms", // Total Blocking Time
    cls: "0.15", // Cumulative Layout Shift
    lighthouse: "58/100",
    seoImpact: "Penalizado (-15% tráfego orgânico)",
  },

  minimalistStack: {
    // React + Zustand + Tailwind + Native JS + day.js
    bundleSize: "220kb gzipped",
    fcp: "0.9s",
    lcp: "1.4s",
    tti: "1.8s",
    tbt: "120ms",
    cls: "0.02",
    lighthouse: "94/100",
    seoImpact: "Favorecido (+18% tráfego orgânico)",
  },

  improvements: {
    bundleSize: "-74%",
    lcp: "-68%",
    tti: "-65%",
    tbt: "-86%",
    lighthouse: "+62%",
    seoTraffic: "+33%",
  },

  businessImpact: {
    conversionRate: "+12% (faster load = more sales)",
    bounceRate: "-28%",
    seoRevenue: "+$45k/month (small e-commerce)",
  },
};

Estratégias de Dependency Reduction

// Auditoria de dependencies

// 1. Analisar bundle atual
// npx webpack-bundle-analyzer dist/stats.json

// 2. Encontrar substitutos menores
const replacements = {
  moment: "day.js", // 72kb → 2kb (-97%)
  lodash: "native JS", // 24kb → 0kb (-100%)
  axios: "native fetch", // 13kb → 0kb (-100%)
  uuid: "crypto.randomUUID()", // 4kb → 0kb (-100%)
  classnames: "clsx", // 1.5kb → 0.5kb (-66%)
  redux: "zustand", // 67kb → 1.2kb (-98%)
};

// 3. Tree-shaking correto
// ❌ Importa tudo
import _ from "lodash";
import * as MUI from "@mui/material";

// ✅ Importa apenas necessário
import debounce from "lodash/debounce";
import { Button, TextField } from "@mui/material";

// 4. Lazy loading
// ❌ Carrega tudo upfront
import Dashboard from "./Dashboard";
import Settings from "./Settings";
import Analytics from "./Analytics";

// ✅ Carrega sob demanda
const Dashboard = lazy(() => import("./Dashboard"));
const Settings = lazy(() => import("./Settings"));
const Analytics = lazy(() => import("./Analytics"));

// 5. Remover código morto
// npx depcheck (mostra deps não usadas)
// npx unimported (mostra imports não usados)

// 6. CDN para libs específicas
// Exemplo: Chart.js (só usado em 1 página)
// <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
// Não inclui no bundle principal

Conclusão: Less is More em 2025

O JavaScript minimalista não é regredir — é evoluir para soluções mais eficientes.

Realidade comprovada:

const minimalistBenefits = {
  performance: "+60-80% faster load times",
  seo: "+15-30% tráfego orgânico",
  maintenance: "-50% tempo debugando dependencies",
  development: "Feature velocity igual ou maior (IA compensa)",
  costs: "-30% custos de infraestrutura (menos CDN, menos compute)",
};

const actionPlan = {
  immediate: [
    "Audit dependencies (npx depcheck)",
    "Substituir Lodash por native JS",
    "Considerar Zustand se usa Redux",
    "Lazy load rotas pesadas",
  ],
  shortTerm: [
    "Refactor para Vanilla JS onde faz sentido",
    "Avaliar Jotai para state granular",
    "Implementar code splitting agressivo",
  ],
  longTerm: ["Web Components para design system", "Zero-dependency culture"],
};

Menos código, mais resultado.

Se você quer entender mais sobre performance web moderna, recomendo: WebAssembly + JavaScript Performance.

Bora pra cima! 🦅

📚 Quer Dominar JavaScript Moderno?

Este artigo mostrou técnicas minimalistas, mas dominar JavaScript sólido é fundamental para aplicá-las corretamente.

Material de Estudo Completo

Preparei um guia completo de JavaScript do básico ao avançado com foco em performance e boas práticas:

Opções de investimento:

  • R$9,90 (pagamento único)

👉 Conhecer o Guia JavaScript

💡 Fundamentos sólidos para construir aplicações minimalistas e performáticas

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário