Como um auto-reloader júnior verde, escreveu o seu quente . Parte 2. CSS


Antes do prefácio


Nos comentários da primeira parte, notei , com razão, um esclarecimento da terminologia. Portanto, agora chamarei o recarregador automático do meu projeto (daqui em diante AR). Manterei o título da primeira parte do artigo antigo para a história.

Prefácio


Duas semanas depois de escrever este recarregador mais simples, eu consegui configurar completamente o webpack e o webpack-dev-server, o que, em teoria, deveria levar a uma recusa completa em usar minha "bicicleta".


Ao montar uma nova montagem, tentei apoiar a possibilidade de uma montagem de trabalho antiga e garantida no caso de "mas você nunca sabe".
A montagem antiga é caracterizada pela ausência de importação / exigência no projeto, que não é suportada pelos navegadores, e pelo fato de que, no estágio de desenvolvimento, todos os arquivos .js estão incluídos no index.html dentro do corpo. Esses são os arquivos de trabalho do projeto e todas as bibliotecas.

Ao mesmo tempo, como eu disse anteriormente, todas as bibliotecas estão no puro pai da lib.
O arquivo package.json era quase puro em termos de dependências e devDependencies (além de scripts). Expresse apenas, um pacote para proxy em um servidor e meu socket.io e watch-node adicionados.

Assim, a tarefa de preservar a montagem antiga era bastante simples, mas a tarefa de montar uma nova montagem - pelo contrário - foi complicada pela busca pelos pacotes necessários e suas versões.
Como resultado, o objetivo foi alcançado. Eu preenchi package.json com os pacotes necessários, criei entry.js e entry.html como arquivos de entrada do webpack. Em .js, coloquei todas as importações de tudo o que cheguei ao mais necessário, e entry.html é apenas uma cópia de index.html, na qual removi todos os scripts extras para conectar arquivos.

Para ter certeza, no plug-in ProvidePlugin, descrevi uma biblioteca que o webpack substituirá nos lugares certos "sob demanda". Mas agora entendo que você pode ficar sem ele. Vou tentar excluir, vamos ver o que acontece.
Assim, ele apoiou a falta de importações no projeto principal e manteve o index.htm original.

Isso deveria, em teoria, permitir que eu coletasse com o coletor antigo e - o que é muito importante para mim pessoalmente - apoiar o desenvolvimento usando meu recarregador automático.
Na prática, encontrei um lugar onde a exportação apareceu. Uma de nossa mini-biblioteca auto-escrita foi feita na forma de um objeto.

Para que o assembly webpack funcione corretamente, ele precisa ser exportado; para um assembly antigo normal, basta um script em index.html. Bem, nada, eu pego e reescrevo com o serviço do angular. Copiar + passado + pequenas alterações e - voila - funciona!

Eu tento um novo assembly - ele funciona, webpack-dev-server, respectivamente, também.
Eu tento meu recarregador automático - funciona!
Buzz!
O prefácio acabou, siga em frente.

A trama.


Depois de trabalhar alguns dias no webpack-dev-server, não consigo mais pensar em quanto tempo isso leva.
E ele é muito rápido ao mesmo tempo, reiniciando literalmente 3-4 segundos após salvar.
Mas eu já estou acostumado a isso meu AR, devido à falta de montagem, reinicia logo após ctrl + s.
Como resultado, inicialmente eu mantenho as duas ferramentas em execução e depois trabalho apenas através do AR.

Para mim, identifiquei 4 razões pelas quais:
1. AR é mais rápido.
2. É mais claro para ele o que quebrou, porque toda a pilha de erros nos arquivos de origem é visível. Um tour do pacote wds não é necessário.
3. O WDS não é reiniciado quando altero algo no arquivo html inserido via include em outro arquivo html. Meu - devido ao fato de ler toda a pasta do projeto e recarregar em qualquer alteração, exceto exceções - recarrega. Aqui a otimização wds se atira no pé.
4. Bem, subjetivo. Preciso olhar para trás frequentemente de servidores diferentes e, portanto, executá-lo no navegador em portas diferentes. 1 script é suficiente para atender a AR
“example”: “node ./server.js”

Que roda
npm run example 1.100 9001

ou
npm run example 1.101 9002

Onde servidor 1.101 e porta 9001 para exibir no meu host local.
Conveniente em geral, você não precisa se lembrar de nomes diferentes de scripts, mas apenas escreva os parâmetros quando iniciar o script e tudo estiver ok.
As variáveis ​​entram em process.argv e eu as removo com êxito dentro do server.js

Quanto ao wds, até agora não consegui implementar essa conveniência, tive que fazer vários scripts para as principais combinações. Bem, pelo menos uma configuração é usada para o desenvolvimento.

Em geral, é mais conveniente trabalhar com uma bicicleta escrita.
Bem, como é mais conveniente, decidi desenvolver o projeto.

Quais são as opções?
1. Ao alterar arquivos css, não recarregue a página, mas role as alterações.
2. Da mesma forma, mas já .html
3. Tente outras opções diferentes, além de location.reload () para js.
4. Semelhante ao 1-2, mas já .js
5. Afaste-se de index.html + entry.html em direção a um único arquivo para os dois assemblies. isto é chegar a uma situação em que o que está indo para o webpack funcionará no meu AR.
6. Aperte o suporte ao scss

CSS é legal, o que você precisa.
HTML também é divertido, exatamente o que você precisa.
Location.reload (). Não sei por que é ruim, mas seria interessante considerar as várias opções disponíveis.
JS - é legal, seria legal fazê-lo, mas é realmente necessário, considerando quanto esforço vou gastar?
5 e 6 - isso já parece montagem, o que significa que a velocidade provavelmente desapareceu.
Conclusão: os itens 4 - 6 não estão planejados, farei os itens 1 - 2, item 3, examinarei o que geralmente é. À primeira vista, a tarefa é difícil, mas posso dividir em subtarefas! Eu divido e decido ir por etapas, enquanto penso apenas no estágio atual (sim, bem! Eu já penso em html com poder e main)

A parte principal.


CSS


A tarefa consiste em duas subtarefas:
1. Encontre o arquivo modificado.
2. Recarregue o css sem recarregar a página.

Eu começo com o segundo. Usando a experiência anterior, a primeira coisa que vou ao google, mas como você geralmente recarrega o css sem recarregar a página.

Me deparo com vários artigos, entre os quais este me parece o mais interessante

Os caras apenas percorrem todo o link do css para o cabeçalho e alteram o atributo href para um novo.
Tomei a ideia deles como base e implementei minha versão.
Eu ainda tenho 2 arquivos.
server.js é um servidor simples + verificação de exceções + lógica de rastreamento de alterações + envio de alterações por soquete.

watch.js - no cliente. Recebe mensagens de alteração + location.reload () do servidor. Agora, no watch.js, foi adicionada a lógica de verificar o nome no css e substituir o css, se necessário. Pode ser retirado em um módulo separado, mas até agora não vejo muito código. A primeira iteração ficou assim:

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(); }); 



Curiosamente, o pacote node-watch me envia o nome do arquivo modificado no caminho do caminho \ to \ file.css, enquanto em href o caminho é gravado no caminho / to / file.css. porque Verifico o arquivo pelo nome completo; tive que alterar a barra para o reverso para verificar.
E funciona!

No entanto, três problemas permanecem.
1.Option funciona bem para chrome e definitivamente não funciona para edge. Aqui você tem que cavar, porque no entanto, o design de vários navegadores é muito necessário (e é exatamente para o layout que é uma melhoria), mas isso provavelmente se deve a dois problemas.

2. O navegador é inteligente: armazena em cache os arquivos já carregados e, se os parâmetros não forem alterados, ele não muda nada. Ou seja, em teoria, se você salvar o arquivo com o mesmo nome, o navegador considerará que nada mudou e não recarregará o conteúdo. Para combater isso, os caras mudam de nome toda vez. Funciona para mim no chrome sem isso, no entanto, isso é uma nuance muito importante.

3. É necessária uma coincidência inequívoca de um nome. isto é se você definir o caminho absoluto para o link (começa com ./), o programa não encontrará uma correspondência.
./caminho/para/arquivo! = caminho / para / arquivo para entender a lógica do meu código. E isso também precisa ser corrigido.

Portanto, preciso atualizar o nome do arquivo sempre para que não haja armazenamento em cache.
Mais precisamente, sempre que você altera o atributo href do link, no qual o arquivo css foi alterado.
Leia mais sobre isso aqui.

Os caras no link acima lutam com o cache de maneira muito elegante, tomo a opção deles:
 cssLink.setAttribute('href', `${hrefToReplace}?${new Date().getTime()}`); 

Em seguida, preciso comparar o nome do arquivo. Eu tenho 1 ponto de interrogação por linha, para que eu possa fazer sem expressões regulares (ainda não as estudei) a favor de um método auto-escrito:
 const makeCorrectName = (name) => name .replace('\\', '/') .split('?')[0]; 


Isso funciona!

Em seguida, preciso determinar exclusivamente o caminho para o arquivo.
Não conheço muito bem a magia dos caminhos absolutos, relativos e geralmente. Algum mal-entendido da questão é justamente por isso.
O caminho para href pode começar com '.', '/' Ou imediatamente com um nome.
No meu tempo livre, pensei sobre esse assunto.
O ponto de entrada - index.html (e no meu caso entry.html) - está sempre (geralmente) no nível superior. E arquivos CSS conectados por scripts estão sempre (geralmente) em algum lugar profundo. Assim - repito - o caminho sempre será o mesmo (nomes de pastas e arquivos), apenas o primeiro caractere será diferente.
Assim, depois de separar a peça com um ponto de interrogação, quebro a linha novamente da mesma maneira, mas já em '/', removo o primeiro ponto esperado e conecto os elementos da matriz em uma linha, de acordo com a qual compararei para uma pesquisa exata.
É assim:
 const findFullPathString = (path) => path .split('/') .filter((item) => item !== '.') .filter((item) => item) .join(''); 


Eu corro o código, saúde, funciona!

E o Edge?


E com o Edge, o problema não estava escondido onde foi pesquisado.
Aconteceu que meu código css não funcionou no Edge e, devido ao meu descuido, simplesmente não percebi isso.
O problema estava oculto no método de processamento da coleção de elementos DOM.
Como você sabe, uma coleção de elementos DOM não é uma matriz; portanto, os métodos de matriz não funcionam com ela (mais precisamente, alguns funcionam, outros não).
Eu costumava fazer isso:
 const cssLink = [...head.getElementsByTagName('link')] 

Mas o bom e velho Edge não entende isso e esse foi o motivo.
Sinta-se livre para mudar e agora é feito assim:
 const cssLink = Array.from(head.getElementsByTagName('link'))// special for IE 

Corra, verifique, trabalhe!
imagem
A imagem é pequena, uma pequena explicação.
Chrome esquerdo, centro do Firefox, borda direita. Especificamente, insiro um valor na entrada para mostrar que a página não é recarregada e o css muda quase que instantaneamente.
O atraso no vídeo está relacionado ao atraso entre editar e salvar o arquivo.

Em termos de css, o trabalho com o chromeDevTools pode ser mais rápido devido ao fato de que eles podem, por exemplo, alterar a margem com a seta para cima / para baixo, mas meu css também atualiza o mais rápido e tudo em um editor.
Vale a pena notar que, no momento da publicação do artigo, eu uso minha bicicleta continuamente, sem modificações por cerca de duas semanas, e não há desejo de mudar para wds. Como não há desejo de css em situações simples de usar o devTools!

No total, server.js permanece o mesmo e watch.js assume o seguinte formato:
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(); }); 



Beleza!

Posfácio.


O próximo passo é tentar recarregar o HTML, mas até agora minha visão parece muito complicada. A ressalva é que eu tenho angularjs e isso deve funcionar juntos.
Eu ficaria muito feliz por críticas construtivas e seus comentários sobre como melhorar meu pequeno projeto, além de dicas e artigos sobre o assunto com HTML.

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


All Articles