Envie de contribuer à l’open source ? Vous vous y prenez mal
Je contribue activement à l’open source depuis 2020. Depuis, j’ai parlé avec beaucoup de développeurs qui veulent contribuer mais ne savent pas par où commencer, demandant souvent une issue sur laquelle travailler.
Je suis profondément convaincu que l’open source, c’est plus que ça et, surtout, que ça ne marche pas comme ça.
Pour moi, l’open source, au fond, est un état d’esprit. C’est la liberté d’explorer, de comprendre, de modifier et d’améliorer le code source du logiciel que tu veux. Bien sûr, ce modèle encourage la collaboration et le partage en permettant à chacun de contribuer, mais il ne t’y oblige pas.
L’open source ne devrait pas, et ne doit jamais, être une fin en soi. Ça fait partie du chemin, pas de la destination. Sinon, tu finiras frustré et insatisfait de ne jamais réussir à contribuer.
Laisse-moi te raconter une histoire pour illustrer ce point.
Je construis un nouveau site appelé infra.soubiran.dev pour documenter ma propre infrastructure. C’est un wiki personnel mais ouvert à tous. Je n’entrerai pas dans les détails, un article dédié viendra plus tard.
Pour le construire, j’ai dû trouver les bons outils. Jusqu’à présent, j’utilisais VitePress avec un thème personnalisé pour mon blog personnel, celui que tu es en train de lire. Ça marche très bien, mais j’en ai presque atteint les limites, et avec toutes les idées que j’ai, je sais qu’il pourrait être temps de passer à autre chose.
Alors, j’ai commencé à regarder des alternatives. Honnêtement, il y a deux options :
- Utiliser Nuxt avec Nuxt Content
- Utiliser Vite avec Vue et quelques unplugins pour gérer les fichiers Markdown.
J’ai choisi la seconde option. Assez drôle, avant de passer à VitePress, j’avais le même dilemme : Nuxt Content, VitePress, ou Vite avec Vue. À l’époque, j’avais choisi VitePress.
Bref, j’ai commencé à construire le site.
Depuis quelques mois, il y a une techno dont je ne peux plus me passer : Nuxt UI. C’est une bibliothèque de composants incroyable réalisée par l’équipe Nuxt. Tous mes projets qui utilisent Vue ou Nuxt utilisent Nuxt UI, et je le pousse régulièrement dans ses retranchements. C’est aussi une des raisons pour lesquelles j’ai décidé de m’éloigner de VitePress, qui n’est pas compatible avec Nuxt UI.
Construire avec des logiciels open source
Le site que je construis est orienté contenu. Ça signifie que le contenu est principalement textuel et, dans mon cas, toutes les pages sont écrites en Markdown. Pour mettre à jour le site, j’édite les fichiers Markdown puis je reconstruis le site.
Les pages sont générées et pré-rendues au moment du build. Il n’y a pas besoin de les générer à l’exécution, puisque chaque utilisateur recevra le même contenu pré-rendu. Pour que ça marche, j’utilise Vite SSG.
C’est un remplaçant pour Vite lors du build du site. Il construit automatiquement le client et le serveur, puis utilise le serveur pour pré-rendre chaque page, comme si un utilisateur la demandait.
Après l’installation, tout fonctionnait comme prévu.
J’ai donc continué à construire le site. J’ai installé Nuxt UI et commencé à le configurer. Pour utiliser Nuxt UI dans une application Vue, il faut utiliser un plugin.
import ui from '@nuxt/ui/vue-plugin'
import { ViteSSG } from 'vite-ssg'
import App from './App.vue'
export const createApp = ViteSSG(
App,
{},
({ app }) => {
app.use(ui)
},
)Et là, tout a cassé. Pendant le pré-rendu, quelque chose a essayé d’accéder à document pour créer un style : const style = document.createElement('style'). Vraiment étrange.
Lors du pré-rendu, Vite SSG injecte import.meta.env.SSR pour détecter s’il s’exécute en mode SSR. Ça permet aussi au bundler d’éliminer efficacement le code non utilisé. Donc, pour corriger le bug, j’ai encapsulé l’usage du plugin dans une vérification conditionnelle du mode SSR.
import ui from '@nuxt/ui/vue-plugin'
import { ViteSSG } from 'vite-ssg'
import App from './App.vue'
export const createApp = ViteSSG(
App,
{},
({ app }) => {
if (import.meta.env.SSR) {
app.use(ui)
}
},
)À présent, le pré-rendu fonctionnait comme prévu, du moins c’est ce que je pensais.
J’ai écrit quelques pages, créé des composants, et tout poussé en production. La construction s’est bien passée, j’ai ouvert le site et j’ai vu ceci :
Tu le vois ?
Avant d’aller plus loin, et si tu l’as remarqué, l’aurais-tu corrigé ? Tu sais que ça va prendre du temps, et tu ne pourras pas continuer à écrire du contenu. C’est un vrai dilemme. Prendre du temps pour quelque chose que tu pourrais considérer comme un détail, ou continuer à travailler sur ton contenu.
Les sites remarquables sont faits de détails. Pour moi, les détails sont très importants, et il est essentiel d’y prêter attention. Ils peuvent transformer l’expérience utilisateur, améliorer le confort et augmenter la fidélité. Ne les sous-estime jamais.
Pour ceux qui ne le voient pas, ça s’appelle un FOUC, un flash de contenu non stylisé. Le contenu apparaît avant que tous les styles ne soient disponibles. Ça arrive généralement quand un script doit ajouter une classe dark à l’élément HTML. Le site se charge avec le thème par défaut (mode clair), apparaît, puis le JavaScript est analysé et exécuté. Le script ajoute la classe dark et le thème passe en mode sombre. Le mode clair dure quelques centaines de millisecondes, mais c’est suffisant pour être visible. Tout ça est lié à la façon dont un navigateur rend une page.
Ça peut être très compliqué à corriger. En général, ça arrive quand les balises de ton head ne sont pas correctement ordonnées ou quand le script n’est pas exécuté au bon moment. J’ai donc plongé dans le head de la page générée :
<html lang="en">
<head>
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin="anonymous">
<link rel="preload" as="style" onload="this.rel='stylesheet'" href="https://fonts.googleapis.com/css2?family=DM Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=DM Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&family=Sofia Sans:ital,wght@0,1..1000;1,1..1000&display=swap">
<meta charset="utf-8">
<meta name="author" content="Estéban Soubiran">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Estéban's Infra · Estéban Soubiran</title>
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
<script>
;(function () {
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'
if (setting === 'dark' || (prefersDark && setting !== 'light'))
document.documentElement.classList.toggle('dark', true)
})()
</script>
<script type="module" crossorigin="" src="/assets/app-IQH-kuhC.js"></script>
<link rel="stylesheet" crossorigin="" href="/assets/app-B27WEjTb.css">
<link rel="modulepreload" crossorigin="" href="/assets/pages-DeF1Ma75.js">
<link rel="modulepreload" crossorigin="" href="/assets/WrapperContent-BVl8okbX.js">
<meta property="og:title" content="Estéban's Infra">
<meta name="twitter:title" content="Estéban's Infra">
</head>
</html>Et j’ai essayé de comprendre ce qui se passait.
Pour savoir si le head est correctement trié, tu peux utiliser capo.js. Après une rapide inspection, j’ai obtenu le résultat suivant :

Dans la documentation de Vite SSG, il est indiqué d’utiliser Unhead, alors j’ai lu toute la documentation. J’ai découvert qu’Unhead trie automatiquement le head, mais si c’est déjà utilisé, pourquoi le head n’est-il pas correctement trié ?
Tout ne s’est pas passé comme prévu
Si Unhead est censé trier les éléments du head, le problème pourrait-il venir de Vite SSG ?
Une seule façon de le savoir : déboguer.
Après avoir lu tout le code source de Vite SSG, j’ai trouvé une partie intéressante :
// create jsdom from renderedHTML
const jsdom = new JSDOM(renderedHTML)
// render current page's preloadLinks
renderPreloadLinks(jsdom.window.document, ctx.modules || new Set<string>(), ssrManifest)
// render head
if (head)
await renderDOMHead(head, { document: jsdom.window.document })
const html = jsdom.serialize()
let transformed = (await onPageRendered?.(route, html, appCtx)) || html
if (beasties)
transformed = await beasties.process(transformed)
const formatted = await formatHtml(transformed, formatting)Cette partie est responsable de la génération du HTML avant son enregistrement. La ligne intéressante est await renderDOMHead(head, { document: jsdom.window.document }). C’est la seule partie où le head est utilisé et rendu dans le HTML.
Encore plus intéressant, la fonction vient de @unhead/dom. Étrange, puisque nous utilisons le SSR, avoir une fonction front côté serveur paraît suspect.
Et j’avais raison. Cette fonction n’optimise pas le head. Elle est censée modifier les éléments du head une fois la page chargée, côté client. Amener toute la logique de tri côté client est complètement inutile.
En lisant la documentation d’Unhead, j’ai découvert la manière recommandée de l’utiliser avec le SSR.
import { transformHtmlTemplate } from '@unhead/vue/server'
const rendered = await render(url)
const html = await transformHtmlTemplate(
rendered.head,
template.replace(`<!--app-html-->`, rendered.html ?? '')
)La fonction transformHtmlTemplate extrait les éléments actuels du head depuis le template et les injecte dans le head avant de re-render le HTML et de l’injecter dans la page. Ça garantit que le head est correctement trié avant d’être envoyé au client.
Ça semblait être une bonne solution pour corriger le tri du head. J’ai donc créé une PR sur Vite SSG pour améliorer l’intégration avec Unhead.
refactor: uses unhead/server to optimize head
Pour utiliser rapidement ce correctif dans mon projet, j’ai patché Vite SSG en local. Ça m’a permis de continuer à développer mon projet sans attendre que ma PR soit mergée puis publiée.
Mais tout ne s’est pas passé comme prévu.
Le head était presque bien ordonné, mais après ce correctif, le FOUC était toujours là. Triste.
Ça signifie que le problème n’est pas lié au tri du head.
Enquête plus approfondie
Le patch de Vite SSG n’est pas inutile, car il améliore le tri du head. Cependant, il n’a pas résolu le FOUC. Il a fallu creuser davantage.
J’ai continué à chercher la cause racine. J’ai essayé d’analyser les performances de l’application pour identifier d’éventuels goulots d’étranglement, conditions de course ou autres problèmes pouvant causer le FOUC. Je n’ai rien trouvé.
J’ai inspecté le CSS et constaté que les styles de la prose s’appliquaient correctement, mais pas ceux de Nuxt UI. Si tu regardes attentivement la vidéo, tu verras la couleur du titre passer du noir au blanc, alors que le texte en dessous est déjà blanc. Les variables CSS de Nuxt UI ne sont pas disponibles lors du premier rendu, mais sont appliquées quand le JavaScript se charge.
Pour confirmer ce soupçon, j’ai vérifié le code source de Nuxt UI où j’ai trouvé un plugin Vue nommé colors.ts. Le même qui a causé le problème de build au début.
Tout menait à ce plugin, alors j’ai pris le temps de le comprendre, et j’ai trouvé la cause racine à la fois du FOUC et du problème de build :
const headData: UseHeadInput = {
style: [{
innerHTML: () => root.value,
tagPriority: -2,
id: 'nuxt-ui-colors'
}]
}
if (import.meta.client && nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {
const style = document.createElement('style')
style.innerHTML = root.value
style.setAttribute('data-nuxt-ui-colors', '')
document.head.appendChild(style)
headData.script = [{
innerHTML: 'document.head.removeChild(document.querySelector(\'[data-nuxt-ui-colors]\'))'
}]
}
useHead(headData)Plusieurs problèmes se mêlent ici.
Les sept premières lignes, ainsi que la dernière, servent à injecter les variables CSS de Nuxt UI dans le head, à la fois au build et à l’exécution. En désactivant le plugin, j’ai supprimé ce comportement, donc le FOUC se produit inévitablement.
Le bloc if crée un élément style et ne doit s’exécuter que côté client, pendant l’hydratation, si le contenu n’est pas rendu côté serveur. Mon app utilise Vite SSG pour être rendue côté serveur, donc ça ne devrait pas arriver.
Alors, c’est quoi ce nuxtApp ? Continuons l’enquête.
Au départ, Nuxt UI est fait pour Nuxt, mais grâce au travail de Daniel Roe et à beaucoup de stubs, Nuxt UI est aussi utilisable en dehors de Nuxt. L’un des stubs est le composable useNuxtApp :
export function useNuxtApp() {
return {
isHydrating: true,
payload: { serverRendered: false },
hooks,
hook: hooks.hook
}
}Le voilà. La propriété serverRendered est fixée à false, tout le temps. Donc même quand Vite SSG est utilisé, le plugin pense qu’il est dans un navigateur et essaie de créer l’élément style. Évidemment, ça ne marche pas.
Le correctif est vraiment simple. Au lieu de renvoyer toujours false, il faut renvoyer import.meta.env.SSR || false pour détecter correctement le rendu côté serveur.
fix(vue): check import.meta.env.SSR to support vite-ssg
Ensuite, j’ai réactivé le plugin Nuxt UI, le build est passé, et le FOUC avait disparu ! J’adore. Un petit booléen a fait une énorme différence.
Améliorer le head
Le build passait et le FOUC avait disparu. J’ai continué à vérifier le head du fichier généré pour m’assurer que tout était en ordre.
Ce n’était pas le cas.
Je pourrais m’arrêter là, mais j’étais lancé dans une série de pull requests, plongé dans Unhead, Nuxt UI et Vite SSG.
J’ai découvert deux choses.
- Nuxt UI définit un
tagPriorityà-2sur l’élément style lors de son injection. Ça signifie qu’il sera l’un des premiers éléments dans le head au lieu d’être placé automatiquement. - Unhead gérait mal l’ordre des scripts
inlinedetmodule.
Pour le premier point, j’ai seulement écrit un commentaire pour comprendre pourquoi il était défini. Ça pourrait être un prérequis pour l’intégration Nuxt.
En revanche, pour le second point, sachant que c’était un bug, j’ai ouvert deux pull requests.
En travaillant sur ces pull requests, j’ai découvert que la CI ne fonctionnait pas, que certains tests échouaient et que la vérification de type était cassée, alors j’ai créé 3 PR pour tout corriger.
Je corrigeais juste un petit bug dans la façon dont Unhead gérait l’ordre des scripts, et j’ai fini par corriger les tests et l’intégration.
Quand tu prends soin des détails, ça finit souvent comme ça, et en faisant des pull requests propres et qui fonctionnent, tu augmentes tes chances d’être accepté.
Je n’ai même pas eu le temps d’écrire un article de blog à leur sujet avant qu’elles ne soient mergées.
L’open source, pas par choix
Je voulais juste construire mon site pour documenter mon infrastructure. J’ai fini par corriger des bugs dans trois outils et ouvrir 7 pull requests.
Honnêtement, j’adorerais ne jamais avoir à les corriger, et j’aimerais ne jamais avoir à passer du temps à corriger ces bugs. Mais ce n’est pas le contrat implicite de l’open source, ni son état d’esprit. C’est ainsi que je le vois. Tout le monde ne le voit pas comme ça.
Des gens te proposent, gratuitement, et souvent en l’état, leur code pour ton propre usage. Ça te fait gagner du temps et de l’argent et te permet de construire plus vite et mieux. Oui, je passe du temps à corriger des bugs, mais combien de temps ai-je gagné en utilisant des logiciels open source ? À quelle fréquence ai-je pu contribuer à des logiciels open source pour construire l’expérience que je voulais dans mes projets ? Ça ne vaut même pas la peine de commencer à compter. Rendre un peu de ton savoir et de ton temps semble être un échange équitable.
Et c’est normal que les logiciels aient des bugs, des fonctionnalités manquantes ou simplement des cas d’usage non prévus. C’est inhérent au logiciel et ni le logiciel propriétaire ni l’open source n’y échappent.
Cependant, avec l’open source, tu peux changer la donne en ayant cette liberté de contribuer aux logiciels que tu utilises.
Construis des choses, n’importe quoi, mais donne vie à tes envies, sur Internet, ne t’arrête pas à localhost.
En chemin, tu rencontreras des bugs, des fonctionnalités manquantes, ou simplement des choses qui pourraient être améliorées. Quand c’est le cas, n’hésite pas à plonger dans ta stack, à la comprendre, et à essayer de corriger le problème.
C’est comme ça que tu contribues à l’open source. Pas l’inverse. C’est aussi l’une des meilleures façons d’apprendre.
Cultive l’état d’esprit open source et les opportunités surgiront naturellement.
Merci de me lire ! Je m'appelle Estéban, et j'adore écrire sur le développement web et le parcours humain qui l'entoure.
Je code depuis plusieurs années maintenant, et j'apprends encore de nouvelles choses chaque jour. J'aime partager mes connaissances avec les autres, car j'aurais aimé avoir accès à des ressources aussi claires et complètes lorsque j'ai commencé à apprendre la programmation.
Si vous avez des questions ou souhaitez discuter, n'hésitez pas à commenter ci-dessous ou à me contacter sur Bluesky, X, et LinkedIn.
J'espère que vous avez apprécié cet article et appris quelque chose de nouveau. N'hésitez pas à le partager avec vos amis ou sur les réseaux sociaux, et laissez un commentaire ou une réaction ci-dessous—cela me ferait très plaisir ! Si vous souhaitez soutenir mon travail, vous pouvez me sponsoriser sur GitHub !
Discussions
Ajouter un commentaire
Vous devez être connecté pour accéder à cette fonctionnalité.