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

tarte-aux-etrons

v0.4.0

Published

Parce que les cookies, c'est de la merde. Une lib RGPD moderne, typée, et qui assume.

Readme

💩 tarte-aux-etrons

Parce que les cookies, c'est de la merde.

Une lib RGPD/cookie consent moderne, typée TypeScript, sans dépendances, et qui assume totalement son thème.

npm license

📖 Documentation · 🛝 Playground


Pourquoi ?

Parce que les cookies, c'est de la merde. Alors autant en faire un truc agréable.

tarte-aux-etrons est une lib légère, typée TypeScript, sans dépendances, avec une API pensée pour ne pas vous pourrir la vie.


Installation

npm install tarte-aux-etrons
# ou
pnpm add tarte-aux-etrons
# ou
yarn add tarte-aux-etrons

Démarrage rapide

Le plus court chemin vers une bannière RGPD qui tourne :

import { createTaE, ga4 } from 'tarte-aux-etrons'

createTaE({
  services: [ga4('G-XXXXXXXXXX')],
})

C'est tout. La bannière 💩 s'affiche, le consentement est persisté en localStorage, GA4 se charge si l'utilisateur accepte.


Concepts clés

| Concept | Rôle | |---|---| | ConsentManager | Cerveau. Gère l'état, le storage, les événements. | | Banner | UI. Affiche la bannière et le panneau de personnalisation. | | createTaE() | Factory. Câble les deux dans le bon ordre en une ligne. | | Services | Définissent quoi charger/nettoyer selon le consentement. |


Services disponibles

Google Analytics 4

import { ga4 } from 'tarte-aux-etrons'

ga4('G-XXXXXXXXXX')

// Plusieurs propriétés GA4 sur le même site
ga4('G-XXXXXXXXXX')              // id: 'ga4' (défaut)
ga4('G-YYYYYYYYYY', 'ga4-blog') // id custom pour éviter les conflits

Google Tag Manager

import { gtm } from 'tarte-aux-etrons'

gtm('GTM-XXXXXXX')
gtm('GTM-XXXXXXX', 'gtm-shop') // id custom

YouTube

import { youtube } from 'tarte-aux-etrons'

youtube()

Utilisez data-src au lieu de src sur vos iframes YouTube — la lib active le chargement une fois le consentement donné :

<iframe
  data-src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
  width="560" height="315"
  allowfullscreen
></iframe>

Fonctionne aussi avec youtube.com dans l'URL. Sur refus, le src est vidé pour stopper le chargement.

Hotjar

import { hotjar } from 'tarte-aux-etrons'

hotjar(1234567)
hotjar(1234567, { version: 6, id: 'hotjar-custom' })

Facebook Pixel

import { facebookPixel } from 'tarte-aux-etrons'

facebookPixel('1234567890123456')
facebookPixel('1234567890123456', 'fb-pixel') // id custom

LinkedIn Insight Tag

import { linkedinInsight } from 'tarte-aux-etrons'

linkedinInsight('1234567')
linkedinInsight('1234567', 'linkedin') // id custom

Google Maps

import { googleMaps } from 'tarte-aux-etrons'

googleMaps()

Utilisez data-src au lieu de src sur vos iframes Google Maps :

<iframe
  data-src="https://www.google.com/maps/embed?pb=..."
  width="600" height="450"
  allowfullscreen loading="lazy"
></iframe>

Matomo

import { matomo } from 'tarte-aux-etrons'

matomo('https://analytics.monsite.fr', 1)
matomo('https://analytics.monsite.fr', 1, 'matomo-custom') // id custom

Le trailing slash dans l'URL est normalisé automatiquement.

Google reCAPTCHA v3

import { recaptchaV3 } from 'tarte-aux-etrons'

recaptchaV3('6LeXXXXXXXXXXXXXXXXXXXXXXXX')
recaptchaV3('6LeXXXXXXXXXXXXXXXXXXXXXXXX', 'recaptcha-contact') // id custom

Le script n'est chargé qu'après consentement. Une fois chargé, grecaptcha.execute() reste disponible pour vos formulaires. Sur refus, le cookie _GRECAPTCHA est supprimé — le script déjà chargé ne peut pas être déchargé, mais il ne se rechargera pas lors des visites suivantes.

Microsoft Clarity

import { microsoftClarity } from 'tarte-aux-etrons'

microsoftClarity('abcdefghij')
microsoftClarity('abcdefghij', 'clarity-custom') // id custom

Clarity dispose d'une Consent API native : clarity('consent') est appelé automatiquement sur acceptation. Sur refus, les cookies sont supprimés et Clarity bascule en mode cookieless.

Crisp

import { crisp } from 'tarte-aux-etrons'

crisp('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
crisp('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'crisp-support') // id custom

Sur refus, la session est réinitialisée et le chatbox masqué. Crisp utilise principalement le localStorage — aucun cookie first-party propre n'est déposé.

Chicken Player

Intégration native avec chicken-player. Gère le consentement pour YouTube, Vimeo et Dailymotion embarqués via le player.

import { chickenplayer } from 'tarte-aux-etrons'

chickenplayer()

Côté player, activez la gestion du consentement et listez les plateformes concernées :

import ChickenPlayer from 'chicken-player'

new ChickenPlayer({
  cookies: {
    active: true,
    types: ['youtube', 'vimeo', 'dailymotion'],
  },
})

Sur acceptation, tarte-aux-etrons dispatche chickenPlayer.cookies.consent — le player déverrouille automatiquement les vidéos. Sur refus, chickenPlayer.cookies.reject — le player réaffiche le message de consentement.


Créer un service personnalisé

import { defineService } from 'tarte-aux-etrons'

const myPixel = defineService({
  id: 'my-pixel',
  name: 'Mon Pixel',
  category: 'advertising', // 'analytics' | 'advertising' | 'social' | 'functional' | 'other'
  description: 'Suit les conversions.',
  cookieNames: ['_mypixel'],
  onAccept() {
    // Charger le script, initialiser le tracker, etc.
  },
  onRefuse() {
    // Nettoyer les cookies, désactiver le tracker, etc.
  },
})

Utilitaires disponibles pour écrire vos services :

import { loadScript, deleteCookies, deleteCookiesMatching } from 'tarte-aux-etrons'

// Charge un script (idempotent — pas de double chargement)
loadScript('https://example.com/tracker.js')

// Supprime des cookies par nom exact (racine + sous-domaine)
deleteCookies(['_tracker', '_tracker_session'])

// Supprime tous les cookies dont le nom commence par l'un des préfixes
// Utile pour les cookies à suffixe dynamique (_pk_id.1.xxxx, _hjSession_abc...)
deleteCookiesMatching(['_tracker_', '_tracker_sess'])

Thème et personnalisation

Presets

createTaE({
  services: [...],
  banner: {
    preset: 'poop',    // 💩 brun ambré — défaut
    preset: 'serious', // bleu sobre
    preset: 'none',    // aucun style — apportez le vôtre
  },
})

Surcharge de tokens CSS

import { poopTheme } from 'tarte-aux-etrons'

createTaE({
  services: [...],
  banner: {
    vars: {
      ...poopTheme,          // base poop
      accent: '#e11d48',     // juste la couleur principale
      stripeA: '#e11d48',
      stripeB: '#f97316',
    },
  },
})

Tokens disponibles :

| Token | Variable CSS | Description | |---|---|---| | accent | --tae-accent | Couleur principale (boutons, toggles) | | accentHover | --tae-accent-hover | Hover sur la couleur principale | | accentLight | --tae-accent-light | Fond de hover sur les services | | accentMid | --tae-accent-mid | Couleur des catégories | | bg | --tae-bg | Fond de la bannière | | border | --tae-border | Bordures | | stripeA | --tae-stripe-a | Début du dégradé de la bande | | stripeB | --tae-stripe-b | Fin du dégradé de la bande | | text | --tae-text | Texte principal | | textMuted | --tae-text-muted | Texte secondaire | | textSubtle | --tae-text-subtle | Texte tertiaire (descriptions) | | serviceBg | --tae-service-bg | Fond des cards de service | | serviceBorder | --tae-service-border | Bordure des cards de service | | radius | --tae-radius | Border-radius de la bannière |

Surcharge CSS directe (contrôle total) :

.tae-banner {
  --tae-accent: #e11d48;
  --tae-bg: #fff1f2;
  --tae-border: #fecdd3;
}

Localisation

Des locales prêtes à l'emploi sont fournies. Importez, étendez, ou remplacez.

import { createTaE, ga4, frPoop, en } from 'tarte-aux-etrons'

// Locale 💩 française (défaut du preset 'poop')
createTaE({ services: [ga4('G-XXX')], banner: { labels: frPoop } })

// Locale anglaise sobre
createTaE({ services: [ga4('G-XXX')], banner: { labels: en } })

// Override partiel
createTaE({
  services: [ga4('G-XXX')],
  banner: {
    labels: { ...frPoop, title: 'Vos cookies, votre choix' },
  },
})

Locales disponibles :

| Export | Langue | Style | |---|---|---| | fr | Français | Sobre | | frPoop | Français | 💩 | | en | Anglais | Sobre | | enPoop | Anglais | 💩 |

Créer sa propre locale :

import type { BannerLabels } from 'tarte-aux-etrons'

const de: BannerLabels = {
  title: 'Diese Website verwendet Cookies',
  description: 'Wählen Sie aus, was Sie akzeptieren.',
  acceptAll: 'Alle akzeptieren',
  refuseAll: 'Alle ablehnen',
  customize: 'Anpassen',
  save: 'Meine Auswahl speichern',
  categoryLabels: {
    analytics: 'Analyse',
    advertising: 'Werbung',
    social: 'Soziale Medien',
    functional: 'Funktional',
    other: 'Sonstiges',
  },
}

Événements

Écouter les changements

const { manager } = createTaE({ services: [...] })

// Changement individuel (clic sur un toggle dans le panneau)
manager.on('consent:change', ({ serviceId, status, state }) => {
  console.log(`${serviceId} → ${status}`) // 'accepted' | 'refused'
})

// Changement en masse (boutons "Tout accepter" / "Tout refuser")
manager.on('consent:bulk', ({ action, state }) => {
  console.log(action) // 'swallow-all' | 'flush-all'
})

// Prêt (appelé une fois à l'init, avec l'état restauré du storage)
manager.on('ready', (state) => {
  console.log(state)
})

Désabonnement

manager.on() retourne une fonction de désabonnement :

const unsubscribe = manager.on('consent:change', handler)
// Plus tard :
unsubscribe()

Vérifier le consentement à la demande

manager.isDigested('ga4')  // boolean — consentement donné
manager.isFlushed('ga4')   // boolean — refusé
manager.isFloating('ga4')  // boolean — pas encore décidé

manager.getServiceStatus('ga4') // 'accepted' | 'refused' | 'pending'
manager.getState()              // ConsentState complet

Actions programmatiques

manager.swallowAll() // Accepte tous les services
manager.flushAll()   // Refuse tous les services
manager.swallow('ga4') // Accepte un service précis
manager.flush('ga4')   // Refuse un service précis
manager.plunge()       // Réinitialise tout + réaffiche la bannière 🪠

Utilisation avancée — sans bannière

Vous pouvez utiliser le ConsentManager seul et construire votre propre UI :

import { ConsentManager, ga4 } from 'tarte-aux-etrons'

const manager = new ConsentManager({
  services: [ga4('G-XXXXXXXXXX')],
  consentVersion: 1,
  onReady(state) { /* état restauré */ },
  onConsentChange(serviceId, status) { /* changement individuel */ },
  onBulkChange(action, state) { /* swallowAll / flushAll */ },
})

manager.init() // À appeler après avoir monté votre UI

API complète — createTaE(options)

createTaE({
  // Obligatoire
  services: ServiceDefinition[],

  // Optionnel — ConsentManager
  storageKey?: string,       // clé localStorage, défaut: 'tae_consent'
  consentVersion?: number,   // incrémenter pour forcer un re-consentement
  onReady?: (state) => void,
  onConsentChange?: (serviceId, status) => void,
  onBulkChange?: (action, state) => void, // action: 'swallow-all' | 'flush-all'

  // Optionnel — Bannière
  banner?: {
    target?: HTMLElement,              // où monter la bannière, défaut: document.body
    preset?: 'poop' | 'serious' | 'none',
    vars?: ThemeVars,
    labels?: Partial<BannerLabels>,
  },
})

Compatibilité

  • Navigateurs : tous les navigateurs modernes (ES2020+)
  • Frameworks : framework-agnostic — fonctionne avec React, Vue, Svelte, Next.js, Nuxt, vanilla JS...
  • SSR : ConsentManager.init() ne s'exécute pas côté serveur (typeof window === 'undefined' guard)
  • Bundle : ESM + CJS, types .d.ts inclus

Shitmap

  • Ajout de isPerfect() à l'API quand tous les services ont été acceptés (ou refusés)
  • Ajout progressif de services d'étrons complémentaires (Google Fonts, Cloudflare Turnstile, TrustPilot, Brevo, Intercom...)

Licence

MIT