Jekyll2019-08-02T19:24:09+00:00https://tech.onestopbeauty.online/feed.xmlOSBO Tech BlogBlogging about tech in OSBOLiliana ZiolekIntercepting back button on mobile in Vue/Nuxt/Vuetify apps2019-08-03T00:00:00+00:002019-08-03T00:00:00+00:00https://tech.onestopbeauty.online/front-end/intercepting-back-button-on-mobile-in-nuxt<h1 id="problem-statement">Problem statement</h1>
<p>The first version of OSBO was not particularly mobile-friendly. Thanks to great work done in Vuetify and Nuxt, once we started paying more attention to being mobile-friendly, the transition wasn’t difficult and fairly quickly we had a page that worked quite well on mobile.<br />
Or so we thought. The very first test with a “real user” has shown us that on mobile clicking back button is a very strong urge when trying to close full-screen popups - for example when we show a zoomed-in image of a product. Since we’re just in a browser, back button takes the user to the previous page, rather than closing the pop-up. This can be very frustrating - you are on a product page, you look at a product image, you click back - and suddenly you’re back on the products list page. We decided we needed to intercept back button, and if any pop-up is open, close it instead. Simple?</p>
<p>Unfortunately, it is easier said than done. There isn’t really such thing as “listening to back button event” in Javascript.</p>
<p>Another complication is that we don’t want to intercept back button on desktop - only where the users are likely to be on a touch screen - that is, on mobiles and tablets.</p>
<h1 id="device-detection">Device detection</h1>
<p>This is quite a touchy subject. Unfortunately, there still isn’t a 100% reliable method to do this that would work both server- and client-side. Remember, we have SSR and we want to serve correct HTML immediately - before we get to the browser and can question its capabilities or execute any clever Javascript. On the server we can only really rely on one thing - User-Agent. Yes, we know it’s not 100% reliable, but there simply doesn’t seem to be a better way (happy to be corrected - feel free to comment if you know a more reliable way to detect mobiles/tablets <em>during SSR rendering</em>).</p>
<p>To avoid reinventing the wheel, we decided to use a Nuxt module: <strong><a href="https://www.npmjs.com/package/nuxt-device-detect">nuxt-device-detect</a></strong>. It exposes a set of flags, like isMobile, isTablet, isWindows, etc. via an object injected into Nuxt context and Vue instances. It is therefore possible to call something like: <code>this.$device.isMobileOrTablet</code> and get a true/false value, depending on the user agent. It works on both client and server side, which is great.</p>
<p>To include it in your project you only need to change two files:</p>
<p>package.json</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="s2">"dependencies"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"nuxt-device-detect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"~1.1.5"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>nuxt.config.js</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nl">modules</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'nuxt-device-detect'</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then in any of your vue files you can start having conditional logic, for example:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template></span>
<span class="nt"><section></span>
<span class="nt"><div</span> <span class="na">v-if=</span><span class="s">"$device.isMobileOrTablet"</span><span class="nt">></span>
<span class="nt"><touchscreen-navbar></touchscreen-navbar></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">v-else</span><span class="nt">></span>
<span class="nt"><desktop-navbar></desktop-navbar></span>
<span class="nt"></div></span>
<span class="nt"></section></span>
<span class="nt"></template></span>
</code></pre></div></div>
<p>Neat!</p>
<h1 id="intercept-back-button-on-mobile-and-close-popup-instead">Intercept back button on mobile and close popup instead</h1>
<p>As mentioned, there isn’t an event you could subscribe in Javascript that would communicate that back button is pressed. However, there is a fairly simple workaround.</p>
<ol>
<li>We need to track if we have a popup open or not. If no popups are open, we should act as normal, i.e. navigate back. If any popups are open AND we are on mobile/tablet, then we will not navigate back and close the pop-up instead.</li>
<li>We need to hook into Vue router to get notified that the route is about to change (go back to previous page). We can achieve this by implementing <code>beforeRouteLeave</code> and/or <code>beforeRouteUpdate</code>. When Vue Router triggers event (“we’re about to leave the current page”) we can react and prevent this from happening, if appropriate.</li>
</ol>
<p>We should make sure we don’t end up with similar code duplicated all over the place. We decided to use a combination of <a href="https://tech.onestopbeauty.online/front-end/simple-eventBus-in-nuxt/">eventbus plugin</a> and 2 complementing mixins.</p>
<h2 id="subscribe-to-notifications-about-open-popups">Subscribe to notifications about open popups</h2>
<p>When the page component is mounted, we subscribe to be notified about any open popups. If a popup is not open and the user presses back button, we will let the app go back.</p>
<p>We will create a mixin and then import it in any <strong>page</strong> that needs to have this functionality.</p>
<p>mobileBackButtonPageMixin.js (subscription part)</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">mobileBackButtonPageMixin</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">data</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">dialogOpen</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="nx">mounted</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">$device</span><span class="p">.</span><span class="nx">isMobileOrTablet</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s2">"dialogOpen"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">dialogOpen</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s2">"dialogClosed"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">dialogOpen</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="nx">beforeDestroy</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">//always remember to unsubscribe</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">$device</span><span class="p">.</span><span class="nx">isMobileOrTablet</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$off</span><span class="p">(</span><span class="s1">'dialogOpen'</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$off</span><span class="p">(</span><span class="s1">'dialogClosed'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="p">};</span>
</code></pre></div></div>
<h2 id="notify-from-child-components-that-a-dialog-is-open">Notify from child components that a dialog is open</h2>
<p>In every component that can open a popup we need to send a notification (<code>dialogOpen</code>) to eventBus, this will allow tracking if any popups are open. Additionally, the component needs to subscribe to <code>closeAllDialogs</code> so that a request to close dialog can be made (when back button is pressed on mobile).
Again, we will use a mixin.</p>
<p>mobileBackButtonDialogComponentMixin.js</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">mobileBackButtonDialogComponentMixin</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">methods</span><span class="p">:</span> <span class="p">{</span>
<span class="nx">notifyDialogStateViaEventBus</span><span class="p">(</span><span class="nx">open</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">open</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$emit</span><span class="p">(</span><span class="s1">'dialogOpen'</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s2">"closeAllDialogs"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">closeAllDialogs</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$emit</span><span class="p">(</span><span class="s1">'dialogClosed'</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$off</span><span class="p">(</span><span class="s2">"closeAllDialogs"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>
<p>This mixin needs to be imported into components:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">import</span> <span class="p">{</span><span class="nx">mobileBackButtonDialogComponentMixin</span><span class="p">}</span> <span class="k">from</span> <span class="s2">"@/mixins/mobileBackButtonDialogComponentMixin"</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="na">mixins</span><span class="p">:</span> <span class="p">[</span><span class="nx">mobileBackButtonDialogComponentMixin</span><span class="p">],</span>
<span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>
<p>On top of that, you need to add a watch for the property that controls the visibility of the popup. For example, if in template you have something like this:
<code><v-dialog v-model="popupVisible"></code>
then in the component you will need to add this:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">watch</span><span class="p">:</span> <span class="p">{</span>
<span class="nl">popupVisible</span><span class="p">:</span> <span class="s1">'notifyDialogStateViaEventBus'</span>
<span class="p">},</span>
<span class="nx">methods</span><span class="p">:</span> <span class="p">{</span>
<span class="nx">closeAllDialogs</span><span class="p">(){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">popupVisible</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Since we use Vuetify, our popups are v-dialogs, but this technique would work with any other component framework.</p>
<p>Note that you can have more than one popup in a component - you would simply add another watch, and another property set to false in “closeAllDialogs” method.</p>
<h2 id="intercept-back-navigation-close-popup-instead-if-appropriate">Intercept back navigation, close popup instead if appropriate</h2>
<p>To get notified about route changes, we need to add two methods to the page that contains the popup (<strong>important</strong> - this has to be a page, and not a component).</p>
<p>mobileBackButtonPageMixin.js (intercept part)</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">mobileBackButtonPageMixin</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">...</span>
<span class="nx">beforeRouteUpdate</span><span class="p">(</span><span class="nx">to</span><span class="p">,</span> <span class="k">from</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">$device</span><span class="p">.</span><span class="nx">isMobileOrTablet</span> <span class="o">&&</span> <span class="k">this</span><span class="p">.</span><span class="nx">dialogOpen</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$emit</span><span class="p">(</span><span class="s1">'closeAllDialogs'</span><span class="p">);</span>
<span class="nx">next</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="nx">beforeRouteLeave</span><span class="p">(</span><span class="nx">to</span><span class="p">,</span> <span class="k">from</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">$device</span><span class="p">.</span><span class="nx">isMobileOrTablet</span> <span class="o">&&</span> <span class="k">this</span><span class="p">.</span><span class="nx">dialogOpen</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$emit</span><span class="p">(</span><span class="s1">'closeAllDialogs'</span><span class="p">);</span>
<span class="nx">next</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>
<p>Once <code>beforeRouteUpdate</code> and <code>beforeRouteLeave</code> hooks get called, we can check if we should stop navigating to previous page (are we on mobile/tablet and do we have open popups?). If we need to close popups instead, we issue an event to communicate it to components containing popups (<code>this.$eventBus.$emit('closeAllDialogs');</code>). Then <code>next(false)</code> tells Nuxt and VueRouter that navigation should not happen. Next() tells it to proceed as usual.</p>
<h1 id="summary">Summary</h1>
<p>This post shows you how to intercept mobile back button navigation and close a dialog instead. The example uses Nuxt and Vuetify - it is possible to apply the same concept without having Nuxt, and of course it would also work with a component framework other than Vuetify.</p>
<p>As usual, the full working code can be found in <a href="https://github.com/lilianaziolek/blog-examples/tree/eventBusAndBackButtonIntercept/dry-examples">github</a> - make sure to use the <code>eventBusAndBackButtonIntercept</code> branch . To test it, make sure you switch a sample mobile phone e.g. in chrome devtools <em>before</em> loading the page, so that correct UserAgent is sent.</p>
<p>P.S. There is a cute little surprise in the popup, I hope you like it :dog:</p>Liliana ZiolekProblem statement The first version of OSBO was not particularly mobile-friendly. Thanks to great work done in Vuetify and Nuxt, once we started paying more attention to being mobile-friendly, the transition wasn’t difficult and fairly quickly we had a page that worked quite well on mobile. Or so we thought. The very first test with a “real user” has shown us that on mobile clicking back button is a very strong urge when trying to close full-screen popups - for example when we show a zoomed-in image of a product. Since we’re just in a browser, back button takes the user to the previous page, rather than closing the pop-up. This can be very frustrating - you are on a product page, you look at a product image, you click back - and suddenly you’re back on the products list page. We decided we needed to intercept back button, and if any pop-up is open, close it instead. Simple?How to build a simple event bus in Vue & Nuxt?2019-07-25T00:00:00+00:002019-07-25T00:00:00+00:00https://tech.onestopbeauty.online/front-end/simple-eventBus-in-nuxt<p>This post will be short and sweet, as it’s really just a prep for another one coming up soon (intercepting back button on mobile in Vue/Nuxt apps).</p>
<h1 id="the-problem">The problem</h1>
<p><a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern">Event bus, related to publish-subscribe pattern</a> is a fairly fundamental concept in software development. If you have not heard of it, I’d recommend reading the wikipedia entry to understand the rest of the post.</p>
<p>In short, event bus allows you to decouple various parts of the system which somehow depend on things (events) happening in another part of the system. As an example, think of a situation where user being logged in should trigger a fetch of extra data in certain components.<br />
Some people might argue that with Vue’s reactivity and VueX an event bus is not necessary. It is true to some degree - in that these two mechanisms greatly reduce the need for any explicit publish/subscribe to happen. However, in my opinion, whilst you could try to always just use computed properties or watches, the event bus might be in some cases a much simpler, and well-known pattern. As a developer, it’s good to have various tools and choose them depending on what produces the easiest, most readable and maintainable code.</p>
<h1 id="vue-onemitv-on">Vue $on/$emit/v-on</h1>
<p>Vue comes with an inbuilt event bus / publish-subscribe mechanism. Any Vue instance exposes a few related methods, including: <a href="https://vuejs.org/v2/api/#vm-on"><code>$on</code></a> and <a href="https://vuejs.org/v2/api/#vm-emit"><code>$emit</code></a>.</p>
<h2 id="reminder-local-events">Reminder: Local events</h2>
<p>Usually, we are using the $emit method and v-on directive for communication between parent and child components.</p>
<p>For example, in a child component consisting of a dialog (<code>ComponentPart.vue</code>) with a close button, we could have the following:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><v-btn</span> <span class="err">@</span><span class="na">click=</span><span class="s">"$emit('close')"</span><span class="nt">></span>
<span class="nt"><v-icon></span>close<span class="nt"></v-icon></span>
<span class="nt"></v-btn></span>
</code></pre></div></div>
<p>And then the following in the parent component:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><v-dialog</span> <span class="na">v-model=</span><span class="s">"dialog"</span> <span class="nt">></span>
<span class="nt"><component-part</span> <span class="err">@</span><span class="na">close=</span><span class="s">"dialog = false"</span><span class="nt">></component-part></span>
<span class="nt"></v-dialog></span>
</code></pre></div></div>
<p>Note that the <code>@close</code> is just a shortcut for <code>v-on:close</code>. (Can you guess what happens inside v-btn that allows us to write <code>@click</code>?)</p>
<h2 id="event-bus-plugin">event bus plugin</h2>
<p>event bus is using the same mechanism, except that we need to get hold of an instance of a component available globally, and instead of using <code>v-on</code>, we will use <code>$on</code>. As we covered in <a href="https://tech.onestopbeauty.online/front-end/understanding-nuxt-vue-hooks-and-lifecycle-part3/">previous series of posts</a>, to do something for each visitor and do it only once, on the client, we can create a plugin. This will initialise our event bus.</p>
<p><em>eventBus.client.js</em></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">Vue</span> <span class="k">from</span> <span class="s1">'vue'</span>
<span class="kd">const</span> <span class="nx">eventBus</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Vue</span><span class="p">();</span>
<span class="c1">//this helps WebStorm with autocompletion, otherwise it's not needed</span>
<span class="nx">Vue</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">$eventBus</span> <span class="o">=</span> <span class="nx">eventBus</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">({</span><span class="nx">app</span><span class="p">},</span> <span class="nx">inject</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">inject</span><span class="p">(</span><span class="s1">'eventBus'</span><span class="p">,</span> <span class="nx">eventBus</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="example-usage">Example usage:</h2>
<p>Let’s say that in our VueX store we have communication with the back-end that gets kicked off after user logs in (simulated here just by having a Login button) and retrieves user details, e.g. telling us if the user is admin. Once we know if user is admin, we want to fetch some extra admin data to display in a component. With the $eventBus, it would look like so:</p>
<h3 id="notify-when-user-details-change">Notify when user details change</h3>
<p>store/user.js</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">({</span>
<span class="na">userDetails</span><span class="p">:</span> <span class="p">{</span>
<span class="na">admin</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">},</span>
<span class="p">});</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">mutations</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">reverseUserDetails</span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">state</span><span class="p">.</span><span class="nx">userDetails</span> <span class="o">=</span> <span class="p">{</span><span class="na">admin</span><span class="p">:</span> <span class="o">!</span><span class="nx">state</span><span class="p">.</span><span class="nx">userDetails</span><span class="p">.</span><span class="nx">admin</span><span class="p">};</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">actions</span> <span class="o">=</span> <span class="p">{</span>
<span class="k">async</span> <span class="nx">fetchUserDetails</span><span class="p">({</span><span class="nx">commit</span><span class="p">})</span> <span class="p">{</span>
<span class="c1">// normally we'd have an axios call here, it would call our API to get user details</span>
<span class="c1">// here I'm just hardcoding the userDetails to values opposite to what they were</span>
<span class="c1">// every time when you "Login" and fetchUserDetails is called you will switch between admin and non-admin</span>
<span class="nx">commit</span><span class="p">(</span><span class="s2">"reverseUserDetails"</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$emit</span><span class="p">(</span><span class="s2">"userDetailsChanged"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>
<h3 id="subscribe-to-the-event-in-the-respective-component">Subscribe to the event in the respective component</h3>
<p>components/AdminDataDemo.vue</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template></span>
<span class="nt"><div></span>
<span class="nt"><span</span> <span class="na">v-if=</span><span class="s">"isAdmin"</span><span class="nt">></span></span>
<span class="nt"><span</span> <span class="na">v-else</span><span class="nt">></span>Current user is not admin<span class="nt"></span></span>
<span class="nt"></div></span>
<span class="nt"></template></span>
<span class="nt"><script></span>
<span class="k">import</span> <span class="p">{</span><span class="nx">mapState</span><span class="p">}</span> <span class="k">from</span> <span class="s1">'vuex'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="s2">"AdminDataDemo"</span><span class="p">,</span>
<span class="na">computed</span><span class="p">:</span> <span class="p">{</span>
<span class="p">...</span><span class="nx">mapState</span><span class="p">({</span>
<span class="na">isAdmin</span><span class="p">:</span> <span class="nx">state</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">userDetails</span><span class="p">.</span><span class="nx">admin</span><span class="p">,</span>
<span class="na">adminData</span><span class="p">:</span> <span class="nx">state</span> <span class="o">=></span> <span class="nx">state</span><span class="p">.</span><span class="nx">admin</span><span class="p">.</span><span class="nx">adminData</span>
<span class="p">})</span>
<span class="p">},</span>
<span class="nx">created</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">//this listener is not needed in SSR-mode</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">client</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Subscribing to know when userDetails change"</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$on</span><span class="p">(</span><span class="s2">"userDetailsChanged"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"We were notified that user details changed, reacting, admin: "</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">isAdmin</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="s1">'admin/fetchAdminData'</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="s1">'admin/removeAdminData'</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="nx">beforeDestroy</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">//make sure to always unsubscribe from events when no longer needed</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Switching off userDetails listener"</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$eventBus</span><span class="p">.</span><span class="nx">$off</span><span class="p">(</span><span class="s2">"userDetailsChanged"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nt"></script></span>
</code></pre></div></div>
<h3 id="admin-data-refresh">Admin data refresh</h3>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">({</span>
<span class="na">adminData</span><span class="p">:</span> <span class="p">{}</span>
<span class="p">});</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">mutations</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">setAdminData</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">state</span><span class="p">.</span><span class="nx">adminData</span> <span class="o">=</span> <span class="nx">value</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">actions</span> <span class="o">=</span> <span class="p">{</span>
<span class="k">async</span> <span class="nx">fetchAdminData</span><span class="p">({</span><span class="nx">commit</span><span class="p">})</span> <span class="p">{</span>
<span class="c1">// normally we'd have an axios call here, it would call our API to get some data specific to admin.</span>
<span class="c1">// here we're just setting something random</span>
<span class="nx">commit</span><span class="p">(</span><span class="s2">"setAdminData"</span><span class="p">,{</span><span class="na">someValue</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()});</span>
<span class="p">},</span>
<span class="k">async</span> <span class="nx">removeAdminData</span><span class="p">({</span><span class="nx">commit</span><span class="p">})</span> <span class="p">{</span>
<span class="c1">// if a user logs out, or stops being an admin, we want to remove existing adminData</span>
<span class="nx">commit</span><span class="p">(</span><span class="s2">"setAdminData"</span><span class="p">,</span> <span class="p">{});</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>
<h3 id="whats-the-benefit">What’s the benefit?</h3>
<p>You could argue that user.js could dispatch to admin.js directly and make it get the extra data directly - but this would mean that, potentially, you would be fetching admin data even when component that requires it is not active. Also, you’d be coupling the getting general user details with admin functionality.</p>
<p>In this very simple case, you could also monitor user.js store state and react when <code>userDetails.admin</code> value changes. I hope this simple example show how this can be used for more complicated scenarios. I will show one such scenario (intercepting back button on mobile) in the next post.</p>
<h1 id="full-code">Full code</h1>
<p>As always, a fully working project with this example is located in <a href="https://github.com/lilianaziolek/blog-examples/tree/eventBusAndBackButtonIntercept/dry-examples">Github</a> - note it is just a branch on the project I used so far.</p>
<h1 id="other-notes">Other notes:</h1>
<ul>
<li>
<p>In Nuxt context, you could simply use <code>this.$root</code>, as it’s the shared root Vue instance. However, I am a huge fan of communicating your intent in code as clearly as possible, so I chose to create a very simple plugin with a meaningful name.</p>
</li>
<li>
<p>My sample code always has a lot of console.log statements so that, if you run it, you can quickly and easily see on the console what’s happening. If using this code in an actual application, remove all of that to avoid excessive noise, or replace with proper logging framework (if you use it).</p>
</li>
</ul>Liliana ZiolekThis post will be short and sweet, as it’s really just a prep for another one coming up soon (intercepting back button on mobile in Vue/Nuxt apps).People don’t leave their jobs, they leave their managers - or do they?2019-07-21T00:00:00+00:002019-07-21T00:00:00+00:00https://tech.onestopbeauty.online/opinions/people-dont-leave-their-jobs-they-leave-their-managers-or-do-they<p>Every now and again I come across this “pearl of wisdom” (in slightly different variations):</p>
<blockquote>
<p>People don’t leave bad jobs / companies, they leave because of bad bosses / managers</p>
</blockquote>
<p>Whilst I’m sure this is true in some/many cases, I always hear an internal voice of protest.</p>
<p>I’ve been in the industry for close to 15 years. I went through a number of different bosses/projects (a lot of them contracts). In all of my career, I left due to my management exactly once.</p>
<p>All the other times?</p>
<ul>
<li>it was always supposed to be a short-term engagement (early in career - internships etc.)</li>
<li>a (small) company lost a client I was at, so was forced to change my role and I prefered to stay a developer</li>
<li>I wanted to travel more and my job-at-the-time couldn’t offer enough flexibility (switched from perm to contracts)</li>
<li>the project has reached a maintenance phase, a big part of the team was leaving, plus I wanted to go on a long holiday</li>
<li>a different offer, “too good to pass”, came my way - I wasn’t looking at the time, but I decided I had to take it</li>
<li>the project was coming to a slow, uncertain phase (lack of funding), my contract was up for renewal and I decided not to</li>
<li>I decided to work on my own startup</li>
</ul>
<p>Vast majority of cases where I stopped working for my managers, it was one of two reasons (sometimes both at the same time):</p>
<ul>
<li>the project was winding down anyway (dev complete, or funding cut) and it was time to move on. As a contractor you always take into account that after the active phase, the contract might not be renewed</li>
<li>other circumstances completely outside of control of my manager (my travel-bug, or another amazing offer)</li>
</ul>
<p>In all these cases I always made an effort to explain my reasons to my boss. In all cases I found understanding, we split our ways with no hard feelings, and in fact some of my ex-bosses became my again-bosses later on.</p>
<p>I really wish this quote stopped doing rounds, because I feel it might be putting an unnecessary feeling of <em>“what did I do wrong / what could I have done better”</em> on many perfectly good managers out there.</p>
<p>So to all of the “good ones”, take my word: <strong>Sometimes a person handing in their notice is really not your fault, and there is absolutely nothing you could have done to prevent this.</strong></p>
<p>What were your reasons for leaving your role? What’s your ratio of bad-boss-vs-other-reasons? Was I very lucky - or are “bad bosses” not that common after all?</p>Liliana ZiolekEvery now and again I come across this “pearl of wisdom” (in slightly different variations):Understanding Nuxt & Vue hooks and lifecycle (part 3)2019-07-19T00:00:00+00:002019-07-19T00:00:00+00:00https://tech.onestopbeauty.online/front-end/understanding-nuxt-vue-hooks-and-lifecycle-part3<p>This is part 3 of mini-series - Understanding Nuxt & Vue hooks and lifecycle - a quick reference table to refresh memory.</p>
<p>If you’ve missed the previous parts:</p>
<ul>
<li><a href="https://tech.onestopbeauty.online/front-end/understanding-nuxt-vue-hooks-and-lifecycle-part1/">Part 1 here</a> - which explains each of the mechanisms in more details,</li>
<li><a href="https://tech.onestopbeauty.online/front-end/understanding-nuxt-vue-hooks-and-lifecycle-part2/">Part 2 here</a> - which shows each of the mechanisms on an example app,</li>
<li><a href="https://tech.onestopbeauty.online/high-level/quick-guide-to-javascript-ecosystem-from-senior-java-dev-pov/">Quick guide to Vue and Nuxt from Java dev PoV</a>.</li>
</ul>
<p>I’m not adding modules to this table because, as explained in Parts 1 & 2, the module code only gets executed on Nuxt startup. Of course module code might initialise/attach various hooks - but then they’d follow the rules below.</p>
<table>
<tr>
<th>What</th>
<th>SSR (1st page)</th>
<th>Client (1st page)</th>
<th>Client (Next pages)</th>
<th>Notes</th>
<th>Example usage</th>
</tr>
<tr>
<td>beforeCreate</td>
<td>:heavy_check_mark:</td>
<td>:heavy_check_mark:</td>
<td>:heavy_check_mark:</td>
<td>Does not have access to component's *this* (does not exist yet)</td>
<td>If you're not using Nuxt: getting/preparing any data that is required by the component. With Nuxt, fetch/asyncData is easier</td>
</tr>
<tr>
<td>created</td>
<td>:heavy_check_mark:</td>
<td>:heavy_check_mark:</td>
<td>:heavy_check_mark:</td>
<td>Has access to component's data, but not DOM (no `this.$refs`)</td>
<td>(in client mode) generate and attach extra styles to document; process data/props with extra logic (could also be in computed prop)</td>
</tr>
<tr>
<td>mounted</td>
<td>:x:</td>
<td>:heavy_check_mark:</td>
<td>:heavy_check_mark:</td>
<td>First hook with access to data and DOM</td>
<td>DOM operations, client-side operations such as subscribing to events</td>
</tr>
<tr>
<td>plugins (dual mode)</td>
<td>:heavy_check_mark:</td>
<td>:heavy_check_mark:</td>
<td>:x:</td>
<td>use inject to make plugins available globally</td>
<td>globally shared functionality, e.g. this.$user.isLoggedIn (goes to store behind the scenes)</td>
</tr>
<tr>
<td>plugins (client)</td>
<td>:x:</td>
<td>:heavy_check_mark:</td>
<td>:x:</td>
<td>use inject to make plugins available globally</td>
<td>actions that need to be performed once per visitor (client-side), e.g. setting up authorisation tokens</td>
</tr>
<tr>
<td>plugins (server)</td>
<td>:heavy_check_mark:</td>
<td>:x:</td>
<td>:x:</td>
<td>use inject to make plugins available globally</td>
<td>actions that need to be performed once per visitor (server-side)</td>
</tr>
<tr>
<td>nuxtServerInit</td>
<td>:heavy_check_mark:</td>
<td>:x:</td>
<td>:x:</td>
<td>used for VueX initialisation</td>
<td>fetch globally used data, e.g. elements for navigation menu or other configuration from API</td>
</tr>
<tr>
<td>middleware</td>
<td>:heavy_check_mark:</td>
<td>:x:</td>
<td>:heavy_check_mark:</td>
<td>can be attached globally, or just to some pages</td>
<td>automatic redirects for certain pages - e.g. when content moved, or if user tries to access protected page when not logged in</td>
</tr>
<tr>
<td>asyncData / fetch</td>
<td>:heavy_check_mark:</td>
<td>:x:</td>
<td>:heavy_check_mark:</td>
<td>only executed for pages, not components</td>
<td>fetch data (into store or component) required on certain route</td>
</tr>
</table>Liliana ZiolekThis is part 3 of mini-series - Understanding Nuxt & Vue hooks and lifecycle - a quick reference table to refresh memory.Understanding Nuxt & Vue hooks and lifecycle (part 2)2019-07-16T00:00:00+00:002019-07-16T00:00:00+00:00https://tech.onestopbeauty.online/front-end/understanding-nuxt-vue-hooks-and-lifecycle-part2<p>This is part 2 of mini-series - Understanding Nuxt & Vue hooks and lifecycle. You can start with <a href="https://tech.onestopbeauty.online/front-end/understanding-nuxt-vue-hooks-and-lifecycle-part1/">Part 1 here</a>, to make sure you are at least vaguely familiar with most of the required concepts. If you have other programming background, but not in Vue/Nuxt, you might also find <a href="https://tech.onestopbeauty.online/high-level/quick-guide-to-javascript-ecosystem-from-senior-java-dev-pov/">my other post</a> useful.</p>
<h1 id="whats-in-the-app">What’s in the app</h1>
<p>The <a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples">sample code</a> contains very simple examples of all the mechanisms/hooks discussed in Part 1. For this article to make sense, especially “How it all works” section, you will need to <strong>download it and run locally</strong> to follow along.</p>
<p>Noteworthy files:</p>
<ul>
<li><a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/components/LinksComponent.vue">LinksComponent.vue</a>
<ul>
<li>Contains a (hardcoded) list of various links in the project to allow user-navigation.</li>
<li>Includes <strong>mixin</strong> <a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/mixins/logRouteQueryAndParams.js">logRouteQueryAndParams.js</a>. It demonstrates that what’s in mixin (computed property <em>routeParams</em>) is executed in a same way as if it was directly defined in the component, and that mixin code has access to <em>this</em>.</li>
<li>Shows most of the Vue component lifecycle methods</li>
</ul>
</li>
<li><a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/middleware/globalMiddleware.js">globalMiddleware.js</a> and <a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/middleware/localMiddleware.js">localMiddleware.js</a> - as the names suggest, global middleware is attached from <code>nuxt.config.js</code> and thus executed before every route, whereas local middleware is only included for <code>test1/_param1/_param2</code> route.</li>
<li>A few routes (pages):
<ul>
<li><a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/pages/index.vue">index.vue</a> - the starting point, contains LinksComponent</li>
<li><code>test1/param1?/param2?</code> - a route with two optional parameters, meaning that <code>test1/</code>, <code>test1/lorem</code> and <code>test1/lorem/ipsum</code> all would land on the page generated by code in <a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/pages/test1/_param1/_param2.vue">_param2.vue</a> file</li>
<li><code>test1/param1?/param2?</code> - a route equivalent to test1 route, it shows that if you don’t like naming your vue files with the name of last parameter to the route, you can name place them in subdirectory and name them <a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/pages/test2/_param1/_param2/index.vue">index.vue</a> instead</li>
<li><code>foo/x/_id?</code> and <code>foo/y/_id?</code> - shows how dynamic nested routes work. A nested route is where a page contains another <code>router-view</code> component, like in <a href="https://github.com/lilianaziolek/blog-examples/blob/master/dry-examples/pages/foo.vue">foo.vue</a>. You always get one by default with Nuxt (you don’t explicitly include it, Nuxt does it for you), so this is effectively a router-inside-router. Hence the name, <strong>nested</strong>.</li>
</ul>
</li>
</ul>
<h1 id="how-does-it-all-work">How does it all work?</h1>
<p>Let’s assume the user first navigates to our main page (e.g. <a href="http://localhost:3000">http://localhost:3000</a>) followed by navigating to various other pages, by clicking appropriate links. You need to click on the links rather than put URLs directly in the browser if you want to observe SPA mode in action. This is because navigating from address bar would force SSR mode.</p>
<p>Let’s take a look at an example user journey:</p>
<h2 id="first-visit-a-hrefhttplocalhost3000httplocalhost3000a">(first visit) <a href="http://localhost:3000">http://localhost:3000</a></h2>
<h3 id="whats-in-the-logs">What’s in the logs?</h3>
<p><em>On the server, before answer returned to client:</em></p>
<pre><code>(AlternativeEventBus Plugin) SSR: true inject component with id: 4
(NuxtServerInit) SSR: true
(Global Middleware) SSR: true
(LinksComponent) SSR: true [BeforeCreate]
(LinksComponent) SSR: true [Created] SampleProp: Prop from main page, SampleData: Lorem Ipsum Data
(LinksComponent) Created Refs:
</code></pre>
<p><em>On the client (browser) side:</em></p>
<pre><code>(EventBus Plugin) SSR: false inject component with id: 1
(AlternativeEventBus Plugin) SSR: false inject component with id: 2
(LinksComponent) SSR: false [BeforeCreate]
(LinksComponent) SSR: false [Created] SampleProp: Prop from main page, SampleData: Lorem Ipsum Data
(LinksComponent) Created Refs:
(LinksComponent) SSR: false [Mounted] SampleProp: Prop from main page, SampleData: Lorem Ipsum Data
(LinksComponent) Mounted Refs: Foo With No Params,Foo X With Param1,(...)
</code></pre>
<h3 id="what-just-happened">What just happened?</h3>
<ul>
<li>globalMiddleware is only executed in SSR in this call</li>
<li><em>AlternativeEventBus Plugin</em> is setup on both sides (client and server)</li>
<li><em>EventBus Plugin</em> is only setup on the client</li>
<li>beforeCreate and created are called on both server and client</li>
<li>Mounted is only called on the client</li>
<li>this.$refs are only populated in Mounted</li>
</ul>
<h3 id="where-did-we-go-wrong">Where did we go wrong?</h3>
<p>Imagine you have code somewhere in middleware or fetch and you register <code>this.$eventBus.$on</code> event listener. Then, based on some user interaction, you dispatch an event via <code>this.$eventBus.$emit</code>. Things don’t work, listener is not called - why?</p>
<p>As you might notice, <em>AlternativeEventBus Plugin</em> <strong>id</strong> is different on client and server (if this is not the case for you, refresh the page, as ID on the server will change on subsequent SSR calls). That’s because this plugin’s code is executed on both client and server, and <strong>both sides create an object</strong>. Middleware and fetch are only executed in SRR on first call, so your <strong>listener</strong> is registered on the SSR instance of eventBus. The client-interaction code runs in browser, so your <strong>emit event</strong> triggers on the client-side instance of eventBus. Not the same instance - communication does not happen.</p>
<p>The main usage for eventBus is for one part of the code to notify another that something happened. This allows us to write a <strong>more decoupled application</strong>. For example your login code could publish an event that a user has just logged in, so that other parts of code can react and fetch extra user data into VueX. This way login code itself does not need to know anything about user data required in other parts of the app.</p>
<p>Therefore, in reality, such eventBus plugin may not make sense in dual (SSR/client) mode. If interaction always happens in the browser, it makes more sense to make such plugin client-side only. This way, if somebody tries to register a listener to event in SSR code, they will get a nice error saying that eventBus is undefined - rather than allowing them to register on an instance that will never receive any events.</p>
<h2 id="click-on-link-codetest1-with-param-1code">Click on link <code>Test1 with Param 1</code></h2>
<h3 id="whats-in-the-logs-1">What’s in the logs?</h3>
<p><em>In this, and all the following calls everything happens on the client (browser) side only :</em></p>
<pre><code>(Global Middleware) SSR: false
(Local Middleware) SSR: false
(Mixin) /test1/val1AsyncData: {"param1":"val1"}
(Mixin) /test1/val1Fetch: {"param1":"val1"}
(LinksComponent) SSR: false [BeforeCreate]
(LinksComponent) SSR: false [Created] SampleProp: Test1, SampleData: Lorem Ipsum Data
(LinksComponent) Created Refs:
(LinksComponent) SSR: false [Mounted] SampleProp: Test1, SampleData: Lorem Ipsum Data
(LinksComponent) Mounted Refs: Foo With No Params,Foo X With Param1,(...)
</code></pre>
<h3 id="what-just-happened-1">What just happened?</h3>
<ul>
<li>Global Middleware, and now also local middleware, are processed on the client.</li>
<li>Mixin code from <em>logRouteQueryAndParams</em> for fetch and asyncData is now called</li>
<li>all Vue lifecycle hooks from LinksComponent are called again. The route has changed and the instance of LinksComponent that was used in index.vue would now be destroyed and a new one (for test1 route) created</li>
</ul>
<h3 id="where-did-we-go-wrong-1">Where did we go wrong?</h3>
<p>Fetch and asyncData was not called on home page but it was on this page, why? That’s because index.vue does not include it as mixin, and _param2.vue does. LinksComponent does contain this mixin too, but <strong>asyncData and fetch are not called for components</strong>. If you have a situation where your data does not seem to populate your UI, always double check if your fetching code is in a page, not in a component.</p>
<h2 id="click-on-link-codetest2-with-param1param2code">Click on link <code>Test2 with Param1/Param2</code></h2>
<h3 id="whats-in-the-logs-2">What’s in the logs?</h3>
<pre><code>(Global Middleware) SSR: false
(Mixin) /test2/val1/val2AsyncData: {"param1":"val1","param2":"val2"}
(Mixin) /test2/val1/val2Fetch: {"param1":"val1","param2":"val2"}
(LinksComponent) SSR: false [BeforeCreate]
(LinksComponent) SSR: false [Created] SampleProp: Test32, SampleData: Lorem Ipsum Data
(LinksComponent) Created Refs:
(LinksComponent) SSR: false [Mounted] SampleProp: Test2, SampleData: Lorem Ipsum Data
(LinksComponent) Mounted Refs: Foo With No Params,Foo X With Param1,(...)
</code></pre>
<h3 id="what-just-happened-2">What just happened?</h3>
<ul>
<li>Global Middleware is processed on the client. Local is not as it was not attached to this route.</li>
<li>Mixin code from <em>logRouteQueryAndParams</em> for fetch and asyncData is now called.</li>
<li>all Vue lifecycle hooks from LinksComponent are called</li>
</ul>
<h2 id="click-on-link-codefoo-x-with-param1code">Click on link <code>Foo X with Param1</code></h2>
<h3 id="whats-in-the-logs-3">What’s in the logs?</h3>
<pre><code>(Global Middleware) SSR: false
(Mixin) /foo/x/val1AsyncData: {"id":"val1"}
(Mixin) /foo/x/val1Fetch: {"id":"val1"}
(Mixin) /foo/x/val1AsyncData: {"id":"val1"}
(Mixin) /foo/x/val1Fetch: {"id":"val1"}
(LinksComponent) SSR: false [BeforeCreate]
(LinksComponent) SSR: false [Created] SampleProp: SampleProp from Foo, SampleData: Lorem Ipsum Data
(LinksComponent) Created Refs:
(LinksComponent) SSR: false [Mounted] SampleProp: SampleProp from Foo, SampleData: Lorem Ipsum Data
(LinksComponent) Mounted Refs: Foo With No Params,Foo X With Param1,(...)
</code></pre>
<h3 id="what-just-happened-3">What just happened?</h3>
<ul>
<li>Global Middleware is processed on the client.</li>
<li>Mixin code from <em>logRouteQueryAndParams</em> for fetch and asyncData is now called - TWICE! This is because both <code>foo.vue</code>, and <code>foo/x/_id.vue</code> include the mixin, and both are pages. In reality, you wouldn’t have the same fetch (from mixin) included in parent and nested route, so the fetch/asyncData would not be doing the same thing.</li>
<li>all Vue lifecycle hooks from LinksComponent are called</li>
</ul>
<h2 id="click-on-link-codefoo-y-with-param2code">Click on link <code>Foo Y with Param2</code></h2>
<h3 id="whats-in-the-logs-4">What’s in the logs?</h3>
<pre><code>(Global Middleware) SSR: false
(Mixin) /foo/y/val1AsyncData: {"id":"val1"}
(Mixin) /foo/y/val1Fetch: {"id":"val1"}
</code></pre>
<h3 id="what-just-happened-4">What just happened?</h3>
<ul>
<li>Oh dear! Why is this output so different than for Foo X? This is because we are navigating within a <strong>nested route</strong> now. The app is smart enough to know that the shell (<code>foo.vue</code>) has not changed between <code>foo/x/val1</code> and <code>foo/y/val1</code> - it’s only the nested part (<code>x/_id.vue</code> vs <code>y/_id.vue</code>) that has changed. Therefore, there is no point regenerating anything related to foo.vue. We only execute what’s specific to y/_id.vue - and this file does not contain a separate LinksComponent, so does not run its lifecycle methods.</li>
<li>Global Middleware is still processed on the client.</li>
<li>Mixin code from <em>logRouteQueryAndParams</em> for fetch and asyncData is now called - but only for foo/y/_id.vue</li>
</ul>
<h3 id="where-did-we-go-wrong-2">Where did we go wrong?</h3>
<p>We completely misunderstood/didn’t even read what nested components are, so at one point we had a structure like in foo route, but foo.vue page did not include <code><router-view></code>. The routing was working fine, but the fetch was then only called for route change - not params change. For example, if you went from <code>/foo/x/1</code> to <code>/foo/x/2</code> - the fetch for <em>/foo/x/2</em> would not be called. But if you went from <code>/foo/x/1</code> to <code>/test1</code> and then to <code>/foo/x/2</code>, then fetch is called.</p>
<p>If you are in similar situation, and for some reason you actually need to make some changes in foo.vue data, then your best option is to add watch on route, i.e.:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">watch</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'$route'</span><span class="p">(</span><span class="nx">to</span><span class="p">,</span> <span class="k">from</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// whatever you need to refresh goes here</span>
<span class="c1">// you can get route (URL) params and query arguments before and after from `to` and `from` method parameters</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h1 id="play-yourself">Play yourself!</h1>
<p>I hope the above go-through makes sense - but nothing will be as enlightening as taking this example project and playing with it yourself. Add hooks, extend existing code, navigate through the app and observe what happens. Let me know if anything is unclear!</p>
<p>In the last part, coming up soon, I’ll summarise both parts with a short neat table - stay tuned!</p>Liliana ZiolekThis is part 2 of mini-series - Understanding Nuxt & Vue hooks and lifecycle. You can start with Part 1 here, to make sure you are at least vaguely familiar with most of the required concepts. If you have other programming background, but not in Vue/Nuxt, you might also find my other post useful.Understanding Nuxt & Vue hooks and lifecycle (part 1)2019-07-15T00:00:00+00:002019-07-15T00:00:00+00:00https://tech.onestopbeauty.online/front-end/understanding-nuxt-vue-hooks-and-lifecycle-part1<h1 id="remember-young-padawan-dry">Remember, Young Padawan: DRY</h1>
<p>One of the software development principles that we get taught very early in our dev careers is DRY - <strong>Don’t Repeat Yourself</strong>. It’s a good thing too, as there isn’t much worse than trying to crawl through a massive codebase trying to find all the copy-pasted instances of the same piece of logic.</p>
<p>When we first started with Vue (and later Nuxt) I wasn’t always sure where to put certain bits of code, like getting data from the server, or checking if the user is logged in. Enter: the topic of this mini series. I will start with a quick recap of what mechanisms are available in Vue/Nuxt landscapes, I’ll follow with examples of situations when each of these might be useful, I’ll point out places where we got it wrong so that you don’t have to, and summarise the whole thing with a little reference table.</p>
<p>One of the trickiest aspects was to reconcile how the situation varies between SSR and client-side, and there were a few cases we had to work out why things would work on refresh but not route change, or vice-versa. We sometimes got it wrong when various hooks/methods get called and, more importantly, when they don’t get called. The information is usually somewhere in the docs (plus, the documentation has massively improved in the last year or so) - but I think it’s nice to have it collected all in one place.</p>
<h1 id="recap-vue-lifecycle">Recap: Vue lifecycle</h1>
<p>Vue documentation has an <a href="https://vuejs.org/v2/guide/instance.html#Lifecycle-Diagram">excellent diagram</a> showing the order/situations in which Vue component methods are called. Unfortunately, it does not clearly mention a few important things (as it’s related more to how Nuxt operates in universal mode, than to pure Vue).</p>
<ul>
<li><em>Only <strong>beforeCreate</strong> and <strong>created</strong> are called during SSR</em> (as well as on the client). All the other methods (most importantly: mounted, which is one used quite often in examples) are only called on the client. So, if you have a piece of logic that needs to be executed during SSR, mounted (which, otherwise, is often a good place for some extra logic) is not a good place for this.</li>
<li><em>beforeCreate</em> does not have access to the components props/data because <strong>this</strong> (the component reference) is still undefined.</li>
<li><em>created</em>, does have access to <strong>this</strong>, including data and props, but does not have access to DOM. How does it matter? If you ever want to use e.g. <a href="https://vuejs.org/v2/api/#ref">this.$refs</a>, they are not initialised at this point. They will only be processed (visible) in mounted. Which does not run in SSR.</li>
</ul>
<h1 id="recap-nuxt-specific-tools">Recap: Nuxt-specific tools</h1>
<p>Note: many of the following methods accept <a href="https://nuxtjs.org/api/context/">Nuxt context</a> as one of the parameters.</p>
<h2 id="plugins">Plugins</h2>
<p><a href="https://nuxtjs.org/guide/plugins">Plugins</a> are bits of code that get executed <strong>once or twice</strong> per visitor, before Vue.js app instance gets created. You can have plugin that’s executed on both server and client side (thus twice total), or only on one side. Nuxt has useful convention that any plugin called XXX.client.js get executed only on client side, and YYY.server.js is only in SSR. Additionally, Nuxt makes available an <strong>inject</strong> method that allows you to make shared code/functionality available in vue instances/components, Nuxt context, and/or VueX store. A popular plugin is Axios, which allows you to access a shared Axios instance e.g. via this.$axios. Similarly, you can create your own plugin and access it, e.g. via this.$eventBus.</p>
<h2 id="modules">Modules</h2>
<p>A <a href="https://nuxtjs.org/guide/modules/">module</a> code is executed on Nuxt startup (i.e. <strong>once during the lifetime of your Node.js server</strong>). Modules extend nuxt functionality - for example they can automatically add and configure a plugin. It is NOT executed in browser/on each page, or even on the server for each client accessing your page. Therefore, modules are not a good place for any code that should be executed for each visitor. Unless, of course, your Nuxt module adds code into one of the hooks that do get executed for each visitor - but the module code itself would run just once, to initialise certain hooks.</p>
<h2 id="nuxtserverinit-in-storeindexjs">nuxtServerInit in store/index.js</h2>
<p>One of the first actions executed in SSR (only) is <a href="https://nuxtjs.org/guide/vuex-store#the-nuxtserverinit-action">nuxtServerInit</a>. It is executed just <strong>once</strong> for each visitor to your website (when they first navigate to your website, or when they hit refresh). It is a good place to put Axios calls to get some commonly used data and put it in store.</p>
<h2 id="middleware">Middleware</h2>
<p><a href="https://nuxtjs.org/guide/routing#middleware">Middleware</a> is executed before rendering each page (before route is loaded), regardless if you’re on server- or client-side. You can have global middleware (configured in nuxt.config.js), or localised middleware, attached only to certain layouts and/or pages. It’s important to know that middleware is only executed once before render - i.e. on first hit to the page it will be executed in SSR only. On subsequent pages/routes it will be executed on the client only. It does not get called on both client and server for the same page.</p>
<h2 id="mixins">Mixins</h2>
<p><a href="https://vuejs.org/v2/guide/mixins.html">Mixins</a> are extensions to components, pages, or layouts. They have access to the whole component that they are mixed into - so they can use this.$route, this.$store, and anything else you’d be able to call in the component. They are very useful to extract common functionality (including hooks like mounted) that for some reason cannot be extracted as standalone components. In simple terms, they behave in the same way as if you had copy-pasted the mixin code into each component where it’s used.</p>
<h2 id="asyncdata-amp-fetch">asyncData & fetch</h2>
<p>Both <a href="https://nuxtjs.org/guide/async-data">asyncData</a> and <a href="https://nuxtjs.org/api/pages-fetch">fetch</a> methods are executed before the component is initialised and so do not have access to <strong>this</strong>. Both can be used to get some data from an API to populate the component. Both are *<em>only</em> executed for pages (NOT components). Both take Nuxt context as parameter. Both will be executed on server-side on first load, and on client side for subsequent route changes. (<em>Note</em>: there are some subtle caveats here as to when these are called or not which I’ll get into in a separate post)</p>
<ul>
<li><strong>asyncData</strong> should return a promise, or use async/await - but in either case, the result returned will be integrated into <strong>data</strong> part of the component</li>
<li><strong>fetch</strong>, on the other hand, should be used for data intended for VueX store - it does not need to return anything and should instead commit to store any required data. It can use async/await.</li>
</ul>
<h2 id="bonus-watch-route">Bonus: watch route</h2>
<p>None of asyncData or fetch get triggered in some specific situations when only route params change. For this situation, you may need to watch the route to refresh data - or change your router configuration. More details in a separate post.</p>
<p>Official Nuxt documentation has a <a href="https://nuxtjs.org/guide#schema">useful diagram</a> showing the order in which things are called. Let’s go into a bit more details into what it means in relation to typical user-app interaction.</p>
<h1 id="example">Example</h1>
<p>The code for this post (and all the more detailed follow-ups in this series) can be found on <a href="https://github.com/lilianaziolek/blog-examples/tree/master/dry-examples">Github</a>.</p>
<p>In the next post (or a few) in this series, I’ll go through what exactly happens as the user navigates through the app, and will point out various techniques and gotchas related to the tools covered above.</p>Liliana ZiolekRemember, Young Padawan: DRY One of the software development principles that we get taught very early in our dev careers is DRY - Don’t Repeat Yourself. It’s a good thing too, as there isn’t much worse than trying to crawl through a massive codebase trying to find all the copy-pasted instances of the same piece of logic.To 10x-engineer or not, that is the question2019-07-13T00:00:00+00:002019-07-13T00:00:00+00:00https://tech.onestopbeauty.online/opinions/to-10x-engineer-or-not<p>Every now and again my Twitter blows up with tweets about 10x developers. I usually don’t engage because I find the topic way more complex than I can fit in (even extended) Twitter limits. But now - ha! - now I have a blog. What better way to make some immediate enemies? ;)</p>
<p>I first came across the concept of 10x-developer (or engineer) ages ago; back when, I think, it didn’t immediately invoke a hate reaction from a lot of people. When I first heard it, I knew quite a lot of developers I immensely appreciated and respected (still to this day some of them are my friends). When I first heard the term, I immediately thought of them. Incredibly talented and experienced folks, hugely dedicated to writing good code, to solving the right problem, in the right way - but also super friendly and helpful and respectful people. To me, “10x developer” term always brought to mind these qualities, and these people.</p>
<p>So to hear that now 10x developers are considered “something that doesn’t exist”, or almost an insult? I don’t know. I just don’t buy into this hate. Doesn’t it all depend on <strong>your</strong> definition of what a 10x developer is?</p>
<p>I mean… Do we truly want to say that all developers are equal? Did you truly never work with someone that was a terrible developer - either from technical/delivery perspective, creating chaos whatever they touched, <strong>or</strong> being a terrible human, or (the worst case) - both? Why do we have interviews when we hire? Is it not because we do implicitly admit that no, developers are not identical machines coming from a factory, some are better, some are worse, and most definitely some are better or worse for specific project/task at hand?</p>
<p>And if we admit that there are bad developers out there, if we admit that there are people we would <em>never, ever</em> want to work with again - doesn’t that lead to: there are people we would <em>love</em> to work with again? Are there any people that were so amazing, that you could easily imagine taking them into your team instead of 2 or 3 others?</p>
<p>And, in extreme case, can you not think of a single person (A) that you would much rather have on your team than 10 “instances” of a certain (terrible) person B? Does this not make person A a 10x B developer?</p>
<p>I feel that the backlash comes against the concept that churning out 10X code / features somehow makes someone a better developer by and of itself. To some people, developers with great technical skills can get away with being a terrible person. If it’s in this case that they are called a 10X developer, just because they are very smart, a lot of us (myself included!) do not feel comfortable with that.</p>
<p>However, I’m just not sure if fighting the concept of 10X developers is necessarily the best way to achieve the goal - which, I think, should be to make sure that we appreciate not only somebody’s technical skills, but also a whole lot of other characteristics, such as being kind and helpful and being able to make other people grow in their careers (and loads of other things). I feel that reacting with backlash at the 10X developer idea implies that there are no better and worse developers, everybody is equal - and this is not something I agree with.</p>
<p>What I’d much rather see is redefining, reclaiming of the term 10X developer. I’d prefer it clearly meant not just somebody’s technical ability, but a whole set of other things. I’d love it, for example, to include ability to help other people grow. As in: your X factor is composed of Y in your own technical skills + Z of how much other folks in your team learnt in your presence. Or: your X number is your personal output multiplied by how much output of other people increased thanks to your help. If we had a <strong>good</strong> definition of 10X developer, we could all try and aspire to become one - based on a <em>sane</em> set of goals and truly valuable characteristics.</p>
<p>So, if we were to reclaim the <strong>10X developer</strong> - what would <strong>your</strong> definition be?</p>Liliana ZiolekEvery now and again my Twitter blows up with tweets about 10x developers. I usually don’t engage because I find the topic way more complex than I can fit in (even extended) Twitter limits. But now - ha! - now I have a blog. What better way to make some immediate enemies? ;)Solving Lighthouse ‘Avoid an excessive DOM size’ issue2019-07-07T00:00:00+00:002019-07-07T00:00:00+00:00https://tech.onestopbeauty.online/front-end/solving-lighthouse-avoid-excessive-dom-size<p>Recently we started looking a bit into <a href="https://www.onestopbeauty.online">OSBO</a> performance. As the page was built mostly at the time when we didn’t understand front-end development that well (British for: <em>we had no idea what we were doing</em>), plus we didn’t have any active monitoring on performance, various problems obviously managed to sneak in.</p>
<h1 id="if-you-dont-know-lighthouse-check-it-out-first">If you don’t know Lighthouse, check it out first</h1>
<p>There are plenty of articles on how to launch Lighthouse and they contain a number of very helpful suggestions, so I won’t reiterate this here. There was one issue where the advice was not particularly friendly though: “Avoid an excessive DOM size”. In our case, even our home and signup pages had around 3500 DOM nodes and, considering they are fairly simple, this sounded excessive. We struggled to understand where all these nodes were coming from. All the advice was around “avoid creating too many DOM nodes” - but I just couldn’t find any useful info on how do I find out where (logically in my codebase) the nodes are created. Which part of my code is the problem? It’s hard to optimise until you know which component(s) you need to optimise.</p>
<p>So, I quickly knocked out a tool to help us find the “DOM bottlenecks”. And as I still lurrrrve Java (or rather: that’s a tool I’m most productive in), it’s in Java - sorry folks ;)</p>
<h1 id="find-the-dom-branches-to-trim">Find the DOM branches to trim</h1>
<p>The principle is actually really simple, and similar to how you’d go around finding where all the space on your hard drive goes if you suddenly run out of space. You find the biggest folder. Then the biggest folder in the biggest folder. And so on, until you see something suspicious - a folder bigger than you would normally expect.</p>
<p>In order to do that without spending too much time writing the tool itself (ultimately it took me maybe 30mins) I decided to use JSoup (to parse the DOM tree from our website), and Jackson - to print the results nicely, as I can then easily collapse/expand JSON in IntelliJ (helpful hint: open any .json file and press <strong>CTRL-ALT-L</strong> to nicely indent a single massive line of JSON).</p>
<p>The full result is in <a href="https://github.com/lilianaziolek/blog-examples/tree/master/lighhouse-performance-issues">Github Repo</a> (I have a feeling we might need more stuff to help us with Lighthouse reports).
There are two classes in the project:</p>
<details><summary><b>OsboDomNode</b> - a class representing DOM in terms of what we care about: total number of child (and grand... child nodes), and some basic stats on direct children. It uses recursive functions to aggregate total number of nodes in each of the DOM elements.
</summary>
<p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="n">online</span><span class="o">.</span><span class="na">onestopbeauty</span><span class="o">.</span><span class="na">blog</span><span class="o">.</span><span class="na">examples</span><span class="o">.</span><span class="na">lighthouse</span><span class="o">.</span><span class="na">dom</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.fasterxml.jackson.annotation.JsonIgnore</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.fasterxml.jackson.annotation.JsonPropertyOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">lombok.*</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.jsoup.nodes.Element</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.jsoup.select.Elements</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.List</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Map</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Optional</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.stream.Collectors</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">Comparator</span><span class="o">.</span><span class="na">naturalOrder</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">stream</span><span class="o">.</span><span class="na">Collectors</span><span class="o">.</span><span class="na">groupingBy</span><span class="o">;</span>
<span class="nd">@Data</span>
<span class="nd">@Builder</span>
<span class="nd">@JsonPropertyOrder</span><span class="o">({</span> <span class="s">"description"</span><span class="o">,</span> <span class="s">"type"</span><span class="o">,</span> <span class="s">"allChildNodesCount"</span><span class="o">,</span> <span class="s">"childNodesSummary"</span> <span class="o">})</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OsboDomNode</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">type</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">description</span><span class="o">;</span>
<span class="nd">@JsonIgnore</span>
<span class="nd">@Singular</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">OsboDomNode</span><span class="o">></span> <span class="n">childNodes</span><span class="o">;</span>
<span class="nd">@Getter</span><span class="o">(</span><span class="n">AccessLevel</span><span class="o">.</span><span class="na">NONE</span><span class="o">)</span>
<span class="kd">private</span> <span class="n">Integer</span> <span class="n">allChildNodesCount</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">getAllChildNodesCount</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">allChildNodesCount</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">allChildNodesCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">childNodes</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">childNodes</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">mapToInt</span><span class="o">(</span><span class="nl">OsboDomNode:</span><span class="o">:</span><span class="n">getAllChildNodesCount</span><span class="o">).</span><span class="na">sum</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">allChildNodesCount</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="nf">getChildNodesSummary</span><span class="o">()</span> <span class="o">{</span>
<span class="n">Integer</span> <span class="n">allChildNodesCount</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">getAllChildNodesCount</span><span class="o">();</span>
<span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">childNodes</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="n">child</span> <span class="o">-></span> <span class="n">percentageInChild</span><span class="o">(</span><span class="n">child</span><span class="o">,</span> <span class="n">allChildNodesCount</span><span class="o">)).</span><span class="na">collect</span><span class="o">(</span><span class="n">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">OsboDomNode</span><span class="o">></span> <span class="nf">getNodesWithHighestNumberOfChildren</span><span class="o">()</span> <span class="o">{</span>
<span class="n">Map</span><span class="o"><</span><span class="n">Integer</span><span class="o">,</span> <span class="n">List</span><span class="o"><</span><span class="n">OsboDomNode</span><span class="o">>></span> <span class="n">nodesWithChildCount</span> <span class="o">=</span> <span class="n">childNodes</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">collect</span><span class="o">(</span><span class="n">groupingBy</span><span class="o">(</span><span class="nl">OsboDomNode:</span><span class="o">:</span><span class="n">getAllChildNodesCount</span><span class="o">));</span>
<span class="n">Optional</span><span class="o"><</span><span class="n">Integer</span><span class="o">></span> <span class="n">maxNodes</span> <span class="o">=</span> <span class="n">nodesWithChildCount</span><span class="o">.</span><span class="na">keySet</span><span class="o">().</span><span class="na">stream</span><span class="o">().</span><span class="na">max</span><span class="o">(</span><span class="n">naturalOrder</span><span class="o">());</span>
<span class="k">if</span> <span class="o">(</span><span class="n">maxNodes</span><span class="o">.</span><span class="na">isPresent</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">nodesWithChildCount</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">maxNodes</span><span class="o">.</span><span class="na">get</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">emptyList</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="n">String</span> <span class="nf">percentageInChild</span><span class="o">(</span><span class="n">OsboDomNode</span> <span class="n">child</span><span class="o">,</span> <span class="n">Integer</span> <span class="n">allChildNodesCount</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">double</span> <span class="n">percentage</span> <span class="o">=</span> <span class="mf">100.0</span> <span class="o">*</span> <span class="n">child</span><span class="o">.</span><span class="na">getAllChildNodesCount</span><span class="o">()</span> <span class="o">/</span> <span class="n">allChildNodesCount</span><span class="o">;</span>
<span class="k">return</span> <span class="n">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%d [%.2f%%] in %s %s"</span><span class="o">,</span> <span class="n">child</span><span class="o">.</span><span class="na">getAllChildNodesCount</span><span class="o">(),</span> <span class="n">percentage</span><span class="o">,</span> <span class="n">child</span><span class="o">.</span><span class="na">type</span><span class="o">,</span> <span class="n">child</span><span class="o">.</span><span class="na">description</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">OsboDomNode</span> <span class="nf">fromElement</span><span class="o">(</span><span class="n">Element</span> <span class="n">element</span><span class="o">)</span> <span class="o">{</span>
<span class="n">OsboDomNode</span><span class="o">.</span><span class="na">OsboDomNodeBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="n">OsboDomNode</span><span class="o">.</span><span class="na">builder</span><span class="o">();</span>
<span class="n">builder</span><span class="o">.</span><span class="na">type</span><span class="o">(</span><span class="n">element</span><span class="o">.</span><span class="na">tag</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"["</span> <span class="o">+</span> <span class="n">element</span><span class="o">.</span><span class="na">siblingIndex</span><span class="o">()</span> <span class="o">+</span> <span class="s">"]"</span><span class="o">);</span>
<span class="n">builder</span><span class="o">.</span><span class="na">description</span><span class="o">(</span><span class="n">element</span><span class="o">.</span><span class="na">attributes</span><span class="o">().</span><span class="na">toString</span><span class="o">());</span>
<span class="n">Elements</span> <span class="n">children</span> <span class="o">=</span> <span class="n">element</span><span class="o">.</span><span class="na">children</span><span class="o">();</span>
<span class="n">children</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">child</span> <span class="o">-></span> <span class="n">builder</span><span class="o">.</span><span class="na">childNode</span><span class="o">(</span><span class="n">OsboDomNode</span><span class="o">.</span><span class="na">fromElement</span><span class="o">(</span><span class="n">child</span><span class="o">)));</span>
<span class="k">return</span> <span class="n">builder</span><span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
</p></details>
<details><summary><b>OsboPerfHelper</b> - a simple runner, you put in the URL of your website (could even be localhost), it goes off, reads the DOM structure, and then we feed it into the OsboDomNode to be analysed.</summary>
<p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="n">online</span><span class="o">.</span><span class="na">onestopbeauty</span><span class="o">.</span><span class="na">blog</span><span class="o">.</span><span class="na">examples</span><span class="o">.</span><span class="na">lighthouse</span><span class="o">.</span><span class="na">dom</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">com.fasterxml.jackson.databind.ObjectMapper</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.jsoup.Jsoup</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.jsoup.nodes.Document</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.jsoup.nodes.Element</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.File</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.io.IOException</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OsboPerfHelper</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">ObjectMapper</span> <span class="n">OBJECT_MAPPER</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ObjectMapper</span><span class="o">();</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">osboUrl</span> <span class="o">=</span> <span class="s">"http://localhost:8081"</span><span class="o">;</span>
<span class="n">Document</span> <span class="n">doc</span> <span class="o">=</span> <span class="n">Jsoup</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="n">osboUrl</span><span class="o">).</span><span class="na">get</span><span class="o">();</span>
<span class="n">Element</span> <span class="n">body</span> <span class="o">=</span> <span class="n">doc</span><span class="o">.</span><span class="na">body</span><span class="o">();</span>
<span class="n">OsboDomNode</span> <span class="n">osboDomNode</span> <span class="o">=</span> <span class="n">OsboDomNode</span><span class="o">.</span><span class="na">fromElement</span><span class="o">(</span><span class="n">body</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">((</span><span class="n">Integer</span><span class="o">)</span> <span class="n">osboDomNode</span><span class="o">.</span><span class="na">getAllChildNodesCount</span><span class="o">());</span>
<span class="n">printJson</span><span class="o">(</span><span class="n">osboDomNode</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">printJson</span><span class="o">(</span><span class="n">OsboDomNode</span> <span class="n">osboDomNode</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
<span class="c1">// System.out.println(OBJECT_MAPPER.writeValueAsString(osboDomNode));</span>
<span class="n">File</span> <span class="n">resultFile</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="s">"domNode.json"</span><span class="o">);</span>
<span class="n">OBJECT_MAPPER</span><span class="o">.</span><span class="na">writeValue</span><span class="o">(</span><span class="n">resultFile</span><span class="o">,</span> <span class="n">osboDomNode</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Written JSON result into "</span> <span class="o">+</span> <span class="n">resultFile</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
</p></details>
<details><summary>Respective build.gradle file</summary>
<p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plugins</span> <span class="o">{</span>
<span class="n">id</span> <span class="s1">'java'</span>
<span class="o">}</span>
<span class="n">group</span> <span class="s1">'online.onestopbeauty.blog.examples'</span>
<span class="n">version</span> <span class="s1">'1.0-SNAPSHOT'</span>
<span class="n">sourceCompatibility</span> <span class="o">=</span> <span class="mf">1.8</span>
<span class="n">repositories</span> <span class="o">{</span>
<span class="n">mavenCentral</span><span class="o">()</span>
<span class="o">}</span>
<span class="n">dependencies</span> <span class="o">{</span>
<span class="c1">// https://mvnrepository.com/artifact/org.jsoup/jsoup</span>
<span class="n">compile</span> <span class="nl">group:</span> <span class="s1">'org.jsoup'</span><span class="o">,</span> <span class="nl">name:</span> <span class="s1">'jsoup'</span><span class="o">,</span> <span class="nl">version:</span> <span class="s1">'1.12.1'</span>
<span class="c1">// https://mvnrepository.com/artifact/org.projectlombok/lombok</span>
<span class="n">compileOnly</span> <span class="nl">group:</span> <span class="s1">'org.projectlombok'</span><span class="o">,</span> <span class="nl">name:</span> <span class="s1">'lombok'</span><span class="o">,</span> <span class="nl">version:</span> <span class="s1">'1.18.8'</span>
<span class="c1">// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind</span>
<span class="n">compile</span> <span class="nl">group:</span> <span class="s1">'com.fasterxml.jackson.core'</span><span class="o">,</span> <span class="nl">name:</span> <span class="s1">'jackson-databind'</span><span class="o">,</span> <span class="nl">version:</span> <span class="s1">'2.9.9'</span>
<span class="n">testCompile</span> <span class="nl">group:</span> <span class="s1">'junit'</span><span class="o">,</span> <span class="nl">name:</span> <span class="s1">'junit'</span><span class="o">,</span> <span class="nl">version:</span> <span class="s1">'4.12'</span>
<span class="o">}</span>
</code></pre></div></div>
</p></details>
<p>Oh yes, I use Lombok for constructors, builders and other boilerplate (getters etc.) - just because Lombok is awesome and it’s the first thing I always add to any Java project. Just remember to add Lombok plugin and turn on annotation processing in IntelliJ, otherwise you’ll get compilation errors.</p>
<h1 id="our-real-world-experience">Our real-world experience</h1>
<p>So how did things look like for us when running on the live version?
The first few levels of nodes looked fairly healthy, with body and direct subnodes containing around 99% of nodes each (simply a few layers of wrappers, nothing to worry about).
But then I saw something suspicious (and here hat-tip to Vuetify for using meaningful class names in components - makes troubleshooting so much easier):</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="s2">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">" class=</span><span class="se">\"</span><span class="s2">application--wrap</span><span class="se">\"</span><span class="s2">"</span><span class="p">,</span><span class="w">
</span><span class="s2">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"div[0]"</span><span class="p">,</span><span class="w">
</span><span class="s2">"allChildNodesCount"</span><span class="p">:</span><span class="w"> </span><span class="mi">3401</span><span class="p">,</span><span class="w">
</span><span class="s2">"childNodesSummary"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"[39.05] in div[2] class=</span><span class="se">\"</span><span class="s2">layout</span><span class="se">\"</span><span class="s2"> data-v-3a808de6"</span><span class="p">,</span><span class="w">
</span><span class="s2">"[56.40] in main[4] class=</span><span class="se">\"</span><span class="s2">v-content</span><span class="se">\"</span><span class="s2"> style=</span><span class="se">\"</span><span class="s2">padding-top:0px;padding-right:0px;padding-bottom:56px;padding-left:0px;</span><span class="se">\"</span><span class="s2">"</span><span class="p">,</span><span class="w">
</span><span class="s2">"[4.38] in footer[6] data-cy=</span><span class="se">\"</span><span class="s2">osboFooter</span><span class="se">\"</span><span class="s2"> class=</span><span class="se">\"</span><span class="s2">v-footer v-footer--absolute v-footer--inset theme--light</span><span class="se">\"</span><span class="s2"> style=</span><span class="se">\"</span><span class="s2">height:auto;margin-bottom:56px;border-radius:10px;</span><span class="se">\"</span><span class="s2"> data-v-3645c51c"</span><span class="p">,</span><span class="w">
</span><span class="s2">"[0.06] in button[8] type=</span><span class="se">\"</span><span class="s2">button</span><span class="se">\"</span><span class="s2"> medium=</span><span class="se">\"\"</span><span class="s2"> class=</span><span class="se">\"</span><span class="s2">v-btn v-btn--bottom v-btn--floating v-btn--fixed v-btn--right v-btn--small theme--dark secondary fab-style</span><span class="se">\"</span><span class="s2"> style=</span><span class="se">\"</span><span class="s2">display:none;</span><span class="se">\"</span><span class="s2"> data-v-045da490"</span><span class="w">
</span><span class="p">]}</span><span class="w">
</span></code></pre></div></div>
<p>The “main” part of our app was taking under 60% of the nodes, and the “div[2] / layout” element was taking nearly 40%.
At this point I added an extra log statement in the OsboPerfHelper, drilling down into the correct node. This of course could be done in a much nicer way, and if I have to use it more often perhaps I’d add some nicer “drill down” tooling - but at this point, it was a “quick and dirty” job of half an hour or so - and did the job well enough:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">printJson</span><span class="o">(</span><span class="n">osboDomNode</span><span class="o">.</span><span class="na">getNodesWithHighestNumberOfChildren</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">getNodesWithHighestNumberOfChildren</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">getNodesWithHighestNumberOfChildren</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">getNodesWithHighestNumberOfChildren</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
<span class="o">.</span><span class="na">getChildNodes</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span>
</code></pre></div></div>
<p>The result was:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="s2">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">" class=</span><span class="se">\"</span><span class="s2">flex offset-md1 md10 xs12</span><span class="se">\"</span><span class="s2"> data-v-3a808de6"</span><span class="p">,</span><span class="w">
</span><span class="s2">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"div[0]"</span><span class="p">,</span><span class="w">
</span><span class="s2">"allChildNodesCount"</span><span class="p">:</span><span class="w"> </span><span class="mi">1327</span><span class="p">,</span><span class="w">
</span><span class="s2">"childNodesSummary"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"[0.45] in div[0] class=</span><span class="se">\"</span><span class="s2">layout</span><span class="se">\"</span><span class="s2"> data-v-0c4978b8 data-v-3a808de6"</span><span class="p">,</span><span class="w">
</span><span class="s2">"[65.49] in aside[2] data-cy=</span><span class="se">\"</span><span class="s2">mobileNavBar</span><span class="se">\"</span><span class="s2"> class=</span><span class="se">\"</span><span class="s2">offWhite1 v-navigation-drawer v-navigation-drawer--clipped v-navigation-drawer--close v-navigation-drawer--fixed v-navigation-drawer--temporary theme--light</span><span class="se">\"</span><span class="s2"> style=</span><span class="se">\"</span><span class="s2">height:100%;margin-top:0px;transform:translateX(-375px);width:375px;</span><span class="se">\"</span><span class="s2"> data-v-c332d172 data-v-3a808de6"</span><span class="p">,</span><span class="w">
</span><span class="s2">"[33.84] in nav[4] id=</span><span class="se">\"</span><span class="s2">attachMenu</span><span class="se">\"</span><span class="s2"> data-cy=</span><span class="se">\"</span><span class="s2">osboToolBar</span><span class="se">\"</span><span class="s2"> class=</span><span class="se">\"</span><span class="s2"> text-xs-center px-0 toolbarStyle v-toolbar elevation-0 v-toolbar--dense v-toolbar--extended theme--light</span><span class="se">\"</span><span class="s2"> style=</span><span class="se">\"</span><span class="s2">margin-top:0px;padding-right:0px;padding-left:0px;transform:translateY(0px);</span><span class="se">\"</span><span class="s2"> data-v-3a808de6"</span><span class="w">
</span><span class="p">]}</span><span class="w">
</span></code></pre></div></div>
<p>This allowed me to see that I have nearly 900 nodes in my mobile navbar. The funny thing was that I don’t even need mobileNavbar (with mobile version of the menu) on the desktop version of the page, which I was testing at this point. Thus, we went off and did some simple cleanups, to reduce the size of the mobile menu (900 nodes sounds excessive even when it’s needed), and to make sure it’s not generated on desktops (as it’s a waste and never displayed).</p>
<p>This was just the beginning of trimming down the DOM tree (we’re now on around 1700 nodes on localhost, so massive reduction, and still more to come) - but the key was to know which DOM “branches” to trim.</p>
<h1 id="is-there-anything-better-out-there">Is there anything better out there?</h1>
<p>If you know a better tool for this job, please leave a note in the comments below. I find it very hard to believe that such a simple problem doesn’t have something already existing - but a quick google search gave me mostly results with many articles describing why large DOM is bad - not how to find your worst offenders in the DOM tree. Otherwise, feel free to report if this “micro-tool” was helpful in any way.</p>Liliana ZiolekRecently we started looking a bit into OSBO performance. As the page was built mostly at the time when we didn’t understand front-end development that well (British for: we had no idea what we were doing), plus we didn’t have any active monitoring on performance, various problems obviously managed to sneak in.A (super)quick guide to VueJS ecosystem - from senior Java dev point of view2019-07-02T00:00:00+00:002019-07-02T00:00:00+00:00https://tech.onestopbeauty.online/high-level/quick-guide-to-javascript-ecosystem-from-senior-java-dev-pov<h1 id="javascript-ecosystem-in-a-nutshell">JavaScript ecosystem in a nutshell</h1>
<p>At the end of 2016 or beginning of 2017 I came across this <a href="https://hackernoon.com/how-it-feels-to-learn-javascript-in-2016-d3a717dd577f">blog post</a>. It was at a point when I was beginning to think of building <a href="https://www.onestopbeauty.online">OSBO</a>, and I knew that this would involve finally leaving my Java/backend-only fortress that I happily occupied throughout my whole carrier, and moving at least somewhat into the “enemy grounds”. This blog post was funny, but also in some ways petrifying. It confirmed all my worries about what it will look like - to have to do any front-end work. It sounded simply crazy.</p>
<p>Now, 2.5 years later and many many lines of Vue code later, I want to make the front-end world a little bit less intimidating for folks who are like me (back then). Competent / senior Java devs, who for this reason or other (choice or circumstances) did not get much experience doing (much) front-end work, and are not really sure where to start.</p>
<h1 id="java-ecosystem-in-a-nutshell">Java ecosystem in a nutshell</h1>
<p>When you stop and think for a moment, Java world is also way more than just Java once it comes to anything more than HelloWorld. I used to mentor a few junior developers, and I recently felt a little bit sorry for the steep learning curve they must face. If you join a modern project these days, from day one you’ll probably come across a number of the following names (in no particular order):<br />
Maven / Gradle; Spring, Spring JDBC, Spring MVC, Spring Boot, Spring Cloud, Spring … ; Hibernate; Lombok, Guava, Apache Commons; Jackson, GSON, Jaxb; Spark; Camel; JMS; Tomcat, Jetty, Netty; Eureka, Hystrix, Ribbon; JUnit, Mockito, AssertJ, Cucumber; Slf4j, Logback, Log4j; Docker<br />
<em>Not to mention</em>: traditional DBs + SQL; MongoDB; Elasticsearch; Cassandra; Neo4j; Couchbase; Kafka; Ehcache, …<br />
<em>And also</em>: AWS, Google Cloud Platform, Azure - all with their respective hundreds of products.<br />
And that’s just stuff from the top of my head, a tip of the iceberg. There is just so much more.</p>
<p>Most of us don’t really think about it because we’re already familiar with this stack. We add tools and frameworks as and when we need them, we learn another thing, we move on. It’s when you look at all of this in one place, from the perspective of a newbie, that you realise the number of moving parts involved.</p>
<p>And with this small detour I arrive at a confession: I honestly don’t know why I thought JavaScript world would be any different :)</p>
<h1 id="making-sense-of-both">Making sense of both</h1>
<p>Luckily, a lot of things map conceptually fairly easily to what we’re already familiar with, and the rest makes sense logically. Our stack at the moment consists of Vue/Nuxt/Vuetify and as such, I’ll go from that perspective.<br />
So without further ado:</p>
<ul>
<li>
<p><strong>Vue</strong> - the mapping into Java world is not always going to be obvious, and I think Vue vs React vs Angular is one of these things that is not strictly translatable. Maybe the closest would be Java vs Kotlin vs Clojure vs Scala vs… - you still have the underlying core (JVM + bytecode) just as with web frameworks you have browsers, HTTP, HTML, CSS, JavaScript/Typescript at the core of what eventually runs.<br />
Why would you need Vue instead of just HTML/CSS/plain Javascript/other simpler Javascript libraries? To me it’s a similar question as when people ask “why do I need Spring portfolio”. You don’t technically need it - but if you don’t use it (or use only a plain dependency injection framework like Guice), on any more complex project you’ll likely end up writing your own version of various Spring libraries, with plenty of bugs and loads of wasted time.<br />
What I really love about Vue, and I don’t know, perhaps it’s similar in React, is the reactivity. That is, you sort of tell Vue that this bit of UI depends on this particular variable (you <strong>bind</strong> it to this bit of data), and you can then simply modify the data, and the UI components automatically update - with no funky listeners, or callbacks or any other boilerplate. It makes creating lovely, interactive UIs extremely simple.</p>
</li>
<li>
<p><strong>NodeJS</strong> - think: Tomcat/Jetty and the likes. Just as much as you don’t need them for every single Java app, once you hit any more sophisticated/dynamic projects, you’ll most likely use it.</p>
</li>
<li>
<p><strong>Nuxt</strong> - this is like a swiss-army knife of the Vue world. It’s what Spring Boot is for Java. Opinionated framework, and you better stick to the conventions - but when you do it can save so, SOOOO much time. It integrates a number of other goodies, from VueX, Vue Router, to webpack, and loads of other things, and Just Works. I love it. All the following comes for free (otherwise it will be up to you to make these things play nicely together)</p>
<ul>
<li><strong>Vuetify</strong> - a material-design components library. Vue itself is mostly about “language” to describe your app. Think loops, and conditionals, and structure. Vuetify is what brings you out-of-the-box nicely styled buttons, tables, iterators, tabs and many many other building blocks so that your page can look pretty. You could use Vue with pure HTML/CSS, or many other components libraries, or some simple CSS layers above - it’s all up to personal taste. We found Vuetify extremely beginner friendly so if you’re not a CSS Ninja, you can’t go wrong starting here.</li>
<li><strong>VueX</strong> - state management library, kind of like an in-memory globally available cache for Vue apps. You’ll probably need it for pretty much any app more complex than a static page with very little data.</li>
<li><strong>VueRouter</strong> - a bit like Spring MVC/Controllers routes - basically, indicates which bit of your code is responsible for which part of your app</li>
<li><strong>SSR vs client mode vs statically rendered content</strong> - this deserves its own post really, to go into nitty-gritty details, but for now there is one thing to understand. Nuxt gives you three options to run Vue:
<ul>
<li><em>statically rendered site</em>, meaning you write your code in Nuxt+Vue, and then you create a beautiful static page, i.e. there is no Node.js, you just serve plain HTML/CSS/Javascript, even from something like S3. Think, a static HTML on your hard drive.</li>
<li><em>full SPA (Single Page Application) mode</em>, that is, your app is delivered as a pretty much empty shell to the browser, and the browser executes Javascript to dynamically create HTML/DOM</li>
<li><em>universal mode</em> - the first hit to your page will be executed on the Node.js server (thus name: SSR, server-side-rendering), and then subsequent pages/routes within this client’s session (to be precise: until someone closes/reopens tab, or clicks refresh) will be handled by the browser</li>
</ul>
</li>
</ul>
<p>The benefit of generated static site is pretty obvious - it’s much easier to serve. However, you won’t be able to use it for heavily dynamic/data driven apps. If you can’t use that, what’s the benefit of universal/SSR mode vs SPA? In short: SEO. These days search bots are much much better with Javascript than they used to. If you have a page with just a bit of JS on it, you’ll probably still get the page index just fine. Unfortunately, in our experience, with anything more sophisticated, when you drive your page from quite a few data-calls, bots don’t wait long enough/process everything sufficiently to index it correctly.<br />
Nuxt makes it incredibly easy to use SSR, and when we realised we needed SSR, that was the point when we started using Nuxt as without it we were in a World-Of-Pain.</p>
<ul>
<li><strong>Axios</strong> - Axios =~ Spring RestTemplate. A neat library to make HTTP calls. Nicely integrated into Nuxt so that you can access it anywhere you need it with very little configuration.</li>
<li><strong>PWA (Progressive Web App)</strong> - according to Google, <em>A Progressive Web App (PWA) is a web app that uses modern web capabilities to deliver an app-like experience to users.</em> Nuxt comes with a module which makes creating PWA easier. (We’re only at the beginning of the journey here but I may write more about it later on)</li>
<li><strong>npm/yarn + webpack</strong> - I roll it into one point even though these are independent technologies - because to me, it all fits into “how do I manage my dependencies and build the thing”. That is, Maven/Gradle equivalent. The centre here is package.json (think: build.gradle / pom.xml). The webpack part is not something you need to think about much when you use Nuxt - so we don’t - but you can configure it quite a bit when needed.</li>
<li><strong>Babel</strong> - kind of related to above. would you be happy to be stuck on Java 1.4 or 5.0 and not to be able to use all the stuff that came in Java 6, 7, 8… ? (rhetorical question) Not surprisingly, JavaScript folks are not to keen on being stuck on some old JavaScript syntax either. But unlike in Java world, you have very little control over what environment (browser) your code will run in. So in some ways, Babel is kind of like a clever Java compiler, which converts your brand-new-code into something that an old JVM… I mean old browser… can still understand. Neat. Oh, and the best thing? If you use Nuxt, all the magic happens without you even thinking about it.<br />
BTW, have you noticed I keep using “JavaScript” here - in fairness, I should probably say JS, EcmaScript, TypeScript… - but that would just confuse things at this stage, so let’s stick to JS as a mental shortcut, knowing it’s not strictly just that.</li>
<li><strong>Eslint + Prettifier</strong> - sort of like Findbugs, PMD and code style checkers in Java world. We actually don’t have them turned on as they were extremely noisy in default config, and I didn’t have the time to fine-tune it - but it’s something on my (never-ending) TODO list.</li>
<li><strong>Jest and Cypress</strong> - testing testing testing. Jest is like JUnit, Cypress we find useful for high-level/functional testing. Many options out there, these seemed to agree with us most.</li>
</ul>
<p>And, frankly, that’s it! That’s all you need to know to start your journey with Vue/Vuetify/Nuxt. Yes, of course there is way way more, especially when you start looking under the hood a bit more, or have unusual requirements - but it is entirely possible to get productive just being vaguely familiar with the above. It’s all you need to build an app, and not just a super simple Hello World!</p>
</li>
</ul>
<h1 id="bonus-1-why-vuejs-and-not-react-or-angular">BONUS 1: Why VueJS and not React or Angular?</h1>
<p>I get this question a lot from my dev friends so might just as well address it here, once. Angular was easy - I absolutely hate Google’s tendency to just abandon projects, and I’m convinced they’ll do it again, so I didn’t even look into it any further than that. To be frank, I have nothing against React per se - maybe except that it’s made by an evil evil company that I prefer to keep at arms length. But otherwise, I’m sure it’s a brilliant piece of tech. So why not?</p>
<p>Our project is built by two people, myself and a brand new developer, a person who at the start of the project could claim as much experience as doing an HTML website in Dreamweaver. I looked into React first, but the whole “Javascript only” attitude scared me a bit. Even for me, getting a simple app only just a bit beyond “Hello World” was not a 5 minute job, I didn’t understand what was happening. The fact that Vue has this neat concept of combining HTML (structure) + CSS (style) + Javascript (actions) into components seemed much easier to grasp for a newbie, and it makes an awful lot of sense to me. There is also a great choice of really basic materials about HTML and CSS. You can learn more gradually. React? It just felt like too steep a curve to start with.</p>
<p><em>A fun fact</em>: when we first started, because I was so “hardcore Java” we didn’t even use Nuxt. We didn’t use Node.js. We started with all rolled into single app, a Spring Boot with a bit of FreeMarker sprinkled with plain Vue. Times of Javascript libraries served from Webjars. Then adding Vue Router and VueX manually. It was fun times, I’ve learned a lot about the stack that way - but it’s not necessarily a way I’d recommend if you value your time ;) I think the React docs might be a bit better now, but back then, it was really pushing you down the full-stack route, and I simply wasn’t ready for that.</p>
<p>So here we are. I did not regret this decision at any point. Yes, having React skills would probably be a bit more practical from perspective of “more jobs out there” - but otherwise, we are very happy with how Vue works.</p>
<h1 id="bonus-2-what-are-the-gotchas">BONUS 2: What are the gotchas?</h1>
<h2 id="environment">Environment</h2>
<p>So far, there is one major “gotcha” that really bugs me about Nuxt/Vue combo and is something that as a back-end dev you’re likely to trip on. The concept of “build-once-deploy-multiple-times”. This is something really tricky to do at the moment, and it involves a bunch of hacks rather than a neat, standard solution.<br />
In your usual Java app (not going too crazy with sth like Spring Cloud Config Server), you’ll often have externalised config in the form of properties/yml files, and/or passing in environment variables. It’s the latter that will likely give you an infinite amount of grief because <em>environment variables in certain parts of Nuxt are baked in at build time</em>. Let me repeat that. Nuxt/Webpack build takes your environment variables <strong>during build time</strong> and bakes them into the generated resources. They are not taken from the environment at runtime.</p>
<p>What makes it more confusing is that this is not 100% the case for all of your app / use cases. There is a <a href="https://www.npmjs.com/package/nuxt-env">plugin</a> for Nuxt that allows you to read and use runtime environment variables. A good rule of thumb is: if you’re using something in your own code, in your components - you’ll be fine using runtime $env variables. However, and this is where things are getting nasty, if you’re using a 3-rd party Nuxt plugin or module (e.g. for google analytics) and it’s configured in nuxt.config.js - you’re stuffed. There is currently no elegant way for you to use environment variables for this purpose. It’s extra confusing as nuxt.config.js is run twice - during build, and then on your (built) server startup. So if you have something like:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Full environment we're running in: "</span> <span class="o">+</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">));</span>
</code></pre></div></div>
<p>at the beginning of your nuxt.config.js, then it might SEEM like the env variable is set correctly. Except, by the time this code is run, the variable in your config has already been hardcoded to the value that was present during the build.<br />
It’s even more (!) confusing because if you run in dev mode (the one you will usually use during testing on localhost) everything will work because build and run are effectively the same process - so setting an environment variable for this process will work just fine.</p>
<p>Yuck. This makes running things in Docker / cloud non-trivial, and effectively forces you to rebuild (at least part of) the app when you deploy (or use one of many possible hacks, which I may go into in a future post). I really hope that Nuxt team will find a neater solution at some point as at the moment, it feels really bad.</p>
<h2 id="reactivity">Reactivity</h2>
<p>When you start using Vue, it may take a little bit of time to get your head around how exactly Vue’s <em>magic</em> reactivity works. We used to have cases where we were trying to use a dynamic value, and it was not updating the view the way we expected it to. It doesn’t happen to us anymore, so I think now we intuitively grasped how reactivity works - but in the past it wasn’t always obvious. If people come up with any examples of reactivity not working, I think I could try work out why, and perhaps break it down into more intuitive rules/way of looking at it.</p>
<h1 id="code">CODE</h1>
<p>Technically, there isn’t much of code to show here. Nuxt has a great generator for a skeleton project, all you need to do (after installing yarn and node.js), is run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn create nuxt-app plain-nuxt-app
</code></pre></div></div>
<p>It will ask you a couple of questions about what you want included in your project. An example with choices equivalent to what we have in our project can be found in <a href="https://github.com/lilianaziolek/blog-examples/tree/master/plain-nuxt-app">examples/plain-nuxt-app</a>
The linting configuration that comes enabled by default is super strict so you might want to skip it if you are only just starting - otherwise you can get some scary looking confusing errors and warnings.</p>
<p>And that’s all for today, folks. If any of the points or topics above is particularly interesting, please comment/request more info below!</p>Liliana ZiolekJavaScript ecosystem in a nutshell At the end of 2016 or beginning of 2017 I came across this blog post. It was at a point when I was beginning to think of building OSBO, and I knew that this would involve finally leaving my Java/backend-only fortress that I happily occupied throughout my whole carrier, and moving at least somewhat into the “enemy grounds”. This blog post was funny, but also in some ways petrifying. It confirmed all my worries about what it will look like - to have to do any front-end work. It sounded simply crazy.(Re)starting blogging in 3..2..12019-06-24T00:00:00+00:002019-06-24T00:00:00+00:00https://tech.onestopbeauty.online/blog/restarting-blogging-in-3-2-1<p>Many many years ago, I had a tech blog ( <a href="https://london-geekette.blogspot.com">London Geekette</a> ). I may at some point move the stuff from there here - if I deem it remotely relevant / up-to-date. In the meantime, it is a brand new world for me, and a brand new blog seemed fitting.</p>
<p>Also, to be frank, another part of the motivation was that writing (formatting) tech content in blogspot was a real PitA. This new way of writing in Markup, and committing to Github seemed SO much easier (and, of course, geekier). If you don’t know what I’m talking about, check out <a href="https://help.github.com/en/articles/using-jekyll-as-a-static-site-generator-with-github-pages">Github Pages</a>. Or, even easier, start from <a href="https://mmistakes.github.io/minimal-mistakes/">Minimal Mistakes Template</a> which is the one currently on this blog. It’s quite customizable (I’ll play with it more later) and comes with a <a href="https://github.com/mmistakes/mm-github-pages-starter">nice example project</a> - just copy and commit under your_github_user.github.io repo. The page will shortly be available under <a href="https://your_github_user.github.io">https://your_github_user.github.io</a> - and you can also <a href="https://help.github.com/en/articles/using-a-custom-domain-with-github-pages">add a custom domain</a>, which I’ll be doing later.</p>
<p>Anyway, that’s a small digression but I was quite excited about how easy it was to set it all up. :)</p>
<p>The brand new world I’m talking about is getting out of corporate environment of contracting for financial institutions and into a world of “indie hackers” - that is, going solo (well, almost, I have a +1 for this), building something from scratch, using any tech stack I want. Naturally, a lot of that stack is stuff I used before and liked. Despite (some) hate Java gets, I still consider it one I’m most productive in. I could, I’m sure, learn Kotlin, Clojure or Scala, or maybe take Go, or whatever else is the “cool new kid” - and they all have some benefits, definitely. But the honest truth is, I knew I had SO MUCH new stuff to learn, I just wanted to have a handful of reliable parts of my tech stack where I genuinely know what I’m doing and don’t waste time googling something obvious.</p>
<p>There were 2 main areas I needed to get into - the first was front-end, and the other one was (or rather, will be, if all goes well), machine learning, and thus Python (rejected idea of going with R, sorry data scientists, too much out of my comfort zone). Therefore, this blog will contain the following mixture of topics, depending on what I’m working on and what I find challenging:</p>
<ul>
<li><strong>Java</strong> and all stuff related: Spring, JVM, libraries/frameworks</li>
<li>Front-end: our choice of poison is <strong>Vue</strong> with Nuxt.js and Vuetify (could not be happier with our choice, I’ll get to why in a dedicated post soon)</li>
<li><strong>Neo4j</strong> as the database - again, quite happy how it worked so far, the lack of rigid schema really helped us make changes in data structure quickly which is nice when you’re working with domain that you don’t know so well initially</li>
<li>At the time of writing, we run things on <strong>Google Cloud Platform</strong> (plus search in AWS, just because I was too lazy/busy to setup Elastic search myself) and fought some battles here already. This may or may not change - most of the project is 100% portable, there is one very isolated area which would be easy to rewrite, although obviously deployment would need to be re-done.</li>
<li><strong>Python & ML</strong> - honestly, this is very very basic at the moment, and I won’t be writing about it for quite some time. <a href="https://www.onestopbeauty.online">OSBO</a> needs to grow first so that I have data to machine-learn on!</li>
</ul>
<p>The main goal I have for this blog is, whenever talking about specific tech problem, to provide a fully working github repo with an example. I haven’t yet decided how exactly it’s going to work, if I’ll share any code between different posts or not, but here is the challenge I often come across. Product you’re using has feature A, and there are docs, and plenty examples for how to set-up feature A. Same product (or sometimes, for more fun, another) has feature B, and again, there’s documentation, examples, happy days. The problems start when you try to take a bit of feature A and a bit of feature B and make them play with each other. And suddenly there are no examples, and nothing works, and you’re left banging your head against the wall desperately googling for that one, just one example that uses both <em>at the same time</em>, AND is more than just a few code snippets which you don’t know where to paste and when you try to put them in your project, still don’t work.</p>
<p>I know exactly why many blogs are written this way. There is great benefit to learning things in isolation. It’s easier to show, easier to understand, easier to find. Still, I sometimes think that more complex examples are missing (especially for relatively modern technologies) - and this is what I’d love to focus on. How this works out in practice - we’ll see :)</p>
<p>So, that’s it folks in the way of introduction. If any of this sounds interesting, tune in and follow for updates :)</p>Liliana ZiolekMany many years ago, I had a tech blog ( London Geekette ). I may at some point move the stuff from there here - if I deem it remotely relevant / up-to-date. In the meantime, it is a brand new world for me, and a brand new blog seemed fitting.