Sept ans, trois fichiers, une permission
extension-chromejavascriptopen-sourceprojet-personnel

Sept ans, trois fichiers, une permission

J'ai écrit une extension lorem ipsum en 2018 parce que celle que j'utilisais est devenue payante. Sept ans, 38 commits et 311 utilisateurs plus tard, elle fait toujours la même chose.

·11 min de lecture

Sept ans, trois fichiers, une permission

En juin 2018, l'extension lorem ipsum que j'utilisais depuis des mois pour remplir mes maquettes a publié une mise à jour. Même icône, même popup, sauf qu'elle était passée derrière un paywall. Générer du texte de remplissage, c'est tirer au hasard dans un tableau de phrases latines. J'ai fermé l'onglet et j'ai écrit la mienne le soir même.

Le premier commit date du 13 juin 2018 : dix fichiers, 177 lignes. Sept ans et 38 commits plus tard, elle fait exactement la même chose, et je crois que c'est l'histoire la plus intéressante que j'aie à raconter dessus.

L'extension en entier

On imagine souvent les extensions de navigateur plus compliquées qu'elles ne sont. Voici le manifeste complet, dans sa version actuelle :

{
  "name": "Simple Lorem Ipsum Generator",
  "version": "1.4.0",
  "description": "Quick way to generate lorem ipsum text with customizable size. Light and forever free.",
  "permissions": ["storage"],
  "offline_enabled": true,
  "incognito": "spanning",
  "action": { "default_popup": "popup.html" },
  "manifest_version": 3
}

Et voici le générateur tel qu'il était le jour où je me suis assis pour écrire cet article, sans les vingt phrases dans lesquelles il pioche :

function appendContent(content) {
  const paragraphCount = parseInt(document.getElementById('paragraph-count').value, 10)
  const paragraphLength = parseInt(document.getElementById('paragraph-length').value, 10)
  const paragraphType = document.getElementById('paragraph-type').value
  let text = ''

  for (let paragraphIndex = 0; paragraphIndex < paragraphCount; paragraphIndex++) {
    if (paragraphType === 'Yes')
      text += '&lt;p&gt;'

    for (let sentenceIndex = 0; sentenceIndex < paragraphLength; sentenceIndex++) {
      const random = Math.floor(Math.random() * content.length)
      const sentence = content[random]
      text += sentence + (sentenceIndex === paragraphLength - 1 || paragraphType === 'Yes' ? '.' : '. ')
    }

    if (paragraphType === 'Yes')
      text += '&lt;/p&gt;'

    if (paragraphIndex !== paragraphCount - 1)
      text += paragraphType === 'Yes' ? "\n<br/>\n" : "\n\n"
  }

  document.getElementById('content-area').value = text
}

Lisez-le si vous voulez, il n'y a rien de caché dedans. J'y reviendrai à la fin de l'article, parce qu'il est plus cassé qu'il n'en a l'air.

Et voici à quoi ça ressemblait en juin 2018, sur la capture que j'avais envoyée au store, option balises HTML activée :

Le popup de Simple Lorem Ipsum Generator en juin 2018, version 1.0.5, avec les paragraphes générés entourés de balises p

Version 1.0.5, avant le thème sombre et avant les boutons. Elle méritait un regard plus attentif que celui que je lui ai donné.

Trois fichiers : popup.html pour le formulaire, popup.css pour le thème, popup.js pour environ 140 lignes. Aucune étape de build à part un script shell qui zippe huit chemins. Aucune dépendance, donc pas de lockfile, pas de bruit d'audit, pas de dependabot. L'extension empaquetée pèse 25 Ko.

Deux choses m'agaçaient chez les alternatives

La première, c'est que la plupart appelaient une API distante. Un aller-retour réseau pour un tirage au hasard dans un tableau figé. Ça rend l'extension inutilisable dans un train sans couverture ou derrière un proxy d'entreprise verrouillé, et ça veut dire que celui qui héberge l'API voit chaque clic.

La seconde, c'est qu'elles demandaient des permissions dont elles n'avaient aucun usage. Lire et modifier les données sur tous les sites, pour un popup qui produit du latin.

J'ai donc posé deux règles, et je les ai écrites dans le manifeste plutôt que dans un README que personne ne lit. offline_enabled à true, et une seule permission, storage, pour retenir les réglages des menus déroulants. Les phrases vivaient dans un loremIpsum.txt local, lu par une XMLHttpRequest à l'ouverture du popup. Pas élégant, mais rien ne quittait la machine. J'ai ajouté un lien GitHub dans le pied de page pour que n'importe qui puisse aller vérifier, renommé le projet de "Lipsum Generator" en "Simple Lorem Ipsum Generator" pour qu'il remonte quand quelqu'un cherche sur le store ce qu'il fait, et mis la promesse dans la description : "Light and forever free."

Je n'avais rien inventé, et le store le montre bien. Les générateurs de lorem ipsum s'y comptent par dizaines, gratuits, hors ligne, souvent plus complets que le mien. Ce que j'avais pris pour une idée était en fait un besoin, partagé par assez de monde pour que beaucoup d'autres y répondent en même temps. Le mien a 311 utilisateurs et une seule note, ce qui donne à peu près la taille du trou qu'il bouche.

Ce qu'un projet fini fait de son temps libre

Rien, essentiellement. Après juillet 2018, le code de l'extension n'a pas bougé pendant cinq ans. Les commits de l'intervalle changent une licence, ajoutent un code de conduite, ajoutent une politique de sécurité. Pas une ligne de ce qui génère le texte.

Un seul événement extérieur m'a fait rouvrir le projet : la dépréciation du manifeste v2. La migration, le 6 décembre 2023, a duré une matinée et a retiré du code. browser_action est devenu action. La XHR appelait chrome.extension.getURL, que la v3 a supprimé, alors plutôt que de la porter vers chrome.runtime.getURL j'ai intégré les vingt phrases directement dans un tableau et supprimé loremIpsum.txt, avec l'entrée web_accessible_resources qui n'avait jamais eu de raison d'être là. Le générateur est devenu une fonction pure sur un littéral.

Ce passage à la v3 a été douloureux pour beaucoup de monde. Il a supprimé le webRequest bloquant, vidant les bloqueurs de contenu de leur substance, et interdit le code hébergé à distance, cassant tout ce qui chargeait sa logique à l'exécution. Rien de tout ça ne m'a touché, pour la raison exacte qui fait que l'extension fonctionne dans un avion : quand on n'a jamais dépendu ni du réseau ni de code distant, il n'y a rien à réécrire.

Les contraintes que j'avais choisies en 2018 pour protéger les utilisateurs ont fini par me protéger moi.

Le reste tient en trois soirées. La migration a introduit un bug de ponctuation que j'ai attrapé soixante-dix minutes plus tard, les phrases intégrées gardant le point final que l'ancien découpage du fichier retirait, pendant que le générateur en ajoutait un second. Un thème sombre en février 2024, via une media query plutôt qu'un bouton, pour suivre ce que le navigateur fait déjà. Et en septembre 2025, les deux gestes que je faisais encore à la main : copier, et régénérer, puisque jusque-là le seul moyen d'obtenir un nouveau texte était de changer un menu déroulant puis de le remettre.

Terminé ne veut pas dire correct

En préparant cet article, j'ai relu popup.js en entier pour la première fois depuis longtemps, et j'ai découvert que l'option des balises HTML était cassée.

Regardez à nouveau l'extrait plus haut. Le code écrit les entités échappées &lt;p&gt; et &lt;/p&gt;, ce qui est curieux, puisque l'élément de sortie est un simple textarea depuis le tout premier commit. Ça marchait quand même, pendant sept ans, par accident : l'ancienne ligne affectait le résultat avec innerHTML, et affecter innerHTML sur un textarea fait passer la chaîne par le parseur HTML, qui retransforme les entités en vrais chevrons avant que quiconque lise value.

En septembre 2025, en ajoutant le bouton de copie, j'ai remplacé ce innerHTML par le value évidemment plus correct. Une affectation simple ne décode rien. À partir de ce jour, ceux qui cochaient l'option des balises HTML ont récupéré des &lt;p&gt; littéraux dans leur presse-papiers. Onze mois, sur une fonctionnalité que j'ai livrée, dans un fichier que je peux lire d'une traite, cassée par le commit qui améliorait le code.

Deux autres bugs se tenaient juste à côté, tous les deux datés de juin 2018 et tous les deux invisibles pendant huit ans. Une condition qui mangeait l'espace entre les phrases dès que l'option était active, et un <br/> parasite inséré entre des paragraphes qui portaient déjà des balises <p>. Remontez à la capture d'écran. Les balises sortent en vrais chevrons, c'est l'accident qui fonctionne. Le <br/> est là, seul sur sa ligne entre deux paragraphes. Et voilà ligula.Nulla quis lorem, sans espace après le point. Les deux bugs étaient posés dans la fiche du store, en 1280 par 800, pendant huit ans, sur l'image que j'avais moi-même choisie pour montrer l'extension sous son meilleur jour. Et cet innerHTML avait une conséquence à laquelle je n'avais jamais réfléchi : un textarea cesse de refléter son balisage dès que l'utilisateur tape dedans, donc ceux qui modifiaient le texte généré puis changeaient un menu déroulant regardaient un générateur qui avait silencieusement arrêté de répondre.

Tout est corrigé dans le dépôt, et ça part avec la prochaine version publiée sur le store. Un projet fini ne se surveille pas tout seul, et l'absence de tickets ne prouve rien. Avec 311 utilisateurs, une note, et une option que la plupart ne touchent probablement jamais, le silence n'allait jamais être un signal.

Ce que j'en retire

Le vrai coût d'un logiciel ne se trouve pas dans ce qu'il fait, mais dans ce dont il dépend. Cette extension m'a demandé quatre jours en sept ans, non pas parce qu'elle est petite, mais parce qu'elle n'a rien à qui obéir : pas de serveur à maintenir, pas de dépendances à mettre à jour, pas de permission à justifier, pas de plateforme dont les changements la concernent. Une extension équivalente qui appelle une API aurait consommé ces quatre jours en factures d'hébergement et en migrations subies.

Le corollaire est moins confortable. Un projet qui ne demande rien ne se rappelle jamais à vous, donc vous ne le regardez plus, donc ses bugs vivent des années. Pire : le bug qui a duré le plus longtemps ici n'est pas né d'un abandon, il est né d'un nettoyage. C'est le prix, et pour un générateur de lorem ipsum je le paie volontiers. Pour quelque chose dont les gens dépendent vraiment, il faudrait le remplacer par autre chose que de la bonne volonté.

Quant à l'extension payante qui a déclenché tout ça, je ne la retrouve plus, elle semble avoir disparu du store. Elle cherchait sans doute à financer sa propre maintenance. La mienne n'a jamais eu besoin d'être financée parce qu'elle n'a jamais eu besoin d'être maintenue, et c'était le seul choix de conception qui comptait.

Le code source est sur Github, en GPLv3, et il reste gratuit.

Mise à jour (6 septembre 2026)

Cet article décrit l'extension telle qu'elle était le 31 août 2026. Cinq jours plus tard elle ne l'est plus, et plusieurs passages ci-dessus ne décrivent plus le dépôt.

Les corrections annoncées à la fin sont publiées : elles sont dans la 1.5.0, sortie le 5 septembre 2026. La même version a réécrit le générateur. Il vit maintenant dans un generator.js sans DOM ni API navigateur, donc testable ; il tire ses phrases sans remise, un mélange de Fisher-Yates et un sac par paragraphe, si bien qu'un paragraphe ne se répète plus tant qu'il n'a pas épuisé les vingt phrases ; et il sait compter en mots autant qu'en phrases. Le popup a gagné un raccourci clavier, Alt+Shift+L, et une interface traduite. Un browser.js absorbe la différence entre les espaces de noms chrome et browser, et ./build --firefox produit un zip acceptable par AMO, avec le data_collection_permissions à none que Mozilla exige depuis novembre 2025. La 1.6.0, le même jour, a porté les traductions à huit langues.

Le passage sur les trois fichiers ne tient donc plus, ni le manifeste cité plus haut : il passe maintenant par __MSG_ et default_locale, déclare les icônes et la commande clavier. La permission unique storage, offline_enabled et l'absence totale de réseau, eux, n'ont pas bougé.

Le paragraphe sur l'absence de dépendances demande une nuance plutôt qu'une correction. Il y a bien maintenant un pnpm-lock.yaml, une dépendance de développement (Biome), 36 tests lancés par node --test, une CI GitHub Actions qui lint, teste et vérifie que les deux zips se construisent, et oui, un dependabot.yml. Mais tout ça est optionnel et reste au bord du projet : rien n'entre dans le zip, l'extension n'a toujours aucune dépendance à l'exécution, et elle s'empaquette sans rien installer, avec le même script shell qu'en 2018. La CI ne fait que vérifier, elle ne publie rien : la mise à jour sur les stores reste manuelle. Le zip est passé de 25 à 36 Ko, l'écart étant surtout les huit fichiers de traduction.

Ces treize commits sont probablement les derniers avant un moment. Il ne reste rien sur ma liste pour cette extension.