Like a green junior Auto-reloader wrote his hot . Part 2. CSS



Before the preface



In the comments to the first part, I rightly noticed a clarification of terminology. Therefore, I will now call my project auto reloader (hereinafter AR). I will keep the title of the first part of the article old for history.



Foreword



2 weeks after writing this simplest reloader, I was able to fully configure webpack and webpack-dev-server, which in theory should lead to a complete refusal to use my "bike".





When setting up a new assembly, I tried to support the possibility of an old, guaranteed working assembly in case of "but you never know what."

The old assembly is characterized by the absence of import / require in the project, which are not supported by browsers, as well as the fact that at the development stage all .js files are included in index.html inside the body. These are project work files and all libraries.



At the same time, as I said earlier, all libraries are in the neat lib daddy.

The package.json file was almost virgin in terms of dependencies and devDependencies (as well as scripts). Only express, a package for proxy on a server and my added socket.io and node-watch.



Thus, the task of preserving the old assembly was quite simple, but the task of setting up a new one - on the contrary - was complicated by the search for the necessary packages and their versions.

As a result, the goal was achieved. I populated package.json with the necessary packages, created entry.js and entry.html as webpack input files. In .js, I put all-all imports of everything I reached for the most necessary, and entry.html is just a copy of index.html, in which I removed all the extra scripts for connecting files.



To be sure, in the ProvidePlugin plugin I described one library that webpack will substitute in the right places “on demand”. But now I understand that you can do without it. I'll try to delete, let's see what happens.

Thus, he supported the lack of imports in the main project and kept the original index.htm.



This in theory was supposed to allow me to collect with the old collector and - which is very important for me personally - to support the development using my auto reloader.

In practice, I found one place where export appeared. One of our self-written mini-library was made in the form of an object.



For the webpack assembly to function properly, it needs to be exported; for a normal old assembly, just a script in index.html is enough. Well, nothing, I take and rewrite it with the service of the angular. Copy + past + small changes and - voila - it works!



I try a new assembly - it works, webpack-dev-server, respectively, too.

I try my auto reloader - it works!

Buzz!

The preface is over, move on.



The plot.



After working a couple of days on webpack-dev-server, I just can’t drive away thoughts of how long it is.

And he is very fast at the same time, rebooting literally 3-4 seconds after saving.

But I'm already used to that my AR, due to the lack of assembly, reboots right after ctrl + s.

As a result, first I keep both tools running, and then I work only through AR.



For myself, I identified 4 reasons why:

1. AR is faster.

2. It’s more clear with him what has broken, because the entire error stack in the source files is visible. Traveling through the wds bundle is not required.

3. Wds does not restart when I change something in the html file that is inserted via include in another html file. My - due to the fact that it reads the entire project folder and reloads on any change, except for exceptions - reloads. Here wds optimization shoots itself in the foot.

4. Well, and subjective. I need to look at the back often from different servers and run it accordingly in the browser on different ports. 1 script is enough for servicing AR

“example”: “node ./server.js”







Which runs

npm run example 1.100 9001







or

npm run example 1.101 9002







Where 1.101 server, and 9001 port to display on my localhost.

Convenient in general, you do not need to remember different names of scripts, but just write the parameters at the start of the script and everything is ok.

The variables get into process.argv and I successfully remove them from there inside server.js



As for wds, so far I have not been able to implement such convenience, I had to make several scripts for the main combinations. Well at least one config is used for development.



In general, it’s more convenient for me to work with a written bike.

Well, since it’s more convenient, I decided to develop the project.



What are the options?

1.When changing css files, do not reload the page, but roll the changes.

2. Similarly, but already .html

3. Try different other options besides location.reload () for js.

4. Similar to 1-2, but already .js

5. Get away from index.html + entry.html towards one single file for both assemblies. those. come to a situation where what is going to webpack will work on my AR.

6.Tighten scss support



CSS is cool, what you need.

HTML is also fun, just what you need.

Location.reload (). I do not know why it is bad, but it would be interesting to consider the various options available.

JS - it’s cool, it would be cool to do it, but is it really necessary, considering how much effort is wasted?

5 and 6 - this already smacks of assembly, which means that the speed is likely to be gone.

Conclusion: items 4 - 6 are not planned, I will do items 1 - 2, item 3 I'll take a look at what is in general. At first glance, the task is difficult, but I can break down into subtasks! I break it down and decide to go in stages, while I only think about the current stage (yeah, well! I already think about html with might and main)



Main part.



CSS



The task consists of two subtasks:

1.Find the modified file.

2. Reload css without reloading the page.



I start with the second. Using previous experience, the first thing I’m going to google is, but how can css be reloaded at all without a page reload.



I come across a number of articles, among which this one seems to me the most interesting



The guys just go around all the link from css to head and change the href attribute to a new one.

I took their idea as a basis and implemented my version.

I still have 2 files.

server.js is a simple server + exception checking + change tracking logic + sending changes by socket.



watch.js - on the client. Receives change messages + location.reload () from the server. Now in watch.js I added the logic of checking the name with css and replacing css if necessary. It could be taken out in a separate module, but so far I see little sense in the code. The first iteration turned out like this:



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









Interestingly, the node-watch package sends me the name of the modified file in the form path \ to \ file.css, while in href the path is written path / to / file.css. because I check the file by its full name; I had to change the slash to the reverse for checking.

And it works!



However, 3 problems remain.

1.Option works fine for chrome and definitely doesn't work for edge. Here you have to dig, because nevertheless, multi-browser in layout (and this is just an improvement for layout) is very necessary. But this is probably due to 2 problems.



2. The browser is smart: it caches already uploaded files, and if the parameters are not changed, it does not change anything. That is, in theory, if you save the file with the same name, the browser will consider that nothing has changed and will not reload the contents. To fight this, the guys change their name every time. It works for me on chrome without this, however, this is too important a nuance.



3. unambiguous coincidence of a name is necessary. those. if you set the absolute path to link (starts with ./), the program does not find a match.

./path/to/file! = path / to / file in understanding the logic of my code. And this also needs to be fixed.



Thus, I need to update the file name every time so that there is no caching.

More precisely, each time you change the href attribute of link, in which the css file has changed.

Read more about it here.



The guys at the link above struggle with caching very elegantly, I take their option:

 cssLink.setAttribute('href', `${hrefToReplace}?${new Date().getTime()}`);
      
      





Next I need to compare the file name. I got 1 question mark per line, so I can do without regular expressions (have not studied them yet) in favor of such a self-written method:

 const makeCorrectName = (name) => name .replace('\\', '/') .split('?')[0];
      
      







Works!



Next, I need to uniquely determine the path to the file.

I do not know very well the magic of the absolute, relative and generally paths. Some misunderstanding of the issue is precisely because of this.

The path to href can begin with '.', '/' Or immediately with a name.

In my free time I thought about this issue.

The entry point - index.html (and in my case entry.html) - is always (usually) at the top level. And css files connected by scripts are always (usually) somewhere in the depths. Thus - I repeat - the path will always be the same (folder and file names), only the first character will be different.

Thus, after separating the part with a question mark, I break the line again in the same way, but already in '/', then remove the expected first point and connect the array elements into one line, by which I will compare for an exact search.

It looks like this:

 const findFullPathString = (path) => path .split('/') .filter((item) => item !== '.') .filter((item) => item) .join('');
      
      







I run the code, cheers, it works!



What about Edge?



And with Edge, the problem was not hiding where it was searched.

It turned out that my css code did not work in Edge, and because of my carelessness, I just did not notice this.

The problem was hidden in the method of processing the collection of DOM elements.

As you know, a collection of DOM elements is not an array; accordingly, array methods do not work with it (more precisely, some work, some do not).

I used to do this:

 const cssLink = [...head.getElementsByTagName('link')]
      
      





But the good old Edge does not understand this and that was the reason.

Feel free to change and now it is done like this:

 const cssLink = Array.from(head.getElementsByTagName('link'))// special for IE
      
      





Run, check, work!

image

The picture is small, a little explanation.

Left Chrome, center Firefox, right Edge. I specifically enter a value in input to show that the page does not reload, and css changes almost instantly.

The delay in the video is related to the delay between editing and saving the file.



In terms of css, working with chromeDevTools can be faster due to the fact that they can, for example, change the margin with the up / down arrow, but my css also updates as fast and all from one editor.

It is worth noting that at the time of publication of the article I have been using my bike on an ongoing basis without any modifications for about 2 weeks and there is no desire to change to wds. As there is no desire for css in simple situations to use devTools!



Total, server.js remains the same, and watch.js takes the following form:

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









Beauty!



Afterword.



The next step I want to try to reload HTML, but so far my vision looks very complicated. The caveat is that I have angularjs and this should work together.

I would be very happy for constructive criticism and your comments on how to improve my small project, as well as tips and articles on the subject with HTML.



All Articles