eZecosystem / Mirror / Damien Pobel

Ce texte est une traduction de l'excellent Less Snake Oil, More Context par Surma.

Obtenir de bonnes performances sur le web est un défi permanent. Les développeur·ses essaient et essaieront encore d'en repousser les limites et c'est une bonne chose. Je ne veux pas changer cela.

Je veux changer comment nous — en tant que communauté — approchons, analysons et comprenons les problèmes de performances. Je vois souvent des questions du type « Quel est la meilleure manière de faire X ? », « Quel bibliothèque est la plus rapide pour réaliser Y ? ». Il semble que nous aimions les superlatifs mais lorsqu'il s'agit de performance, ils peuvent être contre-productifs.

Appliquer généreusement la poudre de perlimpinpin aux zones concernées

ou autrement dit « Des règles pas des outils ». Alex Russell a utilisé « Poudre de perlimpinpin » dans un tweet (NDT: Alex Russel étant anglophone, il a utilisé Snake oil) et je pense que cette expression transmet parfaitement à la fois l'opacité et le manque de fiabilité de ce type de traitement.

Quelques exemples :

  • Une animation est saccadée. Utilisez will-change: transform sur l'élément animé.
  • N'utilisez pas forEach(), les boucles for sont plus rapides.
  • Pour une chargement plus rapide, groupez les ressources.
  • N'utilisez pas le sélecteur * car il est lent.

Tout ceci est vrai dans un contexte particulier. Il faut bien comprendre une chose : la lenteur ou une animation saccadée n'est qu'un symptôme, pas une maladie. Ce qui est donc nécessaire ici, c'est une procédure de diagnostique différentiel. La fluidité d'une animation peut être gâchée pour de nombreuses raisons mais il est probable qu'une soit réellement en cause. Par exemple, si l'effet saccadé est causé par le ramasse miette traitant de gros morceaux de données à chaque frame, will-change: transform n'aura aucun effet positif. Au contraire, cette déclaration augmentera la pression sur la mémoire et pourrait même empirer le phénomène.

Je ne me souviens pas qui a énoncé « Si vous ne l'avez pas mesuré, ce n'est pas lent » mais cette phrase résonne en moi même si se concentrer sur la mesure peut mener à la Frénésie du Microbenchmark™️.

Note : pour le reste de ce billet, je vais parler d'optimisations en terme de vitesse mais tout ceci s'applique à d'autres types d'optimisations comme la réduction de l'empreinte mémoire.

Microbenchmarks

J'ai noté que récemment une grande attention était portée vers les microbenchmarks. Dans un microbenchmark, on essaie de départager deux approches en les exécutant plusieurs milliers de fois en isolation pour déterminer quelle solution est la plus rapide.

Comprenez-moi bien, les microbenchmarks ont une utilité, j'en ai même écrit et comme beaucoup d'autres avant moi. Ce sont des outils intéressants en particulier avec des frameworks comme BenchmarkJS qui permet d'obtenir des nombres statistiquement signifiants. En revanche, les frameworks de benchmark ne sont d'aucune aide pour s'assurer que votre benchmark a réellement un sens. Si vous ne connaissez pas ce que vous êtes en train de tester, les résultats peuvent mener à une mauvaise interprétation. Par exemple, dans mon billet sur le deep-cloning, je vérifiais les performances de const copyOfX = JSON.parse(JSON.stringify(x)). Il s'avère que V8 possède un cache d'objets. Le fait de réutiliser la même valeur x fois dans les tests a faussé les résultats. En réalité, je testais le cache plus qu'autre chose. Et si Mathias n'avait pas lu mon article, je ne l'aurais jamais découvert.

Compromis

Imaginons que vous ayez écrit ou trouvé un microbenchmark avec un sens. Il montre que vous devriez plutôt utiliser l'approche A au lieu de l'approche B. Il est important de comprendre que passer de B à A ne va pas uniquement rendre le code plus rapide. Quasiment toutes les optimisations de performance sont un compromis entre la vitesse et autre chose. Dans la plupart des cas, vous abandonnez un peu de lisibilité, d'expressivité et/ou d'idiomatisme. Ces propriétés ne se verront pas dans vos mesures pour autant il ne faut pas les ignorer. Le code devrait être écrit pour les humain·es (ce qui inclut le/la futur·e vous) mais pas l'ordinateur.

C'est là où les microbenchmarks nous abusent. Être capable de réaliser une opération plus rapidement ne signifie pas que le compromis en terme d'expressivité soit valable. En se basant sur des résultats de microbenchmarks, certaines personnes prendront pour évident que A est mieux que B et donc que vous devriez toujours mettre en œuvre A. C'est ainsi que la poudre de perlimpinpin est faite. Une partie du problème vient du fait qu'il est difficile de quantifier l'expressivité. À quel point un bout de code doit-il être plus rapide pour justifier une perte de lisibilité ? 10% ? 20% ?

Un point à propos de l'analyse statique et des transpilers s'impose. Il est possible d'écrire du code lisible et idiomatique tout en délivrant une version moins lisible et plus performante en production. Des outils comme @babel/present-env permettent d'écrire du JavaScript moderne et idiomatique sans avoir à se soucier de la prise en charge par les navigateurs et des implications en terme de performance. Le compromis ici se fait sur la taille et l'impénétrabilité du code généré. Certaines fonctionnalités ne peuvent être transformées qu'avec une importante augmentation de la taille du code ce qui détériore les temps de téléchargement et de compilation. La transformation des générateurs est un exemple extrême de ce phénomène. Un exécuteur de générateur doit être ajouté tout en rendant les fonctions génératrices significativement plus lourdes. Une fois encore, ce n'est pas une raison pour ne pas utiliser les générateurs ou pour ne pas les transformer. En revanche, c'est une information importante pour prendre une décision. Il s'agit encore et toujours de faire des compromis.

Exemple de transformation d'une fonction génératrice par Babel

Budgets

Dans ce domaine, les budgets peuvent aider. Il est important de budgétiser différents aspects de votre projet. Pour les applications web, les préconisations RAIL constitue un choix populaire. Si vous souhaitez construire une application tournant à 60 images par seconde, vous avez 16ms par frame. Pour produire une interface qui paraît fluide, il faut répondre visuellement aux action des utilisateur·rices en moins de 100ms. À partir du moment où vous avez des budgets, vous pouvez profiler votre application et vérifier si vous restez dans les limites fixées. Et si ce n'est pas le cas, vous savez par où commencer les travaux d'optimisation.

Les budgets contextualisent les coûts. Imaginons que vous ayez un bouton dans votre interface qui lorsqu'il est utilisé entraîne la récupération avec fetch() et l'affichage à l'écran des dernières données liées aux stocks. Avec l'appel réseau, le traitement des données et le rendu, le délai entre le clic de l'utilsateur·rice et l'affichage est de 60ms. Nous somme parfaitement dans les préconisations RAIL abordées plus haut avec même une marge de 40ms ! Si vous considérez l'utilisation d'un worker pour le traitement des données, la communication entre les fils d'exécution impliquera un délai supplémentaire. Par expérience, ce délai est de l'ordre d'une frame (16ms) ce qui donne un total de 76ms.

Si vous aviez à prendre une décision avec un état d'esprit microbenchmark — en regardant uniquement les nombres sans contexte — la solution à base de workers vous paraîtra une mauvaise idée. Cependant, la vraie question n'est pas « Quelle est la solution la plus rapide ? » mais plutôt « Quel compromis puis-je faire ? » ou encore « Mon budget me permet-il de le faire ? » Dans l'exemple du worker, nous payons 16ms mais cette dépense rentre facilement dans les 40ms de marge par rapport à notre budget RAIL. Ce que nous obtenons en retour dépend de votre perspective; dans cet exemple je souhaite me concentrer sur la robustesse. Si le serveur envoie une énorme structure JSON liée aux stocks, le décodage prendra un temps considérable pendant lequel le main thread sera bloqué. En décodant et traitant les données dans un worker, le main thread sera épargné et l'usage de l'application restera fluide.

Capture d'écran du benchmark six-speed

Prenons un autre exemple : jusqu'à il y a un an environ, utiliser une boucle for of pour parcourir un tableau était 17 fois plus lent qu'une boucle for classique (Note : six-speed a été mis en place en avril 2017. Depuis, de nombreux changements ont été apportés à V8 et Babel). À cause de ces résultats, certaines personnes évitent toujours les boucles for of.

Penchons nous sur des chiffres concrets : en parcourant un tableau de 100 éléments dans Chrome 55 (sorti en décembre 2016, avant le lancement de six-speed) avec un boucle for of puis un boucle for classique, j'obtiens :

  • boucle for of : 134µs
  • boucle for classique : 65µs

Sans conteste, la boucle for classique est plus rapide (dans Chrome 55) mais la boucle for of donne une vérification implicite des limites et rend le corps de la boucle plus lisibe en évitant l'utilisation d'un index. Y'a t il un intérêt à gagner ~60µs ? ça dépend mais la plupart du temps la réponse est non. Si vous utilisez des boucles for of dans un chemin critique (comme du code qui construit chaque frame dans une application WebGL), c'est peut-être le cas. Cependant, si vous ne parcourez que quelques dizaines d'éléments lorsque l'utilisateur·rice clique sur un bouton, je ne m'embêterais même pas à penser aux performances. Je choisis toujours la lisibilité. Et pour information, dans Chrome 70, les deux types de boucle ont exactement les mêmes performances. Un grand merci à l'équipe travaillant sur V8 !

Capture d'écran d'un benchmark de boucles for

Ça dépend (du contexte)

Bref, il n'existe aucune optimisation de performance qui soit toujours bonne. En fait, il n'y a pratiquement aucune optimisation de performance qui soit généralement bonne. Les pré-requis techniques, les audiences, les appareils et les priorités sont trop différentes d'un contexte à un autre. Ça dépend. Si vous voulez mon conseil, voici comme j'essaie d'aborder les optimisations :

  1. Définir un budget
  2. Mesurer
  3. Optimiser les parties qui explosent le budget
  4. Prendre une décision en tenant compte du contexte
11/12/2018 01:25 am   pwet.fr/blog   Mirror   Link  

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS correspondant)

11/01/2018 07:46 am   pwet.fr/blog   Mirror   Link  

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS correspondant)

10/04/2018 05:25 am   pwet.fr/blog   Mirror   Link  

Et un peu hors-sujet :

  • How to Build a Low-tech Website? (en) : j'adore la démarche, j'ai déjà le site statique (très) optimisé et plutôt low-tech et il me manque "juste" le serveur, le panneau solaire et la batterie pour faire pareil :-)

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS correspondant)

09/27/2018 08:15 am   pwet.fr/blog   Mirror   Link  

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS correspondant)

09/20/2018 07:07 am   pwet.fr/blog   Mirror   Link  

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS correspondant)

09/13/2018 05:42 am   pwet.fr/blog   Mirror   Link  

C'est la rentrée alors c'est reparti pour la veille hebdomadaire !

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS correspondant)

09/06/2018 06:55 am   pwet.fr/blog   Mirror   Link  
  • PHP: Never type hint on arrays (en) : array ne donne quasi aucune information et d'une manière générale (il y a toujours des exceptions…), un type hint sur un type précis spécifique au projet est mieux que l'utilisation des types primitifs du langage.
  • The First Thing That Ever Sold Online Was Pizza (en) : ahah :) et le plus génial, 24 ans plus tard, le formulaire marche probablement toujours (malheureusement le lien vers ce bout d'histoire d'Internet redirige sur le site de PizzaHut…)
  • Logging Activity With The Web Beacon API (en) : Une API web plutôt méconnue qui peut rendre service pour envoyer des messages de manière non urgente à un serveur.
  • Art of debugging with Chrome DevTool (en) : des astuces plutôt intéressantes et utiles, notamment le console.log en point d'arrêt conditionnel ou le console.log({ foo, bar }) au lieu de console.log(foo, bar) qui permet de logger à la fois la valeur et son nom. D'ailleurs, ces 2 là doivent fonctionner aussi dans Firefox.
  • How to Read an RFC (en) : quelques clarifications sur comment aborder les RFC, ces documents qui standardisent les protocoles sur Internet.
  • How to add product features without making it more complex (en) : une clé exposée dans cette article est de construire des interfaces ou des fonctionnalités utilisables par défaut.
  • The Cost Of JavaScript In 2018 (en) : Let’s design for a more resilient mobile web that doesn’t rely as heavily on large JavaScript payloads.

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

08/02/2018 06:35 am   pwet.fr/blog   Mirror   Link  

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

07/26/2018 06:25 am   pwet.fr/blog   Mirror   Link  
Un chien sous une couverture

Le code coverage ou la couverture de code par les tests en bon français est probablement l'une des métriques les plus incomprise et l'un des concepts les plus maltraité lorsqu'on parle de tests logiciels et de qualité de code en général. C'est d'ailleurs un bon sujet de conversation pour animer un open space rempli de développeur·ses un tant soit peu intéressé·es par les tests. Mais au fait qu'est-ce que c'est au juste ?

D'après Wikipédia:

En génie logiciel, la couverture de code est une mesure utilisée pour décrire le taux de code source exécuté d'un programme quand une suite de test est lancée.

Si on met de côté le fait qu'en fonction des outils, des configurations ou de qui extrait cette valeur, ce taux est un pourcentage d'instructions, de branches, de fonctions ou de lignes, il n'y a rien de très compliqué. Mais généralement, les choses se corsent dès lors qu'il s'agit d'interpréter ce nombre. Doit on absolument viser 100% ? Je plafonne à 40%, est-ce grave ? À partir de quel pourcentage mon code est il bien testé ? Pourquoi je ne parviens pas à couvrir les 2 cas qui me permettrait d'arriver à 100% ? Et bien d'autres… À ces questions relativement légitimes s'ajoute le fait que ce sujet a le pouvoir de polariser bon nombre de personnes sur des positions extrêmes avec d'un côté les partisans du il faut atteindre 100% de couverture pour bien tester et de l'autre les défenseurs du le taux couverture des tests ne sert à rien.

Assez paradoxalement, chaque camp n'a pas totalement tort. Ce paradoxe tient au fait que le taux couverture ne mesure que l'exécution d'une portion de code, ou en d'autres termes, un bout de code peut être couvert mais mal voire pas du tout testé. Par exemple, si dans un test vous appelez une fonction sans jamais vérifier directement ou indirectement sa valeur de retour, elle fera partie du code couvert mais on ne peut pas considérer qu'elle soit bien testée. À partir de là, on comprend bien la faiblesse de cette métrique. Elle est d'ailleurs d'autant plus faible qu'il est possible de gonfler artificiellement le taux de couverture. En effet, la plupart (tous ?) des outils permettent d'ignorer des morceaux de code soit grâce à des annotations dans le code, soit en se basant sur le nom des fichiers. Hormis lorsqu'il s'agit d'exclure des dépendances, j'ai toujours trouvé étrange l'utilisation de cette fonctionnalité qui transforme une métrique déjà faible en une métrique faible et fausse.

Malgré tout, le taux de couverture peut être un outil intéressant. Si sa valeur absolue à un instant t reste relativement anecdotique, mesurer son évolution dans le temps est un indicateur déjà plus pertinent. En fonction de l'historique du projet et si l'équipe a décidé de diriger des efforts sur les tests (et d'écrire de bons tests), le code coverage devrait soit rester relativement stable, soit augmenter dans le temps mais sauf exception, il ne devrait pas baisser significativement. Une autre manière de voir ce phénomène est de considérer que, passée la phase de prototypage où généralement on écrit peu de tests, toute modification du projet devrait venir avec des tests qui couvrent au maximum ces changements. Au fur et à mesure des évolutions et par refactorings successifs, le taux de couverture va donc mécaniquement et progressivement augmenter. Dans cet exercice, le rapport de couverture constitue un outil précieux en permettant de naviguer dans le code source tout en différenciant les parties couvertes du code qui ne l'est pas. D'ailleurs, viser un maximum de couverture pour chaque (petit) changement a aussi la vertu de pousser vers une certaine simplification du code pour éliminer du code inutile et donc difficilement testable.


Si on en fait pas une religion, le code coverage peut être un outil intéressant. Mais plutôt que de le considérer sur l'ensemble du projet, il me semble nettement plus pertinent de le prendre en compte sur de petites modifications où il est plus simple de vérifier que la couverture provient de tests de qualité. À noter qu'il est possible de tester l'efficacité des tests et la réalité du taux de couverture avec des tests de mutations (mutation testing) mais ce sera pour un autre billet.

07/23/2018 03:45 pm   pwet.fr/blog   Mirror   Link  
  • Objects should be constructed in one go (en) : réflexions intéressantes sur l’instanciation des objets et surtout sur comment garantir la cohérence de ceux-ci à tout moment dans une application.
  • Programming Sucks (en) : bon peut-être pas à ce point là mais on s'en approche parfois :-)
  • Defining Component APIs in React (en) : des bons conseils et en y regardant de plus près, en changeant quelques mots, ce sont pour la plupart des déclinaisons façon React de conseils de programmation qui s'appliquent partout ou presque.
  • What's So Great About OOP? (en) : Remember, OOP is about more than just a single object here or there. The benefits of OOP emerge in a community of objects, when objects begin collaborating with each other.
  • When 7 KB Equals 7 MB (en) : quelques subtilités dans la gestion du cache de ressources venant de CDN dans une Progressive Web App
  • Automating Accessibility and Performance Testing with Puppeteer and AxeCore (en) : une chouette utilisation de puppeteer pour tester sur une plateforme d'intégration continue à la fois l'accessibilité et les performances d'une application. Pour ce dernier cas, Chrome est configuré avec puppeteer pour simuler un CPU et/ou un connexion lente.
  • I Want Scalar Objects in PHP (en) : MOI AUSSI :)

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

07/19/2018 06:45 am   pwet.fr/blog   Mirror   Link  

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

07/12/2018 06:26 am   pwet.fr/blog   Mirror   Link  

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

07/05/2018 07:31 am   pwet.fr/blog   Mirror   Link  
  • I discovered a browser bug (en) : L'histoire de la découverte d'un bug de sécurité dans les navigateurs…
  • React Native at Airbnb (en) : Une série d'article expliquant pourquoi Airbnb abandonne React Native pour ses applications mobiles. Plutôt instructif même si il semble que l'abandon ne soit pas uniquement une question technique.
  • Pixels vs. Ems: Users DO Change Font Size (en) : il semble que oui certains utilisateurs changent la taille de police par défaut des navigateurs. Un argument de poids pour utiliser des unités relatives.
  • Le danger de l’élégance (fr) : et même le piège de l'élégance, comme souvent le piège se refermera quand il faudra maintenir ce code
  • Integers and Estimates (en) : de la difficulté l'impossibilité d'estimer :-)
  • Introducing the Single Element Pattern (en) : des règles et des suggestions intéressantes (et un outil pour vérifier leur application) à suivre lors de l'écriture de composant notamment React.
  • Anti-If: The missing patterns (en) : Quelques patterns pour se débarrasser des if. Tous ne sont pas à supprimer mais au delà de ça, ces conseils sont plutôt bons (surtout les 3 premiers)
  • DevTube (en) : Des vidéos pour développeur·se, encore des vidéos de pour dév…

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

06/28/2018 06:00 am   pwet.fr/blog   Mirror   Link  

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

04/05/2018 05:12 am   pwet.fr/blog   Mirror   Link   @8

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

03/29/2018 05:12 am   pwet.fr/blog   Mirror   Link   @12

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

03/22/2018 06:12 am   pwet.fr/blog   Mirror   Link   @8
  • En finir avec les bugs (fr) : non pas de recette magique dans ce billet, plutôt un plaidoyer pour regarder la vérité en face :)
  • Being in control of time in PHP (en) : Une manière élégante de rendre explicite et testable du code qui utilise une date.
  • The Practical Test Pyramid (en) : Une approche pratique et relativement pragmatique des tests automatisés (unitaires, fonctionnels, d'intégration, end-to-end, ...). C'est un peu long mais plein de bons conseils.
  • Why GitHub Won't Help You With Hiring (en) : Je serais pas aussi catégorique (et je dis pas ça parce mon compte est approximativement le 64 392 plus suivi :-)). Le profil Github est une information comme un joli CV, des recommandations sur Linkedin, un blog ou autre. Mais en effet, se baser uniquement là dessus est totalement ridicule.
  • How to write a git commit message that won't disappoint your future self (en) : sans doute discutable sur certains aspects mais trop souvent, les messages de commits ne sont assez pris au sérieux et pourtant ils sont là pour rester.
  • The Struggle (en) : tellement vrai :-)
  • Notes for new Make users (en) : pour les nouveaux utilisateurs de Make (et aussi pour les plus anciens qui auraient oublié quelques subtilités avec les années ;))

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

03/15/2018 06:12 am   pwet.fr/blog   Mirror   Link   @6

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

03/08/2018 05:12 am   pwet.fr/blog   Mirror   Link   @10

Compressing served files is a very usual trick to increase the loading performance of a website. The principle, defined in HTTP 1.1, is quite simple: when requesting a file, the browser announces the encoding it accepts in the Accept-Encoding header (for instance gzip) and thanks to it, the server knows how it can serve the file.

Typically, this is done on the fly by the web server with a dedicated module, for instance in Apache, mod_deflate does that pretty well (I've been using it for years (fr)) and nowadays this requires almost no configuration besides being activated unless you want to support the venerable most-hated browser of all time aka Internet Explorer 6 :)

Alternatively, it's possible to pre-generate compressed files along with the normal ones to serve the best one supported by the browser visiting your website. This has the advantage of requiring almost no resource on the web server while allowing you to use the highest compression level available even this takes a bit of time. And depending on the static site generator this is maybe super simple to setup.

So in this post, I'm gonna try to compress each page with Gzip and Brotli and to configure Apache to serve the best possible version.

Brotli ?

According to Wikipedia:

Brotli is a data format specification for data streams compressed with a specific combination of the general-purpose LZ77 lossless compression algorithm, Huffman coding and 2nd order context modelling. [...]

Brotli was first released in 2015 for off-line compression of web fonts. The version of Brotli released in September 2015 by the Google software engineers contained enhancements in generic lossless data compression, with particular emphasis on use for HTTP compression.

Brotli logo

If I believe caniuse.com, Brotli is now supported by most browsers. As usual, only Internet Explorer (11 and below) and Safari (before High Sierra) are a bit behind so for those and for probably tons of bots out there, Gzip compressed files or uncompressed files are still useful.

Brotli files are said to offer a higher compression rate than Gzip while remaining fast to decode. On the other hand, Brotli is also known to be slower to compress especially if you are using the highest compression level. Let's have a look at that.

Compressing files

Since I'm using Metalsmith to generate this web site, I can use metalsmith-gzip and metalsmith-brotli to compress generated documents. Both plugins are very similar and are configured to compress files matching the regular expression /\.[html|css|js|json|xml|svg|txt]/. I just had to configure metalsmith-gzip to compress at level 9 instead of 6 by default.

If you use Metalsmith, that's pretty much it! Of course, it's possible to do the same with a "simple" shell oneliner, something like:

find path/to/files -type f -a \( -name '*.html' -o -name '*.css' -o -name '*.js' \
-o -name '*.json' -o -name '*.xml' -o -name '*.svg' -o -name '*.txt' \) \
-exec brotli --best {} \+ -exec gzip --best -k {} \+

Apache configuration

This part is a bit tricky, at least it took me some time to figure it out, especially the part about preventing the double compression when you still need mod_deflate for other websites.

First, you need to make sure that mod_mime, mod_headers and mod_rewrite are enabled in Apache. Under Debian, if you are unsure just run as root:

# a2enmod mime
# a2enmod headers
# a2enmod rewrite

Then, the VirtualHost for your website requires a bit of configuration. Here is the relevant configuration excerpt for serving my pre-compressed website:

# Otherwise Content-Language: br is added
# Only needed if mod_mime configures that language
# in /etc/apache2/mods-enabled/mime.conf
RemoveLanguage .br

# Encoding for Brotli files
AddEncoding br .br

# Set gzip encoding instead of setting as a Content Type
RemoveType .gz
AddEncoding x-gzip .gz

# Mapping foo.suffix.gz or foo.suffix.br => Type
# see following repositories for recognized suffixes
# https://github.com/michel-kraemer/metalsmith-brotli
# https://github.com/ludovicofischer/metalsmith-gzip
AddType "text/html" .html.br .htm.br .html.gz .htm.gz
AddType "text/css" .css.br css.gz
AddType "text/plain" .txt.br txt.gz
AddType "text/xml" .xml.br .xml.gz
AddType "application/javascript" .js.br .js.gz
AddType "application/json" .json.br .json.gz
AddType "image/svg+xml" .svg.br .svg.gz
# Depending on what you compress, some more might be needed

# Proxy configuration
Header append Vary Accept-Encoding

RewriteEngine On

RewriteCond %{HTTP:Accept-Encoding} br
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME}.br -s
RewriteRule ^(.*)$ %{DOCUMENT_ROOT}/%{REQUEST_FILENAME}.br [E=no-gzip,L]

RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME}.gz -s
RewriteRule ^(.*)$ %{DOCUMENT_ROOT}/%{REQUEST_FILENAME}.gz [E=no-gzip,L]

So to explain it shortly:

  • this changes the configuration so that .br and .gz files have the same type for Apache as the ones without those suffixes. This has to be in sync with what is done by the static site generator or your shell script.
  • if a browser accepts Brotli compressed files and the requested file exists with a .br suffix, serve this file.
  • if a browser accepts Gzip compressed files and the requested file exists with a .gz suffix, serve this file.
  • in both cases, if the file with the suffix is served, the no-gzip environment variable is set so that mod_deflate does not try to compress again the file.

And that's it for serving pre-compressed files! You can see in the network panel that files are now served with Content-Encoding: br and maybe loading feels a bit snappier.

Screenshot of Firefox dev
    tools showing the HTTP headers

Some stats

This little experiment is a good opportunity to look at some numbers about Gzip vs. Brotli vs. no compression.

Time to compress files

At their maximum compression level, Brotli is way slower than Gzip. At the time of writing, 1452 files are matching the regular expression mentioned above. On my Macbook pro, metalsmith-gzip takes less than 400 milliseconds to compress those while for Brotli, this takes almost 6 seconds! Using the shell version, I find out that it takes almost 24 seconds to compress those files with Brotli and a bit more than 1 second for Gzip.

Even if in this setup, this does not matter much, it's interesting to note that the difference is somehow of an order of magnitude.

Resulting sizes

After all, that's the point of compressing, so let's have a look at the resulting size of some files (unless mentioned, sizes are in bytes).

File(s) Size Gzip Brotli Gzip - Brotli
Homepage 8293 239929% 199124% -408
RSS feed 57368 1963634% 1699830% -2638
Main stylesheet 10943 303428% 259124% -443
robots.txt 24 44183% 28117% -16
Posts index 6772 189228% 158723% -305
Latest post in English 13814 443732% 368127% -756
Total 11.65Mb 4Mb34% 3.4Mb29% -579Kb

Almost no surprise here, Brotli compressed files are about 5% (of the initial size) smaller than the Gzipped one. Given that most of my pages are quite small already, that's not a lot in absolute value but still an interesting gain. Only exceptions to that are very small files like my robots.txt where compressed ones are bigger than the original. So for the sake of completeness, I should not compress those but we are talking of 4 or 20 bytes depending on the algorithm :)

03/05/2018 02:10 am   pwet.fr/blog   Mirror   Link  
  • 5 Questions Every Unit Test Must Answer (en) : Une synthèse intéressante tant il est facile se perdre pendant l'écriture de tests unitaires.
  • Mutation testing with infection in big PHP projects (en) : où finalement on peut se mettre à tester les tests :-) Blague à part, il est surtout intéressant de comprendre qu'un code couvert 100% par des tests ne veut pas dire que le code est bien testé. Et dans ce cadre, ce type d'outil permet d'approcher la couverture réelle du code par les tests.
  • Using Vim to View Git Commits (en) : Vim (ou neovim) est non seulement capable d'afficher les logs git mais est aussi capable de permettre la navigation dans les commits!
  • WIP de 1, une histoire de WIP limits qui finit bien (fr) : Retour d'expérience intéressant sur la définition du paramètre WIP (Limitation du Work In Progress/Process) en Kanban.
  • Lasagna code - too many layers? (en) : Matthias Noback est comme d'habitude très pertinent avec plusieurs bons conseils dans ce billets.
  • Third party CSS is not safe (en) : Au delà du Keylogger en CSS qui a agité le petit monde du développement web dernièrement, l'inclusion d'une feuille de style externe expose à d'autres problèmes de sécurité.
  • The inception of ESLint (en) : la genèse du maintenant fameux projet ESLint
  • Genuine guide to testing React & Redux applications (en) : Un article plein de bon sens sur une stratégie de test possible d'une application React/Redux. Pour résumer, tests unitaires et tests d'intégration peuvent être complémentaires. Le paragraphe sur les snapshot tests me paraît également très pertinent.

Et un peu totalement hors-sujet :

  • Une chanson, l'addition (fr) : Une chaîne Youtube vraiment sympa qui présente chaque semaine en moins de 3 minutes l'histoire d'une chanson.

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

03/01/2018 06:57 am   pwet.fr/blog   Mirror   Link   @6

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

02/22/2018 05:16 am   pwet.fr/blog   Mirror   Link   @14

Via le Journal du Hacker, je suis tombé sur Config pour ne plus taper ses mots de passe MySQL et plus encore avec les Options file qui rappelle que le client MySQL en ligne de commande propose un fichier de configuration (~/.my.cnf) permettant de se simplifier la vie si on se connecte toujours aux mêmes machines/bases. Ce billet montre aussi l'option pager de ce fichier de configuration qui, comme son nom l'indique, permet de configurer un pager (more, less, neovim, ... ou ce que vous voulez) que l'auteur utilise pour mettre de la couleur dans le client MySQL / MariaDB avec Generic Colouriser. Bref, ce sont deux très bonnes astuces pour les utilisateurs de mysql en ligne de commande dont je fais partie.

Il se trouve qu'en plus, au travail, j'utilise une machine virtuelle. Et donc, pour accèder à MySQL, il me faut d'abord faire ouvrir un shell avec ssh pour ensuite lancer le client. Bien sûr, un bête alias permet de faire tout ça plus rapidement mais j'aime bien avoir mes outils de développement en local. En cherchant comment installer le client MySQL (et uniquement celui-ci) sur mon Mac, je suis tombé sur mycli et autant de le dire tout de suite, j'ai abandonné l'idée d'installer le client officiel :) En fait, mycli est un client MySQL (compatible avec MariaDB ou Percona) qui vient avec tout un tas de fonctionnalités vraiment pratiques et bien documentées comme la coloration syntaxique des requêtes, l'édition multi-ligne ou non, quelques commandes pratiques et surtout un complètement intelligent !

Capture d'écran de mycli dans un terminal

Il a sa propre configuration dans ~/.myclirc (qu'il génère au premier lancement avec les commentaires, encore une bonne idée) mais le plus beau, c'est qu'il utilise aussi ~/.my.cnf le fichier de configuration du client officiel et donc les 2 astuces citées plus haut fonctionnent parfaitement et directement dans cet outil !

Bref, pour le moment, mon .myclirc est celui par défaut (sauf le thème fruity) et mon .my.cnf ressemble à 

[client]
user = MONUSER
password = PASSWORD
host = vm.local

# ~/.grcat/mysql provient de https://github.com/nitso/colour-mysql-console
pager = 'grcat ~/.grcat/mysql|most'

J'utilise most comme pager mais j'hésite encore avec less qui propose une option pour ne pas paginer lorsque les données sont trop courtes ou Neovim dont j'ai vraiment l'habitude.

Dernier point, vous n'utilisez pas MySQL (ou MariaDB ou Percona) ? Pas de problème, l'auteur a écrit le même genre de clients pour d'autres serveur de base de données.

02/17/2018 11:28 am   pwet.fr/blog   Mirror   Link   @24

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

02/15/2018 06:13 am   pwet.fr/blog   Mirror   Link   @27

Ce texte est une traduction de l'excellent A Tale of Two Rooms: Understanding screen reader navigation.

Pour ceux d'entre nous qui utilisent un lecteur d'écran comme JAWS, NVDA or VoiceOver pour accéder au Web, l'expérience utilisateur peut être très différente de celle et ceux qui peuvent visualiser le contenu. J'ai donné de nombreuses formations sur l'accessibilité dont l'un des buts est d'aider les stagiaires à mieux comprendre la navigation sur le web à l'aide d'un lecteur d'écran.

Il est courant pour la plupart des gens de vouloir passer directement aux détails techniques 

  • Quelles touches du clavier presser ?
  • Avec quel lecteur d'écran devrais je tester ?
  • Quel navigateur devrais je utiliser ?

Ces considérations sont importantes mais il est préférable de prendre un peu de recul et de se demander :

À quoi ressemble cette expérience et comment puis je la simuler si je peux voir l'écran ?

À cette fin, je voudrais présenter plusieurs illustrations qui ont été efficaces pour mieux appréhender cette situation.

Une porte ouverte

Commençons par définir la scène de notre première histoire. Imaginez vous venez d'ouvrir une porte et vous regardez dans une vaste salle de conférence. Au centre de la pièce se trouve une grande table avec 10 chaises (5 de chaque côté de la table). Deux hommes et deux femmes sont installées à cette table. Ces 4 personnes se trouvent du même côté de la table (ils et elles font face à la porte où vous vous tenez). À l'autre bout de la pièce (derrière ces personnes) se trouvent 3 grandes fenêtres qui donnent sur une cour avec des bancs, des fleurs et de petits arbres. Sur la partie droite de la pièce, se trouvent un comptoir avec une cafetière et un micro-onde. Sur la gauche, une écran plat est fixé au mur.

En supposant que vous ne connaissiez pas la disposition de cette pièce, quelle est la première chose que vous feriez en ouvrant la porte ? Certains balaieraient la pièce de gauche à droite. D'autres le feraient de droite à gauche. D'autres encore regarderaient d'abord la table au centre puis parcourraient les contours de la pièce. Peu importe comment vous le feriez, la plupart balaieraient la pièce du regard pour avoir un rapide aperçu de la disposition et du contenu de la pièce. Le balayage ne prendrait que quelques secondes et la plupart d'entre vous le ferait sans même y penser. Ensuite, vous vous pourriez vous concentrer sur certains points comme les personnes assises autour de la table ou l'écran de télévision au mur.

Une pièce plongée dans le noir

Maintenant, reprenons la même scène et cette fois, quand vous ouvrez la porte, la pièce est totalement dans le noir. Aucune lumière n'est présente et par conséquent vous ne pouvez absolument rien distinguer au premier coup d'œil. On vous a donné une petite lampe de poche et en étant allumée, elle vous permet de voir une toute petite zone. Cette zone visible est un petit cercle d'environ 60 centimètres de diamètre et rien en dehors de ce cercle n'est éclairé.

Comment allez vous observer le contenu de cette pièce ?

Certains d'entre vous feraient faire des allers-retours de gauche à droite à la lampe en partant des pieds et en s'éloignant progressivement. Certains commenceraient par le fond de la pièce avec la lampe face à eux alors que d'autres pourraient pointer la lampe au hasard à différents endroits sans schéma particulier. En déplaçant la lampe, il vous faudrait construire une carte mentale ou une image de ce qui se trouve dans la pièce et de comment elle est organisée. Construire cette image mentale prendra nettement plus de temps que de balayer la pièce illuminée. En déplaçant la lampe, il vous faudra vous souvenir de chaque chose et de comment elle se positionne par rapport aux autres. Si vous oubliez l'emplacement de quelque chose, il vous faudra un certain temps pour la retrouver.

Le comptoir avec la cafetière était-il sur la droite ou au fond de la pièce ? Combien de personnes étaient assises autour de la table ? 4 ou peut-être 5 ?

Répondre à ces questions quand vous pouvez voir l'ensemble de la pièce en un coup d'œil ne demande que peu d'effort mais y répondre quand vous ne pouvez voir qu'une petite zone à la fois est beaucoup plus long.

Un scénario analogue

Ce second scénario est analogue à comment un utilisateur ou une utilisatrice d'un lecteur d'écran perçoit une page web ou un application pour smartphone.

Bien qu'une commande au clavier ou l'utilisation de l'écran tactile permette de déplacer le lecteur d'écran sur la page, il n'est toujours possible de lire qu'une chose à la fois. L'utilisateur ou l'utilisatrice malvoyante n'a aucun moyen d'avoir un aperçu rapide (disons en 1 à 3 secondes) de la page comme une personne qui peut voir l'écran.

Heureusement, des mécanismes techniques d'accessibilité comme des titres ou des régions peuvent aider l'utilisateur ou l'utilisatrice d'un lecteur d'écran à se concentrer sur certaines parties de la page.

De retour dans notre scénario avec la pièce sombre, imaginez qu'il y ait maintenant un petit point rouge lumineux sur chaque élément important de la pièce comme la table, le comptoir, la télévision et sur chaque personne autour de la table. Vous auriez toujours besoin de la lampe de poche pour explorer la zone mais les points rouges vous donneraient une idée de l'emplacement des choses importantes.

Les changements dynamiques de contenu d'une page peuvent être un autre défi pour les personnes utilisant un lecteur d'écran. Reprenons nos exemples de pièce sombre et de lampe de poche. Imaginons maintenant qu'un des hommes se lève pour se déplacer de l'autre côté de la table. Il y a maintenant 2 femmes et un homme d'un côté de cette table et un homme de l'autre. Dans le scénario de la pièce éclairée, vous remarqueriez très probablement le mouvement au moment où il se produit. Même si vous n'étiez pas en train de regarder l'homme qui s'est déplacé, vous auriez remarqué le déplacement du coin de l'œil et vous vous seriez tourné pour regarder ce qui était en train de se produire. Dans le scénario de la pièce plongée dans le noir, il serait très difficile de remarquer que quelque chose s'est produit à moins d'avoir pointé la lampe de poche sur l'homme en déplacement à au bon moment. Il est fort probable que vous n'auriez remarqué son déplacement qu'après avoir pointé la lampe sur la chaise laissée vacante.

C'est exactement ce qui se produit lorsque le contenu d'une page change sans alerter le lecteur d'écran. L'utilisateur ou l'utilisatrice peut ne jamais voir le changement à moins de se déplacer vers la nouvelle information et remarquer la différence.

Ce problème est soluble en s'assurant que les contenus dynamiques sont mis en œuvre avec des techniques d'alerte ou de zones "live" ce qui permet au lecteur d'écran d'annoncer la mise à jour à l'utilisateur ou l'utilisatrice.

Dans notre exemple de la pièce plongée dans le noir, l'homme pourrait annoncer qu'il est en train de bouger de l'autre côté de la table. Même si la lampe de poche n'était pas pointée vers lui, vous entendriez son annonce et vous pourriez comprendre la situation.

Pour terminer, regardons du côté de la fenêtre qui donne sur la cour. Dans le scénario de la pièce éclairée, vous pourriez voir rapidement que la fenêtre s'ouvre sur la cour avec des bancs, des fleurs et des arbres. Alors que dans le scénario de la pièce plongée dans le noir, même en pointant la lampe sur la fenêtre, vous ne seriez absolument pas capable de distinguer ce qui se passe dehors. Ceci illustre parfaitement ce qui se produit avec les éléments visuels comme les images lorsqu'ils n'ont pas de libellé textuel associé. Par exemple, un lecteur d'écran peut identifier qu'une image est présente dans une page mais la seule information qu'il peut communiquer sur elle est son texte alternatif. Sans ce texte, la personne utilisant le lecteur d'écran n'aura aucune idée de ce que l'image représente. Dans l'exemple de la pièce plongée dans le noir, ce texte pourrait être placé à côté de la fenêtre et pourrait décrire ce qu'il y a dehors. En localisant la fenêtre avec la lampe de poche, vous pourriez alors lire la description.

Pour une meilleure compréhension

Une des meilleures manières de comprendre l'utilisation d'un lecteur d'écran est d'essayer par vous-même. Il est probablement bénéfique d'essayer par vous même de naviguer sur le web avec un lecteur d'écran. En plus de cela, voici un exercice simple pour simuler les scénarios décris plus haut. (Je ne recommande pas visiter au hasard des salles de conférence avec des gens en éteignant les lumières !)

  1. Imprimez une page web. Prenez plutôt une page pas trop grande mais contenant une large variété d'éléments comme du texte, des liens, des menus, ...
  2. Prenez une feuille blanche et faites un petit trou en son centre. Le trou devrait être de la taille de 2 ou 3 mots (15 millimètres de diamètre environ est généralement suffisant)
  3. Placer la feuille avec le trou sur l'impression de la page de web et essayez de comprendre son contenu en déplaçant la feuille trouée.

La compréhension du contenu de la page sera probablement très compliquée et prendra du temps mais cette expérience donne une bonne idée de l'utilisation d'un lecteur d'écran (en particulier si aucune technique de navigation n'est mise en œuvre).

02/08/2018 04:59 pm   pwet.fr/blog   Mirror   Link   @8

Et un peu hors-sujet :

  • Pourquoi y a-t-il des gauchers ? (fr) : Bah en fait, on sait pas trop et en plus on n'est pas forcément 100% gaucher ou droitier... Bref, la vidéo est passionnante :-)

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

02/08/2018 05:25 am   pwet.fr/blog   Mirror   Link   @10

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

02/01/2018 05:28 am   pwet.fr/blog   Mirror   Link   @12

I've been using vim (and now neovim (fr)) for more than 15 years and I still discover new tricks regularly. This post is about one of those about vim's gf command. This command allows the user to open the file whose path is under the cursor. I guess it's clear how this can be handy to explore the source code of any application where the source contains references to others files.

These days I'm working on an application built with the popular stack composed of React, Redux, Babel and Webpack (and 2876 more friends ;-)) where quite obviously import is used to load dependencies of a given module. For those who don't know the import statement yet, as usual MDN provides a nice documentation about it. So the application source code contains code like:

// this file is src/common/mymodule.js
// Note: I always run neovim at the project root

import whatever from 'path/to/dependency';
import relative from './relative/path/to/dependency';

Depending, on your stack and in my case depending on Webpack configuration, import path resolution can be quite complicated. In my case, the first one could mean:

  • path/to/dependency.js
  • path/to/dependency/index.js
  • src/path/to/dependency.js
  • src/path/to/dependency/index.js
  • node_modules/path/to/dependency.js
  • node_modules/path/to/dependency/index.js

While the second one potentially means:

  • src/common/relative/path/to/dependency.js
  • src/common/relation/path/to/dependency/index.js

The path can also reference a CSS file (path/to/file.css) or a SVG file (path/to/file.svg) with the same resolution directories or even a node module which means the main file of that module should be imported:

import stuff from 'a-node-module'
// means importing
// node_modules/a-node-module/<path indicated in main entry of package.json>

Initially, I thought I would need a plugin so that gf is able to resolve all those paths. I even tested some but they failed for me. After a deeper look at gf documentation, it turns out that 2 lines of configuration to set path and suffixesadd allows to almost solve the issue:

set path=.,src,node_nodules
set suffixesadd=.js,/index.js

With that configuration, when hitting gf, neovim (or vim) will try to load the file in the given paths with the suffixes. The only case not fully solved by this is the one where the path refers to the main file of a node package, with the configuration above, neovim will open the directory node_modules/a-node-module which is already quite nice but for sure neovim can do better :)

This time includeexpr setting is the way out. It allows the developer to define a function to run if the editor was unable to find a file path. So by removing node_modules from the path and implementing a function, we can try to load the package.json file and build a file path with its main entry, this results in the following configuration:

set path=.,src
set suffixesadd=.js,/index.js

function! LoadMainNodeModule(fname)
    let nodeModules = "./node_modules/"
    let packageJsonPath = nodeModules . a:fname . "/package.json"

    if filereadable(packageJsonPath)
        return nodeModules . a:fname . "/" . json_decode(join(readfile(packageJsonPath))).main
    else
        return nodeModules . a:fname
    endif
endfunction

set includeexpr=LoadMainNodeModule(v:fname)

As far as I can tell, any imported module is now just a gf away from me. As a complementary tip, after using gf you can get back to the first file with Ctrl+O (as get Out) and get back again to the imported file with Ctrl+I (as get In).

The path resolution can be quite specific to the project so this configuration plays particularly well with a config by project which is also an out of the box vim feature (another trick I discovered lately ;)), in neovim just use .nvimrc instead of .vimrc.

01/30/2018 12:34 pm   pwet.fr/blog   Mirror   Link   @20

Et un peu hors-sujet :

(En plus du flux RSS global, les billets veille et uniquement ceux là sont listés dans le flux RSS veille)

01/25/2018 06:58 am   pwet.fr/blog   Mirror   Link   @24

Comme je l'écrivais dans ma rétrospective 2017, j'ai pour objectif de me dégoogliser. Début janvier, j'écrivais même complètement mais plus j'y réfléchis et plus je me dis que ce sera difficile.

Mais pourquoi au fait ?

Nous, on a peur que d'une chose : que le flicage nous tombe sur l'Internet

Dégooglisons Internet l'explique bien mieux que moi. Je vois ça comme une version moderne et numérique de ne pas mettre tout ses œufs dans le même panier. J'ajoute aussi que je suis sensible aux pratiques d'optimisation d'évasion fiscale de ces grands groupes et notamment de Google et que rien ne m'oblige à l'encourager avec mes données personnelles.

Après j'ai découvert récemment l'outil My Activity de Google et y voir notamment tous les endroits où je me suis déplacé depuis plus de 5 ans fait un effet "bizarre"...

De quels services suis-je dépendant ?

Je n'utilise plus Google pour mes recherches web depuis déjà quelques années. Mon choix s'est porté vers DuckDuckGo parce que les résultats à l'époque du changement me paraissaient (et paraissent toujours) corrects et aussi parce que j'aime bien son aspect épuré... a la Google :-) Dans une optique plus européanno-patriotique, il faudrait quand même que je jette un œil à Qwant.

Mais en ce qui me concerne, le moteur de recherche web est juste la partie émergée de l'iceberg de ma google dépendance, voire même une toute petite partie. J'utilise activement pas mal de leurs services. J'ai en particulier un compte Google Apps pour mon domaine pobel.fr qui date de l'époque où l'offre était gratuite pour les particuliers. Et donc, à partir de là j'utilise les services suivants :

  • Le mail et les contacts
  • Le calendrier
  • Hangout : essentiellement pour communiquer avec ma compagne
  • Drive : pour stocker et retrouver quelques documents dont je peux avoir besoin et très occasionnellement Google Doc et Spreadsheet
  • Youtube : sans être un accro aux vidéos en ligne, j'en consulte pas mal et j'en ai publié quelques-unes soit des screencasts des fonctionnalités que je développais dans mon ancien travail, soit des vidéos des sentiers du Revermont que j'adore emprunter à VTT.
  • Je suis identifié via Google sur quelques applications/services en ligne dont certains que j'utilise activemment notamment sur le lecteur RSS Feedly qui est l'outil principal derrière ma veille techno.

Liés à mes différents sites, je suis aussi un utilisateur des plusieurs services pour webmaster :

  • Analytics : sur pwet.fr et vtt.revermont.bike des statistiques que je n'ai pas consultées depuis des mois et où un bête traitement des logs du serveur avec AWStats me serait largement suffisant.
  • Adsense : sur pwet.fr mais vu ce que ça me rapporte, je me demande pourquoi c'est encore là
  • Webmaster Tools : celui là je l'utilise de temps à autres, je trouve intéressant de voir comment Google voit mes sites (oh ironie :)) et avec quels mots-clés les utilisateurs y arrivent.

Et enfin, ce qui sera sans doute le plus compliqué à gérer, j'ai un téléphone et une tablette Android rattachées à mon compte Google. Ces deux appareils sont dans un fonctionnement classique où j'utilise le store de Google pour installer des applications (quelques-unes sont vraiment utiles) et où j'utilise aussi quelques applications Google notamment Maps ou Youtube Kids qui me semblent pour le moment assez compliquées à remplacer pour des raisons différentes ;-)

Bref, il y a plus qu'à mais ce sera pour un prochain épisode.

01/19/2018 04:58 pm   pwet.fr/blog   Mirror   Link   @18