<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Valor Software on Medium]]></title>
        <description><![CDATA[Stories by Valor Software on Medium]]></description>
        <link>https://medium.com/@valorsoftware?source=rss-23e367deeb51------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*nzAiP6W-dCHO_eULMPaqAg.jpeg</url>
            <title>Stories by Valor Software on Medium</title>
            <link>https://medium.com/@valorsoftware?source=rss-23e367deeb51------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 21 Jun 2026 22:27:18 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@valorsoftware/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Nx, Next.js, and Module Federation]]></title>
            <link>https://valorsoftware.medium.com/nx-next-js-and-module-federation-78882a1b68ab?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/78882a1b68ab</guid>
            <category><![CDATA[webpack-5]]></category>
            <category><![CDATA[micro-frontends]]></category>
            <category><![CDATA[nx]]></category>
            <category><![CDATA[module-federation]]></category>
            <category><![CDATA[nextjs]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Thu, 21 Jul 2022 16:14:04 GMT</pubDate>
            <atom:updated>2022-07-21T22:44:13.265Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Table of Contents:</strong></p><ul><li>About the author</li><li>Micro Frontends, Nx, and monorepos</li><li>Next.js, Nx + Next.js</li><li>Module Federation, Next.js + Module Federation</li><li>Starting the project</li><li>Generating new pages and new applications</li><li>Running in the development environment</li><li>Generating new components</li><li>Installing nextjs-mf</li><li>Creating hooks</li><li>Deploying projects on Vercel + private dependencies with Vercel</li><li>References</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/992/1*HIH6Zyv2P5Ljsh_6ljUzUg.png" /></figure><h3>The author</h3><p>Bruno Silva, the Next.js developer of Valor Software with the team’s support, has created the solution described below and this material was originally published on <a href="https://valor-software.com/articles%5E">Valor’s blog</a>.</p><p>The article is also available in Portuguese: <a href="https://valor-software.com/articles/nx-next-js-e-module-federation">https://valor-software.com/articles/nx-next-js-e-module-federation</a>. Enjoy the reading and the dev experience :)</p><h3>Micro Frontends</h3><p>You’ve probably heard about microservices and the number of benefits they bring to both the scalability of a backend application and the team that develops it. Now imagine if these same advantages could be brought to the front-end. Well, that’s what we’re going to talk about today.<br> First, let’s talk about what a micro frontend is and what are its benefits. Micro frontends are a way to organize both an application and the team that develops it. Think about the following: a web application is usually composed of several resources, and depending on the size of the application it depends on certain synchrony between the teams that develop it so that new versions are released for production. Let’s take an example: Imagine that we have an online course sales website and that website has the following features:</p><ul><li>Institutional page (landing page/marketing)</li><li>Catalog / advanced search</li><li>Checkout</li><li>Courses player page</li><li>Upload page</li><li>Forum (for students to interact with tutors)</li><li>Account Settings pages (for students and tutors)</li></ul><p>Imagine that each of these features can be easily split into microservices when it comes to the backend, but most of the time the frontend ends up being a kind of “monolith” containing a single stack and forcing the entire team to stay in sync at all times. that a new feature goes into production. Can you see this? Clearly, we could split each part of this eCommerce into distinct applications for smaller teams with unique responsibilities and possibly different stacks. Think about it, the team responsible for the checkout could work <strong>100% focused</strong> on improving the user’s shopping experience flow, reducing noise that made them give up on the purchase, or for example the catalog and institutional team (landing page) that could work on different marketing fronts, as well as the team responsible for the video area of the platform, which would be focused on ensuring speed / high quality in the delivery of classes to students.<br> Anyway, there are many examples that we could cite about the use of micro frontends, but as the purpose of this article is to be a little more practical, let’s skip this part and if you are more interested in understanding the advantages of using micro frontends, I suggest you read more about it in <a href="https://micro-frontends.org/">this article</a>.</p><h3>Nx and monorepos</h3><p>Well, as we saw earlier it is possible to divide some web applications into micro frontends which, initially, may bring you some questions such as: “But then I need to divide all applications into separate repositories? Imagine the headache it will be to test all this!” and that’s where we’ll talk about mono repositories. Mono repository or Monorepo is a single git repository that seeks to manage all the source code of an application, this brings us a series of advantages and some disadvantages, here are some of them:</p><h4>Pros</h4><ul><li>Standardization (lint) of the code for the entire team</li><li>Test management in one place</li><li>Centralization of dependency management</li><li>Code reuse between applications due to dependency centralization</li><li>Transparency as we can see all code from a single workspace</li></ul><h4>Cons</h4><ul><li>.git folder can end up getting big due to a high number of contributions because the whole team is contributing commits in the same project</li><li>Increase of build time of some applications depending on the dependency level and size of shared files/data</li><li>No permission granularity, since the entire team needs to have access to the monorepo, the power to restrict the access of certain users to certain parts of the application is lost Looking at the benefits and the context, I saw that it would be an excellent opportunity to use <a href="https://nx.dev/">Nx</a> as a manager for our project. Nx is a monorepo manager with a huge range of <a href="https://nx.dev/community#create-nx-plugin">plugins</a> to facilitate the creation of new applications, libraries, tests, build execution, lint standardization, centralization, dependency management, and many other features.</li></ul><h3>Next.js</h3><p>It is indisputable that currently <a href="https://nextjs.org/">Next.js</a> is one of the web frameworks that has been gaining more and more adoption in recent times and all this is due to the range of features such as server-side rendering, static optimization, file-system routing, API routes, and <a href="https://nextjs.org/docs/basic-features/data-fetching/overview">data-fetching</a> strategies he proposes. Next.js is an awesome tool, but we’ll assume you already know it and skip to the next part.</p><h3>Nx + Next.js</h3><p>According to the Nx team, their development philosophy is very similar to Visual Studio Code’s, in which they focus on maintaining a powerful and generic tool, while extensions, or plugins, are fundamental for increasing your productivity with it. As such, <a href="https://nx.dev/packages/next">@nrwl/next</a> is the plugin that we will use to create and manage our applications with Next.js within our Nx workspace.</p><h3>Module Federation</h3><p>Module Federation is a <a href="https://webpack.js.org/concepts/module-federation">Webpack 5</a> feature that has arrived to make it possible to share parts of an application to another at runtime. This makes it possible for multiple applications compiled with webpack to repurpose parts of their code as the user interacts with them, which takes us to the next step.</p><h3>Next.js + Module Federation</h3><p>Let’s start with our first example of this article where we talk about an eCommerce application, now imagine that our marketing team decides to create a mega Black Friday campaign and decides to change several parts of our application by inserting different components with dynamic banners, carousels, countdowns, themed offers, etc… this would probably be a headache for all teams responsible for our micro frontend applications since each one would have to implement the new requirements of the marketing team in their projects and that would have to be very well tested and synchronized so that everything went right and nothing could be released ahead of time… Anyway, all this could easily generate a lot of work and a lot of headaches for the team, but that’s where the very powerful Module Federation comes in.</p><p>Thanks to it, only one team would be responsible for developing the new components along with their respective logic, and the rest of the team would only be responsible for implementing the use of these new complements, which could bring with them, hooks, components in React, among others.</p><p>Unfortunately, implementing and using the Module Federation features of Webpack with Next.js is not that easy, as you would need to deeply understand how both tools work to be able to create a solution that facilitates the integration between the two. Fortunately, there is already a solution and has several features including support for SSR (server-side rendering), these tools are called <a href="https://app.privjs.com/package?pkg=@module-federation/nextjs-mf">nextjs-mf</a> and <a href="https://app.privjs.com/package?pkg=@module-federation/nextjs-ssr">nextjs-ssr</a> and together we are going to explore a proof-of-concept application that I created to show you the power of these tools together.</p><p>⚠️ Attention: for the application to work with Module Federation features you need to have access to the <a href="https://app.privjs.com/package?pkg=@module-federation/nextjs-mf">nextjs-mf</a> or <a href="https://app.privjs.com/package?pkg=@module-federation/nextjs-ssr">nextjs-ssr</a> plugin which currently requires a paid license!</p><h3>About the project</h3><p>This project will show, how to create the basis for a fully scalable application both in production and in development. In it, we will see some small examples of how the tools mentioned above can be used.</p><h3>Starting the project</h3><p>Initially, we will need to install Nx in our environment to handle the commands needed to manage our monorepo. To do this, open a terminal and run:</p><p>Once this is done, navigate to a directory where you want to create the project and run the command below, this command will use <a href="https://nx.dev/packages/next">@nrwl/next</a> to create our workspace (monorepo) and our first application:</p><p>An interactive terminal will guide you through the creation process, you can follow as I did below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/631/0*gPeVbIfsjmSuSokc.png" /></figure><p>Once this is done, you must wait for the workspace (monorepo) to be created and the project’s dependencies to be downloaded after that you can open vscode in the workspace root, in my case:</p><pre>code ./nextjs-nx-module-federation</pre><p>Looking at the file explorer you can see that the project has a structure similar to this:</p><pre>├── apps<br>│   ├── store (...)<br>│   └── store-e2e (...)<br>├── babel.config.json<br>├── jest.config.ts<br>├── jest.preset.js<br>├── libs<br>├── nx.json<br>├── package.json<br>├── package-lock.json<br>├── README.md<br>├── tools<br>│   ├── generators (...)<br>│   └── tsconfig.tools.json<br>├── tsconfig.base.json<br>└── workspace.json</pre><p>ℹ️ Note that our application in Next.js is inside the “apps” folder, this folder will contain all the other applications you are going to create, we can also see other configuration files of our workspace. It is important to note that there is only one “node_modules” folder in the entire project, this happens because all dependencies will be in one place, at the root of the repository.</p><h3>Generating new pages</h3><p>The <a href="https://nx.dev/packages/next">@nrwl/next</a> plugin has several <a href="https://nx.dev/packages/next#generators">generators</a>, and commands that serve to automate the creation of pages, components, and other common structures in the project.</p><p>Knowing this we will create our first page using a generator called “page” for this run the following command in the terminal</p><pre>nx g @nrwl/next:page home --project=store</pre><p>ℹ️ Note that we use the — project flag to indicate to the generator in which project the new page should be created.</p><p>This will generate a page called “home” which will be located at</p><pre>apps/store/pages/home/index.tsx</pre><h3>Generating new applications</h3><p>Now we will need to create another application, which we will call “checkout”. Unlike the first application we created together with the workspace, we will need to use the following command to create a new Next.js application in the current workspace:</p><pre>nx g @nrwl/next:app checkout</pre><p>Your “apps” folder should look like this:</p><pre>├── apps<br>│   ├── checkout (...)<br>│   ├── checkout-e2e (...)<br>│   ├── store (...)<br>│   └── store-e2e (...)<br>...</pre><h3>Running in the development environment</h3><p>To see our changes running, we will need to run the following command in the terminal:</p><p>Also, we can run all applications at the same time using:</p><pre>nx run-many --target=serve --all</pre><p>ℹ️ Note that we use the — target flag to indicate to nx which executor we want to run on all projects.</p><h3>Generating new components</h3><p>As we saw earlier, we have the possibility to create structures in our application using the Nx CLI tool, now we are going to create a simple button component in the “checkout” project, that executes the following command:</p><pre>nx g @nrwl/next:component buy-button --project=checkout</pre><p>Now let’s edit the component in the directory below so that it looks like <a href="https://github.com/BrunoS3D/nextjs-nx-module-federation/blob/main/apps/checkout/components/buy-button/buy-button.tsx">this</a></p><pre>apps/checkout/components/buy-button/buy-button.tsx</pre><p>We’ll use this simple app “checkout” component in the app “store” to exemplify code sharing with Module Federation and that takes us to the next step.</p><h3>Installing nextjs-mf</h3><p>⚠️ Attention: for the application to work with Module Federation features you need to have access to the <a href="https://app.privjs.com/package?pkg=@module-federation/nextjs-mf%5E">nextjs-mf </a>or <a href="https://app.privjs.com/package?pkg=@module-federation/nextjs-ssr%5E">nextjs-ssr</a> plugin which currently requires a paid license!</p><p>To install the tool, we need to login to <a href="https://privjs.com/">PrivJs</a> using npm, to do so, run the following command:</p><p>Once this is done a file containing your credentials will be saved in ~/.npmrc. Now you can install nextjs-mf using the command below:<br> npm install @module-federation/nextjs-mf — registry <a href="https://r.privjs.com">https://r.privjs.com</a></p><p>Now we will need to modify our “next.config.js” file in both projects so that the installed plugin can work, for that open the following files:</p><ul><li>apps/store/next.config.js</li><li>apps/checkout/next.config.js You will see that in them we have an Nx plugin being used, we will need to maintain it, for that, make the files of each project similar to these:</li><li><a href="https://github.com/BrunoS3D/nextjs-nx-module-federation/blob/b20485c501c8c8353aca9b7a2b0bbf376c43348d/apps/store/next.config.js">store/next.config.js</a></li><li><a href="https://github.com/BrunoS3D/nextjs-nx-module-federation/blob/b20485c501c8c8353aca9b7a2b0bbf376c43348d/apps/checkout/next.config.js">checkout/next.config.js</a> You will notice that we have two environment variables being used in this file, we will need to define them in each project so create a “.env.development.local” file in each project and leave each file with the following values:</li></ul><pre>NEXT_PUBLIC_CHECKOUT_URL=http://localhost:4200 NEXT_PUBLIC_STORE_URL=http://localhost:4300</pre><p>So far no new changes can be noticed, but we can already use the Module Federation resources, but before that, we will make some modifications in our development environment so that applications can communicate without generating warnings in the console by local port collision, to this open and edit the following files:</p><p>“apps/store/project.json”</p><pre>{<br>  // ...<br>  &quot;targets&quot;: {<br>    // ...<br>    &quot;serve&quot;: {<br>      // ...<br>      &quot;options&quot;: {<br>        &quot;buildTarget&quot;: &quot;checkout:build&quot;,<br>        &quot;dev&quot;: true,<br>        &quot;port&quot;: 4300<br>      },<br>      // ...<br>    },<br>    // ...<br>}</pre><p>“apps/checkout/project.json”</p><pre>{<br>  // ...<br>  &quot;targets&quot;: {<br>    // ...<br>    &quot;serve&quot;: {<br>      // ...<br>      &quot;options&quot;: {<br>        &quot;buildTarget&quot;: &quot;checkout:build&quot;,<br>        &quot;dev&quot;: true,<br>        &quot;port&quot;: 4200<br>      },<br>      // ...<br>    },<br>    // ...<br>}</pre><p>In order for the component to be federated, we must add it to the “next.config.js” file, open the file and add a new entry in the “exposes” object:</p><pre>module.exports = withFederatedSidecar({<br>  // ...<br>  exposes: {<br>    &#39;./buy-button&#39;: &#39;./components/buy-button/buy-button.tsx&#39;,<br>  },<br>  // ...<br>})(nxNextConfig);</pre><p>Now with everything configured, we must restart any next process that is running and we are going to import the button component that we created in the “checkout” project in the “store” project using the Module Federation resources, for that open the “home” page that we created in the “store” project and import the Next.js <a href="https://nextjs.org/docs/advanced-features/dynamic-import">dynamic</a> function as shown below:</p><pre>import dynamic from &#39;next/dynamic&#39;;</pre><p>This function will help us to import the component only on the client-side, so add the following code snippet on the page:</p><pre>const BuyButton = dynamic(<br>  async () =&gt; import(&#39;checkout/buy-button&#39;),<br>  {<br>    ssr: false,<br>  }<br>);</pre><p>And then we can use the component in the page content</p><pre>export function Page() {<br>  return (<br>    &lt;div className={styles[&#39;container&#39;]}&gt;<br>      &lt;h1&gt;Welcome to Store!&lt;/h1&gt;<br>      &lt;BuyButton onClick={() =&gt; alert(&#39;Hello, Module Federation!&#39;)}&gt;Add to Cart&lt;/BuyButton&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><p>Now you can see the following result</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/427/0*J1nE3BiCRLssoJ_y.png" /></figure><h3>Creating hooks</h3><p>One of the powers of nextjs-mf is the federation of functions, including hooks. An important detail is that we cannot import hooks asynchronously, which leads us to adopt a solution where we import functions using “require” and the page or component that uses the hook being loaded lazily/asynchronously, what we call “top-level-await”.</p><p>First, we will need to create a hook, for that, we are going to make a simple state function. Create a file in the “checkout” app in “apps/checkout/hooks/useAddToCart.ts” and insert the code below in the file:</p><pre>import { useState } from &#39;react&#39;;<br><br>export default function useAddToCartHook() {<br>  const [itemsCount, setItemsCount] = useState&lt;number&gt;(0);<br>  return {<br>    itemsCount,<br>    addToCart: () =&gt; setItemsCount((i) =&gt; i + 1),<br>    clearCart: () =&gt; setItemsCount(0),<br>  };<br>}</pre><p>Once this is done, add the file to the list of modules exposed in the “next.config.js” file:</p><pre>import { useState } from &#39;react&#39;;<br><br>export default function useAddToCartHook() {<br>  const [itemsCount, setItemsCount] = useState&lt;number&gt;(0);<br>  return {<br>    itemsCount,<br>    addToCart: () =&gt; setItemsCount((i) =&gt; i + 1),<br>    clearCart: () =&gt; setItemsCount(0),<br>  };<br>}</pre><p>To import the hook, let’s create a new page that will be imported asynchronously, for that create a new folder in the store app called async-pages. Create a custom-hook.tsx file that will be our page inside the async-pages folder, then add the following code to the file:</p><pre>// typing for the hook<br>type UseAddToCartHookType = () =&gt; UseAddToCartHookResultType;<br><br>// hook function return typing<br>type UseAddToCartHookResultType = {<br>  itemsCount: number;<br>  addToCart: () =&gt; void;<br>  clearCart: () =&gt; void;<br>};<br><br>// hook default value<br>let useAddToCartHook = (() =&gt; ({})) as UseAddToCartHookType;<br><br>// import the hook only on the client-side<br>if (process.browser) {<br>  useAddToCartHook = require(&#39;checkout/useAddToCartHook&#39;).default;<br>}<br><br>export function Page() {<br>    // on server side extracts the values as undefined<br>    // on the client side extracts the hook values<br>  const { itemsCount, addToCart, clearCart } =<br>    useAddToCartHook() as UseAddToCartHookResultType;<br><br>  return (<br>    &lt;div&gt;<br>      &lt;h1&gt;Welcome to Custom Hook!&lt;/h1&gt;<br><br>      &lt;p&gt;<br>        Item Count: &lt;strong&gt;{itemsCount}&lt;/strong&gt;<br>      &lt;/p&gt;<br>      &lt;button onClick={addToCart}&gt;Add to Cart&lt;/button&gt;<br>      &lt;button onClick={clearCart}&gt;Clear Cart&lt;/button&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>// here you can use the getInitialProps function normally<br>// it will be called on both server-side and client-side<br>Page.getInitialProps = async (/*ctx*/) =&gt; {<br>  return {};<br>};<br><br>export default Page;</pre><p>Now we need to create a page in the “pages” folder that loads our page asynchronously, for that use the command below:</p><pre>nx g @nrwl/next:page custom-hook --project=store</pre><p>Now open the newly created page file and add the following code</p><pre>import dynamic from &#39;next/dynamic&#39;;<br>import type { NextPage, NextPageContext } from &#39;next&#39;;<br><br>// import functions from page in synchronously way<br>const page = import(&#39;../../async-pages/custom-hook&#39;);<br><br>// lazy import the page component<br>const Page = dynamic(<br>  () =&gt; import(&#39;../../async-pages/custom-hook&#39;)<br>) as NextPage;<br><br>Page.getInitialProps = async (ctx: NextPageContext) =&gt; {<br>    // capture the getInitialProps function from the page<br>  const getInitialProps = ((await page).default as NextPage)?.getInitialProps;<br>  if (getInitialProps) {<br>        // if the function exists, call the function on server-side and client-side<br>    return getInitialProps(ctx);<br>  }<br>  return {};<br>};<br><br>export default Page;</pre><p>Now you can see the following result</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/428/0*IOVU780sTgQJ_Fq1.gif" /></figure><p>Some errors at the time of writing this article may be occurring, so if in doubt, consider looking at <a href="https://github.com/BrunoS3D/nextjs-nx-module-federation">this project</a> I created as a proof of concept, I’m actively working with Zackary to make it up to date and functional.</p><h3>Deploying projects on Vercel</h3><p>The procedure that we are going to perform now will be done at <a href="https://vercel.com/">Vercel</a>, but we can replicate it without much difficulty on other serverless hosting platforms such as <a href="https://www.netlify.com/">Netlify</a>, <a href="https://docs.amplify.aws/guides/hosting/nextjs/q/platform/js/">AWS Amplify</a>, and Serverless with a <a href="https://www.serverless.com/plugins/serverless-nextjs-plugin">plugin</a> for Next.js or even in a <a href="https://en.wikipedia.org/wiki/Self-hosting_(web_services)">self-hosted</a> way using Docker with a private server.<br> We can carry out the process in two ways: by <a href="https://vercel.com/new">interface</a> or by <a href="https://vercel.com/cli">CLI</a>, but to facilitate the process we will do it by the interface, you just need to host the project on <a href="https://github.com/">GitHub</a> so that we can import it in a few clicks, once the project is on GitHub you can open <a href="https://vercel.com/new">this page</a> on Vercel to deploy the first application… exactly, although it’s a monorepo, we’re going to configure everything so that separate deployments are made.<br> First, we will deploy the “checkout” app because it has fewer dependencies, for that select the repository as in the following image and click on the button to import it:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*HoZL_Ym6KMeD493b.png" /></figure><p>Choose a name for the application on the screen that opens but remember that we are still going to do the same step for the app “store” so define a different name for each project. We must change some commands for the project build in the “Build and Output Settings” tab, for this, check the override option and leave the fields as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9fZOwrEpqdp0fHMu.png" /></figure><pre>npx nx build checkout --prod</pre><p>Output directory (checkout)</p><p>For now, let’s skip the environment variables section, as we don’t have the URLs where the applications will be hosted, we can click on the “Deploy” button. You may notice that we may have an error during the build, but don’t worry if that happens, we’ll solve this soon. Now we are going to deploy our app “store” and we are going to do the same steps as before, just changing some fields on the “Build and Output Settings” tab. Build command (store)</p><pre>npx nx build store --prod</pre><p>Once that’s done, we can click on the “Deploy” button. Again, you’ll notice that the build resulted in an error, but that doesn’t matter, the important thing is that we now have the two URLs of the two projects and we can use them to configure our environment. Now go to the settings panel of each application and set the following environment variables</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*C5X9o8svBbQDDZKF.png" /></figure><p>Note that I am using a URL of the “deployment” that I made of my app store, you must do it with the URL that Vercel generated for yours, remember to define the two environment variables “NEXT_PUBLIC_CHECKOUT_URL” and “NEXT_PUBLIC_STORE_URL” each with its respective URL of production.</p><h3>Private dependencies with Vercel</h3><p>If you open the project build logs, you will notice that in both the error is the same, probably something like this</p><pre>npm ERR! 403 403 Forbidden - GET &lt;https://r.privjs.com/@module-federation%2fnextjs-mf/-/nextjs-mf-3.5.0.tgz&gt; - You must be logged in to install/publish packages.<br>npm ERR! 403 In most cases, you or one of your dependencies are requesting<br>npm ERR! 403 a package version that is forbidden by your security policy, or<br>npm ERR! 403 on a server you do not have access to.<br>npm ERR! A complete log of this run can be found in:</pre><p>This happens because Vercel does not have the necessary credentials to access a package that is in a private repository, to give access to the repository we need to configure an environment variable called “NPM_RC”, the value of this variable must be the same as what is inside the “~/.npmrc” file which was created when we used the “npm login” command.</p><p>To do so, just create a new variable in Vercel’s environment variables settings panel called “NPM_RC” and insert the entire contents of the “~/.npmrc” file, if you have any doubts read <a href="https://vercel.com/support/articles/using-private-dependencies-with-vercel">this document</a>.</p><p>Finally, you can open the “Deployments” tab and “Redeploy” your application!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1HbSqtG4oy4D91px.png" /></figure><p>Navigating to the application “store” URL you can see the button whose source code is in the “checkout” project being “federated” to our site.</p><p><em>Originally published at </em><a href="https://valor-software.com/articles/nx-next-js-and-module-federation"><em>https://valor-software.com</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=78882a1b68ab" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Debugging NgRx in NativeScript with Redux DevTools]]></title>
            <link>https://valorsoftware.medium.com/debugging-ngrx-in-nativescript-with-redux-devtools-9b5ddfd17d88?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/9b5ddfd17d88</guid>
            <category><![CDATA[nativescript]]></category>
            <category><![CDATA[ngrxplugin]]></category>
            <category><![CDATA[redux-ngrx-nativescript]]></category>
            <category><![CDATA[debuggingngrx]]></category>
            <category><![CDATA[debugging-nativescript]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Wed, 08 Jun 2022 10:03:51 GMT</pubDate>
            <atom:updated>2022-06-08T10:04:32.996Z</atom:updated>
            <content:encoded><![CDATA[<p>The solution and the story were kindly provided by Eduardo Speroni, a NativeScript developer at Valor Software, a talented open-source activist and contributor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/592/1*Q-0em8Kg5DE5lZ5PexB8qA.png" /></figure><h3>Intro</h3><p>NgRx needs practically no introduction in the current state of Angular applications. It allows you to manage your app’s state easily and make your UI react to the changes seamlessly. This tool is not exclusively for the web, so we can expect many NativeScript Angular applications to use it quite extensively.</p><p>Although NgRx is amazing, it still adds certain complexity to your application that is often harder to debug. For such cases, using Redux DevTools becomes indispensable in the debugging process, as it allows you to have a clear view of your state and how it changes over time. Those features, alongside time travel and custom action dispatch, make it a very powerful tool.</p><p>Many stacks have to implement their own way of integrating NgRx with Redux DevTools, for example, with Ionic, as Zack Barbuto describes in <a href="https://medium.com/nextfaze/remote-debugging-ngrx-store-with-ionic-74e367316193">his article about remote debugging</a>. Also, thanks to our recent efforts on <a href="https://www.npmjs.com/package/@valor/nativescript-websockets">WebSockets for NativeScript</a> (find the creation story <a href="https://valor-software.com/articles/implementing-websockets-plugin-for-nativescript.html">on the blog</a>) and the chain webpack configuration added in @nativescript/webpack 5+, you can debug NgRx in NativeScript with ease. In this article I’ll tell you about our implementation details and how you can start using it right away.</p><h4>Table of Contents:</h4><p>1. Intro</p><p>2. Connecting Redux DevTools with NgRx for NativeScript</p><p>3. Overriding the way NgRx fetches the Redux extension implementation</p><p>4. Nativescript-ngrx-devtools plugin: features review</p><p>5. How to use the Plugin, steps to reproduce for debugging</p><p>6. Caveats</p><p>7. Useful Links</p><h3>Connecting Redux DevTools with NgRx for NativeScript</h3><p>NgRx relies on the Redux DevTools Extension to be defined in the window and implement a specific (and relatively simple) interface. Redux Remote DevTools provides an option to connect to it remotely through SocketCluster, which uses WebSockets. Thankfully, we can now use <a href="https://www.npmjs.com/package/@valor/nativescript-websockets">the WebSockets plugin</a> to polyfill them.</p><p>This approach is not new, and part of our implementation can be credited to <a href="https://github.com/zalmoxisus">Zalmoxis</a> and his <a href="https://github.com/zalmoxisus/remotedev">remote debugging method</a>. Not all node or browser libraries support NativeScript, but we can make most of them work with it. Since NS isn’t either “node” or “browser” but a combination of multiple APIs common to both, libraries that support both platforms might be usable here. SocketCluster is one of those libraries that work perfectly with NativeScript as long as we use the browser implementation. As a result, we implemented a <a href="https://github.com/valor-software/nativescript-plugins/blob/3e6bb3ae819b697e78f299e1c2f891b15944316f/packages/nativescript-ngrx-devtools/package-alias-plugin.js">custom way</a> of reading <a href="https://github.com/defunctzombie/package-browser-field-spec">the browser field spec</a>, which is also nicely <a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-json#browser">described on npm</a> and applied the required file substitutions individually for the required libraries. This means we can now use the web version of SocketCluster freely in our implementation without fear that it’ll break existing applications.</p><p>Taking inspiration from previously mentioned implementations of the extension interface, we built our own interface focusing on reliability and small footprint. This was done by converting it fully to RxJS, adding error handling, retrying failed connections, trying to connect to multiple default debugging IPs periodically, and making it all highly configurable. The result was a robust bridge between your application and the remote Redux DevTools so that you can focus less on the details and more on developing your application faster.</p><h3>Overriding the Way NgRx Fetches the Redux Extension Implementation</h3><p>Once we finished writing the Redux DevTools Extension interface, we needed to provide it to NgRx. Unfortunately, NgRx gets this information from the [‘__REDUX_DEVTOOLS_EXTENSION__’] window, which isn’t available in NativeScript. Polyfilling “window” is not the best approach either, as it could also lead to unintended side effects due to many libraries checking if a window exists to determine if they’re running in a browser environment or not. With a <a href="https://github.com/ngrx/platform/pull/3338">PR to NgRx</a>, we were able to export the required symbol to override the way NgRx fetches the extension implementation.</p><p>As a side note, this change isn’t retroactive, but the plugin code has a workaround for cases when this symbol is not defined, so you can use it with older versions.</p><h3>Nativescript-ngrx-devtools Plugin: Features Review</h3><p>Below is the overview of key plugin features, so you get to know it better before the installation.</p><p>The beginning of work with the plugin:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wWPb54kK2vaGRE5o.gif" /></figure><p>Plugin replays the state after skipping actions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sTgQGx-QnKqIWnq-.gif" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gw3Ho2EPRVOfibmN.gif" /></figure><p>The increment with delay:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aIPSqS2vwtReTMup.gif" /></figure><p>Dispatching a custom action:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GuB9zAGB4fRHXop6.gif" /></figure><h3>How to Use the Plugin</h3><p>To start using the plugin, first ensure to install both: it and our WebSockets Polyfill:</p><p><a href="https://market.nativescript.org/plugins/@valor/nativescript-ngrx-devtools/">npm i @valor/nativescript-ngrx-devtools</a><br><a href="https://www.npmjs.com/package/@valor/nativescript-websockets">@valor/nativescript-websockets</a></p><p>In your polyfills.ts, ensure that websockets is properly polyfilled after nativescript’s globals:</p><pre>/**<br> * NativeScript Polyfills<br> */</pre><pre>// Install @nativescript/core polyfills (XHR, setTimeout, requestAnimationFrame)<br>import &#39;@nativescript/core/globals&#39;;</pre><pre>import &#39;@valor/nativescript-websockets&#39;; // add this line!</pre><p>You can then import the modules of your application and start using it:</p><pre>@NgModule({<br> imports: [<br>   NativeScriptModule,<br>   StoreModule.forRoot(<br>     {<br>       counter: reducer,<br>     },<br>     {<br>       initialState: {<br>         counter: initialState,<br>       },<br>     }<br>   ),<br>   ...(__DEV__ ? [ // ensure this code is tree shaken in prod<br>         StoreDevtoolsModule.instrument(), <br>         NativeScriptNgRxDevtoolsModule.forRoot()<br>      ] : []),<br> <br> ],<br> declarations: [AppComponent],<br> bootstrap: [AppComponent],<br> schemas: [NO_ERRORS_SCHEMA],<br>})<br>export class AppModule {}</pre><p>By default the plugin will attempt to connect to many IP addresses that NativeScript automatically detects from __NS_DEV_HOST_IPS__ on port 8000, but those can be configured in the options object in NativeScriptNgRxDevtoolsModule.forRoot(options).</p><p>After configuring our remote devtools, you can just start debugging!</p><p>Steps to Reproduce for Debugging:</p><p>1. Run</p><p>npm i -g @redux-devtools/cli‍</p><p>2. Then<br>‍<br>redux-devtools — open‍</p><p>3. Open ‘Settings’ in the redux-devtools UI and ensure ‘connect local’ is checked and you’re going to use port 8000 which is default, then save those settings.</p><p>4. Run a NativeScript app and start debugging.</p><h3>Caveats</h3><p>Redux DevTools already has a couple of caveats on the web, like having it crash by storing certain kinds of non-serializable objects. This also applies to NativeScript, and the fact that ignoring those caveats can also crash your app makes them even worse. Here’s an example from one of the projects we’re working on now. When testing the plugin on a large mobile app that has modals with high-definition images, we noticed that the app would crash due to a memory lack. The issue was that the action that was sent to the DevTools, contained a reference to the modal itself, so it was never garbage collected, and every time it opened, the memory would increase. Connecting to the DevTools also made the app take a performance hit as it was trying to serialize massive objects.</p><p>The solution, in this case, is to use actionSanitizer and stateSanitizer to make sure your state and actions contain only serializable data.</p><p>How it works with the plugin:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*EK6-B2kz2doekFj2.gif" /></figure><p>Find more on this topic from the <a href="https://dev.to/migsarnavarro/use-sanitizers-to-avoid-redux-devtools-crash-67p">Use sanitizers to avoid Redux Devtools crash</a> article by Migzar Navarro and the <a href="https://v7.ngrx.io/guide/store-devtools/config">NgRx official guide</a>. Also worth mentioning that it’s critical to ensure you’re using webpack flags, not to bundle the DevTools in production. As they have a memory overhead you don’t want in production apps.</p><p>That’s all I wanted to tell in this article. Hope, it was useful, and you’ll enjoy using the plugin!</p><h4>Useful Links</h4><p>- <a href="https://www.npmjs.com/package/@valor/nativescript-websockets">nativescript-websockets</a> plugin</p><p>- <a href="https://www.npmjs.com/package/@valor/nativescript-ngrx-devtools">nativescript-ngrx-devtools</a> plugin</p><p>- <a href="https://github.com/valor-software/nativescript-plugins/blob/3e6bb3ae819b697e78f299e1c2f891b15944316f/packages/nativescript-ngrx-devtools/package-alias-plugin.js">package-alias-plugin.js</a></p><p>- <a href="https://medium.com/nextfaze/remote-debugging-ngrx-store-with-ionic-74e367316193">Remote Debugging @ngrx/store with Ionic</a> article by Zack Barbuto</p><p>- <a href="https://github.com/zalmoxisus/remotedev">Remote debugging method</a> by Zalmoxis</p><p>- <a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-json#browser">Configuring npm — package-json</a> documentation</p><p>- <a href="https://github.com/defunctzombie/package-browser-field-spec">The browser field when packaging modules for client side use</a> spec</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9b5ddfd17d88" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Implementing WebSockets plugin for NativeScript using React Native]]></title>
            <link>https://valorsoftware.medium.com/implementing-websockets-plugin-for-nativescript-using-react-native-1cc1ac4da8e?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/1cc1ac4da8e</guid>
            <category><![CDATA[nativescript]]></category>
            <category><![CDATA[websocket]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Mon, 04 Apr 2022 15:57:36 GMT</pubDate>
            <atom:updated>2022-04-04T15:57:36.121Z</atom:updated>
            <content:encoded><![CDATA[<p>Eduardo, Angular Engineer at Valor Software, put his effort into the creation of the plugin as well as writing the story about it below.<br>This material was originally published on <a href="https://valor-software.com/articles/implementing-websockets-plugin-for-nativescript.html">the company’s blog</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jFAEARBNmsMz0EFQ.png" /></figure><h3>JS empowerment with native platform APIs</h3><p>As friends of the NativeScript community and preferred partners, we’re constantly trying to improve the NativeScript framework, community, and developer experience.</p><p>Sometimes it’s good to reiterate NativeScript’s goal: to empower JavaScript with native platform APIs. This has been the core focus for a while, and contrary to a common perspective among developers, NativeScript itself is actually not a competitor to other frameworks. Rather it is designed fundamentally to celebrate platforms while enhancing the entire JavaScript community (including other approaches, like React Native or Capacitor). As an example, @nativescript/capacitor allows you to use the full NativeScript capabilities within <a href="https://capacitorjs.com/">Capacitor</a> itself.</p><p>@nativescript/core is a JavaScript library that takes full advantage of what NativeScript offers (it’s merely just a glimpse of what is possible with NativeScript as a whole), and all the other flavors like @nativescript/angular, nativescript-vue, and react-nativescript are compliments to each framework itself.</p><h3>WebSocket plugin for NativeScript</h3><p>WebSockets are frequently required for applications, be it for standard WebSocket communication, MQTT, debugging through Redux Remote Devtools, using socket.io, and sometimes even used internally on NativeScript itself.</p><p>When researching the best solutions for a WebSocket polyfill, there were no implementations better and more battle-tested than React Native’s. With that in mind, we decided to use its approach with NativeScript. Something we later discovered to be surprisingly easy and painless due to the overall quality of the original implementation. There were only three functions that were not needed (logs and assertion helpers from the original implementation), but we reimplemented them to maintain a similar behavior. In the end, it proves that React Native and NativeScript are really complementary and quite interoperable.</p><p>Visit <a href="https://www.npmjs.com/package/@valor/nativescript-websockets">npm to use the WebSocket plugin for NativeScript</a>, and scroll down to proceed with the Implementation part!</p><h3>Implementation</h3><p>Check <a href="https://github.com/valor-software/nativescript-plugins">the original React Native implementation</a> from where we started. Except for a few functions, like RCTAssert, RCTAssertParam, and RCTLog, everything else just worked straight out of the box. Since NativeScript allows you to use platform APIs as they were designed (most often synchronously) and without the need for a bridge, we also could drop all related bridge code from the React Native implementation and adjust a few data handling items (eg. converting from NSData to ArrayBuffer).</p><p><strong>Here are the steps we followed:</strong></p><ol><li>Created <a href="https://github.com/valor-software/nativescript-plugins">a public workspace for NativeScript plugins</a> based on the NativeScript <a href="https://docs.nativescript.org/plugins/plugin-workspace-guide.html">plugin workspace docs</a> and generated the plugin through Nx.</li><li>Created a native-src with the Xcode project and built scripts, using the React Native approach.</li><li>Ran `ns typings ios` from within the “apps/demo” folder to generate the typings for the RCTSRWebSocket class.</li><li>Started writing the WebSocket polyfill straight from JavaScript, which involved creating the Native delegate and calling a callback when these events happened.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2D8Cz09_18cgAZMr.png" /></figure><ol><li>Made small adjustments related to data types and converted them to JavaScript types when needed.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ffagN602g9ocLD5a.png" /></figure><p>And the remaining implementations were just a case of translating Objective-C to TypeScript:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bXiboWFgEZhrc24J.png" /></figure><p>One critical detail to remember about JavaScript while handling any platform API is that it’s single-threaded. Blocking calls will block the main thread, which is the reason we needed a native implementation on iOS. The iOS native implementation is responsible for receiving the calls, doing work on other threads, and then returning back to the main thread.</p><p>On the Android side, React Native uses OkHttp for its WebSockets, which already handles all the threading details, so we needed no native code at all! All we did here was translate the Java implementation to JavaScript. However, we could have used the .java “as-is” as well, which is where NativeScript is endlessly versatile (you have the choice), so we just decided to use TypeScript here.</p><p>Another interesting detail with NativeScript is the sheer reduction in code to manage — originally this code was over 400 lines: <a href="https://github.com/facebook/react-native/blob/ff568b0b1f1b58af1fa5704e46e15a0cbd21dc48/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java">WebSocket module</a>. And we were able to transform this into under 150 lines of easy to read JavaScript code. Below you can see a comparison between the original code for WebSocket module in Java and in NativeScript:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9HxUs_Xd7T6fIAQV.png" /></figure><p>A couple of conversions were needed for binary data. In React Native, binary data is converted to a base64 string in JavaScript and converted back to binary on send:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cSSTHXKxr1X76ASZ.png" /></figure><p>In NativeScript, we are able to manipulate native types directly, so we can do a simple conversion:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8E0SqPSB8_WWiGO6.png" /></figure><p>The same adjustments made to convert NSData to ArrayBuffer were needed for Android, but since Android doesn’t have a helper for it, we converted from ByteString:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*X_9AnFGoO1LoyUEw.png" /></figure><p>And that’s it! We now have a nice API for WebSockets for both iOS and Android platforms with a well defined interface:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5j4yZKyBOSxpyrv8.png" /></figure><p>Then it was just a matter of wiring up a simple WebSocket polyfill.</p><h3>Not to compete, but evolve and excel</h3><p>In conclusion, we want to underline that it is part of our mission as a company and engineers individually to contribute to open-source technologies and make them even better. This way, we can provide developers from around the world with more flexible and responsible options to build software products and overcome their daily challenges. Also, NativeScript is in no way competing with other JS frameworks and on the contrary can be applied in combination with other technologies, for example inside Capacitor by Ionic.</p><p>Finally, we admire the quality with which WebSockets polyfill was implemented in React Native. The code structure with minimum dependencies allowed us to use it with NativeScript quite effortlessly.</p><h3>Useful links</h3><p>- <a href="https://www.npmjs.com/package/@valor/nativescript-websockets">WebSockets plugin for NativeScript on npm</a></p><p>- <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket">Connecting WebSocket through API</a></p><p>- <a href="https://reactnative.dev/docs/network#websocket-support">React Native documentation about WebSocket support</a></p><p>- <a href="https://docs.nativescript.org/native-api-access.html">Docs on native API access</a></p><p>- <a href="https://docs.nativescript.org/advanced-concepts.html#adding-objectivec-swift-code">Adding ObjectiveC/Swift code to NativeScript</a></p><p>- <a href="https://docs.nativescript.org/advanced-concepts.html#ios-marshalling">iOS marshalling</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1cc1ac4da8e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Diving into seeking issue with MP3 files]]></title>
            <link>https://valorsoftware.medium.com/diving-into-seeking-issue-with-mp3-files-684631e46822?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/684631e46822</guid>
            <category><![CDATA[mp3]]></category>
            <category><![CDATA[issue-with-mp3]]></category>
            <category><![CDATA[audio-files]]></category>
            <category><![CDATA[bit-rate]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Wed, 23 Mar 2022 19:47:34 GMT</pubDate>
            <atom:updated>2022-03-23T19:48:20.609Z</atom:updated>
            <content:encoded><![CDATA[<p>The following story is kindly provided by Eduardo, an Angular developer at Valor Software.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hAuUtc_U-YFM29cPxfB1Xg.png" /></figure><h3>Playing stops before the end of the track</h3><p>While working on a 10 million-plus user meditation app, I ran into a very weird issue. While the Quality Assurance team was trying to play a track, it would stop playing with around 13 seconds remaining and report as completed. After some testing, it seems the issue was only happening when there was seeking involved. Playing the whole track always ended at 0s remaining, pausing and playing had no impact either.</p><h3>Seeking within MP3</h3><p>Looking into the file format, I found it is an <a href="https://en.wikipedia.org/wiki/MP3#Design">MP3</a>. There are many ways to seek within an audio file. One way is to build an index of timestamps and byte offsets, like checkpoints in the track. Option two is what MP3 does: nothing. MP3 has <a href="https://en.wikipedia.org/wiki/MP3#Bit_rate">bitrate</a>, so you seek it by multiplying bitrate with time. Want to seek 6.3 seconds in a 100 kbps file? — Jump to the 630kb position (small b here means bits, not bytes). Should be somewhat accurate, but a 10s+ difference seems a bit too much. That is until you analyze the files:</p><pre>$ mpck ./track_with_issue.mp3<br>SUMMARY: track_with_issue.mp3<br>    version                       MPEG v1.0<br>    layer                         3<br>    average bitrate               279491 bps (VBR)<br>    samplerate                    44100 Hz<br>    frames                        31503<br>    time                          13:42.935<br>    unidentified                  0 b (0%)<br>    errors                        none<br>    result                        Ok</pre><pre>$ mpck ./other_track.mp3<br>SUMMARY: ./other_track.mp3<br>    version                       MPEG v1.0<br>    layer                         3<br>    bitrate                       160000 bps<br>    samplerate                    44100 Hz<br>    frames                        131786<br>    time                          57:22.573<br>    unidentified                  0 b (0%)<br>    errors                        none<br>    result                        Ok</pre><p>Notice the first track has an Average Bitrate. Welcome to the marvelous world of VBR, also known as Variable Bit Rate. MP3 encoded in VBR means you don’t know where exactly to seek to because the bitrate isn’t constant, so you “guess” based on the average bit rate. Imagine you have 10 seconds of 100 kbps and 10 seconds of 200 kbps bitrate in a single file. You have 100kbps from 0 to 1000kb and 200kbps from 1001 kb to 3000 kb. The average bitrate is 150 kbps, but if you seek to 10 seconds using the previous formula, you’d seek to 1500 kb, which is already in the 200 kbps part, jumping to about 12.5 seconds of the track. Also, note that if you inverted the parts, you’d be at 7.5s.</p><blockquote>Disclaimer.<em><br>‍</em>Please, keep in mind that calculations have been simplified to make them easier to understand and are not 100% accurate.<em>‍</em></blockquote><h3>Solution time!</h3><p>So what could be done? — Well, the first obvious solution would be to just re-encode it in MP3 with <a href="https://en.wikipedia.org/wiki/Constant_bitrate">CBR</a> (constant bit rate). The most elegant solution, though, is to drop MP3 completely and start using better audio formats like Vorbis/Opus (.ogg), which have proper support for seeking.</p><p>Thanks for attention! Hope to be back with the next discovery soon :)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=684631e46822" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TagTide library: make your HTML editor-friendly and more]]></title>
            <link>https://medium.com/tagtide-library-make-your-html-editor-friendly-and/a-couple-of-months-ago-our-customer-approached-me-with-an-interesting-task-that-appeared-not-93b68b17ada5?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/93b68b17ada5</guid>
            <category><![CDATA[html]]></category>
            <category><![CDATA[html-editor]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Wed, 09 Mar 2022 15:47:43 GMT</pubDate>
            <atom:updated>2022-03-09T22:13:25.068Z</atom:updated>
            <content:encoded><![CDATA[<p>This material is created and kindly provided by Vyacheslav, a full-stack software engineer of Valor Software. And he’s telling the story below :)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0fzpcLVpX4Lp9Bpt9bLgCg.jpeg" /></figure><h3>Intro</h3><p>A couple of months ago, our customer approached me with an interesting task that appeared not complicated at first glance. I needed to create <strong>a simple web app with a Grammarly-like view based on a WYSIWYG HTML editor</strong>. This app should count different text units like paragraphs, sentences, words, and characters. The best thing is that despite the specifics of text source: websites, Google Docs, MS Office, email clients, your content will be carefully analyzed for units and displayed in a form convenient for perception.</p><p>No prizes for guessing what kind of issue I faced in this case :) External content from most of the resources contains redundant HTML tags, which influences the correctness of calculations. Moreover, messed code makes testing an annoying process. So, eventually, I came up with the <strong>solution for cleaning HTML from unnecessary tags and polishing it for further work with text</strong>.</p><h3>Table of contents:</h3><p>1. Intro part</p><p>2. The principles of HTML code prettifying</p><p>3. My solution — the TagTide library</p><p>4. Approaches and use cases</p><p>5. An example of Google Docs content cleanup</p><p>6. Kind thanks to colleagues</p><p>7. Useful links</p><h3>The principles of HTML code prettifying</h3><p>Let’s think about basic steps to get <strong>“editor-friendly” HTML code</strong>. I’m going to describe the idea with a demonstrative example. Imagine we have a perfect code that defines paragraphs.</p><p>The code could be the following:</p><pre>&lt;p&gt;Shakespeare produced most of his known works between 1589 and 1613.&lt;/p&gt;</pre><pre>&lt;p&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres. He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/p&gt;</pre><p>The example above contains a couple of paragraphs. The second paragraph has two sentences, and the first one — just one.</p><p>Let’s look at the hypothetical <strong>messy equivalent</strong> of the same content:</p><pre>&lt;div id=&quot;content&quot;&gt;</pre><pre>&lt;div class=&quot;sent&quot;&gt;Shakespeare produced most of his known works between &lt;span style=&quot;color: red; font-size: 24px;&quot;&gt;1589&lt;/span&gt; and</pre><pre>&lt;span style=&quot;color: red&quot;&gt;1613&lt;/span&gt;.</pre><pre>&lt;/div&gt;</pre><pre>&lt;div&gt;</pre><pre>&lt;div class=&quot;sent&quot;&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres.&lt;/div&gt;</pre><pre>&lt;div class=&quot;sent&quot;&gt;He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/div&gt;</pre><pre>&lt;div&gt;</pre><pre>&lt;/div&gt;</pre><h4>What issues this messiness brings:</h4><p>1. <strong>Redundant root div</strong> that should be ignored.</p><p>2. Since a div inside another div is correct, but a <strong>paragraph inside another paragraph</strong> is a mistake, it’s challenging to split the text into paragraphs.</p><p>3. <strong>Inline styles</strong> uglify whole text perception, but they don’t even influence calculation quality. We should get rid of them for aesthetic reasons.</p><h4>Steps to editor-friendly text</h4><p>Our main goal is to transform the messy text into perfect paragraphs. Let’s call this kind of text “editor-friendly”. As far as the task contains various logic, we need to split the flow into separate activities. Let’s dig into that!</p><p>The first reasonable thing is that we need to <strong>ignore root div</strong>.</p><pre>&lt;div class=&quot;sent&quot;&gt;Shakespeare produced most of his known works between &lt;span style=&quot;color: red; font-size: 24px;&quot;&gt;1589&lt;/span&gt; and &lt;span style=&quot;color: red&quot;&gt;1613&lt;/span&gt;.&lt;/div&gt; &lt;div&gt;&lt;div class=&quot;sent&quot;&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres.&lt;/div&gt; &lt;div class=&quot;sent&quot;&gt;He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;</pre><p>Looks better. Secondly, we should flatten the second paragraph. It means we should leave <strong>only one level of divs</strong> (in our case — root):</p><pre>&lt;div class=&quot;sent&quot;&gt;Shakespeare produced most of his known works between 1589 and 1613.&lt;/div&gt; &lt;div&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres. He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/div&gt;</pre><p>Our text looks much better because the second paragraph looks good. But if we talk about editor-friendly HTML code, we should <strong>change divs</strong> <strong>to</strong> pure <strong>paragraphs</strong>:</p><pre>&lt;p class=&quot;sent&quot;&gt;Shakespeare produced most of his known works between 1589 and 1613.&lt;/p&gt;&lt;p&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres. He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/p&gt;</pre><p>Looks almost perfect! We have just minor stuff left. We need to <strong>remove</strong> all redundant <strong>tag attributes</strong> (<strong>“class”</strong> in our case):</p><pre>&lt;p&gt;Shakespeare produced most of his known works between 1589 and 1613.&lt;/p&gt;&lt;p&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres. He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/p&gt;</pre><p>That’s it. We have 100% editor-friendly HTML code that contains a couple of pure paragraphs!</p><p><strong>In conclusion, I want to summarize all provided steps that you can use as separate pattern:</strong></p><p>1. Start from a certain point in the original code. You can ignore redundant tags that aren’t important anymore.</p><p>2. Flatten the code by removing redundant inner tags. The main goal of this step is preparation for paragraph splitting.</p><p>3. Change div tags to paragraphs.</p><p>4. Remove redundant attributes.</p><h3>Technical approaches</h3><p>At the start of the project, I intended to resolve the principles above via sets of pure regular expressions. As you can guess, this attempt failed. Regular expressions are not friendly with deep-nested lexemes. In this case, by lexemes, I mean nested HTML tags and their attributes. Having thought a little, I decided to use the <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">AST-based</a> approach because I shouldn’t reinvent the wheel in this case. I chose the <a href="https://github.com/henrikjoreteg/html-parse-stringify">HTML-parse-stringify library</a> because this solution is minimalistic yet contains all the expected functionality regarding HTML AST processing.</p><h3>My solution — the TagTide library</h3><p>So, please, welcome <a href="https://github.com/buchslava/tag-tide">TagTide</a>. The main idea of the solution is a set of patterns you can apply anytime when you need them. Also, I suggest you apply them in sequence, just like the steps above.</p><p><strong>Let’s repeat the steps of the above text transformations using TagTide.</strong></p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original = `</pre><pre>&lt;div id=&quot;content&quot;&gt;&lt;div class=&quot;sent&quot;&gt;Shakespeare produced most of his known works between &lt;span style=&quot;color: red; font-size: 24px;&quot;&gt;1589&lt;/span&gt; and</pre><pre>&lt;span style=&quot;color: red&quot;&gt;1613&lt;/span&gt;.&lt;/div&gt;</pre><pre>&lt;div&gt;&lt;div class=&quot;sent&quot;&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres.&lt;/div&gt;</pre><pre>&lt;div class=&quot;sent&quot;&gt;He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/div&gt;&lt;div&gt;&lt;/div&gt;`;</pre><pre>const tagTide = new TagTide(original).startAfter(&quot;id&quot;, /^content/);</pre><pre>const startedAfter = tagTide.result();</pre><pre>console.log(startedAfter, &quot;\n&quot;);</pre><pre>tagTide.flatten();</pre><pre>const flattened = tagTide.result();</pre><pre>console.log(flattened, &quot;\n&quot;);</pre><pre>tagTide.rootParagraphs();</pre><pre>const paragraphs = tagTide.result();</pre><pre>console.log(paragraphs, &quot;\n&quot;);</pre><pre>tagTide.removeAttributes();</pre><pre>const pureHtml = tagTide.result();</pre><pre>console.log(pureHtml);</pre><p>Also, TagTide allows you to get the expected result in the shortest way <strong>handling the request in sequence</strong> by different objects (I use <a href="https://www.dofactory.com/javascript/design-patterns/chain-of-responsibility">a chain of responsibility pattern</a>):</p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original = `</pre><pre>&lt;div id=&quot;content&quot;&gt;&lt;div class=&quot;sent&quot;&gt;Shakespeare produced most of his known works between &lt;span style=&quot;color: red; font-size: 24px;&quot;&gt;1589&lt;/span&gt; and</pre><pre>&lt;span style=&quot;color: red&quot;&gt;1613&lt;/span&gt;.&lt;/div&gt;</pre><pre>&lt;div&gt;&lt;div class=&quot;sent&quot;&gt;His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres.&lt;/div&gt;</pre><pre>&lt;div class=&quot;sent&quot;&gt;He then wrote mainly tragedies until 1608, among them Hamlet, Romeo and Juliet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language.&lt;/div&gt;&lt;div&gt;&lt;/div&gt;`;</pre><pre>const pureHtml = new TagTide(original).startAfter(&quot;id&quot;, /^content/).flatten().rootParagraphs().removeAttributes().result();</pre><pre>console.log(pureHtml);</pre><h3>Use cases</h3><p>There is a couple of fundamental approaches how to change your HTML code using TagTide:</p><p>1. Direct changes via <strong>`traverse`</strong>;</p><p>2. Changes via the <strong>set of patterns</strong>.</p><p>This part contains different examples illustrating the approaches above.</p><h4>Change some content in 2nd nesting level</h4><pre>import { El, TagTide } from &quot;tag-tide&quot;;</pre><pre>const original = &quot;&lt;div&gt;level 1 &lt;div&gt;level 2 &lt;div&gt;level 3&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;;</pre><pre>const prettified = new TagTide(original)</pre><pre>.traverse((el: El, level: number) =&gt; {</pre><pre>if (level === 2 &amp;&amp; el.content) {</pre><pre>el.content = `modified ${el.content}`;</pre><pre>}</pre><pre>})</pre><pre>.result();</pre><pre>console.log(prettified);</pre><p>Output:</p><pre>&lt;div&gt;level 1 &lt;div&gt;modified level 2 &lt;div&gt;level 3&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</pre><h4>Aggregate numeric values from different nesting levels</h4><pre>import { El, TagTide } from &quot;tag-tide&quot;;</pre><pre>const original = &quot;&lt;div&gt;1 &lt;div&gt;2 &lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;;</pre><pre>let total = 0;</pre><pre>new TagTide(original).traverse((el: El) =&gt; {</pre><pre>if (el.content) {</pre><pre>total += +el.content.trim();</pre><pre>}</pre><pre>});</pre><pre>console.log(total);</pre><p>Output:</p><pre>`6`</pre><h4>Strip some tags</h4><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original = &quot;&lt;div&gt;level 1 &lt;div&gt;&lt;a href=&#39;#&#39;&gt;&lt;span&gt;level &lt;i&gt;2&lt;/i&gt;&lt;/span&gt;&lt;/a&gt; &lt;div&gt;level 3&lt;br&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&quot;;</pre><pre>const prettified = new TagTide(original)</pre><pre>.result([&#39;a&#39;, &#39;span&#39;, &#39;i&#39;, &#39;br&#39;]);</pre><pre>console.log(prettified);</pre><p>Output:</p><pre>&lt;div&gt;level 1 &lt;div&gt;level 2 &lt;div&gt;level 3&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</pre><h4>Start after or Start from</h4><p>This pattern allows us to <strong>ignore</strong> some <strong>parent structures:</strong></p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const source = `&lt;body&gt;&lt;div class=&quot;container-1&quot;&gt;&lt;p&gt;content&lt;/p&gt;&lt;/div&gt;&lt;/body&gt;`;</pre><pre>const result = new TagTide(source)</pre><pre>.startAfter(&quot;class&quot;, /^container-\d/)</pre><pre>.result();</pre><pre>console.log(result);</pre><p>Output:</p><pre>&lt;p&gt;content&lt;/p&gt;</pre><p>The following code illustrates another approach to <strong>set starter-tag</strong>. Also, a related tag can be included:</p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const source = `&lt;body&gt;&lt;div class=&quot;container-1&quot;&gt;&lt;p&gt;content&lt;/p&gt;&lt;/div&gt;&lt;/body&gt;`;</pre><pre>const result = new TagTide(source)</pre><pre>.startFrom(&quot;class&quot;, /^container-\d/)</pre><pre>.result();</pre><pre>console.log(result);</pre><p>Output:</p><pre>&lt;div class=&quot;container-1&quot;&gt;&lt;p&gt;content&lt;/p&gt;&lt;/div&gt;</pre><p><strong>Important notes:</strong></p><p>1. In the “Start from” case, the result will include the associated (search) tag.</p><p>2. If no matching tag is found, these patterns don’t affect the result.</p><h4>Flatten</h4><p>This pattern will be useful if we need to <strong>remove nested tags:</strong></p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original =</pre><pre>&quot;&lt;div&gt;1 &lt;div id=&#39;first&#39;&gt;2 &lt;div&gt;&lt;span class=&#39;foo&#39; style=&#39;color: red;&#39;&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; middle &lt;div&gt;4 &lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&quot;;</pre><pre>const prettified = new TagTide(original).flatten().result();</pre><pre>console.log(prettified);</pre><p>Output:</p><pre>&lt;div&gt;1 2 3&lt;/div&gt; middle &lt;div&gt;4 5&lt;/div&gt;</pre><p>Also, you can <strong>omit any tags</strong> that you want except for those you need to keep (ex. &lt;br&gt;, &lt;b&gt;,etc.):</p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original =</pre><pre>&quot;&lt;div&gt;1 &lt;div id=&#39;first&#39;&gt;2 &lt;div&gt;&lt;span class=&#39;foo&#39; style=&#39;color: red;&#39;&gt;&lt;a href=&#39;1&#39; target=&#39;_blank&#39;&gt;3&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; middle &lt;div&gt;4 &lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&quot;;</pre><pre>const prettified = new TagTide(original).flatten([&#39;a&#39;]).result();</pre><pre>console.log(prettified);</pre><p>Output:</p><pre>&lt;div&gt;1 2 &lt;a href=&quot;1&quot; target=&quot;_blank&quot;&gt;3&lt;/a&gt;&lt;/div&gt; middle &lt;div&gt;4 5&lt;/div&gt;</pre><h4>Remove attributes</h4><p>This pattern allows removing all or some attributes through the whole content.</p><p>In the following example, I’ve <strong>removed all attributes</strong>:</p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original =</pre><pre>&quot;&lt;div&gt;1 &lt;div id=&#39;first&#39;&gt;2 &lt;div&gt;&lt;span class=&#39;foo&#39; style=&#39;color: red;&#39;&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; middle &lt;div&gt;4 &lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&quot;;</pre><pre>const prettified = new TagTide(original).flatten().removeAttributes().result();</pre><pre>console.log(prettified);</pre><p>Output:</p><pre>&lt;div&gt;1 &lt;div&gt;2 &lt;div&gt;&lt;span&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; middle &lt;div&gt;4 &lt;div&gt;5&lt;/div&gt;&lt;/div&gt;</pre><p>In the following example, all attributes have been removed except:</p><p>* <strong>`id`</strong> attribute in all <strong>`span`</strong> tags</p><ul><li>all <strong>`style`</strong> attributes</li></ul><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original =</pre><pre>&quot;&lt;div&gt;1 &lt;div id=&#39;first&#39;&gt;2 &lt;div&gt;&lt;span id=&#39;s1&#39; class=&#39;foo&#39; style=&#39;color: red;&#39;&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; middle &lt;div style=&#39;color: red;&#39;&gt;4 &lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&quot;;</pre><pre>const prettified = new TagTide(original).removeAttributes({&#39;span&#39;: [&#39;id&#39;], &#39;*&#39;: [&#39;style&#39;]}).result();</pre><pre>console.log(prettified);</pre><p>Output:</p><pre>&lt;div&gt;1 &lt;div&gt;2 &lt;div&gt;&lt;span id=&quot;s1&quot; style=&quot;color: red;&quot;&gt;3&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt; middle &lt;div style=&quot;color: red;&quot;&gt;4 &lt;div&gt;5&lt;/div&gt;&lt;/div&gt;</pre><h4>Root paragraphs</h4><p>We need to <strong>replace the</strong> <strong>`divs`</strong> in the first level of HTML <strong>with</strong> <strong>`paragraphs`</strong>. If the <strong>`plain text`</strong> appears at the <strong>`first level`</strong> instead of another tag, it should be enclosed in a <strong>`paragraph`</strong>.</p><p>The following example shows how this approach works:</p><pre>import { TagTide } from &quot;tag-tide&quot;;</pre><pre>const original = &quot;&lt;div&gt;start&lt;/div&gt; middle &lt;div&gt;finish&lt;/div&gt;&quot;;</pre><pre>const prettified = new TagTide(original).rootParagraphs().result();</pre><pre>console.log(prettified);</pre><p>Output:</p><pre>&lt;p&gt;start&lt;/p&gt;&lt;p&gt; middle &lt;/p&gt;&lt;p&gt;finish&lt;/p&gt;</pre><h3>An example of Google Docs content cleanup</h3><p>The icing on the cake is the case with code that I took from an <strong>actual Google Docs document</strong>. Not to overload this article, I placed this <a href="https://github.com/buchslava/tag-tide/blob/main/examples/complex.ts">example of code containing HTML on GitHub</a>.</p><p>If you checked <a href="https://github.com/buchslava/tag-tide/blob/main/examples/complex.ts">the link</a>, I suppose you might be surprised by the volume of HTML in the document. Now let’s imagine you need to work with the text inside. Can you see the text there, by the way? — I guess not really because of lots of trash tags and attributes. The most exciting thing in this story is that using the TagTide script, you can <strong>transform code </strong>from my example <strong>into the following HTML</strong>:</p><pre>&lt;p&gt; Test test test &lt;/p&gt;&lt;p&gt; Test test test &lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://www.microsoft.com&quot;&gt;Test&lt;/a&gt; test test&lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;p&gt; 12 34 &lt;/p&gt;&lt;br/&gt;&lt;br/&gt;&lt;ul&gt;&lt;li&gt;Aaa&lt;/li&gt;&lt;li&gt;Bbb&lt;/li&gt;&lt;li&gt;Ccc&lt;/li&gt;&lt;/ul&gt;&lt;br/&gt;&lt;p&gt;The end&lt;/p&gt;</pre><p><strong>And you can repeat the same with the code taken from email clients, MS Word documents, and other sources!</strong></p><h3>In conclusion, I want to thank:</h3><p>- <a href="https://www.linkedin.com/in/waqas-younas/">Waqas Younas</a> for long and fruitful cooperation</p><p>- <a href="https://github.com/HenrikJoreteg">Henrik Joreteg</a> for <a href="https://github.com/henrikjoreteg/html-parse-stringify">html-parse-stringify</a></p><p>And in case you’re interested in other solutions for text editing and processing, check my <a href="https://valor-software.com/articles/multi-highlighting-for-draftjs.html">Multi-Highlighting for DraftJS </a>article. There I share how I added an option to highlight paragraphs, phrases, and words in DraftJS with my solution written in TypeScript.</p><h3>Useful links</h3><ul><li><a href="https://valor-software.com/articles/multi-highlighting-for-draftjs.html">‍HTML-parse-stringify library</a> for HTML AST processing</li><li><a href="https://github.com/buchslava/tag-tide">The TagTide library on GitHub</a></li><li><a href="https://github.com/henrikjoreteg/html-parse-stringify">TagTide on npm</a></li><li><a href="https://valor-software.com/articles/multi-highlighting-for-draftjs.html">‍Multi-Highlighting for DraftJS</a> — my other solution for highlighting text units in DraftJS editor</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=93b68b17ada5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/tagtide-library-make-your-html-editor-friendly-and/a-couple-of-months-ago-our-customer-approached-me-with-an-interesting-task-that-appeared-not-93b68b17ada5">TagTide library: make your HTML editor-friendly and more</a> was originally published in <a href="https://medium.com/tagtide-library-make-your-html-editor-friendly-and">TagTide library: make your HTML editor-friendly and more</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Performance Testing via Artillery.io]]></title>
            <link>https://valorsoftware.medium.com/performance-testing-via-artillery-io-59d3d82646c0?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/59d3d82646c0</guid>
            <category><![CDATA[test-automation]]></category>
            <category><![CDATA[load-testing]]></category>
            <category><![CDATA[artilleryio]]></category>
            <category><![CDATA[performance-testing]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Tue, 15 Feb 2022 21:51:59 GMT</pubDate>
            <atom:updated>2022-02-17T17:01:09.096Z</atom:updated>
            <content:encoded><![CDATA[<p>This material was created by Dmitry Zhemchugov, Head of QA at Valor Software. And he’s the one telling the story :)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lbyQgO-PyqfBxe6oO34zNw.jpeg" /></figure><p>Today, I’m going to tell you all about setting up and using <a href="https://artillery.io/">Artillery.io</a> on your projects. We have a lot of popular testing tools like JMeter, Gatling, LoadNinja, etc. So you might be wondering why I’ve chosen Artillery. Well, I’m going to show its advantages and share handy usage tips later in the article.</p><p><strong>‌</strong>Artillery.io is a modern performance testing software that is powerful yet quite simple to navigate. It suits different types of performance testing, on local machines and as part of <a href="https://en.wikipedia.org/wiki/Continuous_integration">CI</a>. What’s even better? — Artillery provides <a href="https://www.artillery.io/pricing">a free ‘Dev’ option</a> if you want to test the waters before diving in. So, let’s make some exploration!</p><h3>Why Artillery?</h3><p>Artillery offers many services that make performance and load testing faster and more reliable. Now let me outline a few reasons why I choose to use it over the better-known software.</p><h4>1. Node.js tool</h4><p>Java-based tools can be complicated and time-consuming to work with. Artillery.io is a Node.js tool with a basic <a href="https://en.wikipedia.org/wiki/Command-line_interface">СLI</a>, which is a far simpler way to run performance tests.</p><h4>2. Broad language support</h4><p>Out of the box<strong>,</strong> Artillery.io will support:</p><ul><li>Out of the box<strong>,</strong> Artillery.io will support:</li><li><a href="https://www.artillery.io/docs/guides/guides/http-reference">HTTP</a></li><li><a href="https://www.artillery.io/docs/guides/guides/socketio-reference">Socket.IO</a></li><li><a href="https://www.artillery.io/docs/guides/guides/ws-reference">WebSocket</a></li></ul><p>However, you can use Artillery.io to test any stack, thanks to the plugin interface. Here are a few examples:</p><ul><li>Apache Kafka</li><li>Amazon Kinesis</li><li><a href="https://www.artillery.io/docs/guides/plugins/plugin-hls">HLS</a> (HTTP Live Streaming)<strong>‌</strong></li></ul><h4>3. YAML format</h4><p>Unlike popular competitors’ testing tools, Artillery.io writes tests in YAML format which makes them readable and easy to understand. Artillery also supports a JSON format for testing.</p><h4>4. Accessible scenarios</h4><p>With Artillery’s scenarios and phases, you can build more flexible tests and cover multiple use cases in parallel. Also, it takes you just a few steps and conditions to set up tests for complex user behaviours. Finally, it’s easy to reuse scenarios for comparison and future testing.</p><h4>5. Easy integration</h4><p>Artillery.io seamlessly integrates with CI <strong>(</strong>for example, check the<strong> </strong><a href="https://artillery.io/docs/guides/integration-guides/github-actions.html">integration guide for GitHub Actions</a><strong>)</strong>, making the process very organized. It also<strong> </strong><a href="https://artillery.io/docs/guides/guides/docker.html">works with Docker</a>, so you can run your Artillery tests inside a Docker container.</p><h4>6. Detailed reports</h4><p>After running tests, you can create a JSON report through the — output flag. Also, you can generate an HTML report out of JSON to save it as a file or export it to your browser. Find details on reporting in the Scenario section below.</p><h4>7. Artillery Docs</h4><p>Artillery provides <a href="https://artillery.io/docs/guides/overview/welcome.html">numerous documents </a>to browse for instructions and other useful info. This helps you get the performance testing software up and running much smoother than with other similar tools. Also, you can look up support from other developers on <a href="https://github.com/artilleryio/artillery/discussions">community forums</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QheH1hv0nVDR7BU_ObKeUg.jpeg" /></figure><h3>How does Artillery.io work?</h3><p>I’m going to walk you through some basic commands to get started with Artillery.io using <a href="https://valor-software.com/ngx-bootstrap/#/">ngx-bootstrap</a> in my examples. Ngx-bootstrap, together with other community libraries, were created by <a href="https://valor-software.com/">Valor Software</a>, the company where I’m working. And as we have hundreds of thousands of engineers using our products monthly, we need scalable performance testing to make sure everything runs smoothly. So, let’s get started!</p><p>First, you need to install the latest version of the software.</p><h4>Install</h4><p>To install the latest version, enter:</p><h4>Fast test</h4><p>To run a fast test, enter:</p><p>With this command, the test will run 15 virtual users with each of them making 30 HTTP GET requests to the URL address that you specified.<strong>‌</strong></p><p>This command also supports other flags to configure your fast tests:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Z8UjcDEEaNpVyawH.png" /></figure><p>Below is a list of possible configurations of the <strong>run</strong> command:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*O9zTA9a5whDhHqwq.png" /></figure><h4>Config</h4><p>For standard test scenarios, you should create a ‘<strong>my.yml</strong>’ file with config.</p><figure><img alt="Artillery configuration file my.yml" src="https://cdn-images-1.medium.com/max/1024/0*JTPpPaiDWIbu3oqa.png" /></figure><p>The <strong>my.yml</strong> file should contain <strong>‘config:’ </strong>paired with the target URL. To set up environments for your tests, you can write environments into the command after the target URL has been entered.</p><figure><img alt="Artillery setup of test environments" src="https://cdn-images-1.medium.com/max/1024/0*bDEFyLP9MQR4FI3p.png" /></figure><h4>Phases in the config</h4><p>Artillery.io allows you to enter<strong> </strong>multiple sequential load options for the application. The testing phase consists of several aspects:</p><ul><li><strong>duration</strong>: the time of one phase;</li><li><strong>arrivalRate</strong>: the number of users added each second;</li><li><strong>rampTo</strong>: up to how many users per second the load will grow by;</li><li><strong>name</strong>: a name of the phases.</li></ul><figure><img alt="Artillery config phases" src="https://cdn-images-1.medium.com/max/1024/0*8oDLrfNx2UpW0hmY.png" /></figure><p>If you only have one target URL, the different phases of the performance testing are placed right after it. If there are multiple environments, then you should add phases to each environment variable.</p><h4>Plugins</h4><p>In the Artillery npm utility, you can find <a href="https://www.npmjs.com/search?q=artillery-plugin-&amp;page=0&amp;perPage=20">lots of plugins</a> that can help you in your performance testing. Install <a href="https://www.npmjs.com/package/artillery-plugin-expect"><em>artillery-plugin-expect</em></a> to compare the expected result with the actually received result.</p><p>Then, after the phases inside the config, enter:</p><figure><img alt="Artillery performance testing — plugins" src="https://cdn-images-1.medium.com/max/1024/0*ylGYg8B8L8Bm4-d7.png" /></figure><p>Now, with the config completed, we can finally write some tests!</p><h4>Scenario</h4><p>All tests should be written in the <strong>`scenarios`</strong> section and should contain:</p><ul><li>GET, POST, PUT, DELETE, and some other commands;</li><li>a URL for every endpoint;</li><li>the body text in JSON format;</li><li>all checks you want to run.</li></ul><figure><img alt="writing tests in Artillery" src="https://cdn-images-1.medium.com/max/1024/0*7BRD3e6MAr2hXMTO.png" /></figure><p>To run tests, you should enter:</p><p>To run a test and then generate a report in a .txt file, run:</p><p>After generating a .txt report, to generate a second report in HTML, run the command:</p><p>Here’s the report example:</p><figure><img alt="Artillery load testing — report example" src="https://cdn-images-1.medium.com/max/1024/0*kd5BqEwo_w4iOB7K.jpg" /></figure><h4>Authentication</h4><p>With Artillery.io, you can use<strong> </strong>basic authentication or get authorized by tokens uploading CSV with your credentials. Just pick an option that suits your project best.</p><p>Check their <a href="https://artillery.io/docs/guides/guides/http-reference.html#Basic-Authentication">official documentation</a> to find information on authentication and many other aspects of Artillery.io.</p><figure><img alt="Artillery — authorisation" src="https://cdn-images-1.medium.com/max/1024/0*8-7tFKiy1uH6GEk9.png" /></figure><p>Let’s first revise how you can get authorized by tokens. To upload a CSV file with credentials, you should add the following lines into the config file:</p><ul><li><strong>payload</strong> — to use the payload functionality;</li><li><strong>path</strong> — to write a path for the CSV file that Artillery needs to use;</li><li><strong>fields</strong> — names of fields that you need to use.</li></ul><figure><img alt="Artillery — upload CSV file to authorise by tokens" src="https://cdn-images-1.medium.com/max/1024/0*qPbGFOkfx4Rgg0dv.png" /></figure><p>See an example with a CSV file:</p><figure><img alt="Artillery — CSV authentification" src="https://cdn-images-1.medium.com/max/1024/0*vvMMER5novgg7gww.png" /></figure><p>The second way to get authorized is through environment variables. And we have several options for this. The first one is to execute an <strong>export</strong> command in your console as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*qV8drkPuW7SMVP-Y.png" /></figure><p>And after it’s done, you can use your variable inside your yml file:</p><figure><img alt="Artillery — authentication with environment variable" src="https://cdn-images-1.medium.com/max/1024/0*-Axw7eeqnubQkohN.png" /></figure><p>Another option is to write a new script with an environment variable in your <strong>package.json</strong> file to execute your tests without typing the <strong>export</strong> variable in the terminal each time. It will look like this:</p><figure><img alt="Artillery performance testing — package json" src="https://cdn-images-1.medium.com/max/1024/0*7P8NaVzQKMZKXqjS.png" /></figure><p>Then all you need to do is run this script in the terminal:</p><p>Finally, you can upload CSV to pass parameters for your test requests: GET, POST, PUT, etc.</p><p>Check an example with POST:</p><figure><img alt="Artillery — past parameters for test requests" src="https://cdn-images-1.medium.com/max/1024/0*3vuuErqTc6o-LswF.png" /></figure><h3>Final thoughts</h3><p>Artillery.io is an accessible and comprehensive tool that I use and can surely recommend for performance testing.</p><p>With a free <a href="https://artillery.io/docs/guides/getting-started/installing-artillery.html">Artillery Core</a> version, you can run tests from a local or virtual machine. And by switching to <a href="https://artillery.io/docs/guides/getting-started/installing-artillery-pro.html">Artillery Pro</a>, you’re getting a self-hosted AWS performance testing platform.</p><p>Other features of Artillery Pro include:</p><ul><li>runs millions of tests per second across 13 geographical regions;</li><li>works with existing security systems;</li><li>no repeat charges or paid maintenance;</li><li>VPC internal test services.</li></ul><p>I hope this article has given you some clarity on Artillery’s software, and helped you make a choice regarding the performance testing tool you want to work with. <strong>‌</strong>‍</p><p>In case you’re looking for help in software testing, or your project needs an advanced quality assurance pipeline — <a href="https://valor-software.com/contact.html">drop us a line</a>!</p><h3>Useful Links</h3><p>- <a href="https://www.artillery.io/docs/guides/getting-started/installing-artillery">Installation through npm utility guide</a><br>- <a href="https://artillery.io/docs/guides/overview/welcome.html">Artillery docs</a><br>- <a href="https://www.npmjs.com/search?q=artillery-plugin-&amp;page=0&amp;perPage=20">Artillery plugins on npm</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=59d3d82646c0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ARC — a new weapon against accessibility bugs]]></title>
            <link>https://valorsoftware.medium.com/arc-a-new-weapon-against-accessibility-bugs-9708af96f6fe?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/9708af96f6fe</guid>
            <category><![CDATA[usability-testing]]></category>
            <category><![CDATA[arcaccessibility]]></category>
            <category><![CDATA[accessibility-testing]]></category>
            <category><![CDATA[arctootkit]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Fri, 01 Oct 2021 11:11:22 GMT</pubDate>
            <atom:updated>2021-10-01T11:11:22.980Z</atom:updated>
            <content:encoded><![CDATA[<h3>ARC — a new weapon against accessibility bugs</h3><p>Hello, my name is Maxym, I am QA Automation Engineer at Valor Software, and today I’ll guide you through ARC — a platform for accessibility testing. I’ll help you with setting it up and with creating a couple of scenarios. Also, I’ll make a short comparison between the web version of<a href="https://www.tpgarc.com/"> ARC</a> and <a href="https://chrome.google.com/webstore/detail/arc-toolkit/chdkkkccnlfncngelccgbgfmjebmkmce?hl=en">ARC Toolkit</a>, a Chrome extension for quick accessibility checks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EI91aDghK0R6rBWJfv60QQ.jpeg" /></figure><h3>Why do we need accessibility testing?</h3><p><a href="https://www.w3.org/wiki/Accessibility_testing">Accessibility testing</a> as part of usability testing helps adjust our app to the needs of as many groups of people as possible. For example, it helps us make sure that users with special conditions like vision impairments, hearing disabilities, color blindness, and others can interact with the software smoothly.</p><h3>The ARC Platform traits</h3><p>In this article, I decided to share my experience of using ARC, a solution for accessibility testing that I find pretty handy and affordable (it even has a free extension). I’ll lead you through the setup of the platform because the process itself turned out to be tricky for a person new to ARC. Also, we’ll take a look at the report and specify the areas that we don’t want to miss. Finally, I’ll briefly cover the Toolkit and how this extension can contribute to your QA pipeline.</p><h4>How it works</h4><p>ARC crawls your website and scans pages while making the accessibility analysis. A nice thing about it is that you can run the check on two engines: the default ARC engine or AXE (in the latter case, you use ARC as a service for check and still stick to your trusted AXE testing platform).<em> </em>You can switch between both anytime.</p><p>ARC checks for accessibility problems and features according to <a href="https://en.wikipedia.org/wiki/Web_Content_Accessibility_Guidelines">WCAG accessibility standards</a> and supports versions 2.0 and 2.1. The default scan runs on the ARC engine with the 2.0, but you’re free to switch versions yourself.</p><p>The best part is that you choose the engine and the WCAG version for every page you want to check. As a result of the evaluation, you get a report with the overall number of failures and suggestions for the first-priority fixes.</p><h4>Getting the installation done</h4><p>First, you need to create an account on the <a href="https://www.tpgarc.com/">ARC portal</a>.</p><p>Then, when you have an account — you create a workspace. For that:</p><p>1. Select “Workspaces” in the left menu.</p><p>2. Click the “Add a new workspace” button on the right.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-F5s0Hrq1VaV-tY_.png" /></figure><p>3. Fill in the form and click “Submit”.</p><p>4. Open the workspace, select the “Monitoring” tab and click on “Add a new dashboard”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KtQ7fXMJuFD9dlgq.png" /></figure><p>5. Select “Domain”.</p><p>To learn more about User Flow, you can check <a href="https://www.tpgarc.com/Support">the Support documentation</a> (available after registration). But in short, the principle is the same as in <a href="https://www.softwaretestinghelp.com/what-is-end-to-end-testing/">end-to-end testing</a>: you can upload your Selenium test or create a new one on the ARC platform. For a more precise explanation, check <a href="https://www.tpgarc.com/KnowledgeBase">the ARC Knowledge Base</a>.</p><p>6. Fill in the form, click “next” and then — “Complete”.</p><p><strong>Congratulations, you’ve created a workspace and a dashboard for the page!</strong></p><p>When the dashboard is ready, the scanning of the page will start automatically, and when it’s finished, you’ll get the report. It will pop up right on the monitor, or you can select the “Reports” tab on the left side of the menu and find your report there.</p><p>Also, it’s up to you how often you want to run the checks and whether you want them to run automatically. In the latter case, you just pick the run frequency, like daily, weekly, biweekly, monthly. Or you choose none of those and make accessibility checks upon request.</p><h4>What to look for in the ARC report</h4><p>After ARC completes its scanning, you get a relatively precise report on the failures and weak sides of the page. Also, you’ll see step-by-step guidance on how to fix the accessibility issues. In this part, I’ll outline the most important categories in the report that you should check in the first place.</p><p>1) WCAG priorities: here, ARC will highlight the most critical errors you need to prioritise and fix.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4Iu53s21vC0jtdlo.png" /></figure><p>2) Top Assertions: ARC will sort failed assertions by quantity.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KdtqprcJcNLxES50.png" /></figure><p>3) Explanation of detected fails with guidelines and recommendations for fixes.</p><p>For viewing the detailed explanation, you click on one of the failed assertions from the list (on the left side of the previous screen).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*le7yBZ2poz3zOZgW.png" /></figure><h3>The ARC Toolkit — extension for a quick check</h3><p>Another tool from the ARC family that I found useful is <a href="https://chrome.google.com/webstore/detail/arc-toolkit/chdkkkccnlfncngelccgbgfmjebmkmce?hl=en">the ARC Toolkit</a>. You simply install it as a Chrome browser extension and evaluate any page you browse through. If the page you want to check is not live yet, you can run the check on your localhost build. A special thing about the tool is that you can check particular sections of the screen instead of getting a whole page evaluation.</p><h4>Installing and applying the Toolkit</h4><p>1. Install <a href="https://chrome.google.com/webstore/detail/arc-toolkit/chdkkkccnlfncngelccgbgfmjebmkmce?hl=en">the extension</a> from the Chrome webstore.</p><p>2. Open the page you want to test for accessibility issues.</p><p>3. Then go to the Chrome dev tools, select the “ARC Toolkit” tab at the end of the list, and click on the “Run tests” big blue button.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AjAb1xFEnnJ1zh15.png" /></figure><p>4. After scanning the page, you will see a complete report with warnings, passed and failed assertions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6uqDL22FYX5087kL.png" /></figure><p>Basically, you get quite the same amount of information as with the ARC platform check: explanations of errors, recommendations for fixes, and guidelines for developers.</p><h4>Scan sections, not pages</h4><p>As I said earlier, unlike the on-site version, the extension can scan sections you need and not full pages. Here are the steps for running this check:</p><p>1. Open DOM tree and select the component/section you need to test.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tiLu0TUQAcrlJojO.png" /></figure><p>2. Then click on the ARC Toolkit tab and select “Filter result for selected node only” from the checkbox. After, you’ll see the selected tag displayed in the “Limited scope”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*uDEQH9rR_P0hMAXZ.png" /></figure><p>3. Run the tests :-)</p><h3>How do the Toolkit and platform versions differ?</h3><p>First of all, the Toolkit is free, and that’s a great advantage. You can start getting to know ARC using the Toolkit, and if it suits you — proceed with the paid version.</p><p>As for functional differences, despite the opportunity for particular page sections check, the Toolkit has a couple of flaws in comparison with the platform:</p><ol><li>Using the Toolkit extension, you can not change the engine (it’s ARC only) and a WCAG version (the 2.1 version is the only one available).</li><li>You can not automate testing using the extension.</li></ol><h3>Pros and cons of ARC for accessibility testing</h3><p>Here are the key things I like about using ARC:</p><ol><li>You get informative reports and suggestions for fixing issues.</li><li>You can switch between different engines and WCAG versions for a more precise check.</li><li>You can integrate the ARC check into your automated testing pipeline.</li></ol><p><strong>What ARC can’t do:</strong></p><ol><li>It does not allow for screen reader testing.</li></ol><p>As an option, you can use a separate tool for this purpose. In the case of my project, I went with the <a href="https://www.freedomscientific.com/products/software/jaws/">JAWS</a> screen reader.</p><ol><li>It can’t check keyboard focus, keyboard navigation, etc.</li></ol><p>Currently, I haven’t found a better solution than testing these things manually :)</p><h3>Prices</h3><p>ARC Toolkit is completely free! As for the site version of ARC — they have 3 plans.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iv2hHXwqtpzyWmlO.png" /></figure><h3>Let’s make accessibility the new norm!</h3><p>ARC platform and its tools for accessibility testing may not be a magic pill that will cover all the aspects in terms of accessibility on your website. But, in my opinion, this solution works and helps you move towards greater usability with simple and clear steps.</p><p>Hopefully, you’ll give it a go and try to reproduce the setup flow I described above to make accessibility testing part of your QA routine. With ARC, you can make your site more friendly to people with special conditions and needs, and it is already a huge change! So, create an account and just do it! :)</p><p>Good luck!</p><h3>Useful links</h3><p><strong>- </strong><a href="https://en.wikipedia.org/wiki/Web_Content_Accessibility_Guidelines">WCAG accessibility standards</a></p><p><strong>- </strong><a href="https://www.tpgarc.com/">ARC portal</a></p><p><strong>- </strong><a href="https://chrome.google.com/webstore/detail/arc-toolkit/chdkkkccnlfncngelccgbgfmjebmkmce">ARC Toolkit for Chrome</a></p><p>Available after you <a href="https://www.tpgarc.com/Signup">get started</a> with the platform:</p><ul><li><a href="https://www.tpgarc.com/KnowledgeBase"><strong>the ARC Knowledge Base</strong></a></li><li><a href="https://www.tpgarc.com/Support"><strong>the ARC Support documentation</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9708af96f6fe" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Setting up your project on GCP fast using Terraform and Kubernetes]]></title>
            <link>https://valorsoftware.medium.com/setting-up-your-project-on-gcp-fast-using-terraform-and-kubernetes-89cdcc5d51f8?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/89cdcc5d51f8</guid>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[cloud-setup]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Mon, 30 Aug 2021 15:25:18 GMT</pubDate>
            <atom:updated>2021-08-30T15:25:18.841Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi! My name is Angelina, and I’m a DevOps Engineer at Valor Software. With the help of this article, we’ll set up your GCP environment to work in tandem with Terraform. I’ll guide you through the setup of a basic cluster. For this, I picked the services that I use daily within my DevOps routine, since they are optimal for your typical project setup. Let me explain myself here! By typical I mean common projects that are natural (habitual) for your company or industry. In this case, I bet you’d appreciate a chance to build the environment once and clone it for future projects. That’s exactly the way you can go with the services I offer below. So, once you learn the basics for the project setup in this stack, you’ll be able to run new ones in the cloud in no time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*scMAuwDpr7whp7VeLO2tqA.png" /></figure><h3>Automated setup with Terraform</h3><p>When we need to set up a cloud infrastructure for the project, we want to make it quickly. That’s when automation tools come to help. <a href="https://www.terraform.io/">Terraform</a> is one of the instruments that serve for automated setup, and it is compatible with more than 70 providers, which is beyond handy. Besides, Terraform is pretty straightforward to get along with thanks to its declarative language. You don’t need to get used to a completely different CLI, like in cases when you switch to another cloud provider.</p><p>Why I suggest picking Terraform from the great variety of services is because it’s perfect for creating reusable infrastructures. You can also clone the existing infrastructure and apply it to your next project, with small alterations if needed. This benefit of reusability, together with the security that Terraform provides, makes it a first-choice technology for solving my daily tasks. BTW, it’s also the right place for keeping electronic keys and hidden or encrypted variables.</p><p>Another good point is that Terraform allows for managing several cloud infrastructures from different providers in parallel with minimal time and effort spent.</p><p>In this article, I’ll lead you through setting up infrastructure on Google Cloud using Terraform, so buckle up!</p><h3>Kubernetes on Google Cloud Platform</h3><p>Being one of the native Google products, Kubernetes is integrated into GCP quite as is, with no alterations. So you can conveniently set it up and manage it through the GCP CLI or with the help of external instruments like Terraform.</p><h3>Before you begin</h3><ol><li><a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects">Create a Google Cloud project</a></li><li><a href="https://cloud.google.com/iam/docs/creating-managing-service-account-keys">Create a service account key</a> and download it in JSON format</li></ol><h3>Terraform and GCP setup step-by-step</h3><p>Create a new directory for the project and create a main.tf file for the Terraform config, and populate it with the following content:</p><pre>provider &quot;google&quot; {<br>credentials = file(&quot;CREDENTIALS_FILE.json&quot;)<br>project = &quot;your-project&quot;<br>region = &quot;us-west1&quot;<br>}</pre><p>Before we jump to the next step of creating a configuration file, we should remember to keep data (e.g. keys, project names) in separate files. This way we ensure project security and give ourselves a chance to reuse the configuration in the future. So, locate the project name, region, and credentials file in a separate file with variables.</p><p>First, create a variables.tf file and declare variables for it:</p><pre>variable &quot;credentials&quot; {<br>  type = string<br>}</pre><pre>variable &quot;project&quot; {<br>  type = string<br>}</pre><pre>variable &quot;region&quot; {<br>  type = string<br>}</pre><p>Add the variables’ values to secrets.tfvars:</p><pre>credentials = &quot;CREDENTIALS_FILE.json&quot;<br>project     = &quot;your-project&quot;<br>region      = &quot;us-west1&quot;</pre><p>Then format your main.tf file like so:</p><pre>provider  &quot;google&quot; {<br>credentials = file(var.credentials)<br>project     = var.project<br>region      = var.region<br>}</pre><p>Now, when your variables are safe, you’ll be able to pick the needed files with variables instead of repeatedly typing in new data in the main file.</p><p>With the `<em>terraform init</em>` command, you’re pulling up modules that you will need to proceed with further steps.</p><pre>terraform init</pre><p>If you see this message, it’s a success! Hurray!‍</p><pre>Terraform has been initialised!</pre><p>For the next step, you add variables to the variables.tf file:</p><pre>variable &quot;cluster_name&quot; {<br>  type = string<br>}</pre><pre>variable &quot;cluster_zone&quot; {<br>  type = string<br>}</pre><pre>variable &quot;app_name&quot; {<br>  type = string<br>}</pre><p>Before creating a cluster, you should first add variables describing this particular cluster to the secrets.tfvars file:</p><pre>cluster_name = “cluster-1”<br>cluster_zone = “us-west1-a”<br>app_name = “test”</pre><p>Now add the cluster configuration to your main.tf file:</p><pre>resource &quot;google_container_cluster&quot; &quot;cluster-1&quot; {<br>  name =  var.cluster_name<br>  location =  var.cluster_zone<br>  initial_node_count = 3<br>  node_config {<br>     labels = {<br>      app = var.app_name<br>      }</pre><pre>    tags = [&quot;app&quot;, var.app_name]<br>  }<br>  <br>  timeouts {<br>    create = &quot;30m&quot;<br>    update = &quot;40m&quot;<br>  }<br>}</pre><p>In the configuration, you define the cluster’s name, its location, and the number of nodes and their labels. The default namespace is the place where you create a cluster.</p><p>To operate with the cluster and its components conveniently, you need to create an output.tf file, where you’ll add variables that will be displayed after running the `<em>terraform apply</em>` command. You can use them later by calling them during configuration.</p><pre>output &quot;cluster&quot; {  value = google_container_cluster.cluster-1.name}</pre><p>Now add a new resource which is deployment:</p><pre>resource &quot;kubernetes_deployment&quot; &quot;example&quot; {<br>  metadata {<br>    name = &quot;terraform-example&quot;<br>    labels = {<br>      app = var.app_name<br>    }<br>  }</pre><pre>  spec {<br>    replicas = 3</pre><pre>    selector {<br>      match_labels = {<br>       app = var.app_name<br>      }<br>    }</pre><pre>    template {<br>      metadata {<br>        labels = {<br>         app = var.app_name<br>        }<br>      }</pre><pre>      spec {<br>        container {<br>          image = &quot;nginx:1.7.8&quot;<br>          name  = &quot;example&quot;</pre><pre>          resources {<br>            limits = {<br>              cpu = &quot;0.5&quot;<br>              memory = &quot;512Mi&quot;<br>            }<br>            requests = {<br>              cpu = &quot;250m&quot;<br>              memory = &quot;50Mi&quot;<br>            }<br>          }</pre><pre>          liveness_probe {<br>            http_get {<br>              path = &quot;/&quot;<br>              port = 80</pre><pre>              http_header {<br>                name  = &quot;X-Custom-Header&quot;<br>                value = &quot;Awesome&quot;<br>              }<br>            }</pre><pre>            initial_delay_seconds = 3<br>            period_seconds = 3<br>          }<br>        }<br>      }<br>    }<br>  }<br>}<br></pre><p>In the deployment configuration, you specify the number of replicas, tags, labels, an image that we’re going to use, as well as internal resources. Finally, you have it all ready for the first launch and go with this command:</p><pre>terraform plan -var-file=secrets.tfvars</pre><p>In this way, you check if the configuration is created properly and if you’re satisfied with the range of resources.</p><p>When you’re quite sure that everything is correct, it’s time for the `<em>terraform apply</em>` command:</p><pre>terraform apply -var-file=secrets.tfvars</pre><p>And don’t forget to specify the file with variables! At this stage, you’ll have to confirm your actions by running a `<em>yes</em>` command. Terraform will build your new GKE cluster on GCP. After the cluster is created, you’ll see the output list.</p><p>For your convenience, in the future, you can split the config file into a few separate files. Place the provider block in the providers.tf file, and a “google_container_cluster” block — in the cluster.tf file.</p><h3>Good job!</h3><p>You’re almost done with your journey, the cluster is up and running, and you can be (ugh) proud of yourself!</p><p>For your future projects, you’ll be able to add more metrics and parameters when creating a resource. This will help you adjust your configuration for solving every particular task.</p><h3>Useful links</h3><ul><li><a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects">Create a Google Cloud project</a></li><li><a href="http://about:blank/">Create a service account key using the Google Cloud Console</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=89cdcc5d51f8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to deploy Firebase Preview Channels on Travis CI]]></title>
            <link>https://medium.com/codex/how-to-deploy-firebase-preview-channels-on-travis-ci-f97ae6bd8d5b?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/f97ae6bd8d5b</guid>
            <category><![CDATA[preview-channel]]></category>
            <category><![CDATA[firebase]]></category>
            <category><![CDATA[travis-ci]]></category>
            <category><![CDATA[firebase-cloud-functions]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Wed, 14 Jul 2021 12:47:37 GMT</pubDate>
            <atom:updated>2021-07-26T15:40:37.805Z</atom:updated>
            <content:encoded><![CDATA[<p>Nikita Glukhi, Software Engineer at Valor Software, kindly provided this story and the solution.</p><p>Firebase has released the long-awaited Preview channel functionality, which allows for the testing of updates. All Firebase (not Firestore!) users can benefit from this hosting feature. And you may be asking what about Travis CI or other hosting users? Can they use preview channels as well?</p><p>At the time of this article’s publication, GitHub Actions does not yet natively provide automated procedures for Travis CI from GitHub. Below is a solution to use preview channels by Firebase with your existing <a href="https://travis-ci.org/">Travis CI</a> deployment environment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BEVNsg89MNonHXDPzzndzA.png" /></figure><h3>Check updates with no risk for users’ experience</h3><p>Let’s face the truth, to check the behavior of your polished and tested updates on production, you had to actually push them to production. Then, in case something fails, — get back to a previous version. Application users might have noticed inappropriate system behavior, which is a pity. To get rid of this kind of working situation, we can now use preview channels. Apply this new Firebase feature to automatically deploy updates and see how they behave, with no risk for real users’ experience. Also, you can comfortably leave comments for your updates, and amend them with your team. The best thing is that this temporary storage will remove itself after a preview channel link expires. No storage place taken!</p><h3>Prerequisites:</h3><ol><li>You need to know Travis CI and have experience with the config file.</li><li>You need to know how to write Bash scripts and know the right stage to run the Bash script for preview channels.</li></ol><h3>Steps to reproduce:</h3><ol><li>Create a new local repository.</li><li>Create a new GitHub repository.</li><li>Run the ‘git remote add’ command to connect your local repository to the GitHub repository.</li><li><a href="https://firebase.google.com/docs/web/setup">Set up Firebase</a> project.</li><li><a href="https://docs.travis-ci.com/user/tutorial/#to-get-started-with-travis-ci-using-github">Set up Travis CI</a> for GitHub repository.</li></ol><p>Set up configuration variables — add <a href="https://docs.travis-ci.com/user/environment-variables/">environment variables in Travis CI</a>. The variables that you need to set up are the two access tokens to work with Firebase and GitHub:</p><ul><li>To get FIREBASE_TOKEN, use the command: firebase login:ci</li></ul><p>If the command doesn’t work, that’s probably because you don’t have firebase-tools</p><p>installed. For installation, run the following command: npm i -g firebase-tools, or curl -sL<a href="https://firebase.tools/"> https://firebase.tools</a> | bash</p><p>Then — run a firebase login command, then — firebase login:ci. The latter command gives you a Firebase access token that you want to use as a value for this step (To get FIREBASE_TOKEN…).</p><ul><li>Get GITHUB_TOKEN using this link: <a href="https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token">Creating a personal access token</a></li></ul><p>Then, apply this value for the GITHUB_TOKEN variable.</p><ol><li>In your local repository, run the ‘firebase init hosting’ command, choose your Firebase project from the list of already existed projects, and <a href="https://firebase.google.com/docs/hosting/full-config">configure firebase.json file.</a></li><li>Create .travis.yml file in your local repository.</li><li>Create a Bash script — <a href="https://devdocs.io/bash/">https://devdocs.io/bash/.</a></li><li>Add job to the .travis.yml file that should call the script that we’ve created as step 8.</li></ol><p>The job you need to insert into the .travis.yml file:</p><pre>stages:<br> - name: &quot;Deploy to Firebase preview channel&quot;<br>   if: branch = master AND type = pull_request</pre><pre>jobs:<br> include:<br> - stage: &quot;Deploy to Firebase preview channel&quot;<br>   skip_cleanup: true<br>   provider: firebase<br>   project: fir-project-dc47e<br>   before_script:<br>     - sudo apt-get install jq<br>     - npm install firebase-tools -g<br>     - npm run build:prod<br>   script: bash deploy-to-firebase-preview-channels.sh</pre><p>Here fir-project-dc47e — an ID of your Firebase project</p><ol><li>Push changes to GitHub and create a pull request.</li></ol><p>Now, back to solving our Travis CI issue and enabling its usage with the Preview channel Firebase functionality. Find the solution I came up with in the repository: <a href="https://github.com/NikitaGlukhi/travis-ci-and-firebase-preview-channels">Travis CI and Firebase preview channels solution.</a></p><p>Or a brief version on gist: <a href="https://gist.github.com/NikitaGlukhi/f094a6a8e6812d104d779e37d6560705">.travis.yml: preview channels deployment.</a></p><p>I’ll separate the script into pieces and describe every step below. To deploy successfully, please, follow my guidelines step by step.</p><p>I start with a deployment to a preview channel:</p><pre>#!/usr/bin/env bash<br>DEPLOY_TO_PREVIEW_CHANNEL_RESULT=$(firebase hosting:channel:deploy pr-$TRAVIS_PULL_REQUEST --expires 30d --token $FIREBASE_TOKEN --json)</pre><p>After running the deploy command, I get a response with an object containing data, and I have the following as a result:</p><pre>{<br> &quot;status&quot;: &quot;success&quot;,<br> &quot;result&quot;: {<br>   &quot;fir-project-dc47e&quot;: {<br>     &quot;site&quot;: &quot;fir-project-dc47e&quot;,<br>     &quot;url&quot;: &quot;https://fir-project-dc47e--pr-1-t36vykr3.web.app&quot;,<br>     &quot;expireTime&quot;: &quot;2021-01-08T09:27:24.847798020Z&quot;<br>   }<br> }<br>}</pre><p>Here fir-project-dc47e — an ID of your Firebase project</p><p>I add this object to the DEPLOY_TO_PREVIEW_CHANNEL_RESULT variable. This object has a .result parameter containing all the data needed for future operations.</p><p>Next, I select data from the .result parameter and add it to a separate variable that corresponds to the parameter name. This variable will contain an object with a key of ngx-bootstrap-demo.</p><pre>RESULT=`echo ${DEPLOY_TO_PREVIEW_CHANNEL_RESULT} | jq -r &#39;.result&#39;`</pre><p>For the next step, I extract fir-project-dc47e object from the RESULT variable:</p><pre>RESULT_DATA=`echo ${RESULT} | jq -r &#39;.&quot;fir-project-dc47e&quot;&#39;`</pre><p>The following goes to RESULT_DATA:</p><pre>{<br>     &quot;site&quot;: &quot;fir-project-dc47e&quot;,<br>     &quot;url&quot;: &quot;https://fir-project-dc47e--pr-1-t36vykr3.web.app&quot;,<br>     &quot;expireTime&quot;: &quot;2021-01-08T09:27:24.847798020Z&quot;<br>   }</pre><p>Now, I extract a website name from the RESULT_DATA variable. The result will go to the SITE variable:</p><pre>SITE=`echo ${RESULT_DATA} | jq -r &#39;.&quot;site&quot;&#39;`</pre><p>I extract a preview channel URL from RESULT_DATA variable, and the result goes to the URL variable:</p><pre>URL=`echo ${RESULT_DATA} | jq -r &#39;.&quot;url&quot;&#39;`</pre><p>Then — extracting data with the expiration time from the RESULT_DATA variable. And, I write it down to the EXPIRE_TIME_UTC variable. UTC format is a default one, so I bring it to the needed format which is GMT, in my case.</p><pre>EXPIRE_TIME_UTC=`echo ${RESULT_DATA} | jq -r .expireTime`<br>EXPIRE_TIME=$(TZ=&#39;GMT&#39; date -d $EXPIRE_TIME_UTC +%c)</pre><p>The NEW_COMMENT variable creates a text with a project name, link to a preview channel, and its life duration. I’ll add this text of the comment to a pull request later (TRAVIS_PULL_REQUEST/comments).</p><p>Then, I extract all the comments from the pull request I want to work on, using the request to GitHub API. The result goes to the COMMENTS variable. The Objects array will have a description for each comment.</p><pre>COMMENTS=$(curl -H &quot;Authorization: token $GITHUB_TOKEN&quot; -X GET &quot;https://api.github.com/repos/$TRAVIS_REPO_SLUG/issues/$TRAVIS_PULL_REQUEST/comments&quot;)</pre><p>I declare variables for test cycles. Using the SUBSTRING variable, I search for a comment that might have been added before to replace it with the latest one.</p><p>COMMENT_ID equals -1 by default. In the future, I’ll assign a comment ID that I find to it. In case of no overlaps appeared, the value stays as default.</p><pre>SUBSTRING=&quot;Project: fir-project-dc47e&quot;<br>COMMENT_ID=-1</pre><p>In this cycle, I sort out the COMMENTS array, and extract the body of each comment — its text, and search for a substring in this body. If an overlap is detected, I take the comment ID and assign it to the COMMENT_ID variable. If no overlaps are detected, then nothing is assigned, the loop just runs as before.</p><pre>for row in $(echo &quot;${COMMENTS}&quot; | jq -r &#39;.[] | @base64&#39;); do<br>echo ${row}<br>  _jq() {<br>     echo ${row} | base64 --decode | jq -r ${1}<br>  }<br>  BODY=$(_jq &#39;.body&#39;)  if [[ ${BODY} == *&quot;$SUBSTRING&quot;* ]]; then<br>    COMMENT_ID=$(_jq &#39;.id&#39;)<br>  fi<br>done</pre><p>Finally, I run a COMMENT_ID test, if it equals 0 or is more than 0, it means a comment like this exists, and I need to refresh it. Then, I refer to GitHub API (GITHUB_TOKEN). If there’s no comment — the command creates a new comment in a pull request (GitHub API, as well).</p><pre>if [[ ${COMMENT_ID} -ge 0 ]];<br> then<br>   curl -H &quot;Authorization: token $GITHUB_TOKEN&quot; -X PATCH -d &quot;{\&quot;body\&quot;: \&quot;$NEW_COMMENT\&quot;}&quot; &quot;https://api.github.com/repos/${TRAVIS_REPO_SLUG}/issues/comments/${COMMENT_ID}&quot;<br> else<br>   curl -H &quot;Authorization: token $GITHUB_TOKEN&quot; -X POST -d &quot;{\&quot;body\&quot;: \&quot;$NEW_COMMENT\&quot;}&quot; &quot;https://api.github.com/repos/${TRAVIS_REPO_SLUG}/issues/${TRAVIS_PULL_REQUEST}/comments&quot;<br>fi</pre><p>As a result, I get the link with the comment to a preview channel. And, the comment we get from the previous operation comes from the person which token we use.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*EHyPqBQzB_J9vV7q.png" /></figure><h3>Any questions?</h3><p>Feel free to contact me if you have any questions or troubles with deploying the script: <a href="mailto:nikita.glukhi@valor-software.com">nikita.glukhi@valor-software.com.</a></p><h3>Useful links:</h3><p><a href="https://developer.github.com/v3/issues/comments/#list-issue-comments-for-a-repository">Issue Comments for a repository</a> — extract all the comments for a repository (as well as for a preview channel, but there’s no example in the doc)</p><p><a href="https://docs.github.com/en/free-pro-team@latest/rest/reference/issues#create-an-issue-comment">Create an issue comment</a> — create a new comment</p><p><a href="https://docs.github.com/en/free-pro-team@latest/rest/reference/issues#update-an-issue-comment">Update an issue comment</a> — refresh an existing comment</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f97ae6bd8d5b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/codex/how-to-deploy-firebase-preview-channels-on-travis-ci-f97ae6bd8d5b">How to deploy Firebase Preview Channels on Travis CI</a> was originally published in <a href="https://medium.com/codex">CodeX</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scully Helped us Reach a 99 Lighthouse Score for a B2C Platform]]></title>
            <link>https://valorsoftware.medium.com/scully-helped-us-reach-a-99-lighthouse-score-for-a-b2c-platform-e1ac5599897b?source=rss-23e367deeb51------2</link>
            <guid isPermaLink="false">https://medium.com/p/e1ac5599897b</guid>
            <category><![CDATA[google-analytics-tips]]></category>
            <category><![CDATA[angular]]></category>
            <category><![CDATA[scully]]></category>
            <category><![CDATA[indexing]]></category>
            <category><![CDATA[jamstack]]></category>
            <dc:creator><![CDATA[Valor Software]]></dc:creator>
            <pubDate>Fri, 25 Jun 2021 14:16:22 GMT</pubDate>
            <atom:updated>2021-06-25T20:06:51.816Z</atom:updated>
            <content:encoded><![CDATA[<p>Ruslan Ponuzhdayev, the software engineer of Valor Software, shared the story and greatly contributed to the solution described.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rjfMhE84olqVDvRLL1XdCw.png" /></figure><p>You may have heard of <a href="https://jamstack.org/what-is-jamstack/">JAMstack</a>. It recently entered top charts of technology choice for the web because of its ease of use, performance, and flexibility. Scully, a static site generator, brings JAMstack to the next level of effectiveness because of its Angular nature. Valor Software decided to adopt Scully to make the client’s platform fast and really convenient to use.</p><p>We sped up the overall page load and increased the platform lighthouse score to 99–100. Also, connecting Google eCommerce Marketing helped us see several areas for improvement on the website and in mobile apps to streamline the user journey. Learn from our experience how you can achieve a boost in website performance and visibility for your project using Scully and Google Analytics. Also, I’m going to help you overcome possible difficulties with integrations since we’ve already been there :)</p><h4>Who the contributors are</h4><p>Alexandr Pavlovskiy and Nikita Glukhi made it possible to bring all the described to life. Many thanks for being great team players and for their great contribution to the project.</p><p>Also, Dima Shekhovtsov deserves special thanks for the idea, direction, and technical guidance!</p><h4>Project background</h4><p>Well, our team’s job on this project was to reconsider the B2C platform for a game company. They had an outdated landing, as well as mobile apps that users didn’t really want to use. We were on a global mission to understand the current project situation, and then — bring value.</p><p>The client had no insights in terms of the website load, performance, storage allocation, etc. And you can easily guess what we found once we peeked under the hood (it was a pile of legacy code there). At this point, we understood that something new, quick, and easy to manage needs to be built instead. That’s how <a href="https://scully.io/">Scully</a> came into play.</p><h4>What is Scully and how to deploy‍</h4><p>JAMstack stands for JavaScript, APIs, and Markup. In this kind of architecture, JavaScript runs entirely on the client-side and handles any dynamic programming. Reusable APIs cover all the server-side processes, and Markup is a markup (a site generator or Webpack), and it needs to be pre-built.</p><p>Scully generates static sites for Angular, those with no backend code, so no API call is needed to get the data from the server. Instead, we put all the content on our pages as data and text and make it available to end-users. This <a href="https://snipcart.com/blog/angular-static-site-generator-scully">Scully Tutorial</a> tells you all you need to know for smooth deployment.</p><h4>Now Lighthouse score reaches 100</h4><p>We use <a href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a>, an open-source tool for checking and improving the quality of web pages in this project. You can run it through any webpage for performance, accessibility, SEO, and other audits. On completion, you get a report with suggestions for improvements, and all that is left is bringing those to life :-)</p><p>The Lighthouse score represents the results from performance metrics that the tool gathered based on real website performance data. We considered the score to evaluate the website&#39;s general performance: whether and how Scully changed the picture. And I can tell that once we switched to Scully, the platform’s Lighthouse score grew from 56 to 99–100. This indicator includes performance, accessibility, SEO, and application of best practices, as you can see from the screen below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wSfSnG7x2kikHrsK.png" /></figure><h4>Better Google indexation — how to spot the company in the top</h4><p>If we remember the basics of the Google search logic, then the crawling stage comes first, to discover publicly available web pages and connected links, and bring this data back to the Google server. Then indexing, when Google tries to understand what the website is about in terms of content, images, video, etc., to categorize the object and put it “on the right shelf” in a huge Google index storage. Finally, we have serving and ranking, when in response to a user’s query Google goes through its index, searching for the most appropriate and quality answer.</p><p>Angular applications are rendered at runtime, so it takes Google bots longer to recognize the content since they have to execute JS code first. And Scully helps us pre-render each route’s HTML content. It generates a static version of each website page and eases the mission for bots to see the content. That’s exactly what helped us improve the platform indexing and visibility.</p><p>Learn more about boosting SEO with the help of Scully from this <a href="https://academind.com/tutorials/scully-introduction/#improving-seo-in-angular-apps">Academind article about SEO optimization in Angular apps</a>.</p><h4>Making the overall CSS footprint smaller with Tailwind</h4><p>To remove CSS that we don’t need when building for production, we used Tailwind CSS. This CSS framework in the first place lets you easily style your website or app. And what’s more important in our case, it helped us to get rid of unused styles and optimize builds’ size.</p><p>As creators claim, when removing unused styles with Tailwind, you never end up with more than 10kb of compressed CSS. Learn more about the framework and its worth from the <a href="https://tailwindcss.com/docs/optimizing-for-production">Optimizing for Production material by Tailwind</a>.</p><h4>User growth and stronger involvement</h4><p>Firstly, our release of an Alpha version of the platform tuned by Scully drew users to the product. By product, I mean in-app traits for gamers like a range of different cosmetics, including skins and pets. Scully brought us more visitors and buyers among those who already played the game and knew about the platform.</p><p>Another part of the audience came because of better SEO optimization. Optimized content made the platform visible and helped new users find the website. You can see the situation before and after from the screen below (please, don’t mind the red area, it shows the moment of deployment when we couldn’t track the users’ activity).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*LnyP90VO52qgyf71.png" /></figure><p>As a result, from the moment we released the new platform version, sales grew twice.</p><h4>Integration of Google eCommerce Marketing</h4><p>The eCommerce part of Google Analytics is what you definitely want to apply to gather insights on user behavior and preferences. You can also understand who makes up your core audience if that’s what you lack, and then — build more efficient marketing campaigns. eCommerce gives you access to your audience’s gender, age, geographical location. The only thing you should take care of to obtain needed data is <a href="https://tagmanager.google.com/">tags</a>. You’ve got to manage them right when setting up analytics. The following links will help you tune your tags for enhanced eCommerce:</p><ul><li><a href="https://www.analyticsmania.com/post/datalayer-push/">dataLayer.push with examples</a></li><li><a href="https://www.analyticsmania.com/post/google-analytics-user-id-with-google-tag-manager/">A Guide to Google Analytics User ID in Google Tag Manager</a></li><li><a href="https://www.analyticsmania.com/post/ecommerce-tracking-with-google-tag-manager/">Set Up Ecommerce Tracking with Google Tag Manager: Full Guidegle-tag-manager/</a></li><li><a href="https://www.simoahava.com/analytics/enhanced-ecommerce-guide-for-google-tag-manager/">Enhanced eCommerce Guide for Google Tag Manager‍</a></li></ul><p>We integrated Google eCommerce tracking and provided our clients with the data they needed for planning future website and mobile apps upgrades. One of the most important things that we started to track is the checkout process — from the moment when the product falls into the basket through filling in the form for online payment to the actual purchase. Clients could realize what may stop a user from a purchase, what stands in the way of accomplishing the checkout. Now they have a data-driven basis for targeting bigger goals in the future.</p><h4>Bridging Google Analytics’ tags and your Scully platform</h4><p>Earlier we added a complete JavaScript tracking-code snippet into the HTML to our platform (just like described in this <a href="https://ppcexpo.com/blog/where-is-the-google-analytics-code">PPCexpo material</a>). But when we switched to Scully and went on connecting Google Analytics to the Scully website, a conflict between Google Tag Manager and HTTPS packages arose. So, we created a plugin to pre-build this connection for Scully to add the piece of code for Google Analytics without any modifications:</p><pre>import { registerPlugin, getMyConfig } from &#39;<a href="http://twitter.com/scullyio/scully">@scullyio/scully</a>&#39;;</pre><pre>export const GoogleAnalytics = &#39;googleAnalytics&#39;;</pre><pre>export const googleAnalyticsPlugin = async (html: string): Promise&lt;string&gt; =&gt; {<br>    const googleAnalyticsConfig = getMyConfig(googleAnalyticsPlugin);</pre><pre>    if (!googleAnalyticsConfig || !googleAnalyticsConfig[&#39;globalSiteTag&#39;]) {<br>        throw new Error(&#39;googleAnalytics plugin missing Global Site Tag&#39;);<br>    }<br>    const siteTag: string = googleAnalyticsConfig[&#39;globalSiteTag&#39;]; // your gtmTagId</pre><pre>    const googleAnalyticsScript = `<br>      // your GA script code here<br>    `;</pre><pre>    return html.replace(/&lt;\/head/i, `${googleAnalyticsScript}&lt;/head`);<br>};</pre><pre>const validator = async () =&gt; [];</pre><pre>registerPlugin(&#39;postProcessByHtml&#39;, GoogleAnalytics, googleAnalyticsPlugin, validator);</pre><p>Use <a href="https://developers.google.com/tag-manager/quickstart">this instruction</a> for implementing Google Tag Manager on your website. That’s where you get the following piece of code from:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RiecpiteKqw6S7Hc.png" /></figure><p>And <a href="https://www.analyticsmania.com/post/google-tag-manager-id/">this article</a> tells how to get your Google Tag Manager ID.</p><p>Here’s where you should place the plugin</p><p>The Scully config file is generated automatically when we connect Scully to our Angular app. It is located in the root folder with package.json. Of course, we get a default Scully config, and then we should customize it for our project. This <a href="https://medium.com/ngconf/a-guide-to-custom-scully-plugins-5558993fd3f8">Guide to custom Scully plugins</a> gives good advice for customizing plugins to your needs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IKuBbrrm8hcr-Mfq.png" /></figure><h4>Summary</h4><p>This switch to JAMstack and Scully gave us a tremendous amount of benefits, even those both we and the client didn’t expect to get. For example, it was a surprise that we’ll have better Google indexing.</p><p>From my point of view, the main gain for this project (just like for most of them) is transparency. With such a clear structure and interaction between frontend and backend, you know exactly what’s happening on your Scully website. And when you know, you can react, and actually handle complexities that arise.</p><p>Sure, there’s still much work to do, but we have bright perspectives. We plan to deepen tracking of eCommerce indicators, since this will give the client more ground for new business turns. Also, we’ll be working on mobile apps to increase users’ engagement even more!</p><p>Here I shared our first experience and thus impressions from the technology. Hopefully, you’ll find the story useful. Please, don’t hesitate to share your feedback, give advice, or contact <a href="https://valor-software.com/contact.html">Valor Software</a> to give your business a boost!‍</p><h4>Useful links</h4><p>‍<br>‍1. <a href="https://snipcart.com/blog/angular-static-site-generator-scully">Scully Tutorial</a></p><p>2. <a href="https://snipcart.com/blog/angular-static-site-generator-scully">Optimizing for Production material by Tailwind</a></p><p>3. <a href="https://academind.com/tutorials/scully-introduction/#improving-seo-in-angular-apps">Academind article about SEO optimization in Angular apps</a></p><p>4.<a href="https://www.simoahava.com/analytics/enhanced-ecommerce-guide-for-google-tag-manager/"> Enhanced eCommerce Guide for Google Tag Manager</a></p><p>5. <a href="https://medium.com/ngconf/a-guide-to-custom-scully-plugins-5558993fd3f8">Guide to custom Scully plugins</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e1ac5599897b" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>