Fermer

mai 23, 2018

Création de l'extension DevTools du Gestionnaire de requêtes de fonctionnalités16 minutes de lecture



Au cours des deux dernières années, plusieurs fonctionnalités CSS ont été déployées pour les principaux navigateurs. CSS Grid Layout, par exemple, est passé de 0 à 80% du support global en l'espace de quelques mois, ce qui en fait un outil incroyablement utile et fiable dans notre arsenal. Même si la prise en charge actuelle d'une fonctionnalité telle que CSS Grid Layout est relativement importante, tous les navigateurs récents ou actuels ne la prennent pas en charge. Cela signifie qu'il est très probable que vous et moi développons actuellement pour un navigateur dans lequel il n'est pas pris en charge.

La solution moderne de développement pour les navigateurs modernes et anciens est la recherche de fonctionnalités. Ils nous permettent d'écrire du CSS qui est conditionnel au support du navigateur pour une fonctionnalité particulière. Bien que travailler avec des requêtes de fonctionnalités soit presque magique, les tester peut être pénible. Contrairement aux requêtes multimédias, nous ne pouvons pas simuler facilement les différents états en redimensionnant simplement le navigateur. C'est là qu'intervient le Feature Queries Manager une extension de DevTools pour vous aider à basculer facilement vos conditions de requête. Dans cet article, je traiterai de la façon dont j'ai construit cette extension, ainsi que d'une introduction à la construction des extensions des outils de développement.

Si une paire valeur-propriété (par exemple ) affiche: grid ), n'est pas supporté par le navigateur dans lequel la page est affichée, il ne se passe pas grand-chose. Contrairement à d'autres langages de programmation, si quelque chose est cassé ou non pris en charge dans CSS, cela n'affecte que la règle cassée ou non prise en charge, laissant tout le reste intact.

Prenons par exemple cette disposition simple:

 support browser
Grand aperçu

Nous avons un en-tête couvrant le haut de la page, une section principale directement en dessous à gauche, une barre latérale à droite et un pied de page couvrant le bas de la page. 19659008] Voici comment nous pourrions créer cette mise en page en utilisant CSS Grid:

Voir le Pen layout-grid par Ire Aderinokun ( @ire ) le CodePen .

Dans un navigateur comme Chrome, cela fonctionne comme nous le voulons. Mais si nous devions voir cette même page dans un navigateur qui ne supporte pas CSS Grid Layout, voici ce que nous aurions:

 La mise en page dans un navigateur ne supportant pas
Grand aperçu

Il est essentiellement le même comme si nous n'avions appliqué aucun des styles liés à la grille en premier lieu. Ce comportement de CSS était toujours intentionnel. Dans la spécification CSS il est dit:

Dans certains cas, les agents utilisateurs doivent ignorer une partie d'une feuille de style illégale, [which means to act] comme si elle n'y était pas

Historiquement, la meilleure façon pour gérer cela a été de faire usage de la nature en cascade de CSS. Selon la spécification, «la dernière déclaration dans l'ordre des documents gagne». Cela signifie que si plusieurs propriétés identiques sont définies dans un seul bloc de déclaration, ce dernier prévaut.

Par exemple, si nous avons les déclarations suivantes :

 body {
  affichage: flex;
  affichage: grille;
}

En supposant que Flexbox et Grid sont supportés dans le navigateur, ce dernier – display: grid – prévaudra. Mais si Grid est et non supporté par le navigateur, alors cette règle est ignorée, et toutes les règles précédentes et supportées, dans ce cas display: flex sont utilisées à la place.

 corps {
  display: flex; 
   affichage: grille; 
}

Problèmes en cascade

L'utilisation de la cascade comme méthode d'amélioration progressive est et a toujours été incroyablement utile. Même aujourd'hui, il n'y a pas de moyen plus simple ou plus efficace de gérer des solutions de remplacement simple, comme celle-ci pour appliquer une couleur solide où la syntaxe rgba () n'est pas supportée.

 div {
    couleur de fond: rgb (0,0,0);
    couleur de fond: rgba (0,0,0,0.5);
}

L'utilisation de la cascade, cependant, a une limitation majeure, qui entre en jeu lorsque nous avons plusieurs règles CSS dépendantes. Reprenons l'exemple de mise en page. Si nous devions essayer d'utiliser cette technique en cascade pour créer un repli, nous nous retrouverions avec des règles CSS concurrentes

Voir la mise en page de Pen – les deux par Ire Aderinokun ( @ire ) CodePen .

Dans la solution de secours, nous devons utiliser certaines propriétés telles que les marges et les largeurs, qui ne sont pas nécessaires et qui interfèrent en fait avec la version Grid "améliorée". Cela rend difficile de s'appuyer sur la cascade pour une amélioration progressive plus complexe.

Les requêtes de fonctionnalités à la rescousse

Les requêtes de fonctionnalités résolvent le problème de devoir appliquer des groupes de styles dépendants de la fonction CSS. Les requêtes de fonctionnalité sont une «règle d'imbrication» qui, comme les requêtes multimédia auxquelles nous sommes habitués, nous permettent de créer un sous-ensemble de déclarations CSS qui sont appliquées en fonction d'une condition. Contrairement aux requêtes multimédias, dont la condition dépend des spécifications de périphérique et d'écran, les conditions de requête de fonction sont basées sur si le navigateur prend en charge une paire propriété-valeur donnée.

Une requête de fonctionnalité est composée de trois parties:

  1. @supports mot-clé
  2. La condition, p.ex. display: flex
  3. Les déclarations CSS imbriquées

Voici à quoi ça ressemble:

 @supports (display: grid) {
    body {display: grille; }
}

Si le navigateur prend en charge display: grid les styles imbriqués s'appliqueront. Si le navigateur ne supporte pas l'affichage : grid alors le bloc est complètement ignoré.

Ce qui précède est un exemple de condition positive dans une requête, mais il y a quatre saveurs de questions:

  1. Condition positive, par ex. @supports (grille d'affichage)

  2. Condition négative, par ex. @supports not (affichage: grille)

  3. Conjonction, p. @supports (affichage: flex) et (affichage: grille)

  4. Disjonction, p. @supports (affichage: -ms-grid) ou (display: grid)

Les requêtes de fonctions résolvent le problème d'avoir des groupes de styles de repli et d'amélioration distincts. Voyons comment nous pouvons l'appliquer à notre exemple de mise en page:

Voir le stylo Exécuter le lapin run par Ire Aderinokun ( @ire ) le CodePen . [19659044] Présentation du Gestionnaire de requêtes de fonctionnalités

Lorsque nous écrivons des requêtes multimédia, nous les testons en redimensionnant notre navigateur de sorte que les styles de chaque point d'arrêt s'appliquent. Alors, comment tester les requêtes de fonctionnalités?

Comme les requêtes de fonctionnalités dépendent de la prise en charge d'une fonctionnalité par un navigateur, il n'existe pas de moyen simple de simuler l'état alternatif. Actuellement, la seule façon de le faire serait d'éditer votre code pour invalider / inverser la requête d'entité.

Par exemple, si nous voulions simuler un état dans lequel CSS Grid n'est pas supporté, nous devrions faire quelque chose comme ceci:

 / * styles de repli ici * /

@supports (affichage: grrrrrrrrid) {
    / * styles d'amélioration ici * /
}

C'est là qu'intervient le Gestionnaire de Requêtes de Fonctionnalités. C'est un moyen d'inverser vos requêtes sans avoir à éditer manuellement votre code.

( Grand aperçu )

Cela fonctionne par il annule simplement la requête d'entité telle qu'elle est écrite. Donc la requête suivante:

 @supports (display: grid) {
    body {display: grille; }
}

Deviendra le suivant:

 @supports not (display: grid) {
    body {display: grille; }
}

Fait amusant, cette méthode fonctionne aussi bien pour les requêtes de caractéristiques négatives. Par exemple, la requête de fonction négative suivante:

 @supports not (display: grid) {
    body {affichage: bloc; }
}

Deviendra le suivant:

 @supports not (not (display: grid)) {
    body {affichage: bloc; }
}

Ce qui revient essentiellement à supprimer le "non" de la requête d'entité.

 @supports (display: grid) {
    body {affichage: bloc; }
}

Construction du gestionnaire de requêtes de fonctionnalités

FQM est une extension des outils de développement de votre navigateur. Il fonctionne en enregistrant tous les CSS sur une page, en filtrant le CSS qui est imbriqué dans une requête d'entité, et en nous donnant la possibilité de basculer la version normale ou "inversée" de cette requête d'entité.

Créer un panneau DevTools [19659023] Avant de passer à la construction spécifique de la FQM, voyons comment créer un nouveau panneau DevTools. Comme toute autre extension de navigateur, nous enregistrons une extension DevTools avec le fichier manifest.

 {
  "manifest_version": 2,
  "name": "Gestionnaire de requêtes de fonctionnalités",
  "nom abrégé": "FQM",
  "description": "Gérer et basculer CSS sur une page derrière une @supports Feature Query.",
  "version": "0.1",
  "permissions": [
    "onglets",
    "activeTab",
    ""
  ],
  "icônes": {
    "128": "images/icon@128.png",
    "64": "images/icon@64.png",
    "16": "images/icon@16.png",
    "48": "images/icon@48.png"
  }
}

Pour créer un nouveau panneau dans DevTools, nous avons besoin de deux fichiers – un devtools_page qui est une page HTML avec un script attaché qui enregistre le second fichier, panel.html

 Le script devtools crée la page du panneau
Grand aperçu

Tout d'abord, nous ajoutons la devtools_page à notre fichier manifeste:

 {
  "manifest_version": 2,
  "name": "Gestionnaire de requêtes de fonctionnalités",
  ...
  "devtools_page": "devtools.html",
}

Ensuite, dans notre fichier devtools.html nous créons un nouveau panneau dans DevTools:




  

    
 </ html

Enfin, nous créons notre page HTML de panneau:




  

  

Bonjour, monde!

Si nous ouvrons notre navigateur, nous verrons un nouveau panneau appelé "FQM" qui chargera la page panel.html .

 Un nouveau panneau dans le navigateur DevTools montrant le "Hello, World" Texte
Grand aperçu

Lecture de CSS à partir de la page inspectée

Dans la FQM, nous devons accéder à tous les CSS référencés dans le document inspecté afin de savoir lesquels sont dans les requêtes de fonctionnalités. Cependant, notre panneau DevTools n'a pas directement accès à quoi que ce soit sur la page. Si nous voulons accéder au document inspecté, nous avons besoin d'un script de contenu.

 Le script de contenu lit le CSS du document HTML
Grand aperçu

Un script de contenu est un fichier javascript qui a le même accès au HTML page comme tout autre morceau de javascript intégré en son sein. Pour enregistrer un script de contenu, il suffit de l'ajouter à notre fichier manifeste:

 {
      "manifest_version": 2,
      "name": "Gestionnaire de requêtes de fonctionnalités",
      ...
      "content_scripts": [{
        "matches": [""],
        "js": ["browser-polyfill.js", "content.js"]
      }],
    }

 

Dans notre script de contenu, on peut alors lire toutes les feuilles de style et css en accédant à document.styleSheets :

 Array.from (document.styleSheets) .forEach ((feuille de style ) => {
      laissez cssRules;
      
      essayez {
        cssRules = Array.from (stylesheet.cssRules);
      } catch (err) {
        return console.warn (`[FQM] Impossible de lire cssRules à partir de stylesheet: $ {stylesheet.href}`);
      }
      
      cssRules.forEach ((rule, i) => {
      
        / * Vérifie si la règle css est une requête de fonctionnalité * /
        if (règle instanceof CSSSupportsRule) {
          / * faire quelque chose avec la règle css * /
        }
        
      });
    });

Connexion du panneau et des scripts de contenu

Une fois que nous avons les règles du script de contenu, nous voulons les envoyer au panneau afin qu'elles puissent être visibles. Idéalement, nous voudrions quelque chose comme ceci:

 Le script de contenu transmet des informations au panneau et le panneau envoie des instructions pour modifier CSS au contenu
Grand aperçu

Cependant, nous ne pouvons pas faire cela exactement, parce que les fichiers de panneau et de contenu ne peuvent pas réellement parler directement les uns aux autres. Pour transmettre des informations entre ces deux fichiers, nous avons besoin d'un intermédiaire – un script d'arrière-plan. La connexion résultante ressemble à ceci:

 Les scripts de contenu et de panneau communiquent via un script d'arrière-plan
Large preview

Comme toujours, pour enregistrer un script d'arrière-plan, nous devons l'ajouter à notre fichier manifeste: ] {
  "manifest_version": 2,
  "name": "Gestionnaire de requêtes de fonctionnalités",
  …
  "Contexte": {
    "scripts": [« browser-polyfill.js », « background.js »]
  },
}

Le fichier d'arrière-plan devra ouvrir une connexion au script du panneau et écouter les messages provenant de là. Lorsque le fichier d'arrière-plan reçoit un message du panneau, il le transmet au script de contenu qui écoute les messages en arrière-plan. Le script d'arrière-plan attend une réponse du script de contenu et renvoie ce message au panneau.

Voici un exemple de base de la façon dont cela fonctionne:

 // Ouvrir une connexion au script d'arrière-plan
const portToBackgroundScript = browser.runtime.connect ();

// Envoyer un message au contenu (via l'arrière-plan)
portToBackgroundScript.postMessage ("Bonjour du panneau!");

// Ecoute les messages du contenu (via le fond)
portToBackgroundScript.onMessage.addListener ((msg) => {
  console.log (msg);
  // => "Bonjour à partir du contenu!"
});
 // backrgound.js

// Ouvre une connexion au script du panneau
browser.runtime.onConnect.addListener ((port) => {
  
  // Ecoute les messages du panneau
  port.onMessage.addListener ((request) => {
  
    // Envoyer un message depuis panel.js -> content.js
    // et renvoie la réponse de content.js -> panel.js
    browser.tabs.sendMessage (request.tabId, requête)
      .then ((res) => port.postMessage (res));
  });
});
 // content.js

// Ecoute des messages en arrière-plan
browser.runtime.onMessage.addListener ((msg) => {

  console.log (msg)
  // => "Bonjour du panneau!"
  
  // Envoyer un message au panneau
  return Promise.resolve ("Bonjour à partir du contenu!");
});

Gérer les requêtes de fonctionnalités

Enfin, nous pouvons aller au cœur de ce que fait l'extension, qui est de "basculer" sur / désactiver le CSS lié à une requête d'entité.

Si vous vous souvenez, dans le contenu script, nous avons bouclé tout le CSS dans les requêtes de fonctionnalités. Lorsque nous faisons cela, nous devons également sauvegarder certaines informations sur la règle CSS:

  1. La règle elle-même
  2. La feuille de style à laquelle elle appartient
  3. L'index de la règle dans la feuille de style
  4. Une version "inversée" de la règle

Voici à quoi cela ressemble:

 cssRules.forEach ((rule, i) => {
  
  const cssRule = rule.cssText.substring (rule.cssText.indexOf ("{"));
  const invertedCSSText = `@supports non ($ {rule.conditionText}) $ {cssRule}`;
  
  FEATURE_QUERY_DECLARATIONS.push ({
    règle: règle,
    stylesheet: stylesheet,
    index: je,
    invertedCSSText: invertedCSSText
  });
  
});

Lorsque le script de contenu reçoit un message du panneau pour inverser toutes les déclarations relatives à la condition de requête, nous pouvons facilement remplacer la règle courante par la règle inversée (ou vice versa).

 function toggleCondition (condition, toggleOn ) {
  FEATURE_QUERY_DECLARATIONS.forEach ((déclaration) => {
    if (declaration.rule.conditionText === condition) {
      
      // Supprimer la règle actuelle
      declaration.stylesheet.deleteRule (declaration.index);
      
      // Remplacer à l'index par une déclaration originale ou inversée
      règle const = toggleOn? declaration.rule.cssText: déclaration.invertedCSSText;
      declaration.stylesheet.insertRule (rule, declaration.index);
    }
  });
}

Et c'est essentiellement ça! L'extension Feature Query Manager est actuellement disponible pour Chrome et Firefox

Limitations de la FQM

Le Gestionnaire de requêtes de fonctionnalités fonctionne en "inversant" vos requêtes de fonctionnalités, de sorte que la condition inverse s'applique. Cela signifie qu'il ne peut pas être utilisé dans tous les scénarios.

Fallbacks

Si votre CSS "enhancement" n'est pas écrit dans une requête, l'extension ne peut pas être utilisée car elle dépend de la recherche d'une règle CSS. 19659119] Fonctionnalités non prises en charge

Vous devez prendre note si le navigateur que vous utilisez dans la fonction FQM prend ou ne prend pas en charge la fonctionnalité en question. Ceci est particulièrement important si votre requête de fonction d'origine est une condition négative, car l'inverser la transformera en une condition positive. Par exemple, si vous avez écrit le CSS suivant:

 div {background-color: blue; }

@supports non (affichage: grille) {
  div {background-color: rose; }
}

Si vous utilisez le FQM pour inverser cette condition, il deviendra le suivant:

 div {background-color: blue; }

@supports (affichage: grille) {
  div {background-color: rose; }
}

Pour que vous puissiez réellement voir la différence, vous devez utiliser un navigateur qui supporte réellement l'affichage : grid .

J'ai construit le gestionnaire de requêtes de fonctionnalités un moyen de tester plus facilement les différents CSS que je développe, mais ce n'est pas un remplacement pour tester la disposition dans les navigateurs et les dispositifs réels. Les outils de développement vont si loin, rien ne vaut les tests de périphériques réels.

 Editorial de Smashing (ra, yk, il)




Source link