npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

brixel

v0.7.0

Published

[beta] node_modules que quase não ocupa disco. Use brixel install no lugar do npm install: cada pacote fica guardado uma vez só no seu computador, e dez projetos com o mesmo React ocupam o espaço de um.

Readme

Beta. Funciona e tem teste, mas ainda rodou em pouca máquina. Use num projeto que dá pra reinstalar sem dor de cabeça. Se não gostar, brixel eject desfaz tudo com um comando.


O que é isso

Todo projeto JavaScript tem uma pasta chamada node_modules. É onde ficam os pacotes que o projeto usa — código pronto, escrito por outras pessoas, que o seu projeto só aproveita. Essa pasta é grande: 300 MB, 500 MB, às vezes mais de 1 GB. Em cada projeto.

O problema é que cada projeto guarda a própria cópia de tudo. Se você tem dez projetos e todos usam o React, são dez Reacts idênticos ocupando espaço no disco.

O brixel guarda um e faz os dez apontarem para ele. Você continua vendo a pasta node_modules de sempre, com arquivos de verdade dentro — o que muda é que o espaço é cobrado uma vez.

Imagine um prédio em que cada apartamento tem a própria caixa d'água cheia: dez caixas guardando exatamente a mesma água. O brixel troca isso por uma caixa só, ligada a todos os apartamentos.

Da torneira continua saindo a mesma água, na mesma hora. O que muda é quanta água você precisa estocar.

Os dois comandos que importam

| comando | quando você usa | o que ele faz | |---|---|---| | brixel install | primeira vez no projeto | Instala os pacotes. Baixa da internet só o que ainda não está guardado na sua máquina. | | brixel restore | você apagou a node_modules | Remonta a pasta inteira a partir do que já está guardado. Sem internet, sem baixar nada — 22 mil arquivos em ~4 s. |

Os outros comandos estão em Comandos.

Quanto encolhe

| situação | npm | brixel | | |---|---|---|---| | 1 projeto sozinho | 460 MB | 171 MB | −63% | | 5 projetos parecidos | 2300 MB | 478 MB | −79% | | 4 projetos bem diferentes entre si | 1725 MB | 1106 MB | −35% |

Repare na primeira linha: mesmo com um projeto só, sem nada para repetir, o disco já cai pela metade. São dois motivos que não dependem de você ter vários projetos:

  • dentro do próprio node_modules já existe arquivo repetido;
  • o brixel descarta o que ninguém usa para rodar o projeto — documentação, exemplos, mapas de código — e comprime o resto.

A partir do segundo projeto entra o ganho principal: o que se repete entre eles é guardado uma vez só. Quanto mais parecidos, mais os projetos convergem para o custo de um.

Números medidos em quatro projetos reais (Next 14, backend Node, monorepo Vite+API) — como foram medidos.

Instalação

npm install -g brixel

Depois, dentro do projeto:

brixel install

Precisa do Node 22 ou mais novo.

Com o gerenciador que você já usa:

pnpm add -g brixel
yarn global add brixel
bun add -g brixel

Sem instalar nada:

npx brixel install
pnpm dlx brixel install
yarn dlx brixel install
bunx brixel install

Tanto faz com qual gerenciador você instala o brixel. O que importa é o lockfile do projeto onde ele vai rodar:

| projeto usa | arquivo | funciona? | |---|---|---| | npm | package-lock.json (v3) | sim | | pnpm | pnpm-lock.yaml (9.x) | sim | | yarn clássico (1.x) | yarn.lock | sim | | bun | bun.lock | sim | | bun antigo | bun.lockb | não — rode bun install --save-text-lockfile para converter | | yarn moderno (berry, 2.x–4.x) | yarn.lock em YAML | não — o lockfile não traz o integrity do tarball, e sem ele o brixel não valida o download |

Não gostou? Um comando desfaz

brixel eject

Isso devolve um node_modules normal, do jeito que o npm faria, e apaga o que o brixel tinha criado. Depois disso o projeto funciona sem o brixel instalado e você volta a usar npm install.

Existe de propósito: você não fica preso.

Comandos

brixel install           # instala os pacotes do projeto
brixel restore           # remonta o node_modules apagado, sem internet
brixel stats             # quanto disco você economizou
brixel gc                # limpa o que não é mais usado por nenhum projeto
brixel doctor            # verifica se está tudo certo
brixel approve-builds x  # deixa o pacote x rodar o próprio instalador
brixel eject             # volta pro node_modules normal e some com o brixel

Durante o install roda uma animação ASCII de uma cidade sendo construída. Ela some sozinha em terminal não interativo, em CI, com --no-animation ou com BRIXEL_NO_ANIMATION=1.


Daqui para baixo é o detalhe de engenharia: como o brixel guarda os arquivos, por que isso é seguro e o que ele nunca faz. Se você só quer usar a ferramenta, pode parar aqui.

Como funciona

| Componente | Responsabilidade | |---|---| | Resolver | Lê o lockfile e produz {nome, versão, resolved, integrity, hasInstallScript} | | Fetcher | Baixa só os tarballs que ainda não estão na store e valida o integrity | | Store (CAS) | Armazém global em ~/.brixel/, um blob por conteúdo, indexado em sqlite | | Manifest | .brixel/manifest.bin — msgpack de caminho → hash + modo + tamanho, mais os links de workspace | | Linker | Materializa o node_modules com clonefile, hardlink ou cópia | | Scripts | Roda lifecycle script dos pacotes aprovados e captura o que eles produzem | | GC | Recalcula o conjunto vivo lendo os manifestos e apaga blobs órfãos | | Stats | Calcula a economia real |

Fluxo do install

  1. lê o package-lock.json, calcula o sha256 dele e descarta as dependências opcionais de outra plataforma (os/cpu/libc), como o npm faz — sem isso um projeto com esbuild ou Next carrega ~20 binários que nunca vão rodar
  2. se o manifesto existente tem o mesmo lockfileHash, pula direto para a materialização
  3. consulta a store, baixa o que falta e valida o integrity antes de gravar qualquer byte
  4. extrai em memória: para cada arquivo, sha256 → grava o blob se for novo
  5. monta e grava o manifesto
  6. materializa o node_modules
  7. roda os lifecycle scripts aprovados e captura o que eles geraram (fromBuild: true)
  8. imprime a economia

Materialização

Na ordem de preferência:

  1. copyFile com COPYFILE_FICLONE_FORCE — copy-on-write (APFS, Btrfs, XFS). Custa ~0 byte e o arquivo é independente.
  2. fs.link — hardlink. Custa 0 byte.
  3. copyFile normal — fallback.

O suporte é detectado uma vez por par de dispositivos e guardado em ~/.brixel/config.json.

Cada blob guarda o próprio modo (0o644 ou 0o755, normalizado como o npm faz). Hardlink só é usado quando o modo do blob é exatamente o que a entrada precisa — assim binário executável também é deduplicado, em vez de virar cópia. Quando não bate, cai para clonefile ou cópia.

Sobre o hardlink: o arquivo no projeto e o blob na store são o mesmo inode. Editar um arquivo dentro do node_modules edita a store — e, por tabela, todos os outros projetos. Em condições normais ninguém edita node_modules, mas fica o aviso. Nunca recebem hardlink: pacotes com lifecycle script (o script escreve no diretório deles) e o brixel eject.

Monorepos

Workspaces npm (workspaces no package.json da raiz) são suportados:

  • os caminhos do manifesto são relativos à raiz do projeto, não ao node_modules/ da raiz — por isso um backend/node_modules/lodash@3 convive com o node_modules/lodash@4 da raiz sem colidir;
  • entradas link: true do lockfile viram symlink de verdade (node_modules/@app/api → ../../backend), gravado no manifesto para o restore recriá-lo offline;
  • cada node_modules tem seu próprio .bin, e o PATH dos lifecycle scripts sobe por essa cadeia.

pnpm-workspace.yaml não é lido — o v1 só entende o lockfile do npm, workspaces incluídos.

Compressão transparente do filesystem

Deduplicação só ajuda quem tem vários projetos parecidos. Para o projeto sozinho, o que reduz de fato é a compressão do próprio filesystem: no macOS os blobs entram na store via ditto --hfsCompression, que ativa a compressão transparente do APFS/HFS+.

O arquivo continua sendo um arquivo normal — o kernel descomprime na leitura. Nenhuma ferramenta precisa saber: next build, tsc, bundler, binário nativo, tudo lê os bytes originais. E como a compressão é uma propriedade do inode, ela sobrevive ao hardlink: os dois ganhos se somam em vez de competir.

Medido no mesmo projeto Next 14:

| | npm | brixel | |---|---|---| | 1 projeto (nenhuma dedup possível) | 460 MB | 171 MB | | 2 projetos iguais | 920 MB | 171 MB |

Fora do macOS a compressão transparente não é aplicada (em Btrfs dá para conseguir o mesmo com chattr +c na store). Desligue com BRIXEL_TRANSPARENT_COMPRESSION=0.

Poda de arquivos descartáveis

Compressão tem um teto: o APFS usa zlib e chega a ~2x. O que reduz além disso é o arquivo que não precisa existir. Por padrão o Brixel descarta na origem — nunca entram na store nem no manifesto:

  • source maps (*.js.map, *.css.map) — 15% do disco de um projeto Next típico
  • docs, exemplos e benchmarks (docs/, examples/, benchmark/, website/)
  • markdown (README.md, CHANGELOG.md) — exceto LICENSE, NOTICE e COPYING, que ficam por obrigação legal

Nunca são podados: package.json, qualquer coisa em bin/, .d.ts (o tsc precisa deles), fontes .ts, e diretórios de teste — o ganho deles é irrisório e o risco não é.

O install imprime quantos arquivos descartou. Para manter tudo:

brixel install --no-prune
{ "brixel": { "prune": { "sourceMaps": false, "docs": true, "markdown": true } } }

O perfil de poda fica gravado por pacote na store, então trocar a configuração rebaixa só o que precisa.

Compressão zstd na store

A store detecta, ao ser criada, se consegue linkar (hardlink/clonefile) do armazém para os projetos:

  • consegue: grava os blobs crus. Blob comprimido não pode ser linkado — o Node leria bytes zstd. Aqui a economia vem da deduplicação, que é a maior parte dela.
  • não consegue (store e projetos em dispositivos diferentes, por exemplo): grava com zstd, já que tudo viraria cópia de qualquer jeito e a compressão passa a ser o único ganho possível.

O zstd usa o nativo do node:zlib quando disponível e cai para @napi-rs/zstd quando não. Dá para forçar com BRIXEL_COMPRESSION=zstd|none. brixel doctor mostra o que foi escolhido.

Lifecycle scripts

Pacotes como esbuild, sharp e better-sqlite3 baixam ou compilam binário no postinstall. Esse binário não existe no tarball. O Brixel só roda script de pacote que você aprovou explicitamente:

{
  "brixel": {
    "buildDependencies": ["esbuild", "sharp", "better-sqlite3"]
  }
}

Ou brixel approve-builds esbuild. Pacote com script fora da lista não executa nada, e o install avisa nomeando cada um. Rodar script arbitrário de dependência transitiva por padrão é a mesma classe de risco que o Brixel existe para não introduzir.

Depois de rodar, tudo que o script gerou dentro do diretório do pacote entra na store e vira entrada de manifesto com fromBuild: true. Efeito colateral bom: o binário compilado é deduplicado como qualquer outro blob — cinco projetos com o mesmo esbuild guardam um binário só. E é isso que faz o restore funcionar offline e sem re-executar script.


Regras inegociáveis

  1. Nunca modificar o conteúdo de um pacote. O byte que o Node lê é idêntico ao que o npm publicou.
  2. Sempre validar o integrity. Hash divergente aborta o install inteiro antes de gravar qualquer coisa na store.
  3. A store é append-only fora do GC. Deleção só no brixel gc, com lock de arquivo exclusivo.
  4. Falhar aberto, nunca fechado. Store corrompida, permissão negada, FICLONE indisponível: cai para o comportamento mais burro que funciona e avisa. Se um script aprovado falhar, o Brixel apaga o node_modules, roda npm install normal e sai com sucesso — manifesto pela metade é pior que não usar a ferramenta.

Adoção no fluxo diário

Shim de shell, para instalar pacote novo:

brix() {
  case "$1" in
    install|i|add)
      shift
      npm install "$@" --package-lock-only || return 1
      brixel install || { echo "brixel falhou, caindo pro npm"; npm install "$@"; }
      ;;
    *)
      npm "$@"
      ;;
  esac
}

--package-lock-only resolve e escreve o lockfile sem materializar nada e sem rodar script nenhum — por isso a allowlist de builds é obrigatória: nesse fluxo o Brixel é o único responsável pelos lifecycle scripts.

Git hook (post-checkout e post-merge), para trocar de branch e dar pull:

#!/bin/sh
[ -f .brixel/manifest.bin ] || exit 0
brixel install >/dev/null 2>&1 || true

Se o lockfile não mudou, o install cai no atalho e custa milissegundos.

Não use postinstall do package.json para isso: o npm já materializou o node_modules inteiro antes do hook rodar, então você paga o disco todo e só depois converte — e roda na máquina de todo mundo que clonar o repo.


Layout no disco

~/.brixel/
  blobs/ab/3f9c8d...        sharding pelos 2 primeiros chars do sha256
  index.db                  sqlite: blobs, projects, packages, package_files
  config.json               compressão e capacidades de link por dispositivo

<projeto>/.brixel/
  manifest.bin              msgpack: caminho → hash + modo + tamanho
  materialized              hash do manifesto já materializado (atalho do install)

O manifesto também grava a plataforma que o gerou (darwin-arm64, linux-x64-glibc, …). Manifesto de outra plataforma é tratado como manifesto velho e regerado, já que a lista de binários opcionais muda.

A tabela projects existe para o GC saber quais manifestos consultar. Não há refcount numérico — ele dessincroniza; o GC recalcula o conjunto vivo lendo os manifestos.


Desenvolvimento

npm install
npm run build       # tsup → dist/index.js (ESM + shebang)
npm test            # vitest
npm run typecheck   # tsc --noEmit
npm run lint        # biome check
npm run format      # biome format --write
npm run verify      # format:check + lint + typecheck + test

Variáveis úteis: BRIXEL_HOME (store alternativa), BRIXEL_COMPRESSION, BRIXEL_NO_ANIMATION, BRIXEL_VERBOSE.


Medições

Medido em quatro projetos reais (Next 14, backend Node, monorepo Vite+API), macOS/APFS, store e projetos no mesmo dispositivo:

| Cenário | npm | brixel | | |---|---|---|---| | 1 projeto sozinho, sem dedup possível | 460 MB | 171 MB | −63% | | 5 projetos com as mesmas dependências | 2300 MB | 478 MB | −79% | | 4 projetos heterogêneos | 1725 MB | 1106 MB | −35% | | Install do 5º projeto (store quente) | 5,7 s (npm ci) | 4,4 s | | | restore de 22 mil arquivos | — | 4,1 s | | | Monorepo (2 workspaces, 21 mil arquivos) | — | 18,7 s no 1º install | |

A economia acompanha a sobreposição entre os projetos: repositórios parecidos convergem para o custo de um só, projetos sem nada em comum economizam pouco. Nos quatro projetos, 22 579 de 22 604 arquivos foram hardlink e 25 viraram cópia (os do pacote com lifecycle script).

Fora de escopo no v1

Filesystem virtual (FUSE/FSKit/ProjFS), yarn.lock do berry, bun.lockb binário, daemon/watcher, telemetria, interface gráfica, filtro de arquivos "não usados" e sandbox dos lifecycle scripts. A allowlist é a única defesa do v1.

Licença

MIT — veja LICENSE. Pode fazer fork, usar em projeto comercial e fechar o código do seu; só mantenha o aviso de copyright.