Articles — For Web2023-10-09T00:00:00Zhttps://forweb.dev/en/blog/feed.xmlAndrey Romanovme@andreyromanov.comData loading state2020-04-29T00:00:00Zhttps://forweb.dev/en/blog/2020-04-29-data-state/<p>One of the daily tasks of every frontend developer is downloading data from the server. During the download, you usually need to show user a spinner or an error message if download fails. In code, loading status is often described with boolean flags:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> comments = { <span class="hljs-attr">isLoading</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">isLoaded</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">isError</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">errorText</span>: <span class="hljs-string">''</span>, <span class="hljs-attr">data</span>: [], }; </code></pre> <p>At first glance, these flags seem convenient. With such approach data loading code looks something like this:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> comments = { <span class="hljs-attr">isLoading</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">isLoaded</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">isError</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">errorText</span>: <span class="hljs-string">''</span>, <span class="hljs-attr">data</span>: [], }; <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadComments</span>(<span class="hljs-params"></span>) </span>{ comments.isLoaded = <span class="hljs-literal">false</span>; comments.isError = <span class="hljs-literal">false</span>; comments.isLoading = <span class="hljs-literal">true</span>; fetch(<span class="hljs-string">'/comments'</span>) .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =></span> response.json()) .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =></span> { comments.isLoading = <span class="hljs-literal">false</span>; comments.isLoaded = <span class="hljs-literal">true</span>; comments.data = data; }) .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =></span> { commments.isLoading = <span class="hljs-literal">false</span>; comments.isError = <span class="hljs-literal">true</span>; comments.errorText = error.message; }); } </code></pre> <p>It turns out to be glibly and confusing. You have to be careful resetting flags, otherwise you’ll get meaningless flag combinations like <code>isLoading: true</code> and <code>isLoaded: true</code>.</p> <p>A more practical approach is to describe the state with single <code>dataState</code> field and automatically calculate flags based on the value of this field:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> dataStates = { <span class="hljs-attr">notAsked</span>: <span class="hljs-string">'notAsked'</span>, <span class="hljs-attr">loading</span>: <span class="hljs-string">'loading'</span>, <span class="hljs-attr">loaded</span>: <span class="hljs-string">'loaded'</span>, <span class="hljs-attr">failed</span>: <span class="hljs-string">'failed'</span>, }; <span class="hljs-keyword">const</span> comments = { <span class="hljs-attr">dataState</span>: dataStates.notAsked, <span class="hljs-attr">errorText</span>: <span class="hljs-string">''</span>, <span class="hljs-attr">data</span>: [], <span class="hljs-keyword">get</span> <span class="hljs-title">isLoading</span>() { <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.dataState === dataStates.loading; }, <span class="hljs-keyword">get</span> <span class="hljs-title">isLoaded</span>() { <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.dataState === dataStates.loaded; }, <span class="hljs-keyword">get</span> <span class="hljs-title">isError</span>() { <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.dataState === dataStates.failed; }, }; </code></pre> <p>The code has grown a bit, but as a result all flags change automatically, and the probability of error due to incorrect flag update is reduced to zero. The comments uploading function becomes much simpler:</p> <pre><code class="language-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadComments</span>(<span class="hljs-params"></span>) </span>{ comments.dataState = dataStates.loading; fetch(<span class="hljs-string">'/comments'</span>) .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =></span> response.json()) .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =></span> { comments.dataState = dataStates.loaded; comments.data = data; }) .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =></span> { comments.dataState = dataStates.failed; comments.errorText = error.message; }); } </code></pre>When code duplication is fine?2020-05-04T00:00:00Zhttps://forweb.dev/en/blog/2020-05-04-code-duplication/<p>Analyzing a large code base, we immediately see cases in which the code should have been reused. But if you are just starting a project, cases for code reuse are not so obvious: it is difficult to predict how this or that code fragment will evolve.</p> <p>It is better to duplicate the code at the initial stage when you don’t know how it will develop further. You will be able to think about reusing it in the future when the code base grows enough and gets more mature.</p> <p>At the initial stage, reusing makes it difficult to change and rewrite the code further. To reuse some code fragment you need to separate it into a module to be used in different parts of the program. Changing such a module requires checking of operability of all the program parts depending on it. Duplication allows you to reduce the number of external dependencies to minimum: a code fragment is used directly where it is written. It allows you to rewrite this fragment and not be afraid that something will break in the other part of the program.</p> <p>So, code duplication allows you to minimize the number of application fragments depending on it and thus simplify its further improvement. One more recommendation follows from this: duplicate the code which is more likely to change in the future. The more specific your code is, the more probable it will change in the future. For example, the business logic changes much more often than the low-level server communication logic (the HTTP standard is updated every few years).</p>URL.createObjectURL instead of FileReader.readAsDataURL2020-05-05T00:00:00Zhttps://forweb.dev/en/blog/2020-05-05-object-url/<p>If you need to show an image from a <a href="https://developer.mozilla.org/en-US/docs/Web/API/File">file</a> or a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">blob</a>, don’t use <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL"><code>FileReader.readAsDataURL</code></a> for this job. This method requires significant work to read the blob and convert it into data URL. And although it works asynchronously, which is good because it doesn’t block the main thread, in general it’s inconvenient.</p> <p><a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL"><code>URL.createObjectURL</code></a> is a better solution: it synchronously and in no time generates temporary URL and binds it to the blob. Generating such URL doesn’t require reading the blob, hence it is much faster and cheaper (see <a href="https://w3c.github.io/FileAPI/#url-model">algorithm details in the specification</a>).</p> <p>The blob can’t be garbage collected while it has temporary URLs bound to it. So don’t forget to use <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL"><code>URL.revokeObjectURL</code></a> to unbind URL after you’re done with it.</p>UX of a drag-and-drop files uploading2020-05-05T00:00:00Zhttps://forweb.dev/en/blog/drag-and-drop-ux/<p>When implementing a form with a file upload field, it is a good practice to support drag and drop gesture from files to form.</p> <p>Usually, the size of the drop area is limited:</p> <img class="bordered" src="https://forweb.dev/en/blog/drag-and-drop-ux/vk.jpg" alt="VK screenshot on file dragging" height="919" width="1200" /> <p>If there is only one file upload field, this restriction is hard to justify, and it can be disturbing for users: the smaller the available area is, the harder it is for a user to aim at it.</p> <p>A more user-friendly approach is to stretch the drop area to the viewport. For example, such approach is used on GitHub and <a href="https://amplifr.com/">Amplifr</a>:</p> <img src="https://forweb.dev/en/blog/drag-and-drop-ux/amplifr.jpg" alt="Amplifr screenshot on file dragging" height="919" width="1200" /> <p>If there are several file upload fields on a page (for example, when uploading scans of several documents is required), the drop area can still be stretched to the viewport and split into parts for each field. These areas will still be large enough.</p> <h2>Don’t show drop area when dragging text</h2> <p>To prevent showing drop area on accidental or intentional text dragging, check that drag events includes files:</p> <pre><code class="language-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isFileDragEvent</span>(<span class="hljs-params">event</span>) </span>{ <span class="hljs-keyword">return</span> event.dataTransfer.types.includes(<span class="hljs-string">'Files'</span>); } <span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'dragenter'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =></span> { <span class="hljs-keyword">if</span> (!isFileDragEvent(event)) { <span class="hljs-keyword">return</span>; } <span class="hljs-comment">// ...</span> }); </code></pre>Timing in JavaScript2020-05-10T00:00:00Zhttps://forweb.dev/en/blog/js-timing/<p>There are some tasks on the frontend that require time measurement, such as calculating the load time of an external resource, or calculating the animation progress.</p> <p>The temptation to use <code>Date.now</code> is huge:</p> <pre><code class="language-js"><span class="hljs-keyword">const</span> start = <span class="hljs-built_in">Date</span>.now(); <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/data'</span>); <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Elapsed time in milliseconds:'</span>, <span class="hljs-built_in">Date</span>.now() - start); </code></pre> <p>Looks like the problem is solved, and you can go grab some coffee. Here is the thing: the duration calculated this way may turn out to be negative.</p> <h2>What’s wrong with Date.now?</h2> <p>The main problems of the <code>Date.now</code> function in the context of our task are as follows:</p> <ol> <li><code>Date.now</code> is not <a href="https://en.wikipedia.org/wiki/Monotonic_function">monotonic</a>, meaning, it can both increase and decrease (which leads to negative duration in calculations).</li> <li>It does not guarantee uniform growth of returned values.</li> </ol> <p>The root of these problems is that <code>Date</code> and its functions use a system clock that is affected by external influences:</p> <ul> <li>system time can be changed by a user;</li> <li>operating system services regularly <a href="https://en.wikipedia.org/wiki/Network_Time_Protocol">synchronize time with the precise internet clock</a>, which may lead to time leaps if the local clock is far behind or ahead.</li> </ul> <h2>What to use instead of Date.now?</h2> <h3>In the browser</h3> <p>The <a href="https://www.w3.org/TR/hr-time">High Resolution Time specification</a> describes <code>performance.now</code> function, which not only solves the stated problems, but also increases the accuracy of measurements to microseconds (<a href="https://github.com/w3c/hr-time/issues/56">depending on browser</a>). For details see the specification or <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/now">MDN</a>.</p> <h3>In Node.js</h3> <p>Node.js provides <a href="https://nodejs.org/api/process.html#process_process_hrtime_time"><code>process.hrtime</code></a> and <a href="https://nodejs.org/api/process.html#process_process_hrtime_bigint"><code>process.hrtime.bigint</code></a> functions that also don’t depend on the system clock and increase the accuracy to nanoseconds.</p>Your SPA doesn’t need a router2020-07-12T00:00:00Zhttps://forweb.dev/en/blog/drop-the-router/<p class="paragraph--lead"> So you are building a client-side web app for that next big project and wondering: “Which router should I use?”. Here is the thing: you don’t need any, and you will understand why shortly. </p> <h2>What is routing?</h2> <p>The first interface for a user to access any website is their browser address bar. Even if your website is visited via a link or from bookmarks, for a user it still goes through the address bar. Change of the address leads to a change of the page.</p> <p>Our application needs to determine from that URL which screen and in what state to show to the user.</p> <figure> <img src="https://forweb.dev/en/blog/drop-the-router/one-step-away.jpg" alt="“One step away” by Anna Zarubey" srcset="https://forweb.dev/en/blog/drop-the-router/one-step-away@2x.jpg 2x" width="1000" height="563" /> <figcaption>“One step away” by <a href="https://t.me/anna_zarubey">Anna Zarubey</a></figcaption> </figure> <p>So, in a nutshell, routing is deriving the state from the input URL. Yes, that simple.</p> <h2>Why is routing difficult then?</h2> <p>When we scale our app, we split the state into many pieces. There are two reasons to do it:</p> <ol> <li>it helps to avoid cognitive overload;</li> <li>it allows sharing the workload between several team members.</li> </ol> <p>Usually, we don’t need all the pieces at once, so we put them to different endpoints and storages. When a user opens the app, we reconstruct the required state from little pieces scattered all over the system. Moreover, some of the state pieces determine which subset of other pieces should be restored.</p> <figure> <img src="https://forweb.dev/en/blog/drop-the-router/state-reconstruction.jpg" alt="Example state reconstruction scheme, drawing by Anna Zarubey" srcset="https://forweb.dev/en/blog/drop-the-router/state-reconstruction@2x.jpg 2x" width="1000" height="563" /> <figcaption>Example state reconstruction scheme, drawing by <a href="https://t.me/anna_zarubey">Anna Zarubey</a></figcaption> </figure> <p>Sometimes state reconstruction is simple. For example, when a user requests the login page, we should just give them the login page. Most of the time, though, this logic is a lot more complex, depending on the current context, system state, and business requirements.</p> <p>The question is how much of this logic should we own, and how much could we generalize and delegate to routing via a framework or a library?</p> <p>Naturally, we would prefer to delegate as much code as possible. There are different approaches to that. One of them would be to fully separate routing and business logic.</p> <p>For instance, we could match a path to some handler function and pass query parameters to it. Then it will decide how to restore the state and what to show to the user.</p> <p>It could look like this (🔀 is for routing, 🅱️️ is for business logic, ❇️ is for dependencies loading):</p> <pre><code>🔀 Receive request path with parameters in it 🔀 Determine handler for this path and separate parameters ❇️ Load user session 🅱️ Check if the user is authenticated ❇️ Load user profile 🅱️ Check if the user is authorized to use this handler ❇️️ Load the first item from the path with parameter 🅱️ Check if it exists 🅱️ Check if the user is authorized to use it ❇️️ Load the second item from the path with parameter 🅱️ Check if it exists 🅱️ Check if it is relevant to the first item 🅱️️ Check if the user is authorized to use it 🅱️️ ... (Other business logic) 🔀 Return the combined result </code></pre> <p>After a while, we will notice that most of these handlers mainly consist of the same instructions — session loading and authentication check, for instance. Maybe we could separate all these checks into another layer to stop repeating the same thing all over again?</p> <p>Paths are hierarchical by design, which can be used to simplify our code. Like, we can agree that all the authenticated paths start with <code>/user</code> — meaning we could match paths from left to right and apply different checks depending on where we are in the hierarchy right now.</p> <p>Welcome to the concept of Routing Middleware. It is still business logic, but it also can’t be separated from paths structure. So it is still routing too.</p> <p>Both routing and business logic? Too complicated! We wanted a clear separation to delegate as much routing code as possible. Instead, we got the opposite. Screw middleware then. Why don’t we just define the list of all the checks and dependencies for each route?</p> <p>That would work, but we still need to provide context for those. For authentication and handler authorization it’s quite straightforward — we can identify the user from the named cookie passed in the request context. But what about data availability and access control? Do we need to invent an additional language to extract ids from the path? Or do we need to always name those ids using a naming convention to uniformly map ids to checks?</p> <p>We also want to optimize things, so we need to define sequences or relations for dependencies loading and checks. And some of them could be done in parallel — that should be defined too. Do we need one more language? Or do we do it imperatively? Then how is it different from middlewares?</p> <p>These questions make routing such a difficult task.</p> <p>Should it be so hard, though? Maybe backend already solved all the problems, and frontend should repeat after it’s elder brother? It already does, but there are multiple important obstacles along the way.</p> <h2>How is frontend routing different from backend routing?</h2> <p>First, <strong>we usually can’t have all the logic on the client-side</strong>: data is stored on a remote server, and we need to check if data is still valid to perform the desired transition. An observant reader will note that the same problems exist on the backend: database requests are asynchronous. The problem is: asynchronous nature of data requests is conflicting with the synchronous nature of the core concept of the web — links.</p> <p>By saying links are synchronous, I don’t mean they transfer you immediately to your target, rather that they don’t require writing any asynchronous javascript. The web platform already handles the links for us.</p> <p>This takes us to the second point. <strong>We need to entertain users while they are waiting</strong>. Modern web apps try to behave more like native apps rather than websites of the past. To make transitions smooth and seamless, we handle link clicks with javascript implementing from scratch all the logic provided by the platform.</p> <p>User falls for this little deception and assumes that all the required resources are already on their device, so there is no need to load the whole page from the server — it can be just shown. It could be a smooth transition, or skeleton UI, or just plain old loader — in any case, we need to show something immediately after user interaction. On the contrary, waiting for a response from the backend is handled by the platform.</p> <p>Third, <strong>client-side logic requires request chains</strong>. We have to ask the server for the first data chunk, then decide to load one of the next chunks depending on the first, then load all the items from the list in the second chunk... Only after a long chain of async requests we finally can make the transition.</p> <p>The backend also has dependent data requests, but they could be optimized with stored procedures or JOIN queries. The only attempt to do something similar for the frontend is GraphQL, but it comes with a lot of disadvantages (which are out of the scope of this article).</p> <p>And the last one — on the frontend, we sometimes have <strong>virtual routes</strong>, meaning we have different screen states for the same path. Because, well, you filled the first two steps of that wizard form — so we need to show you the third one and not allow you to go to the fourth one.</p> <h2>Why none of the popular routers solve the problem?</h2> <p>For some unknown reason, most of the popular routing solutions for web frontend focus on the tip of the iceberg, while making some significant mistakes in core architecture design.</p> <h3>Mistake #1: defining routes in the view layer</h3> <figure> <img src="https://forweb.dev/en/blog/drop-the-router/off-label.jpg" alt="“Off-label” by Anna Zarubey" srcset="https://forweb.dev/en/blog/drop-the-router/off-label@2x.jpg" width="1000" height="563" /> <figcaption>“Off-label” by <a href="https://t.me/anna_zarubey">Anna Zarubey</a></figcaption> </figure> <p>As you already know, the routing process is heavily dependent on business logic. The only two cases when routing and view should collide are mapping resolved state to page and rendering links.</p> <p>So there is no actual reason to use your view logic for routes definition. And when you do something without cause, you make your code difficult to understand and maintain.</p> <p>It still works quite well on small apps, though, because they don’t have any complex or asynchronous business logic, and the only thing they need is a list of route-page pairs.</p> <h3>Mistake #2: routing as a simple mapping from paths to pages</h3> <figure> <img src="https://forweb.dev/en/blog/drop-the-router/obvious.jpg" alt="“Obvious” by Anna Zarubey" srcset="https://forweb.dev/en/blog/drop-the-router/obvious@2x.jpg 2x" width="1000" height="563" /> <figcaption>“Obvious” by <a href="https://t.me/anna_zarubey">Anna Zarubey</a></figcaption> </figure> <p>Some routers selling point is the declarative style of routes definition. Meaning the whole routing problem is just a key-value dictionary.</p> <p>No, it’s not. It could be, but only in simple hello-world-ish cases, which get complex as soon as your app becomes one month old.</p> <p>In general, it is a fully-fledged process with dependency loading, data processing, and decision-making. It is also full of side effects: from external dependencies and data loading to browser history management.</p> <h3>Mistake #3: immediate transitions</h3> <p>Let’s assume you are on a simple website with no javascript at all. When you click the link, are you immediately transitioned to your destination? No, even in this simple case, you have to wait until the next page is loaded.</p> <p>Waiting for transitions is in the DNA of the web from day one. We got used to waiting after clicking the link, and we <strong>expect the next page to be loaded</strong>. This means it’s ok to wait because I requested my entire friend list, and that’s a lot of data, and I’m on 2G internet in the middle of nowhere, so I totally understand.</p> <p>A router should allow to transition out of the page, handle waiting time, then transition to the next page. That’s what browsers already do with websites, and the least we can do is not break it.</p> <h3>Mistake #4: no place for dependencies or common behavior</h3> <figure> <img src="https://forweb.dev/en/blog/drop-the-router/ever-ready.jpg" alt="“Ever-ready” by Anna Zarubey" srcset="https://forweb.dev/en/blog/drop-the-router/ever-ready@2x.jpg 2x" width="1000" height="563" /> <figcaption>“Ever-ready” by <a href="https://t.me/anna_zarubey">Anna Zarubey</a></figcaption> </figure> <p>It is generally a combination of #2 and #3 but feels like something to be addressed explicitly.</p> <p>Routers tend to work with pages as if the app already has everything it needs to display every page. And that may be true for a calculator, or some mini-game.</p> <p>In reality, we have network-heavy applications, which require both data loading and a lot of javascript and styles to display it. And most of the users won’t even visit that one heavy page. So the most logical solution is to separate its resources from the rest of the app.</p> <p>Now if we are going to separate that page, we have to put all the preconditions and dependencies inside of the page itself, but is it really where they belong? Also: do you enjoy repeating “if no user is authorized, redirect to login” for every page?</p> <h3>Mistake #5: one and only one path for every page</h3> <figure> <img src="https://forweb.dev/en/blog/drop-the-router/next-room-is-outside.jpg" alt="“Next room is outside” by Anna Zarubey" srcset="https://forweb.dev/en/blog/drop-the-router/next-room-is-outside@2x.jpg 2x" width="1000" height="563" /> <figcaption>“Next room is outside” by <a href="https://t.me/anna_zarubey">Anna Zarubey</a></figcaption> </figure> <p>There is a stable <abbr title="User experience">UX</abbr> trend to split long forms into multiple steps called “form wizards”. If you aim for the good UX, you would usually prefer to add those steps to browser history. It will allow the user to navigate between form steps with browser buttons.</p> <p>Sounds like a natural feature, right? But you can’t implement it with basic router functionality. You will need to push those steps to browser history manually. The router will still listen to history events, so you will need to find a workaround to ignore them. Or you could just specify service paths that are accessible only if the wizard is in a relevant state. Meaning users will share links to various wizard steps, and you will also need to check if they are allowed to go there directly.</p> <p>You would presume routers don’t support this because there are some browser limitations. Well, actually, there is none: you can push the next history state without route change, but with another data.</p> <p>Routers also don’t allow storing modal dialogs state. It leads either to hacks like allocating a specific path to each dialog or dialogs management without router at all.</p> <p>Some routers even forbid mixing <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname">pathnames</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/hash">hashes</a>, while fragments (defined by hashes) are the vital part of the web platform and are the perfect fit for the use case of virtual routing. Which literally is rendering of different fragments of the same page depending on current data state.</p> <p>Yes, you guessed it correctly: virtual routing cannot be implemented because routers assume you only have plain mapping (see #2).</p> <h2>What should we do then?</h2> <p>All this sounds awful. What should we do to solve our routing problem in a simple, maintainable, and scalable way without going crazy in the meantime? I’m glad you asked.</p> <ol> <li>You still need to deserialize the state from the path and serialize it back. Many libraries do it just fine. For instance, you can check <a href="https://github.com/pillarjs/path-to-regexp">path-to-regexp</a> (used by react-router and page.js), <a href="https://github.com/troch/path-parser">path-parser</a> (used by router5), or <a href="https://github.com/rcs/route-parser">route-parser</a>. You also need to put that state into browser history (or sometimes not), and there is <a href="https://github.com/browserstate/history.js/">history.js</a> lib for you, but you should probably just use <a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API">Browser History API</a> — it is in a good and consistent state across all major browsers right now.</li> <li>You need to add asynchronous dependencies check, which consists of additional script loading and data requests. Usually, this is what your controller handles already: redux-thunk, redux-saga, mobx, effector — whatever you prefer. If it handles your async logic, it could handle your async dependencies management.</li> <li>While the transition is happening, you need to know some meta info about it. It’s always different, so you can just save whatever structure you prefer to your state manager. In most cases, the basics you would like to know are the current screen, the next screen, and the transition state. But you could also store loading progress, all intermediary transitions (or redirects), additional context to help with the transition, scroll position, cause of transition, etc. Your state manager can store all that data with ease and keep it accessible for the business logic mentioned above.</li> <li>You need to render the screen. In a simple case, you need to choose a component by name and render it. In a more complex case, you could choose a component and all the modal screens depending on your business logic. Anyway, that’s the only reason for your view library to exist, so use it!</li> </ol> <p>That’s it. You got the path, got dependencies for it, stored the meta info, and finally rendered the page. <strong>No router was harmed along the way.</strong></p> <h2>Conclusion</h2> <p>It is extremely hard to build a general routing solution because of its high coupling with business logic and libraries used to define business logic.</p> <p>Of course, if you don’t have business logic or put your business logic into the view layer, you will be fine with current solutions. But if you are building for the long term, putting your logic into view is neither maintainable nor scalable. Yes, separating model and view still is a thing.</p> <p>There are some experimental projects though trying to treat routing as a process and mix it with state management. That could be a good angle to view this problem.</p> <p>For example, <a href="https://www.kriasoft.com/universal-router/">universal-router</a> resolves routes to actions that are fully capable of downloading requirements and deriving the current view state. It also allows you to pass any context to the action and return data of any type from that action. This, in turn, allows different approaches to business logic integration. You can inject your state and methods into the router and hand over control to it while the transition is happening. Or you can inject dependencies on action call, wait for the resolution, then continue with the business logic depending on the results. Still, its concept of middlewares is too simple, and it lacks community support and development predictability with at least some basic roadmap.</p> <p>But why use any libraries, if routing can be so dead-simple when you do it in business logic?</p> <p>So for now — you just don’t need a router.</p>Beyond NPM: choosing dependencies wisely2023-10-09T00:00:00Zhttps://forweb.dev/en/blog/npm-tools/<p class="paragraph--lead"> Hardly any front-end projects are built without external dependencies. And when it comes to choosing dependencies, you’d better make concious and informed decisions, since bad choice can lead to worsened <abbr title="User experience">UX</abbr> and even legal consequences. In this article we’ll look into a few tools that’ll help you make those decisions better. </p> <h2>Package discovery</h2> <p>The default search experience on <a href="https://www.npmjs.com/">npmjs.com</a> can sometimes be frustrating. For example, searching for <a href="https://www.npmjs.com/search?q=couchdb+promise">couchdb promise</a> and <a href="https://www.npmjs.com/search?q=couchdb+promises">couchdb promises</a> produces different search results, missing some packages for the second query.</p> <p><a href="https://npms.io/">npms</a> aims to provide a better search experience by enabling advanced Elasticsearch features and using a unique package ranking system.</p> <h2>Package dependencies analysis</h2> <p>It might be a good idea to check what transitive dependencies you’ll get along with the package you are considering using. Particularly interesting aspects to look into include:</p> <ul> <li>licenses that are not suitable for your project</li> <li>the total count of dependencies and the total count of it’s maintainers (usually, more is worse)</li> </ul> <p>There are two quite similar tools for such analysis: <a href="https://npm.anvaka.com/">npm.anvaka.com</a> and <a href="https://npmgraph.js.org/">npmgraph</a>. The first produces nicer visualizations, while the second allows you to upload the entire <code>package.json</code> for bulk analysis of your project’s dependencies.</p> <h2>Package size on disk and in bundle</h2> <p>You’ve probably seen those memes about <code>node_modules</code>, and let’s be honest, the meme is fun, but the situation is scary:</p> <p><img class="bordered" src="https://forweb.dev/en/blog/npm-tools/nodemodules.png" alt="Heaviest objects in the universe: sun, neutron star, black hole, node_modules" width="800" height="575" /></p> <p>When it comes to package size, there are two things to consider:</p> <ul> <li><em>install size</em>, the amount of bytes it takes on the developer’s disk after installation</li> <li><em>bundle size</em>, the amount of bytes it takes in the final application bundle sent to the user</li> </ul> <p>Install size affects developer experience and <abbr title="Continuous integration">CI</abbr> build time and resources, whereas bundle size affects user experience.</p> <p><a href="https://packagephobia.com/">Packagephobia</a> is a great tool for checking install size.</p> <p><a href="https://bundlephobia.com/">Bundlephobia</a> and <a href="https://bundlejs.com/">bundlejs</a> let you check how a package will affect bundle size. Bundlephobia is the first-of-its-kind tool with no fancy features, while bundlejs is newer and more advanced tool that can treeshake and bundle multiple packages (both CommonJS and ESM) together locally in your browser, showing you the total bundle size of those.</p> <h2>Package source exploration</h2> <p>Sometimes you might want to explore the contents of the published package. For instance, to check that published code does exactly what it claims to do, and doesn’t include anything potentially harmful or unexpected. You might also want to check the diff between two versions of the package to see if the new version includes the bugfix you are interested in (because sometimes changelogs lie) or to make a security audit.</p> <p><a href="https://npmfs.com/">npmfs</a> is the right tool for these jobs: it supports viewing the contents of packages, comparing packages across versions, linking to specific lines in files and diffs, and even downloading any file or folder inside a package.</p>