Comme un auto-rechargeur vert junior a écrit son hot . Partie 2. CSS


Avant la préface


Dans les commentaires de la première partie, j'ai remarqué à juste titre une clarification de la terminologie. Par conséquent, je vais maintenant appeler mon chargeur automatique de projet (ci-après AR). Je garderai le titre de la première partie de l'article ancien pour l'histoire.

Préface


2 semaines après avoir écrit ce rechargeur le plus simple, j'ai pu configurer entièrement webpack et webpack-dev-server, ce qui devrait en théorie conduire à un refus complet d'utiliser mon "vélo".


Lors de la mise en place d'un nouvel assemblage, j'ai essayé de soutenir la possibilité d'un ancien assemblage de travail garanti dans le cas de "mais on ne sait jamais quoi".
L'ancien assemblage se caractérise par l'absence d'import / require dans le projet, qui n'est pas pris en charge par les navigateurs, et par le fait qu'au stade de développement, tous les fichiers .js sont inclus dans index.html à l'intérieur du corps. Ce sont des fichiers de travail de projet et toutes les bibliothèques.

En même temps, comme je l'ai dit plus tôt, toutes les bibliothèques sont dans le bon pap de lib.
Le fichier package.json était presque pur en termes de dépendances et devDependencies (ainsi que de scripts). Exprimez seulement, un package pour proxy sur un serveur et mes socket.io et node-watch ajoutés.

Ainsi, la tâche de conserver l'ancien assemblage était assez simple, mais la tâche d'en installer un nouveau - au contraire - était compliquée par la recherche des packages nécessaires et de leurs versions.
En conséquence, l'objectif a été atteint. J'ai rempli package.json avec les packages nécessaires, créé entry.js et entry.html en tant que fichiers d'entrée webpack. Dans .js, je mets les importations globales de tout ce que j'ai atteint pour le plus nécessaire, et entry.html n'est qu'une copie de index.html, dans laquelle j'ai supprimé tous les scripts supplémentaires pour connecter les fichiers.

Pour être sûr, dans le plugin providePlugin, j'ai décrit une bibliothèque que webpack remplacera aux bons endroits «à la demande». Mais maintenant je comprends que vous pouvez vous en passer. Je vais essayer de supprimer, voyons ce qui se passe.
Ainsi, il a soutenu le manque d'importations dans le projet principal et a conservé l'index.htm d'origine.

En théorie, cela devait me permettre de collecter avec l'ancien collectionneur et - ce qui est très important pour moi personnellement - de soutenir le développement à l'aide de mon chargeur automatique.
Dans la pratique, j'ai trouvé un endroit où l'exportation est apparue. L'une de nos mini-bibliothèques auto-écrites a été réalisée sous la forme d'un objet.

Pour que l'assembly du webpack fonctionne correctement, il doit être exporté; pour un ancien assembly normal, il suffit d'un script dans index.html. Eh bien, rien, je le prends et le réécris au service de l'angulaire. Copier + passé + petits changements et - le tour est joué - ça marche!

J'essaye un nouvel assemblage - ça marche, webpack-dev-server, respectivement, aussi.
J'essaie mon chargeur automatique - ça marche!
Buzz!
La préface est terminée, passez à autre chose.

L'intrigue.


Après avoir travaillé quelques jours sur webpack-dev-server, je ne peux tout simplement pas chasser les pensées sur la durée.
Et il est très rapide en même temps, redémarrant littéralement 3 à 4 secondes après la sauvegarde.
Mais je suis déjà habitué à ce que mon AR, en raison du manque d'assemblage, redémarre juste après ctrl + s.
En conséquence, au début, je continue à utiliser les deux outils, puis je ne travaille que via AR.

Pour moi, j'ai identifié 4 raisons pour lesquelles:
1. AR est plus rapide.
2. Il est plus clair avec lui ce qui a cassé, car toute la pile d'erreurs dans les fichiers source est visible. Une visite du bundle wds n'est pas requise.
3. Wds ne redémarre pas lorsque je change quelque chose dans le fichier html qui est inséré via include dans un autre fichier html. Mon - en raison du fait qu'il lit l'intégralité du dossier du projet et se recharge à tout changement sauf exceptions - se recharge. Ici, l'optimisation wds se tire dans le pied.
4. Eh bien, subjectif. Je dois souvent regarder à l'arrière de différents serveurs et l'exécuter en conséquence dans le navigateur sur différents ports. 1 script suffit pour entretenir AR
“example”: “node ./server.js”

Qui court
npm run example 1.100 9001

ou
npm run example 1.101 9002

Où serveur 1.101 et port 9001 à afficher sur mon hôte local.
Pratique en général, vous n'avez pas besoin de vous souvenir des différents noms de scripts, mais écrivez simplement les paramètres au début du script et tout va bien.
Les variables entrent dans process.argv et je les retire de moi-même avec succès dans server.js

Quant à wds, pour l'instant je n'ai pas pu implémenter une telle commodité, j'ai dû faire plusieurs scripts pour les principales combinaisons. Eh bien au moins une configuration est utilisée pour le développement.

En général, il est plus pratique pour moi de travailler avec un vélo écrit.
Eh bien, comme c'est plus pratique, j'ai décidé de développer le projet.

Quelles sont les options?
1.Lorsque vous modifiez des fichiers CSS, ne rechargez pas la page, mais effectuez les modifications.
2. De même, mais déjà .html
3. Essayez différentes options en plus de location.reload () pour js.
4. Similaire à 1-2, mais déjà .js
5. Éloignez index.html + entry.html vers un seul fichier pour les deux assemblys. c'est-à-dire arriver à une situation où ce qui va à webpack fonctionnera sur mon AR.
6. resserrer le support scss

CSS est cool, ce dont vous avez besoin.
Le HTML est également amusant, exactement ce dont vous avez besoin.
Location.reload (). Je ne sais pas pourquoi c'est mauvais, mais il serait intéressant de considérer les différentes options disponibles.
JS - c'est cool, ce serait cool de le faire, mais est-ce vraiment nécessaire, vu combien d'efforts je vais dépenser?
5 et 6 - cela sent déjà le montage, ce qui signifie que la vitesse est probablement partie.
Conclusion: les points 4 - 6 ne sont pas prévus, je vais faire les points 1 - 2, le point 3 Je vais jeter un œil à ce qui est en général. À première vue, la tâche est difficile, mais je peux me décomposer en sous-tâches! Je le décompose et décide d'aller par étapes, alors que je ne pense qu'à l'étape actuelle (ouais, eh bien! Je pense déjà au html avec might et main)

La partie principale.


CSS


La tâche consiste en deux sous-tâches:
1.Trouvez le fichier modifié.
2. Rechargez css sans recharger la page.

Je commence par le second. En utilisant l'expérience précédente, la première chose que je vais faire sur Google, mais comment pouvez-vous généralement recharger css sans recharger une page.

Je tombe sur un certain nombre d'articles, dont celui-ci me semble le plus intéressant

Les gars font le tour de tout le lien de css à head et changent l'attribut href pour un nouveau.
J'ai pris leur idée comme base et mis en œuvre ma version.
J'ai encore 2 fichiers.
server.js est un simple serveur + vérification des exceptions + logique de suivi des modifications + envoi des modifications par socket.

watch.js - sur le client. Reçoit les messages de modification + location.reload () du serveur. Maintenant, dans watch.js a ajouté la logique de vérifier le nom sur css et de remplacer css, si nécessaire. Il pourrait être retiré dans un module séparé, mais jusqu'à présent, je ne vois pas beaucoup de code. La première itération s'est avérée comme ceci:

server.js
 const express = require('express'), http = require('http'), watch = require('node-watch'), proxy = require('http-proxy-middleware'), app = express(), server = http.createServer(app), io = require('socket.io').listen(server), exeptions = ['git', 'js_babeled', 'node_modules', 'build', 'hotreload'], // ,   ,    backPortObj = { /*  ,   back*/ }, address = process.argv[2] || /*    back*/, localHostPort = process.argv[3] || 9080, backMachinePort = backPortObj[address] || /*   back */, isHotReload = process.argv[4] || "y", // "n" || "y" target = `http://192.168.${address}:${backMachinePort}`, str = `Connected to machine: ${target}, hot reload: ${isHotReload === 'y' ? 'enabled' : 'disabled'}.`, link = `http://localhost:${localHostPort}/`; server.listen(localHostPort); app .use('/bg-portal', proxy({ target, changeOrigin: true, ws: true })) .use(express.static('.')); if (isHotReload === 'y') { watch('./', { recursive: true }, (evt, name) => { let include = false; exeptions.forEach(item => { if (`${name}`.includes(item)) include = true; }) if (!include) { console.log(name); io.emit('change', { evt, name, exeptions }); }; }); }; console.log(str); console.log(link); 



watch.js
 const socket = io.connect(); const makeCorrectName = name => name.replace('\\','\/'); const findCss = (replaced) => { const head = document.getElementsByTagName('head')[0]; const cssLink = [...head.getElementsByTagName('link')] .filter(link => { const href = link.getAttribute('href'); if(href === replaced) return link; }) return cssLink[0]; }; const replaceHref = (cssLink, replaced) => { cssLink.setAttribute('href', replaced); return true; }; const tryReloadCss = (name) => { const replaced = makeCorrectName(name); const cssLink = findCss(replaced); return cssLink ? replaceHref(cssLink, replaced) : false; }; socket.on('change', ({ evt, name, exeptions }) => { const isCss = tryReloadCss(name); if (!isCss) location.reload(); }); 



Fait intéressant, le package node-watch m'envoie le nom du fichier modifié sous la forme path \ to \ file.css, tandis que dans href, le chemin est écrit path / to / file.css. parce que Je vérifie le fichier par son nom complet; j'ai dû changer la barre oblique en sens inverse pour vérification.
Et ça marche!

Cependant, 3 problèmes subsistent.
1.Option fonctionne bien pour le chrome et ne fonctionne certainement pas pour le bord. Ici, vous devez creuser, car néanmoins, la conception multi-navigateur est très nécessaire (et c'est précisément pour la mise en page qui est une amélioration), mais cela est probablement dû à 2 problèmes.

2. Le navigateur est intelligent: il met en cache les fichiers déjà chargés, et si les paramètres ne sont pas modifiés, il ne change rien. Autrement dit, si vous enregistrez le fichier sous le même nom, le navigateur considérera que rien n'a changé et ne rechargera pas le contenu. Pour lutter contre cela, les gars changent de nom à chaque fois. Cela fonctionne pour moi sur le chrome sans cela, cependant, c'est une nuance trop importante.

3. Une coïncidence sans ambiguïté d'un nom est nécessaire. c'est-à-dire si vous définissez le chemin absolu à lier (commence par ./), le programme ne trouve pas de correspondance.
./path/to/file! = chemin / vers / fichier pour comprendre la logique de mon code. Et cela doit également être corrigé.

Ainsi, je dois mettre à jour le nom du fichier à chaque fois afin qu'il n'y ait pas de mise en cache.
Plus précisément, chaque fois que vous modifiez l'attribut href du lien, dans lequel le fichier css a changé.
En savoir plus ici.

Les gars du lien ci-dessus ont du mal à mettre en cache très élégamment, je prends leur option:
 cssLink.setAttribute('href', `${hrefToReplace}?${new Date().getTime()}`); 

Ensuite, je dois comparer le nom du fichier. J'ai 1 point d'interrogation par ligne, donc je peux me passer d'expressions régulières (je ne les ai pas encore étudiées) en faveur d'une telle méthode auto-écrite:
 const makeCorrectName = (name) => name .replace('\\', '/') .split('?')[0]; 


Ça marche!

Ensuite, je dois déterminer de manière unique le chemin d'accès au fichier.
Je ne connais pas très bien la magie des chemins absolus, relatifs et généralement. Une mauvaise compréhension de la question est précisément à cause de cela.
Le chemin vers href peut commencer par '.', '/' Ou immédiatement par un nom.
Pendant mon temps libre, j'ai réfléchi à cette question.
Le point d'entrée - index.html (et dans mon cas entry.html) - est toujours (généralement) au niveau supérieur. Et les fichiers CSS connectés par des scripts sont toujours (généralement) quelque part dans les profondeurs. Ainsi - je le répète - le chemin sera toujours le même (noms de dossier et de fichier), seul le premier caractère sera différent.
Ainsi, après avoir séparé la partie avec un point d'interrogation, je romps à nouveau la ligne de la même manière, mais déjà dans '/', puis supprime le premier point attendu et connecte les éléments du tableau en une seule ligne, selon laquelle je comparerai pour une recherche exacte.
Cela ressemble à ceci:
 const findFullPathString = (path) => path .split('/') .filter((item) => item !== '.') .filter((item) => item) .join(''); 


Je lance le code, bravo, ça marche!

Et Edge?


Et avec Edge, le problème ne se cachait pas où il était recherché.
Il s'est avéré que mon code CSS ne fonctionnait pas dans Edge, et à cause de ma négligence, je n'ai tout simplement pas remarqué cela.
Le problème était caché dans la méthode de traitement de la collection d'éléments DOM.
Comme vous le savez, une collection d'éléments DOM n'est pas un tableau; en conséquence, les méthodes de tableau ne fonctionnent pas avec elle (plus précisément, certaines fonctionnent, d'autres non).
Je faisais ça:
 const cssLink = [...head.getElementsByTagName('link')] 

Mais le bon vieux Edge ne comprend pas cela et c'était la raison.
N'hésitez pas à changer et maintenant c'est fait comme ceci:
 const cssLink = Array.from(head.getElementsByTagName('link'))// special for IE 

Courez, vérifiez, travaillez!
image
L'image est petite, une petite explication.
Chrome gauche, Firefox central, Edge droit. J'entre spécifiquement une valeur en entrée pour montrer que la page ne se recharge pas et que css change presque instantanément.
Le retard dans la vidéo est lié au délai entre l'édition et l'enregistrement du fichier.

En termes de css, travailler avec chromeDevTools peut être plus rapide car ils peuvent, par exemple, changer la marge avec la flèche haut / bas, mais mon css est également mis à jour aussi rapidement et tout à partir d'un seul éditeur.
Il est à noter qu'au moment de la publication de l'article, j'utilise mon vélo de manière continue sans aucune modification depuis environ 2 semaines et que je ne souhaite pas passer à wds. Comme il n'y a aucun désir pour css dans des situations simples d'utiliser devTools!

Total, server.js reste le même et watch.js prend la forme suivante:
watch.js
 const socket = io.connect(); const findFullPathString = (path) => path .split('/') .filter((item) => item !== '.') .filter((item) => item) .join(''); const makeCorrectName = (name) => name .replace('\\', '/') .split('?')[0]; const findCss = (hrefToReplace) => { const head = document.getElementsByTagName('head')[0]; const replacedString = findFullPathString(hrefToReplace); const cssLink = Array.from(head.getElementsByTagName('link'))// special for IE .filter((link) => { const href = link.getAttribute('href').split('?')[0]; const hrefString = findFullPathString(href); if (hrefString === replacedString) return link; }); return cssLink[0]; }; const replaceHref = (cssLink, hrefToReplace) => { cssLink.setAttribute('href', `${hrefToReplace}?${new Date().getTime()}`); return true; }; const tryReloadCss = (name) => { const hrefToReplace = makeCorrectName(name); const cssLink = findCss(hrefToReplace); return cssLink ? replaceHref(cssLink, hrefToReplace) : false; }; socket.on('change', ({ name }) => { const isCss = tryReloadCss(name); if (!isCss) location.reload(); }); 



La beauté!

Postface.


La prochaine étape, je veux essayer de recharger HTML, mais jusqu'à présent, ma vision semble très compliquée. La mise en garde est que j'ai angularjs et cela devrait fonctionner ensemble.
Je serais très heureux des critiques constructives et de vos commentaires sur la façon d'améliorer mon petit projet, ainsi que des conseils et des articles sur le sujet avec HTML.

Source: https://habr.com/ru/post/fr467771/


All Articles