Les bibliothèques JavaScript telles que jQuery sont l’approche privilégiée pour écrire du code JavaScript dans le navigateur depuis près de 10 ans. Ils ont été un succès retentissant et une intervention nécessaire pour ce qui était autrefois un navigateur qui regorge de divergences et de problèmes de mise en œuvre. jQuery a parfaitement dissimulé les bugs et les bizarreries des navigateurs et en a fait une approche simple pour faire avancer les choses, comme la gestion d'événements, la manipulation Ajax et DOM.
À l'époque, jQuery a résolu tous nos problèmes, nous incluons son pouvoir tout-puissant et nous nous mettons immédiatement au travail. C’était en quelque sorte une boîte noire dont le navigateur avait «besoin» pour fonctionner correctement.
Mais le Web a évolué, les API s'améliorent, les normes sont mises en place, le Web évolue très rapidement et je ne suis pas sûr que les bibliothèques géantes aient une place dans le futur pour le navigateur. Cela devient un environnement orienté module.
Un module est une fonctionnalité encapsulée qui ne fait qu'une chose, et qui très bien. Par exemple, un module peut être responsable de l'ajout de classes à un élément, de la communication via HTTP via Ajax, etc. - les possibilités sont infinies.
Un module peut prendre de nombreuses formes et tailles, mais son objectif général est d’être importé dans un environnement et de fonctionner immédiatement. En règle générale, chaque module comporte une documentation de base pour les développeurs et un processus d'installation, ainsi que les environnements auxquels il est destiné (tels que le navigateur, le serveur)..
Ces modules deviennent alors des dépendances de projet et les dépendances deviennent faciles à gérer. Les jours passés dans une immense bibliothèque s'estompent lentement, les grandes bibliothèques n'offrent pas autant de souplesse ni de puissance. Des bibliothèques telles que jQuery l'ont également reconnu, ce qui est fantastique: elles disposent d'un outil en ligne qui vous permet de télécharger uniquement les éléments dont vous avez besoin..
Les API modernes sont un puissant accélérateur d'inspiration pour les modules. Maintenant que les implémentations de navigateur se sont considérablement améliorées, nous pouvons commencer à créer de petits modules utilitaires pour nous aider à effectuer nos tâches les plus courantes..
L'ère des modules est arrivée et elle est là pour rester.
L’API classList est une API moderne qui me passionne depuis sa création. Inspirés de bibliothèques telles que jQuery, nous avons maintenant un moyen natif d’ajouter des classes à un élément sans fonctions de bibliothèque ou d’utilitaire..
L'API classList existe depuis quelques années maintenant, mais peu de développeurs le savent. Cela m'a inspiré pour créer un module utilisant l'API classList et, pour les navigateurs moins fortunés, prendre en charge une forme d'implémentation de secours..
Avant de plonger dans le code, voyons ce que jQuery a apporté à la scène pour ajouter une classe à un élément:
$ (elem) .addClass ('myclass');
Lorsque cette manipulation a atterri en natif, nous nous sommes retrouvés avec l'API classList susmentionnée - un objet DOMTokenList (valeurs séparées par un espace) qui représente les valeurs stockées par rapport à className d'un élément. L'API classList nous fournit quelques méthodes pour interagir avec cette DOMTokenList, toutes très «jQuery-like». Voici un exemple de la façon dont l’API classList ajoute une classe, qui utilise le classList.add ()
méthode:
elem.classList.add ('myclass');
Que pouvons-nous apprendre de cela? Une fonction de bibliothèque qui pénètre dans une langue est un gros problème (ou du moins l’inspire). C’est ce qui est formidable avec la plate-forme Web ouverte, nous pouvons tous avoir un aperçu de la façon dont les choses évoluent..
Quoi ensuite? Nous connaissons les modules et nous aimons bien l'API classList, mais malheureusement, tous les navigateurs ne le prennent pas encore en charge. Nous pourrions cependant écrire un repli. Cela semble être une bonne idée pour un module qui utilise classList quand il est supporté ou des replis automatiques sinon.
Il y a environ six mois, j'ai construit un module autonome et très léger permettant d'ajouter des classes à un élément en JavaScript simple. J'ai fini par l'appeler. apollo.js
.
L'objectif principal du module était de commencer à utiliser la brillante API classList et de ne plus avoir besoin d'une bibliothèque pour effectuer une tâche très simple et courante. jQuery n'utilisait pas (et n'utilise toujours pas) l'API classList, j'ai donc pensé que ce serait un excellent moyen d'expérimenter la nouvelle technologie..
Nous allons expliquer comment j’ai fait cela et comment chacun de ces éléments constitue le module simple.
Comme nous l'avons déjà vu, classList est une API très élégante et «jQuery developer-friendly», la transition est simple. Une chose que je n'aime pas à ce sujet, cependant, est le fait que nous devons continuer à faire référence à l'objet classList pour utiliser l'une de ses méthodes. J'avais pour objectif de supprimer cette répétition lorsque j'ai écrit apollo, en décidant de la conception suivante de l'API:
apollo.addClass (elem, 'myclass');
Un bon module de manipulation de classe devrait contenir hasClass
, addClass
, removeClass
et toggleClass
méthodes. Toutes ces méthodes sortiront de l’espace de noms «apollo».
En regardant de près la méthode «addClass» ci-dessus, vous pouvez voir que je passe l'élément dans le premier argument. Contrairement à jQuery, qui est un énorme objet personnalisé auquel vous êtes lié, ce module accepte un élément DOM, son développement dépend du développeur, de méthodes natives ou d'un module sélecteur. Le second argument est une simple valeur de chaîne, n'importe quel nom de classe que vous aimez.
Passons en revue le reste des méthodes de manipulation de classe que je voulais créer pour voir à quoi elles ressemblent:
apollo.hasClass (elem, 'myclass'); apollo.addClass (elem, 'myclass'); apollo.removeClass (elem, 'myclass'); apollo.toggleClass (elem, 'myclass');
Où commençons-nous? Tout d’abord, nous avons besoin d’un objet auquel ajouter nos méthodes, ainsi que de la fermeture de certaines fonctions pour héberger tous les travaux internes / variables / méthodes internes. À l'aide d'une expression de fonction appelée immédiatement (IIFE), j'emballe un objet nommé apollo (et certaines méthodes contenant des abstractions classList) pour créer notre définition de module..
(function () var apollo = ; apollo.hasClass = fonction (elem, className) return elem.classList.contains (className);; apollo.addClass = fonction (elem, className) elem.classList.add (className);; apollo.removeClass = fonction (elem, className) elem.classList.remove (className);; apollo.toggleClass = fonction (elem, className) elem.classList.toggle (className);; window.apollo = apollo;) (); apollo.addClass (document.body, 'test');
Maintenant que classList fonctionne, nous pouvons penser au support des navigateurs existants. Le but de la apollomodule
est de fournir une implémentation API cohérente minuscule et autonome pour la manipulation de classe, quel que soit le navigateur. C'est ici qu'intervient la détection de fonctionnalités simples.
Le moyen facile de tester la présence des fonctionnalités pour classList est la suivante:
if ('classList' dans document.documentElement) // vous avez le soutien
Nous utilisons le dans
opérateur qui évalue la présence de classList à Boolean. La prochaine étape consisterait à fournir sous condition l’API à classList ne prenant en charge que les utilisateurs suivants:
(function () var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' dans document.documentElement) hasClass = function () return elem.classList.contains (className); addClass = function (elem, className) elem.classList.add (className); removeClass = fonction (elem, className) elem.classList.remove (className); toggleClass = fonction (elem, className) elem.classList.toggle (className); apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo;) ();
La prise en charge héritée peut être effectuée de plusieurs manières, en lisant la chaîne className et en parcourant tous les noms, en les remplaçant, en les ajoutant, etc. jQuery utilise beaucoup de code pour cela, en utilisant des boucles longues et une structure complexe, je ne veux pas complètement bouffer ce module frais et léger, alors décidez-vous d'utiliser une correspondance d'expression régulière et remplace pour obtenir le même effet avec next à pas de code du tout.
Voici l'implémentation la plus propre que j'ai pu trouver:
function hasClass (elem, className) renvoie new RegExp ('(^ | \\ s)' + className + '(\\ s | $)'). test (elem.className); function addClass (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className; function removeClass (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) * ',' g '), "); fonction toggleClass (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, className);
Intégrons-les au module en ajoutant le autre
partie pour les navigateurs non compatibles:
(function () var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' dans document.documentElement) hasClass = function () return elem.classList.contains (className);; addClass = function (elem, className) elem.classList.add (className);; removeClass = function (elem, className) elem.classList.remove (className);; toggleClass = fonction (elem, className) elem. classList.toggle (className);; else hasClass = function (elem, className) renvoie new RegExp ('(^ | \\ s)' + className + '(\\ s | $)'). test ( elem.className);; addClass = fonction (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className;; removeClass = fonction (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) * ',' g '), ");; toggleClass = fonction (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, className);; apollo.has Classe = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo; ) ();
Un jsfiddle de travail de ce que nous avons fait jusqu'à présent.
Laissons-le là, le concept a été livré. Le module apollo a quelques fonctionnalités supplémentaires telles que l’ajout de plusieurs classes à la fois, vous pouvez le vérifier ici si vous êtes intéressé..
Alors, qu'avons-nous fait? Construit une fonctionnalité encapsulée dédiée à faire une chose et bien une chose. Le module est très simple à lire et à comprendre, et les modifications peuvent être facilement effectuées et validées en même temps que les tests unitaires. Nous avons également la possibilité de faire appel à apollo pour les projets pour lesquels nous n’avons pas besoin de jQuery et de son offre considérable, et le minuscule module apollo suffira..
Le concept de modules n'est pas nouveau, nous les utilisons tout le temps. Vous savez probablement que JavaScript ne concerne plus uniquement le navigateur, il tourne sur des serveurs et même des téléviseurs..
Quels modèles pouvons-nous adopter lors de la création et de l'utilisation de ces nouveaux modules? Et où pouvons-nous les utiliser? Il existe deux concepts appelés «AMD» et «CommonJS», explorons-les ci-dessous.
Asynchronous Module Definition (généralement appelée AMD) est une API JavaScript permettant de définir des modules à charger de manière asynchrone. Celles-ci sont généralement exécutées dans le navigateur, car le chargement synchrone engendre des coûts de performances, ainsi que des problèmes d’utilisabilité, de débogage et d’accès entre domaines. AMD peut contribuer au développement en maintenant les modules JavaScript encapsulés dans de nombreux fichiers..
AMD utilise une fonction appelée définir
, qui définit un module lui-même et tous les objets d'exportation. À l'aide d'AMD, nous pouvons également faire référence à toutes les dépendances pour importer d'autres modules. Un exemple rapide tiré du projet AMD GitHub:
define (['alpha']], fonction (alpha) return verbe: fonction () return alpha.verb () + 2;;);
Nous pourrions faire quelque chose comme ceci pour apollo si nous utilisions une approche de DMLA:
define (['apollo'], function (alpha) var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' dans document.documentElement) hasClass = function () return elem.classList. contient (className);; addClass = fonction (elem, className) elem.classList.add (className);; removeClass = fonction (elem, className) elem.classList.remove (className);; toggleClass = fonction (elem, className) elem.classList.toggle (className);; else hasClass = function (elem, className) renvoie le nouveau RegExp ('(^ | \\ s)' + className + '(\\ s | $) '). test (elem.className);; addClass = fonction (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className;; removeClass = function (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) *', 'g'), ");; toggleClass = fonction (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, clas Le nom de); ; apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo; );
Node.js est en augmentation depuis quelques années, ainsi que des outils et des modèles de gestion des dépendances. Node.js utilise quelque chose appelé CommonJS, qui utilise un objet "exports" pour définir le contenu d'un module. Une implémentation vraiment basique de CommonJS pourrait ressembler à ceci (l’idée d’exporter quelque chose pour l’utiliser ailleurs):
// someModule.js exports.someModule = function () return "foo"; ;
Le code ci-dessus serait assis dans son propre fichier, j'ai nommé celui-ci someModule.js
. Pour l'importer ailleurs et pouvoir l'utiliser, CommonJS spécifie que nous devons utiliser une fonction appelée «require» pour extraire les dépendances individuelles:
// faire quelque chose avec 'myModule' var myModule = require ('someModule');
Si vous avez également utilisé Grunt / Gulp, vous êtes habitué à voir ce motif.
Pour utiliser ce modèle avec apollo, nous ferions ce qui suit et référencons le exportations
Objet à la place de la fenêtre (voir dernière ligne exports.apollo = apollo
):
(function () var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' dans document.documentElement) hasClass = function () return elem.classList.contains (className);; addClass = function (elem, className) elem.classList.add (className);; removeClass = function (elem, className) elem.classList.remove (className);; toggleClass = fonction (elem, className) elem. classList.toggle (className);; else hasClass = function (elem, className) renvoie new RegExp ('(^ | \\ s)' + className + '(\\ s | $)'). test ( elem.className);; addClass = fonction (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className;; removeClass = fonction (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) * ',' g '), ");; toggleClass = fonction (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, className);; apollo.has Classe = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; exports.apollo = apollo; ) ();
AMD et CommonJS sont des approches fantastiques, mais si nous devions créer un module que nous voulions travailler dans tous les environnements: AMD, CommonJS et le navigateur?
Au départ, nous avons fait quelques si
et autre
Pour simplifier la tâche de passer une fonction à chaque type de définition en fonction de ce qui est disponible, nous recherchons le support AMD ou CommonJS et l’utilisons s’il en existe une. Cette idée a ensuite été adaptée et une solution universelle a été lancée, baptisée «UMD». Il emballe cette sinon
pour nous et nous passons dans une seule fonction en tant que référence au type de module pris en charge, voici un exemple tiré du référentiel du projet:
(fonction (racine, usine) if (typedefin === 'fonction' && define.amd) // AMD. Inscrivez-vous en tant que module anonyme. define (['b'], usine); else // Globaux du navigateur root.amdWeb = fabrique (root.b); (this, function (b) // utilise b de façon quelconque. // Renvoie simplement une valeur pour définir l’exportation du module. // Cet exemple renvoie un objet. , mais le module // peut renvoyer une fonction en tant que valeur exportée. return ;));
Whoa! Il se passe beaucoup de choses ici. Nous passons une fonction comme deuxième argument au bloc IIFE, qui sous un nom de variable local usine
est assigné dynamiquement en tant que AMD ou globalement au navigateur. Oui, cela ne supporte pas CommonJS. Nous pouvons cependant ajouter ce support (en supprimant les commentaires cette fois aussi):
(fonction (racine, usine) if (typedefin === 'fonction' && define.amd) définir (['b'], usine); sinon si (typeof exportations === 'objet') module .exports = fabrique; else root.amdWeb = fabrique (root.b); (this, function (b) return ;));
La ligne magique est ici module.exports = usine
qui assigne notre usine à CommonJS.
Intégrons Apollo dans cette configuration UMD pour qu'il puisse être utilisé dans les environnements CommonJS, AMD et le navigateur! Je vais inclure le script complet apollo, de la dernière version sur GitHub, afin que les choses aient l'air un peu plus complexe que ce que j'ai décrit ci-dessus (certaines nouvelles fonctionnalités ont été ajoutées mais n'ont pas été incluses à dessein dans les exemples ci-dessus):
/ *! apollo.js v1.7.0 | (c) 2014 @toddmotto | https://github.com/toddmotto/apollo * / (fonction (racine, usine) if (typedefin === 'fonction' && define.amd) define (usine); sinon si (typeof exportations == = 'objet') module.exports = usine; sinon root.apollo = usine ();) (this, function () 'utilise strict'; var apollo = ; var hasClass, addClass, removeClass var forEach = function (items, fn) if (Object.prototype.toString.call (items)! == '[objet Array]') items = items.split ("); pour (var i = 0; i < items.length; i++) fn(items[i], i); ; if ('classList' in document.documentElement) hasClass = function (elem, className) return elem.classList.contains(className); ; addClass = function (elem, className) elem.classList.add(className); ; removeClass = function (elem, className) elem.classList.remove(className); ; toggleClass = function (elem, className) elem.classList.toggle(className); ; else hasClass = function (elem, className) return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className); ; addClass = function (elem, className) if (!hasClass(elem, className)) elem.className += (elem.className ?":") + className; ; removeClass = function (elem, className) if (hasClass(elem, className)) elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'),"); ; toggleClass = function (elem, className) (hasClass(elem, className) ? removeClass : addClass)(elem, className); ; apollo.hasClass = function (elem, className) return hasClass(elem, className); ; apollo.addClass = function (elem, classes) forEach(classes, function (className) addClass(elem, className); ); ; apollo.removeClass = function (elem, classes) forEach(classes, function (className) removeClass(elem, className); ); ; apollo.toggleClass = function (elem, classes) forEach(classes, function (className) toggleClass(elem, className); ); ; return apollo; );
Nous avons créé et mis en forme notre module pour qu'il fonctionne dans de nombreux environnements. Cela nous donne une grande flexibilité pour intégrer de nouvelles dépendances à notre travail - un élément qu'une bibliothèque JavaScript ne peut pas nous fournir sans le décomposer en petits morceaux fonctionnels..
En règle générale, nos modules sont accompagnés de tests unitaires, de tests de petite taille qui permettent aux autres développeurs de rejoindre facilement votre projet et de soumettre des demandes d'extraction pour améliorer les fonctionnalités. C'est beaucoup moins intimidant qu'une énorme bibliothèque et le développement de leur système de construction. ! Les petits modules sont souvent rapidement mis à jour alors que les grandes bibliothèques peuvent prendre du temps pour implémenter de nouvelles fonctionnalités et corriger des bugs.
C'était génial de créer notre propre module et de savoir que nous aidons de nombreux développeurs dans de nombreux environnements de développement. Cela rend le développement plus facile à entretenir, amusant et nous comprenons beaucoup mieux les outils que nous utilisons. Les modules sont accompagnés d'une documentation que nous pouvons maîtriser assez rapidement et que nous pouvons intégrer à notre travail. Si un module ne convient pas, nous pouvons en trouver un autre ou en écrire un autre - chose que nous ne pourrions pas faire aussi facilement avec une grande bibliothèque qu'une dépendance, nous ne voulons pas nous lier à une solution unique..
Pour finir, n’est-ce pas formidable de voir comment les bibliothèques JavaScript ont influencé les langages natifs avec des opérations telles que la manipulation de classe? R Eh bien, avec ES6 (la prochaine génération du langage JavaScript), nous avons décroché de l’or! Nous avons des importations et des exportations indigènes!
Check it out, exportation d'un module:
/// myModule.js function myModule () // contenu du module export myModule;
Et en important:
importer monModule de 'monModule';
Vous pouvez en savoir plus sur ES6 et la spécification des modules ici.