Um arquivo, uma tag <script>, e sua página passa a ser reativa. Proxy recursivo, dependency tracking granular, ~8.7KB gzip. Funciona em qualquer stack.
Cada exemplo abaixo está rodando de verdade com shadow-proxy. Edite, clique, experimente.
<div x-data="produto"> <h2 x-bind="{nome}"></h2> <p x-bind="R$ {preco}"></p> <div x-if="ativo">✅ Disponível</div> <div x-if="!ativo">❌ Indisponível</div> </div> <input x-model="produto.nome"> <script> shadowProxy.initProxy(); shadowProxy.template.produto.nome = "Coxinha Supreme"; shadowProxy.template.produto.preco = 9.90; shadowProxy.template.produto.ativo = true; </script>
<div x-data="tarefas"> <div x-for="item, k in lista"> <div class="todo-check {item.done ? 'done' : ''}" x-on:click="toggleTodo(k)" x-bind="{item.done ? '✓' : ''}"></div> <span class="todo-text {item.done ? 'done' : ''}" x-bind="{item.texto}"></span> <button x-on:click="removeTodo(k)">✕</button> </div> </div> <input id="todo-input" type="text"> <button x-on:click="addTodo()">+ Adicionar</button> <script> shadowProxy.initProxy(); shadowProxy.template.tarefas.lista = [ { texto: "Aprender shadow-proxy", done: false }, { texto: "Refatorar o projeto", done: false }, ]; // só muda o dado — classe "done", ✓ e riscado seguem sozinhos shadowProxy.template.tarefas.toggleTodo = function(k) { var item = shadowProxy.template.tarefas.lista[k]; item.done = !item.done; }; shadowProxy.template.tarefas.removeTodo = function(k) { shadowProxy.template.tarefas.lista.splice(k, 1); }; shadowProxy.template.tarefas.addTodo = function() { var inp = document.getElementById('todo-input'); if (!inp.value.trim()) return; shadowProxy.template.tarefas.lista.push({ texto: inp.value, done: false }); inp.value = ''; }; </script>
<div x-data="contador"> <h2 x-bind="{valor}"></h2> </div> <input type="range" x-model="contador.valor"> <script> shadowProxy.initProxy(); shadowProxy.template.contador.valor = 50; // Observa qualquer mudança no valor shadowProxy.on('contador.valor', (novo, velho) => { console.log(`${velho} → ${novo}`); }); </script>
<div x-data="carrinho"> <div x-for="item, k in itens"> <span x-bind="{item.nome}"></span> <span x-bind="R$ {item.preco}"></span> <button x-on:click="decrementar(k)">-</button> <span x-bind="{item.qty}"></span> <button x-on:click="incrementar(k)">+</button> </div> <div>Total: <span x-bind="R$ {total}"></span></div> </div> <script> shadowProxy.initProxy(); shadowProxy.template.carrinho.itens = [ { nome: "Teclado Mecânico", preco: 249.90, qty: 1 }, { nome: "Mouse Gamer", preco: 89.90, qty: 2 }, ]; // shadowProxy.on ouve o array inteiro — muda quando qualquer item muda function recalcularTotal() { var itens = shadowProxy.template.carrinho.itens; var soma = itens.reduce(function(acc, i) { return acc + i.preco * i.qty; }, 0); shadowProxy.template.carrinho.total = soma.toFixed(2); } recalcularTotal(); shadowProxy.on('carrinho.itens', recalcularTotal); shadowProxy.template.carrinho.incrementar = function(k) { shadowProxy.template.carrinho.itens[k].qty++; }; shadowProxy.template.carrinho.decrementar = function(k) { var item = shadowProxy.template.carrinho.itens[k]; if (item.qty > 1) item.qty--; }; </script>
<div x-data="busca"> <input type="text" x-model="busca.termo" placeholder="Buscar produto..."> <div x-for="item in resultado"> <div x-bind="{item.nome}"></div> </div> <p x-if="resultado.length === 0">Nada encontrado</p> </div> <script> shadowProxy.initProxy(); shadowProxy.template.busca.produtos = [ { nome: "Teclado Mecânico" }, { nome: "Mouse Gamer" }, { nome: "Monitor 4K" }, { nome: "Headset" }, ]; shadowProxy.template.busca.termo = ''; function filtrar() { var termo = shadowProxy.template.busca.termo.toLowerCase(); shadowProxy.template.busca.resultado = shadowProxy.template.busca.produtos.filter(function(p) { return p.nome.toLowerCase().includes(termo); }); } filtrar(); shadowProxy.on('busca.termo', filtrar); </script>
Nada encontrado
// registra o handler global de erros ANTES de tudo shadowProxy.onError = function(err) { if (err.type === 'x-on-unsafe') { logConsole('🚫 Bloqueado: ' + err.expr, false); } }; // tenta bindar uma expressão perigosa em runtime shadowProxy.template.seguranca.testarPerigoso = function() { var el = document.createElement('button'); el.setAttribute('x-on:click', "window.location='http://evil.com'"); // nunca vira listener — bloqueado antes de existir shadowProxy.bindEvents(el, {}, 'seguranca'); }; shadowProxy.template.seguranca.testarSeguro = function() { logConsole('✅ Ação permitida — sem acesso a window/document/eval', true); };
shadowProxy.template.perf.gerarLista = function() { var arr = []; for (var i = 0; i < 3000; i++) arr.push({ nome: 'Item #' + i }); var t0 = performance.now(); shadowProxy.template.perf.itens = arr; // dispara render de 3.000 nós requestAnimationFrame(function() { var t1 = performance.now(); shadowProxy.template.perf.tempoRender = (t1 - t0).toFixed(1); }); }; shadowProxy.template.perf.atualizarUm = function() { var itens = shadowProxy.template.perf.itens; var idx = Math.floor(Math.random() * itens.length); var t0 = performance.now(); itens[idx].nome = 'Atualizado ✓ #' + idx; // toca só esse nó requestAnimationFrame(function() { var t1 = performance.now(); shadowProxy.template.perf.tempoUpdate = (t1 - t0).toFixed(2); }); };
shadow-proxy entrega reatividade real sem as abstrações desnecessárias dos grandes frameworks.
lista[250].nome = 'x' toca exatamente 1 nó
no DOM, independente do tamanho da lista.Existe um espaço enorme entre "página HTML com PHP" e "SPA completa com React". shadow-proxy ocupa exatamente esse espaço.
| Característica | ShadowProxy | Alpine.js | Vue.js (CDN) | React (CDN) |
|---|---|---|---|---|
| Tamanho (min+gzip) | ~8.7KB | ~13.5KB | ~34KB | ~45KB |
| Build tools | ✕ Não precisa | ✕ | ✕ | ✕ |
| Dependências | ✓ Zero | ✓ Zero | ✓ Zero | ✓ Zero |
| Curva de aprendizado | 15 minutos | 1 hora | 2 dias | 1 semana |
| Two-way binding | ✓ | ✓ | ✓ | ✕ |
| Error handler customizável | ✓ | ✕ | ✕ | ✕ |
| Destroy real (SPA/fetch) | ✓ | ⚠ | ⚠ | ✓ |
| Funciona em PHP puro | ✓ | ✓ | ⚠ | ✕ |
Um arquivo. Sem npm, sem config, sem opinião sobre sua stack.
x-data no HTML e chame shadowProxy.initProxy(). Sua página já é
reativa.