<?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"><channel><title><![CDATA[Önder Ceylan's Tech Blog]]></title><description><![CDATA[Önder is an award-winning technical leader with two decades of experience designing, planning and implementing software solutions at scale. He's a GDE in Web Te]]></description><link>https://onderceylan.com</link><generator>RSS for Node</generator><lastBuildDate>Sun, 17 May 2026 17:21:45 GMT</lastBuildDate><atom:link href="https://onderceylan.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Making web better as Mobile Web Specialists]]></title><description><![CDATA[On the verge of the fight of JavaScript frameworks and discussions of why we as web developers mostly failed to provide a healthy web, revisiting fundamentals for building websites that don’t only look great but also perform better on mobile becomes ...]]></description><link>https://onderceylan.com/making-web-better-as-a-mobile-web-specialist-834275cb67e6</link><guid isPermaLink="true">https://onderceylan.com/making-web-better-as-a-mobile-web-specialist-834275cb67e6</guid><category><![CDATA[PWA]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Önder Ceylan]]></dc:creator><pubDate>Tue, 10 Dec 2019 14:36:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039390471/MeZrABC2S.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On the verge of the fight of JavaScript frameworks and discussions of why we as web developers <em>mostly</em> failed to provide a healthy web, <strong><em>revisiting fundamentals for building websites that don’t only look great but also perform better on mobile</em></strong> becomes even more important for both developers and businesses.</p>
<p>The web browser is the most widespread deployment platform available to developers today. It’s naturally installed on every smartphone, tablet, laptop, desktop, and any other format in between. Cumulative industry growth projections have been studied over the past years <a target="_blank" href="https://www.statista.com/statistics/802690/worldwide-connected-devices-by-access-technology/">approximating over 38 billion connected devices by 2025</a> — each one of them with a browser, and at the very least, either WiFi or a cellular connection or both. Regardless of the version or OS on them, each device will have at least one browser, which by itself is getting more features every day, some notable ones are PWA, WebVR, WebAssembly and etc.</p>
<p>Today, mobile accounts for <a target="_blank" href="https://www.merkleinc.com/thought-leadership/digital-marketing-report">59% of all searches</a> and 58.7% of all web traffic, according to <a target="_blank" href="https://developer.akamai.com/akamai-mpulse-real-user-monitoring-solution">Akamai mPulse</a> data in July 2019. It’s the primary way people experience the web, not a second thought as it used to be 15 years ago. So given how significant mobile is, what kind of experience are we providing our visitors? Where are we going wrong?</p>
<h3 id="heading-whats-the-problem">What’s the problem?</h3>
<p>The pitfall where many developers and businesses fall into is the assumption of having their users use their products on the latest spec high-end mobile devices over permanent internet access. We lose ourselves in the discussions of micro-optimizations so much that we forget about our core value — <em>being user-centric</em>.</p>
<h4 id="heading-our-delusion"><strong>Our delusion</strong></h4>
<p>In a perfect world, we would have permanent internet access and everybody would own a super-fast mobile device equipped with the latest CPU and memory hardware. Unfortunately, this is not a perfect world.</p>
<p>“A visualization of Time-to-Interactive highlighting how a poorly loaded experience makes the user think they can accomplish a goal, when in fact, the page hasn’t finished loading all of the code necessary for this to be true.” — Addy Osmani “<a target="_blank" href="https://medium.com/@addyosmani/the-cost-of-javascript-in-2018-7d8950fbb5d4">The Cost Of JavaScript In 2018</a>”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039381977/9WGllMQ2e.gif" alt="A visualization of a poor Time-to-Interactive experience" /></p>
<p>We <em>have to</em> maintain users <strong>using low-end mobile devices</strong>, users who <strong>roam around the world</strong>, users who <strong>travel outside of a data coverage area</strong>, users who use <strong>a poor local WiFi in a cafe</strong> as <em>our main target audience</em>.</p>
<p>“The cost of Reddit’s JavaScript across a few different device classes (low-end, average, and high-end)” — Addy Osmani &amp; Mathias Bynens “<a target="_blank" href="https://v8.dev/blog/cost-of-javascript-2019">The cost of JavaScript in 2019</a>”:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039384358/ajknB6ij3.png" alt="The cost of Reddit’s JavaScript across a few different device classes" /></p>
<blockquote>
<p>By adding high-end only features progressively if only a user’s network and hardware can handle it, we can deliver a fast core experience to <strong>all users</strong> and make our products <strong>accessible</strong> more than ever before.</p>
</blockquote>
<h4 id="heading-underestimated-a-core-value-being-user-centric">Underestimated a core value — being user-centric</h4>
<p>We have a tendency of getting lost in discussions on micro-optimization and of overcomplicating things too much that we forget about our main goal while delivering a product to the world.</p>
<p>When you overcomplicate the simplest task:
%[https://www.youtube.com/embed/_sdT9t5Ha_U]</p>
<p>We easily become fanboys of a specific technology or framework and we almost forget what is the most important factor for our website — it’s the users!</p>
<p>Building a friendly, accessible and fast user experience to <strong>ALL</strong> our users should be the core of our business. When we put this goal in the center of our business and culture, <em>what tech we use to deliver an interface becomes obsolete</em> as long as it performs well for all our users.</p>
<p>So, please stop making fun of developers and websites that use jQuery to introduce interactivity to their web pages. It’s still the most popular JavaScript library ever created, and it’s used in 85.03% of desktop pages and 83.46% of mobile pages according to <a target="_blank" href="https://almanac.httparchive.org/en/2019/javascript">WebAlmanac</a>. Although there are many Browser APIs and methods to replace most of the functionality provided by the library into a native form, such as <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch</a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector">querySelector</a>, it might still be a perfect fit for your next mobile web app. Take Booking.com, it still uses jQuery on their home page and it’s one of the fastest loading e-commerce websites.</p>
<blockquote>
<p>Users don’t care if you use latest tech trends or a technique that helps you micro-optimise even though it’s not needed. Users care if it works and if it works fast.</p>
</blockquote>
<h3 id="heading-performance-as-a-culture">Performance as a culture</h3>
<p>The scale of the web brings us billions of connected devices and the enormous user base for online services, with the high demand for high-performance web applications. As noted many times in performance-related books, talks, and articles, <strong>speed indeed is a feature</strong>. And for some businesses, it’s <strong>the</strong> feature.</p>
<p>There are online services like <a target="_blank" href="https://speedcurve.com/">SpeedCurve</a> which benchmark your site against your competitors, correlate web performance with user experience, measure when your most important content renders and track the impact of web performance on your business metrics.</p>
<p>If you don’t bring a performance culture into the core of your business, you might <a target="_blank" href="https://product.voxmedia.com/2015/5/6/8561867/declaring-performance-bankruptcy">declare a performance bankruptcy as Vox</a> did a couple of years ago.</p>
<p>Comparison of the performance metrics of Verge and it’s competitors:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039386478/bJdnHMrN8.png" alt="Comparison of the performance metrics of Verge and it’s competitors" /></p>
<blockquote>
<p>Good performance is everyone’s responsibility; from designers to developers, ops to ads, to editorial and beyond.</p>
<p>If we do our jobs well, everyone will be more informed about how their decisions affect the overall performance of our platform.</p>
<p>— <a target="_blank" href="https://product.voxmedia.com/2015/5/6/8561867/declaring-performance-bankruptcy">Declaring performance bankruptcy</a> By Dan Chilton</p>
</blockquote>
<p>As openly came out on Vox’s statements, we all have responsibilities to bring a performance culture to the core of our organization.</p>
<h3 id="heading-pwa-first-approach-enforces-mobile-web-development-best-practices">PWA-first approach enforces mobile web development best practices</h3>
<p>When you build a mobile application, there are many aspects to consider. Some are the ones you might have never considered while building a desktop website.</p>
<p><a target="_blank" href="https://www.forbes.com/sites/forbestechcouncil/2019/11/26/building-a-mobile-app-for-your-business-consider-these-12-factors-first/">Gartner recently published an article</a> where Forbes Technology Council discusses the 12 key elements that a business should be mindful of when developing a mobile app. As a PWA evangelist, it’s no surprise for me to see the section “<strong>The Advance of Progressive Web Apps</strong>” recommending considering Progressive Web Apps (PWAs) over native apps.</p>
<blockquote>
<p>Consider progressive Web apps (PWAs) over native ones. PWAs are growing in popularity because they create a better user experience. They’re smaller in size, load faster and are more secure, and users can access them the same way as apps downloaded from app stores. Some of the industries already moving to cost-effective PWAs include financial services, medical manufacturing, construction and others. <a target="_blank" href="https://twitter.com/jpmcdon">— John McDonald</a>, <a target="_blank" href="https://www.clearobject.com/">ClearObject</a></p>
</blockquote>
<h4 id="heading-what-is-pwa-first">What is PWA-first?</h4>
<p>Progressive Web Apps have been around for a while now to provide native-like app experiences on both mobile and desktop platforms. And, similar to the <em>mobile-first</em> movement, there’s a new movement called <strong><em>PWA-first</em></strong>.</p>
<p>This idea helps you to build and monitor your website from day 1, caring about many user and performance-oriented features such as;</p>
<ul>
<li>Speed</li>
<li>Offline-capability</li>
<li>Progressive enhancement</li>
<li>Accessibility</li>
<li>Jank-free animations</li>
<li>Fluid layouts</li>
</ul>
<h4 id="heading-metrics-matter">Metrics matter</h4>
<p>Adopting the PWA-first approach helps you on putting metrics and monitoring within your core business and development workflow.</p>
<p>After the initial launch of your website that is optimized for mobile, it’s crucial to monitor your metrics and application performance. It brings user experience and satisfaction into your culture and helps the whole organization to further maintain those metrics by also improving them in time.</p>
<blockquote>
<p>Organizational performance budgets ensure that a budget is owned by everyone rather than just being defined by one group. — <a target="_blank" href="https://addyosmani.com/blog/performance-budgets/">Addy Osmani</a></p>
</blockquote>
<p>To maintain a healthy mobile web strategy for your business;</p>
<ul>
<li>Understand performance metrics and budgets<br /><a target="_blank" href="https://web.dev/performance-budgets-101/">https://web.dev/performance-budgets-101/</a></li>
<li>Introduce budgets for your website, either over rule-based or quantity-based metrics<br /><a target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/budgets">https://developers.google.com/web/tools/lighthouse/audits/budgets</a><br /><a target="_blank" href="https://web.dev/your-first-performance-budget/">https://web.dev/your-first-performance-budget/</a></li>
<li>Integrate lighthouse in your CI either by yourself or using a service like <a target="_blank" href="https://calibreapp.com/">Calibre</a></li>
<li>Monitor your metrics by using services like <a target="_blank" href="https://speedcurve.com/">SpeedCurve</a></li>
<li>Benchmark your site against competitors over monitoring services</li>
</ul>
<h3 id="heading-initiatives-to-make-the-web-better"><strong>Initiatives to make the web better</strong></h3>
<p>Google invests tremendously in making the web better by raising awareness, introducing open-source tools and libraries, helping communities with their developer relations teams, introducing web blogs and certification programs.</p>
<p>Some of the great examples of contributions from a community that is driven by Google engineers are;</p>
<ul>
<li><a target="_blank" href="https://almanac.httparchive.org/en/2019/">Web Almanac</a></li>
<li><a target="_blank" href="https://web.dev/">Web Dev Blog</a></li>
<li><a target="_blank" href="https://v8.dev/blog/">V8 Dev Blog</a></li>
<li><a target="_blank" href="https://developers.google.com/web/fundamentals">Web Fundamentals</a></li>
<li><a target="_blank" href="https://dev.to/addyosmani/adaptive-loading-improving-web-performance-on-low-end-devices-1m69">Adaptive Loading</a></li>
<li><a target="_blank" href="https://developers.google.com/web/fundamentals/performance/rail">RAIL Model</a></li>
</ul>
<p>In addition to those awesome resources, Google also created a <a target="_blank" href="https://developers.google.com/certification/mobile-web-specialist">certification program called Mobile Web Specialist</a> where it acknowledges developers who demonstrate mobile web development expertise. It’s one of the very few certifications in the frontend world.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039388450/Q8HCzH4tK.jpeg" alt /></p>
<p>You can become a Mobile Web Specialist by taking an exam on — <a target="_blank" href="https://developers.google.com/certification/mobile-web-specialist">https://developers.google.com/certification/mobile-web-specialist</a></p>
<p>Certification aside, I believe this is a great initiative to raise awareness for building a healthy web. It’s a token for people to show that they care about a healthy web and they’ve skills and expertise to make the web better.</p>
<h3 id="heading-how-can-we-help-you-help-yourself">How can we help you help yourself?</h3>
<p>We’ve built a 5-days training program called <a target="_blank" href="https://mobilewebcourse.linkit.nl/">Mobile Web Specialist bootcamp</a> at <a target="_blank" href="https://www.linkit.nl/">LINKIT</a> to help developers build great mobile websites, focusing on web fundamentals and technology rather than frameworks and libraries.</p>
<p>It covers the topics on <a target="_blank" href="https://developers.google.com/certification/mobile-web-specialist/study-guide">the study guide of Mobile Web Specialist certification</a> and teaches participants modern web development practices with a <em>hands-on heavy agenda</em>.</p>
<p>It aims to have participants feel confident with building great mobile website experiences at the end as well as helping them get the Mobile Web Specialist certification of Google.</p>
<h4 id="heading-we-help-businesses-note-to-employers">We help businesses — note to employers</h4>
<p>We help managers like you to bring cultural changes to your business. By investing in your employees taking this training, you make sure you’re right on track on building a great mobile strategy and production for your business.</p>
<p>With investing in PWA-first, you de-risk your business by making sure that it can reach the largest number of people with the least amount of friction. Some notable case studies showing a direct positive impact on business objectives are;</p>
<ul>
<li><a target="_blank" href="https://medium.com/dev-channel/a-pinterest-progressive-web-app-performance-case-study-3bd6ed2e6154">Pinterest reduced their JavaScript bundles from 2.5MB to &lt; 200KB and Time-to-Interactive reduced from 23s to 5.6s</a>. Revenue went up 44%, sign-ups are up 753%, <a target="_blank" href="https://medium.com/@Pinterest_Engineering/a-one-year-pwa-retrospective-f4a2f4129e05">weekly active users on mobile web are up 103%</a>.</li>
<li><a target="_blank" href="https://engineering.autotrader.co.uk/2017/07/24/how-we-halved-page-load-times.html">AutoTrader reduced their JavaScript bundle sizes by 56% and reduced Time-to-Interactive for their pages by ~50%</a>.</li>
<li><a target="_blank" href="https://youtu.be/Mv-l3-tJgGk?t=1967">Nikkei reduced their JavaScript bundle size by 43% and Time-to-Interactive improved by 14s.</a></li>
<li>More case studies available at <a target="_blank" href="https://developers.google.com/web/showcase/tags/progressive-web-apps">Google Developers blog</a>, and <a target="_blank" href="https://developers.google.com/web/showcase/tags/progressive-web-apps">PWAStats</a></li>
</ul>
<h4 id="heading-we-help-developers-note-to-employees">We help developers — note to employees</h4>
<p>We help developers like you to focus on business objectives and user experience rather than tech trends. We put web fundamentals and native browser functionality on top of frameworks and libraries so it’s up to you to choose which direction to go.</p>
<p>By investing in yourself with attending to Mobile Web Specialist training of LINKIT, you make sure you keep up with the best practices for building a great website experience satisfying all your visitors from around the world.</p>
<p>We create an alumni community where we exchange ideas, ask questions to each other, share resources that we find useful on the topic from and to a community built by like-minded people, like yourself.</p>
<p>Feel free to DM me on <a target="_blank" href="https://twitter.com/onderceylan">Twitter</a> or <a target="_blank" href="https://www.linkedin.com/in/onderceylan/">LinkedIn</a> if you’ve any questions or remarks about the certification or our training program.</p>
<p>We love the web, we bet on the web and let’s make it better all together!</p>
]]></content:encoded></item><item><title><![CDATA[Automate your integration tests and semantic releases with GitHub actions]]></title><description><![CDATA[I recently migrated CI/CD pipeline of one of my open source projects — pwa-asset-generator from Travis CI to GitHub actions. I was one of the members who got early access to GitHub Actions beta, and I’d like to share my experience of such migration w...]]></description><link>https://onderceylan.com/automate-your-integration-tests-and-semantic-releases-with-github-actions-43875ad83092</link><guid isPermaLink="true">https://onderceylan.com/automate-your-integration-tests-and-semantic-releases-with-github-actions-43875ad83092</guid><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[YAML]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Önder Ceylan]]></dc:creator><pubDate>Mon, 07 Oct 2019 10:30:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646060269221/uHmSIwixJ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently migrated CI/CD pipeline of one of my open source projects — <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator">pwa-asset-generator</a> from Travis CI to GitHub actions. I was one of the members who got early access to GitHub Actions beta, and I’d like to share my experience of such migration with the community in return.</p>
<p><a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator">The referred project</a> and examples in this article are based on <strong>node.js</strong>, <strong>eslint</strong>, <strong>TypeScript</strong>, <strong>Jest, semantic-release</strong> and <strong>npm</strong> stack. The principles are the same for any other tech stack, so you should be able to use a similar flow with your own project as well.</p>
<p>In this article, we’re focusing on building 2 pipelines;</p>
<ul>
<li><strong>CI</strong> — builds a TypeScript node.js project, lints the code and runs tests on the project</li>
<li><strong>Release</strong> — builds the project and deploys an npm package to npm repository with <a target="_blank" href="https://semantic-release.gitbook.io/semantic-release/">semantic-release</a></li>
</ul>
<p>Whether you’re <strong>migrating from Travis CI to GitHub actions</strong> or <strong>migrating from CircleCI to GitHub actions</strong> or <strong>starting a new open source project on GitHub</strong>, this article should give you a head start on your CI/CD journey.</p>
<h3 id="heading-about-github-actions">About GitHub actions</h3>
<p>GitHub announced its own continuous integration service as an alternative to services out there like Travis CI, Circle CI and others.</p>
<p>It’s free for open source projects as Travis CI and Circle CI are, and it’s well integrated with GitHub ecosystem.</p>
<p>GitHub introduces a few concepts in their continuous integration context. Core concepts such as workflows, jobs, steps, actions, runners, events and artifacts are documented on <a target="_blank" href="https://help.github.com/en/articles/about-github-actions#core-concepts-for-github-actions">GitHub actions documentation</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646059890448/_RAcwQXet.png" alt="An overview of core concepts of GitHub Actions" /></p>
<p>I’d like to summarize the concepts that we will use on our CI and CD pipelines for a better understanding of the ecosystem we’re in.</p>
<h4 id="heading-workflows">Workflows</h4>
<p>Workflows are basically pipelines, which can build, test, package, release, or deploy any project on GitHub. They are made up of one or more jobs and can be scheduled or activated by an event. Each workflow must be declared by a YAML file under <code>.github/workflows</code> folder in your repository.</p>
<p><em>For example;</em> <code>ci.yml</code> <em>for a CI workflow, or</em> <code>release.yml</code> <em>for a release workflow.</em></p>
<h4 id="heading-jobs">Jobs</h4>
<p>Jobs are basically tasks made up of steps. A fresh virtual environment is created for each job. Dependencies and configurations of a job are declared on the workflow file.</p>
<p><em>For example;</em> <code>build</code> job <em>for compiling your project, or</em> <code>test</code> <em>job for executing unit/integration tests.</em></p>
<h4 id="heading-actions">Actions</h4>
<p>Actions are basically tasks that you combine as steps to create a job. They are the smallest portable building blocks of a workflow. It provides a flexible architecture as you can create your own actions based on docker images, use actions shared by the open source community, and customize public actions. Actions must be included as a step in order to use within a workflow.</p>
<p><em>For example;</em> <code>actions/checkout</code> action on a step <em>for checking out your project, or</em> <code>actions/setup-node</code> action on a step <em>to setup node dependency.</em></p>
<h3 id="heading-creating-an-integration-workflow-ci">Creating an integration workflow — CI</h3>
<p>In our context, we are going to create an integration workflow with the goal of;</p>
<ol>
<li><strong>Installation</strong> of our project dependencies to <em>install and setup our toolset for further steps</em>.</li>
<li><strong>Compilation</strong> of our TypeScript code to <em>assure we don’t have any compilation issues.</em></li>
<li><strong>Linting</strong> of our TypeScript code with eslint to <em>assure we maintain a specific code quality.</em></li>
<li><strong>Testing</strong> of our functionality with unit and integration tests with Jest to <em>assure our product quality.</em></li>
</ol>
<p>In order to accomplish our goal, we need to create a new workflow file under <code>.github/workflows</code> folder of our repository. Let's call it <code>CI.yml</code> and create this file with the following content.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span>

<span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Test</span> <span class="hljs-string">on</span> <span class="hljs-string">node</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node</span> <span class="hljs-string">}}</span> <span class="hljs-string">and</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.os</span> <span class="hljs-string">}}</span>

    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.os</span> <span class="hljs-string">}}</span>

    <span class="hljs-attr">strategy:</span>
      <span class="hljs-attr">matrix:</span>
        <span class="hljs-attr">node:</span> [<span class="hljs-number">8</span>, <span class="hljs-number">10</span>, <span class="hljs-number">12</span>]
        <span class="hljs-attr">os:</span> [<span class="hljs-string">ubuntu-latest</span>, <span class="hljs-string">macOS-latest</span>, <span class="hljs-string">windows-latest</span>]

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">node</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-string">${{</span> <span class="hljs-string">matrix.node</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">registry-url:</span> <span class="hljs-string">https://registry.npmjs.org</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">install</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">lint</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">lint</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">build</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">test</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>
</code></pre>
<blockquote>
<p>As you might have noticed, I set specific versions to the actions I’m using — actions/checkout@v1. This is a good idea when you deal with a tool on beta stage as it might introduce breaking changes otherwise.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039446052/c6RqtMih_.png" alt /></p>
<p>A demonstration of a CI run of <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator/commit/fbd628d86a9b4f9839a5bb13699925ffc9a2e7bc/checks?check_suite_id=297499835">pwa-asset-generator — you can see it in detail here</a></p>
<p>It introduces node and OS matrixes for the pipeline, which means that test job will run on 3 x 3 = 9 environments, namely;</p>
<ul>
<li><strong>Test on node 8 and ubuntu-latest</strong></li>
<li><strong>Test on node 8 and macOS-latest</strong></li>
<li><strong>Test on node 8 and windows-latest</strong></li>
<li><strong>Test on node 10 and ubuntu-latest</strong></li>
<li><strong>Test on node 10 and macOS-latest</strong></li>
<li><strong>Test on node 10 and windows-latest</strong></li>
<li><strong>Test on node 12 and ubuntu-latest</strong></li>
<li><strong>Test on node 12 and macOS-latest</strong></li>
<li><strong>Test on node 12 and windows-latest</strong></li>
</ul>
<p>See the full list of environments that are supported by GitHub actions here: <a target="_blank" href="https://help.github.com/en/articles/virtual-environments-for-github-actions">https://help.github.com/en/articles/virtual-environments-for-github-actions</a></p>
<blockquote>
<p>The steps on test job can also be individual jobs but good to acknowledge that it will take more time to execute as we need to spinoff a new environment and install dependencies for each job.</p>
<p>You can also schedule your runs based on a cron, for instance <strong>nightly builds</strong>. To read more about all other configuration options on a workflow, visit <a target="_blank" href="https://help.github.com/en/articles/configuring-a-workflow">Configuring a workflow</a> documentation.</p>
</blockquote>
<h3 id="heading-creating-an-automated-release-workflow-with-semantic-release-cd">Creating an automated release workflow with semantic release— CD</h3>
<p>I’m a big fan of one-button deployments aka continuous deployment. It’s quite easy to setup a CD pipeline for node.js projects that are deployed to npm with the use of <a target="_blank" href="https://semantic-release.gitbook.io">semantic-release</a>.</p>
<p>In this context, we are going to create a release workflow with the goal of;</p>
<ol>
<li><strong>Installation</strong> of our project dependencies to <em>install and setup our toolset for further steps</em>.</li>
<li><strong>Compilation</strong> of our TypeScript code to <em>assure we are ready with our build artifact — dist folder in our context.</em></li>
<li><strong>Release</strong> of our artifact with semantic-release to <em>automate releases for each code push on our master branch.</em></li>
</ol>
<p>In order to accomplish our goal, we need to create a new workflow file under <code>.github/workflows</code> folder of our repository. Let's call it <code>release.yml</code> and create this file with the following content.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Release</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">publish:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-number">12</span>
          <span class="hljs-attr">registry-url:</span> <span class="hljs-string">https://registry.npmjs.org</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npx</span> <span class="hljs-string">semantic-release</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">NODE_AUTH_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.NPM_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">NPM_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.NPM_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">GH_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GH_TOKEN</span> <span class="hljs-string">}}</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039448141/KXIJblSVt.png" alt /></p>
<p>A demonstration of a release run on <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator/commit/19cc7c446b5a1731a0c41bc1b300820f501d70da/checks?check_suite_id=240127658">pwa-asset-generator — you can see it here in detail</a></p>
<h4 id="heading-setting-up-secrets">Setting up secrets</h4>
<p>As you might have noticed, there are secret environment variables set for semantic-release, which are required for <em>deploying our package to npm</em> and <em>creating automated chore commits on our GitHub repo</em>.</p>
<p>You can set those secrets on your GitHub settings and use them in your workflow files with this format: <code>${{ secrets.SECRET_NAME }}</code>. To setup secrets;</p>
<ol>
<li>Navigate to the <strong>Settings</strong> of your GitHub repo and click on the <strong>Secrets</strong> menu on the left sidebar. Then click on <strong>Add a new secret</strong> link.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039450411/MpwAOlhHn.png" alt /></p>
<p>Put in a name for your secret, which you’ll later use as environment variable on your workflow file.</p>
<p>2. Repeat the same for both <strong>NPM_TOKEN</strong> and <strong>GH_TOKEN</strong> secrets, which both are required by semantic-release for automated release on npm and administration in your repo on GitHub.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039452582/qW3w4L1eI.png" alt /></p>
<p>You can create your npm token on your npm console: <a target="_blank" href="https://www.npmjs.com/settings/username/tokens">https://www.npmjs.com/settings/username/tokens</a></p>
<p>You can create your GitHub token on your GitHub profile: <a target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></p>
<blockquote>
<p>Note that a typical semantic-release setup requires these permissions on GitHub token: <em>read:org, read:packages, repo, user:email, write:packages, write:repo_hook</em></p>
</blockquote>
<h3 id="heading-skipping-workflow-runs-for-chore-commits">Skipping workflow runs for chore commits</h3>
<p>Semantic release library automates a series of chore commits after each successful release. Those commits are automated by <a target="_blank" href="https://github.com/semantic-release/release-notes-generator">release-notes-generator</a> and <a target="_blank" href="https://github.com/semantic-release/github">github</a> plugins but not limited to — see the <a target="_blank" href="https://semantic-release.gitbook.io/semantic-release/extending/plugins-list">full list of plugins</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039454645/MN_PVNFec.png" alt /></p>
<p>See <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator/search?q=chore&amp;type=Commits">all the commits tagged with chore</a> in pwa-asset-generator</p>
<p>After having the chore commits in the repo, GitHub actions runs an additional workflow run as any commit should trigger a run on CI workflow.</p>
<p>However, having an additional workflow run is not necessary and it’s a good practice to skip CI runs caused by any of chore commits. As you might noticed above, chore commit messages include <strong>[skip ci]</strong> text, added by semantic release for each chore commit.</p>
<p>We can configure GitHub actions to skip runs for all the chore commits including <strong>[skip ci]</strong> message by adding the following configuration;</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Release</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">prepare:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">if:</span> <span class="hljs-string">"! contains(github.event.head_commit.message, '[skip ci]')"</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">echo</span> <span class="hljs-string">"$<span class="hljs-template-variable">{{ github.event.head_commit.message }}</span>"</span>

  <span class="hljs-attr">publish:</span>
    <span class="hljs-attr">needs:</span> <span class="hljs-string">prepare</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v1</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-number">12</span>
          <span class="hljs-attr">registry-url:</span> <span class="hljs-string">https://registry.npmjs.org</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npx</span> <span class="hljs-string">semantic-release</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">NODE_AUTH_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.NPM_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">NPM_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.NPM_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">GH_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GH_TOKEN</span> <span class="hljs-string">}}</span>
</code></pre>
<p>The configuration example above is for <em>release</em> workflow and you can use the same approach for your <em>CI</em> workflow as well.</p>
<h3 id="heading-adding-the-status-badge-of-your-workflow">Adding the status badge of your workflow</h3>
<p>Now it’s time for the fun part. Every open source project deserves a badge/shield to show their users that it’s well maintained. One of the most popular badges for an open source project is the build status badge.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039456587/_AeHEDw6C.png" alt /></p>
<p>You can find many available shields for various vendors here: <a target="_blank" href="https://shields.io/category/build">https://shields.io/category/build</a></p>
<p>GitHub actions provide their own status badges for each workflow. As you can see on my project <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator">pwa-asset-generator</a> on both <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator">GitHub</a> and <a target="_blank" href="https://www.npmjs.com/package/pwa-asset-generator">npm</a>, it uses CI workflow badge to show users that all integration tests are passing.</p>
<p>Demonstration of GitHub actions CI workflow badge on <a target="_blank" href="https://www.npmjs.com/package/pwa-asset-generator">pwa-asset-generator</a> readme:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039458669/MrRZatZe4.png" alt="Demonstration of GitHub actions CI workflow badge on pwa-asset-generator readme" /></p>
<p>You can use an SVG image link on your project to display the badge of your GitHub actions workflow with this pattern:</p>
<p><code>https://github.com/{username}/{repo}/workflows/{workflowname}/badge.svg</code></p>
<p>The SVG link displaying your workflow status with the name of your workflow:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039460751/6m6RZtOX1.png" alt="The SVG link displaying your workflow status with the name of your workflow
" /></p>
<p>Here’s the markdown for badge only, which you can use on the README file of your project;</p>
<pre><code class="lang-markdown">![<span class="hljs-string">Build Status</span>](<span class="hljs-link">https://github.com/{username}/{repo}/workflows/{workflowname}/badge.svg</span>)
</code></pre>
<p>Here’s the markdown for badge and link to your actions together;</p>
<pre><code class="lang-markdown">[<span class="hljs-string">![Build Status</span>](<span class="hljs-link">https://github.com/{username}/{repo}/workflows/{workflowname}/badge.svg</span>)](<span class="hljs-link">https://github.com/{username}/{repo}/actions</span>)
</code></pre>
<blockquote>
<p>GitHub actions currently has an issue with displaying latest workflow status on your status badge if latest run is skipped.</p>
<p>I reported <a target="_blank" href="https://github.community/t5/GitHub-Actions/Badge-shows-no-status-when-latest-run-is-skipped/m-p/36710#M2660">this issue to GitHub</a>. If you’re like me and think that status badge should display status of the latest run before the skipped runs, feel free to give kudos (upvote) to the issue.</p>
</blockquote>
<h3 id="heading-congratulations">Congratulations!</h3>
<p>You made it! Now you have an automated integration and deployment pipeline for your project running on GitHub actions.</p>
<p>You can drop me a message on <a target="_blank" href="https://twitter.com/onderceylan">Twitter</a> if you need any help on your CI/CD setup. Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[PWA splash screen and icon generator]]></title><description><![CDATA[Long story short; while experimenting with ideas on Puppeteer for my next talk, I found myself building an open source CLI tool — pwa-asset-generator! :)
It automatically generates splash screen and icon images for your Progressive Web App in order t...]]></description><link>https://onderceylan.com/pwa-splash-screen-and-icon-generator-a74ebb8a130</link><guid isPermaLink="true">https://onderceylan.com/pwa-splash-screen-and-icon-generator-a74ebb8a130</guid><category><![CDATA[PWA]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Önder Ceylan]]></dc:creator><pubDate>Sun, 25 Aug 2019 22:19:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646058211656/RZM4CHKSg.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Long story short; while experimenting with ideas on Puppeteer for <a target="_blank" href="https://www.youtube.com/watch?v=d2WSO3w5E94">my next talk</a>, I found myself building an open source CLI tool — <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator"><strong>pwa-asset-generator</strong></a><strong>! :)</strong></p>
<p>It automatically generates splash screen and icon images for your Progressive Web App in order to provide native-like user experiences on multiple platforms. It also updates your <code>index.html</code> and <code>manifest.json</code> files to declare generated assets to your PWA.</p>
<p>I’d like to get into details of my motivation for building such a library along with some advanced usage examples in this article.</p>
<p>A basic usage demo of <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator"><strong>pwa-asset-generator</strong></a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039470454/20XuRI9gP.gif" alt="A basic usage demo of [**pwa-asset-generator**]" /></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.npmjs.com/package/pwa-asset-generator">https://www.npmjs.com/package/pwa-asset-generator</a></div>
<p>I’ll explain why such a library is needed and how it’s compared to some other existing options of generating assets for your PWA on the following chapters.</p>
<blockquote>
<p>Feel free to jump to the final chapter of the article ⏬ to see features and advanced usage examples of the library.</p>
</blockquote>
<h3 id="heading-why-do-we-need-such-a-library-anyway">Why do we need such a library anyway?</h3>
<p>When you build a PWA with the goal of providing native-like user experiences on multiple platforms and stores, you need to meet the criteria of those platforms and stores with your PWA assets; <strong>icons</strong> and <strong>splash screens</strong>. Such criteria are;</p>
<ul>
<li><strong>Google’s Android</strong> platform respects Web App Manifest API specs and it expects you to provide <strong>at least 2 icon sizes</strong> in your manifest file — <a target="_blank" href="https://developers.google.com/web/fundamentals/web-app-manifest/#icons">https://developers.google.com/web/fundamentals/web-app-manifest/#icons</a></li>
<li>As it’s noted on Microsoft docs, your PWA has to meet specific image criteria declared on Web App Manifest in order to be automatically packaged for <strong>Microsoft Store</strong> — <a target="_blank" href="https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps/get-started#web-app-manifest">https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps/get-started#web-app-manifest</a></li>
</ul>
<blockquote>
<p>Once you use icons with required sizes as part of Web App Manifest API, you don’t need to provide additional splash screen images for above platforms. They are automatically generated for you.</p>
</blockquote>
<h4 id="heading-criteria-for-ios">Criteria for iOS</h4>
<p>Complicated. Currently, iOS doesn’t support Web App Manifest API specs, although the spec is in development— track the progress <a target="_blank" href="https://webkit.org/status/#specification-web-app-manifest">here</a>. It’s also not clear yet if Apple will change its approach for displaying splash screens during standards implementation, as <em>splash screens are not part of Web App Manifest specs</em>. So far, the only way to setup icons and splash screens for your PWA on iOS is, adding special HTML tags.</p>
<p>A special HTML link tag with rel <code>apple-touch-icon</code> is required to provide icons for your PWA when it's added to the user’s home screen. Read more about it on <a target="_blank" href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/">Apple's Icon Guidelines</a> and <a target="_blank" href="https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html">Safari Web Content Guide</a>.</p>
<p>Example icon specs from <a target="_blank" href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/">Apple’s Icon Guidelines</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039473204/1-ETlaAkk.png" alt="Example icon specs from Apple’s Icon Guidelines" /></p>
<p>Another special HTML link tag with rel <code>apple-touch-startup-image</code> is required if you also would like to provide splash screens for your PWA. iOS will display those screens when your PWA is being opened as well as when it's in the background. So far so good!</p>
<p>But, there’s a catch here: you need to create a splash screen image for <strong>each and every resolution</strong> on <a target="_blank" href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/launch-screen/#static-launch-screen-images-not-recommended">Apple’s Launch Screen Guidelines</a> and add an HTML tag with media attribute for each device resolution and orientation 🙀! Unfortunately, this requirement is not documented on <a target="_blank" href="https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html">Safari Web Content Guide</a> sufficiently.</p>
<p>Example link tag for <strong>just one</strong> resolution &amp; orientation pair;</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-startup-image"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"temp/apple-splash-2048-2732.png"</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039475383/Q5JYWhce0.png" alt="Example splash screen specs from Apple’s Launch Screen Guidelines" /></p>
<p>Example splash screen specs from <a target="_blank" href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/launch-screen/#static-launch-screen-images-not-recommended">Apple’s Launch Screen Guidelines</a></p>
<blockquote>
<p>💡 Creating icon and splash screen images for all the platforms, maintaining sizes and quality for all and adding HTML tags for each image can be overwhelming. So, why not automate it? 🤖</p>
</blockquote>
<h3 id="heading-drawbacks-of-existing-solutions">Drawbacks of existing solutions</h3>
<p>When I decided to build such a CLI library, there were already a couple of options out there to generate assets for PWAs and meta associated with them. They might as well be a good fit for your needs but I think it’s wise to get to know the drawbacks of them when deciding what to use.</p>
<p>Some of the drawbacks that I noticed <em>when I used them</em> are<em>;</em></p>
<h4 id="heading-runtime-dependency">Runtime dependency</h4>
<p>One of the options to generate and use PWA assets is using <a target="_blank" href="https://github.com/GoogleChromeLabs/pwacompat">PWACompat</a>. I think it’s a good option to maintain a standard splash screen look and feel across platforms. However, it uses an approach to generate PWA images on runtime (iOS) and store them in the session storage of your browser. Keep in mind that, this approach not only affects the initialization performance of your PWA, it also takes over the ownership of your <em>manifest.json</em> file and your assets.</p>
<h4 id="heading-brings-maintenance-costs">Brings maintenance costs</h4>
<p>Some other options to generate and use PWA assets are online tools like <a target="_blank" href="https://appsco.pe/developer/splash-screens">AppScope splash screen generator</a> or <a target="_blank" href="https://www.pwabuilder.com/generate">PWABuilder</a>. These tools individually generate either icons <em>OR</em> splash screens and there isn’t any online tool that generates both asset types. They also come with maintenance costs such as;</p>
<ul>
<li>Unzipping content and re-locating the assets</li>
<li>Updating <em>manifest.json</em> or <em>index.html</em> files manually</li>
<li>Keeping an eye on the standards and guidelines to keep your PWA compatible with all device types and platforms</li>
</ul>
<p>And, doing this manual work again and again.</p>
<h4 id="heading-difficult-to-automate"><strong>Difficult to automate</strong></h4>
<p>Another drawback of using online tools is that they are difficult to automate. An ideal scenario of keeping your PWA assets up to date and compatible with all platforms in an automated way would be integrating <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator"><strong>pwa-asset-generator</strong></a> to your build steps on your favorite CI. It can generate all the assets and update your <em>index.html</em> and <em>manifest.json</em> files with associated meta based on the latest platform specifications.</p>
<h4 id="heading-lacks-flexibility-and-ownership-on-the-content">Lacks flexibility and ownership on the content</h4>
<p>You’re not in control of how your assets are generated. None of the existing solutions provide full control of how your assets are generated. PWACompat doesn’t provide any control at all, it automatically generates all splash screens in the same form. And, online tools provide limited customization options when you generate your assets. <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator"><strong>pwa-asset-generator</strong></a> gives you full control of your assets via HTML inputs. I will describe this in detail in the next chapter.</p>
<h3 id="heading-features-and-usage-of-the-lib">Features and usage of the lib</h3>
<p>PWA Asset Generator automates the image generation in a creative way. Having <a target="_blank" href="https://pptr.dev">Puppeteer</a> at its core enables lots of possibilities.</p>
<p>— Generates both icons and splash screens with optional <code>--icon-only</code> <code>--splash-only</code> <code>--landscape-only</code> and <code>--portrait-only</code> flags ✨</p>
<p>— Updates your <em>manifest.json</em> and <em>index.html</em> files automatically for declaring generated image assets 🙌</p>
<p>— Scrapes latest specs from Apple Human Interface guidelines website via Puppeteer to make your PWA ready for all/recent iOS devices out there 🤖</p>
<ul>
<li>Supports offline mode and uses static spec data when things go wrong with scraping 📴</li>
<li>Updates static spec data before each release automatically and <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator/actions?query=workflow%3A%22Sanity+Check%22">monitors spec changes</a> everyday 🔄</li>
</ul>
<p>— Uses the Chrome browser as it’s a canvas of your fav image editor. It uses a shell HTML file as an artboard and centers your logo before taking screenshots for each resolution via Puppeteer 🤖</p>
<p>— You can provide your source in multiple formats; a local image file, a local HTML file, a remote image or HTML file 🙌</p>
<ul>
<li>When it’s an image source, it is centered over the background option you provide 🌅</li>
<li>When it’s an HTML source, you can go as creative as you like; position your logo, use SVG filters, use variable fonts, use gradient backgrounds, use typography and etc. Your HTML file is rendered on Chrome before taking screenshots for each resolution 🎨</li>
</ul>
<p>— It uses <a target="_blank" href="https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core">puppeteer-core</a> instead of puppeteer and only installs Chromium if it doesn’t exist on the system. Saves waste of ~110–150mb of disk space and many seconds from the world per each user 🌎️️⚡️</p>
<p>— Supports dark mode splash screens on iOS! So, you can provide both light 🌕 and dark 🌚 splash screen images to differentiate your apps look &amp; feel based on user preference 🌙</p>
<h4 id="heading-examples">Examples</h4>
<ol>
<li>Basic usage with local PNG input, skips scraping specs, generating both splash screens and icons;</li>
</ol>
<pre><code class="lang-bash">npx pwa-asset-generator ./img/logo.png --background <span class="hljs-string">"#ababab"</span> --scrape <span class="hljs-literal">false</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039477611/mVXs0cpE7.png" alt="Basic usage with local PNG input" /></p>
<p>2. Advanced usage with remote SVG input, using a custom gradient background 🏳️‍🌈, generating icons only;</p>
<pre><code class="lang-bash">npx pwa-asset-generator https://animejs.com/documentation/assets/img/icons/icon-github.svg ./temp -b <span class="hljs-string">"linear-gradient(180deg, #f00000, #f00000 16.67%, #ff8000 16.67%, #ff8000 33.33%, #ffff00 33.33%, #ffff00 50%, #007940 50%, #007940 66.67%, #4040ff 66.67%, #4040ff 83.33%, #a000c0 83.33%, #a000c0) fixed"</span> --icon-only
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039480051/dRRCtztg9.png" alt="Advanced usage with remote SVG input" /></p>
<blockquote>
<p>💡 One can introduce a build step on CI to automate differentiating the branding of a PWA on special occasions. Wouldn’t it be awesome?</p>
</blockquote>
<p>3. Advanced usage with custom HTML input, using background tile and custom variable web-font in it, generating splash screens only, outputting JPEG file type with 70% quality, with code output saved to your PWA’s index.html file;</p>
<pre><code class="lang-bash"><span class="hljs-comment"># The local nyan.html can be a remote url as well  </span>
<span class="hljs-comment"># npx pwa-asset-generator https://ny.an/nyan.html</span>
npx pwa-asset-generator nyan.html ./src/app/temp --splash-only --<span class="hljs-built_in">type</span>=jpeg --quality=70 --index ./src/app/index.html
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039482464/jeqZBD1w5.png" alt="Advanced usage with custom HTML input" /></p>
<p>If you’re curious to see how such an HTML is built, <a target="_blank" href="https://gist.github.com/onderceylan/67f4d33f3317138905e1c7234cb70731">here’s the Gist</a>.</p>
<blockquote>
<p>💡 When HTML input is used, you gain full control on your assets 👩‍💻 You can use any sort of web technology to render the content of your assets. Some might include but not limited to SVG filters, css media queries, variable fonts, css image filters, gradient backgrounds, and image tiles 👩‍🎨</p>
</blockquote>
<p>4. Generating both light and dark mode splash screens for iOS, outputting JPEG file type with 80% quality, with code output saved to your PWA’s index.html file;</p>
<pre><code class="lang-bash">npx pwa-asset-generator light-logo.svg ./assets --dark-mode --background dimgrey --splash-only --<span class="hljs-built_in">type</span> jpeg --quality 80 --index ./src/app/index.html
</code></pre>
<pre><code class="lang-bash">npx pwa-asset-generator dark-logo.svg ./assets --background lightgray --splash-only --<span class="hljs-built_in">type</span> jpeg --quality 80 --index ./src/app/index.html
</code></pre>
<p>Video demonstration of dark mode splash screens on iOS:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/onderceylan/status/1188221265632350208?s=20">https://twitter.com/onderceylan/status/1188221265632350208?s=20</a></div>
<h3 id="heading-bonus-under-the-hood">BONUS: Under the hood</h3>
<p>For curious minds reading up to this point, I’d like to share a simple yet powerful concept that library uses under the hood.</p>
<p>As I mentioned earlier, using Puppeteer under the hood opens a door of possibilities. A key feature of Puppeteer that <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator"><strong>pwa-asset-generator</strong></a> uses, is its <a target="_blank" href="https://pptr.dev/#?product=Puppeteer&amp;version=v1.19.0&amp;show=api-pagescreenshotoptions">screenshot API</a>.</p>
<p>Puppeteer allows saving screenshots in both PNG and JPEG file types with optional compression possibility for JPEG images.</p>
<p>Every time you execute a command with <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator"><strong>pwa-asset-generator</strong></a>, library scrapes latest iOS specs from 2 sources. Then it opens new tabs rendering the content of <em>shell HTML</em>, based on CLI parameters provided. Here’s the <em>shell HTML</em> content;</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>  
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1"</span>&gt;</span>  
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">  
    <span class="hljs-selector-tag">body</span> {  
      <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;  
      <span class="hljs-attribute">background</span>: --background;  
      <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;  
      <span class="hljs-attribute">padding</span>: --padding;  
      <span class="hljs-attribute">box-sizing</span>: border-box;  
    }  
    <span class="hljs-selector-tag">img</span> {  
      <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;  
      <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;  
      <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;  
      <span class="hljs-attribute">object-fit</span>: contain;      
    }  
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>  
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"--image-input"</span>&gt;</span>  
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>  
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>It opens a new browser tab that renders <em>shell HTML</em> file contents via Puppeteer for <em>each icon and splash screen size,</em> and finally saves a screenshot of each.</p>
<p>// You can see the complete source here: <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator/blob/master/src/helpers/puppets.ts">https://github.com/onderceylan/pwa-asset-generator/blob/master/src/helpers/puppets.ts</a></p>
<pre><code><span class="hljs-comment">// You can see the complete source here: https://github.com/onderceylan/pwa-asset-generator/blob/master/src/helpers/puppets.ts</span>
const saveImages <span class="hljs-operator">=</span> async (
  imageList: Image[],
  source: <span class="hljs-keyword">string</span>,
  output: <span class="hljs-keyword">string</span>,
  options: Options,
  browser: Browser,
): Promise<span class="hljs-operator">&lt;</span>SavedImage[]<span class="hljs-operator">&gt;</span> <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
  let <span class="hljs-keyword">address</span>: <span class="hljs-keyword">string</span>;
  let shellHtml: <span class="hljs-keyword">string</span>;

  const logger <span class="hljs-operator">=</span> preLogger(saveImages.<span class="hljs-built_in">name</span>, options);
  logger.log(<span class="hljs-string">'Initialising puppeteer to take screenshots'</span>, <span class="hljs-string">'🤖'</span>);

  <span class="hljs-keyword">if</span> (canNavigateTo(source)) {
    <span class="hljs-keyword">address</span> <span class="hljs-operator">=</span> await url.getAddress(source, options);
  } <span class="hljs-keyword">else</span> {
    shellHtml <span class="hljs-operator">=</span> await url.getShellHtml(source, options);
  }

  <span class="hljs-keyword">return</span> Promise.all(
    imageList.map(async ({ name, width, height, scaleFactor, orientation }) <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
      const { <span class="hljs-keyword">type</span>, quality } <span class="hljs-operator">=</span> options;
      const path <span class="hljs-operator">=</span> file.getImageSavePath(name, output, <span class="hljs-keyword">type</span>);

      <span class="hljs-keyword">try</span> {
        const page <span class="hljs-operator">=</span> await browser.newPage();
        await page.setViewport({ width, height });

        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">address</span>) {
          await page.goto(<span class="hljs-keyword">address</span>);
        } <span class="hljs-keyword">else</span> {
          await page.setContent(shellHtml);
        }

        await page.screenshot({
          path,
          omitBackground: <span class="hljs-operator">!</span>options.opaque,
          <span class="hljs-keyword">type</span>: options.type,
          ...(<span class="hljs-keyword">type</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">'png'</span> ? { quality } : {}),
        });

        await page.close();

        logger.success(`Saved image ${name}`);

        <span class="hljs-keyword">return</span> { name, width, height, scaleFactor, path, orientation };
      } <span class="hljs-keyword">catch</span> (e) {
        logger.error(e.message);
        <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(`Failed to save image ${name}`);
      }
    }),
  );
};
</code></pre><p>I was inspired by this <a target="_blank" href="https://dev.to/dominikfiala/hassle-free-pwa-icons-and-splash-screen-generation-3c24">blog post</a> initially for the idea of saving screenshots of a resized responsive web page, thanks for the inspiration Dominik!</p>
<blockquote>
<p>⚡️ Unlike Puppeteer, pwa-asset-generator uses <em>system browser first</em> approach using <a target="_blank" href="https://github.com/GoogleChrome/chrome-launcher">chrome-launcher</a> with <a target="_blank" href="https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core">puppeteer-core</a> and it only installs chromium if it’s not installed on execution environment.</p>
</blockquote>
<p>Since the library uses Puppeteer in headless mode, you don’t see what’s happening under the hood. To make it visible, here’s a demonstration of the process, by disabling the <code>headless</code> mode in <a target="_blank" href="https://pptr.dev/#?product=Puppeteer&amp;version=v1.19.0&amp;show=api-puppeteerlaunchoptions">Puppeteer launch options</a>;</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/onderceylan/status/1190657108003282945?s=20">https://twitter.com/onderceylan/status/1190657108003282945?s=20</a></div>
<h3 id="heading-support">Support</h3>
<p>If you like the library, I appreciate your star on <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator">GitHub</a> and your like/retweet and feedback on <a target="_blank" href="https://twitter.com/onderceylan/status/1162138795787071490?s=20">Twitter</a>. If you’re a blogger too, it’d be awesome to see a reference to it on your blogs related to PWAs.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/onderceylan/status/1162138795787071490?s=20">https://twitter.com/onderceylan/status/1162138795787071490?s=20</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/onderceylan/pwa-asset-generator ">https://github.com/onderceylan/pwa-asset-generator </a></div>
<p>If you enjoyed my article, follow me here and on <a target="_blank" href="https://twitter.com/onderceylan">Twitter</a> to subscribe to my future posts and projects! Cheers!</p>
]]></content:encoded></item><item><title><![CDATA[Build a production ready PWA with Angular and Firebase]]></title><description><![CDATA[Angular has a great set of tools for developers to help them easily start developing apps. After introducing schematics, Angular team took their game even further.
Being a Progressive Web App evangelist, it’s no surprise that one of my favourite ng s...]]></description><link>https://onderceylan.com/build-a-production-ready-pwa-with-angular-and-firebase-8f2a69824fcc</link><guid isPermaLink="true">https://onderceylan.com/build-a-production-ready-pwa-with-angular-and-firebase-8f2a69824fcc</guid><category><![CDATA[Angular]]></category><category><![CDATA[PWA]]></category><category><![CDATA[Ionic Framework]]></category><category><![CDATA[Firebase]]></category><dc:creator><![CDATA[Önder Ceylan]]></dc:creator><pubDate>Wed, 30 Jan 2019 16:01:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039546563/tI6AZy68x.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Angular has a great set of tools for developers to help them easily start developing apps. After introducing schematics, Angular team took their game even further.</p>
<p>Being a Progressive Web App evangelist, it’s no surprise that one of my favourite ng schematics is <a target="_blank" href="https://www.npmjs.com/package/@angular/pwa">@angular/pwa</a>. This schematic gives you a head start on your PWA development journey. However, you need to do more on your app to create the best experiences for your users on multiple mobile platforms.</p>
<p>This tutorial will cover how to build, optimise and host your PWA to provide native-like app experiences on the production environment. It provides guidelines for best practices and platform optimisations. In addition to that, your PWA will score 💯 on <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a> PWA audit after following the guidelines and tips described in this article.</p>
<h4 id="heading-before-going-forward-keep-in-mind-that-this-is-a-tutorial-and-you-might-want-to-keep-track-of-your-progress-i-host-international-workshops-with-almost-the-same-content-as-in-this-article-thats-why-i-created-a-repo-for-the-workshop-material-on-githubhttpsgithubcomonderceylanpwa-workshop-angular-firebase-you-can-do-the-workshop-yourself-remotely-if-you-wish">Before going forward, keep in mind that this is a tutorial and you might want to keep track of your progress. I host international workshops with almost the same content as in this article. That’s why I created a repo for the workshop material on <a target="_blank" href="https://github.com/onderceylan/pwa-workshop-angular-firebase">GitHub</a>. You can do the workshop yourself remotely if you wish 👇</h4>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/onderceylan/pwa-workshop-angular-firebase">https://github.com/onderceylan/pwa-workshop-angular-firebase</a></div>
<p>If you like the workshop, I appreciate your star on <a target="_blank" href="https://github.com/onderceylan/pwa-workshop-angular-firebase">GitHub</a>. Here we go!</p>
<h3 id="heading-1-adding-angularpwa-schematic-to-an-angular-app">1. Adding @angular/pwa schematic to an Angular app</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039486839/6Nku2ET_6.png" alt /></p>
<p>The first thing you need to do -on your brand new or existing Angular app to build a Progressive Web App - is adding <em>@angular/pwa</em> schematic to your app.</p>
<p>You can do this by simply running <em>add</em> command on your Angular CLI.</p>
<pre><code class="lang-bash">ng add @angular/pwa
</code></pre>
<blockquote>
<p>💡 TIP — <a target="_blank" href="https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner">Use `npx ng` command</a> in your Angular project without installing a global @angular/cli package on your system.</p>
</blockquote>
<p>Adding <em>@angular/pwa</em> schematic will create following files on your project, which we will dive into later on.</p>
<pre><code>ngsw<span class="hljs-operator">-</span>config.json  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>128x128.png  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>144x144.png  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>152x152.png  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>192x192.png  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>384x384.png  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>512x512.png  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>72x72.png  
src<span class="hljs-operator">/</span>assets<span class="hljs-operator">/</span>icons<span class="hljs-operator">/</span>icon<span class="hljs-operator">-</span>96x96.png  
src<span class="hljs-operator">/</span>manifest.json
</code></pre><p>It will also tweak your <code>angular.json</code>, <code>index.html</code> and <code>app.module.ts</code> files accordingly.</p>
<h3 id="heading-2-changing-web-app-manifest-file-manifestjson">2. Changing Web App Manifest file — manifest.json</h3>
<blockquote>
<p>The <a target="_blank" href="http://web%20app%20manifest">web app manifest</a> is a simple JSON file that tells the browser about your web application and how it should behave when ‘installed’ on the user’s mobile device or desktop. Having a manifest is required by Chrome to show the <a target="_blank" href="https://developers.google.com/web/fundamentals/app-install-banners/">Add to Home Screen prompt</a>.</p>
<p>A typical manifest file includes information about the app <code>name</code>, <code>icons</code> it should use, the <code>start_url</code> it should start at when launched, and more.</p>
<p>— <a target="_blank" href="https://developers.google.com/web/fundamentals/web-app-manifest/">Web App Manifest on Google Developers Portal</a></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039488821/m79deh49T.png" alt /></p>
<p>Use Chrome DevTools’ Application tab to inspect your app’s manifest.json file</p>
<h4 id="heading-21-adding-description">2.1 Adding description</h4>
<p>One of the improvement points that I see on the manifest.json file, which was generated by <em>@angular/pwa</em> schematic, is the addition of <em>description</em> field. Description field is documented on both <a target="_blank" href="https://www.w3.org/TR/appmanifest/">W3C Web App Manifest spec</a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Manifest">MDN</a>.</p>
<pre><code class="lang-json">{  
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"My Fancy PWA is a dummy Angular app which demonstrates a PWA's behaviour on different platforms."</span>  
}
</code></pre>
<h4 id="heading-22-tracking-the-start-url">2.2 Tracking the start url</h4>
<p>It is also a good practice to add a query string to the end of the <em>start_url</em> field in manifest.json file to track how often your installed app is launched from users’ home screen.</p>
<pre><code class="lang-json">{  
  <span class="hljs-attr">"start_url"</span>: `<span class="hljs-string">"/?utm_source=a2hs"</span>  
}
</code></pre>
<blockquote>
<p>💡 TIP — Providing a name, description and an app icon that is at least 512 x 512 pixels will help your app to be <a target="_blank" href="https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps/microsoft-store#automatic-pwa-importing-with-bing">automatically indexed and packaged on Microsoft Store</a>.</p>
</blockquote>
<p>Example of a full manifest.json file that I use on one of my PWAs:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"ITNEXT Summit 2018"</span>,
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"ITNEXT"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Be the best explorer in next-gen technologies. Experience use-cases, howtos and best-practices with latest frontend and backend technologies, network and security, engineering and low-code development with OutSystems."</span>,
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#ffffff"</span>,
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#ffffff"</span>,
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
  <span class="hljs-attr">"orientation"</span>: <span class="hljs-string">"portrait"</span>,
  <span class="hljs-attr">"scope"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/?utm_source=itnext_pwa_a2hs"</span>,
  <span class="hljs-attr">"icons"</span>: [
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-72x72.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"72x72"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-96x96.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"96x96"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-128x128.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"128x128"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-144x144.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"144x144"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-152x152.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"152x152"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-192x192.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-384x384.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"384x384"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/icons/icon-512x512.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    }
  ],
  <span class="hljs-attr">"gcm_sender_id"</span>: <span class="hljs-string">"103953800507"</span>
}
</code></pre>
<h3 id="heading-3-displaying-an-a2hs-guideline">3. Displaying an A2HS guideline</h3>
<p>Google Chrome automatically detects a PWA on Android systems and if the site meets the <a target="_blank" href="https://developers.google.com/web/fundamentals/app-install-banners/#criteria">add to home screen criteria</a>, it shows an <a target="_blank" href="https://developers.google.com/web/updates/2018/06/a2hs-updates">install banner or mini info bar</a> to the user to allow them adding it to their home screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039492179/4J2N3kkV6.png" alt /></p>
<p>Add to Home Screen dialog on Android</p>
<blockquote>
<p>💡 TIP — <strong>short_name</strong> field in your manifest.json will represent your PWA’s name when it’s added to a home screen. Additionally, <strong>name</strong> field will be used on <a target="_blank" href="https://developers.google.com/web/fundamentals/app-install-banners/"><em>Add to Home Screen</em> prompt</a> on Android!</p>
</blockquote>
<p>Such functionality does not exist on iOS. But, luckily we can build our own UX to guide users towards the required <em>taps</em> to help them add your app to their home screens.</p>
<p>It can be a good old popup;</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039494429/qeJPUcGaA.png" alt /></p>
<p>A popup to guide users —Source: <a target="_blank" href="https://dockyard.com/blog/2017/09/27/encouraging-pwa-installation-on-ios">https://dockyard.com/blog/2017/09/27/encouraging-pwa-installation-on-ios</a></p>
<p>Or, even better; a mini-info bar which mimics Chrome’s behaviour on Android platforms to create a uniform experience across platforms.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039496582/MvoH8SPfj.png" alt /></p>
<p>A mini-info bar like guidance — Source: <a target="_blank" href="https://www.netguru.com/codestories/few-tips-that-will-make-your-pwa-on-ios-feel-like-native">https://www.netguru.com/codestories/few-tips-that-will-make-your-pwa-on-ios-feel-like-native</a></p>
<p>For example, I used a bit of code on one of my PWAs to introduce this behaviour.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { get, set } <span class="hljs-keyword">from</span> <span class="hljs-string">'idb-keyval'</span>;
<span class="hljs-keyword">import</span> { ToastController } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ionic/angular'</span>;
<span class="hljs-keyword">async</span> showIosInstallBanner() {
  <span class="hljs-comment">// Detects if device is on iOS</span>
  <span class="hljs-keyword">const</span> isIos = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> userAgent = <span class="hljs-built_in">window</span>.navigator.userAgent.toLowerCase();
    <span class="hljs-keyword">return</span> <span class="hljs-regexp">/iphone|ipad|ipod/</span>.test(userAgent);
  };
  <span class="hljs-comment">// Detects if device is in standalone mode</span>
  <span class="hljs-keyword">const</span> isInStandaloneMode = <span class="hljs-function">() =&gt;</span> (<span class="hljs-string">'standalone'</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">window</span>.navigator) &amp;&amp; <span class="hljs-built_in">window</span>.navigator.standalone;

  <span class="hljs-comment">// Show the banner once</span>
  <span class="hljs-keyword">const</span> isBannerShown = <span class="hljs-keyword">await</span> get(<span class="hljs-string">'isBannerShown'</span>);

  <span class="hljs-comment">// Checks if it should display install popup notification</span>
  <span class="hljs-keyword">if</span> (isIos() &amp;&amp; !isInStandaloneMode() &amp;&amp; isBannerShown === <span class="hljs-literal">undefined</span>) {
    <span class="hljs-keyword">const</span> toast = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.toastController.create({
      <span class="hljs-attr">showCloseButton</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">closeButtonText</span>: <span class="hljs-string">'OK'</span>,
      <span class="hljs-attr">cssClass</span>: <span class="hljs-string">'custom-toast'</span>,
      <span class="hljs-attr">position</span>: <span class="hljs-string">'bottom'</span>,
      <span class="hljs-attr">message</span>: <span class="hljs-string">`To install the app, tap "Share" icon below and select "Add to Home Screen".`</span>,
    });
    toast.present();
    set(<span class="hljs-string">'isBannerShown'</span>, <span class="hljs-literal">true</span>);
  }
}
</code></pre>
<h3 id="heading-4-adding-meta-tags-for-platform-optimisation">4. Adding meta tags for platform optimisation</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039498722/g0MXfmZOT.jpeg" alt /></p>
<p>WebKit is the <a target="_blank" href="https://webkit.org/project">web browser engine</a> used by Safari, Mail, App Store, and many other apps on macOS, iOS, and Linux — <a target="_blank" href="https://webkit.org/">https://webkit.org/</a></p>
<p>Although many browsers adopted the <a target="_blank" href="https://w3c.github.io/manifest/">Web App Manifest spec</a>, WebKit (specifically, Mobile Safari on iOS) currently uses custom non-standards track meta tag implementations.</p>
<p>Because of that, Apple’s iOS doesn’t create a splash screen or an app icon for a PWA automatically the same way that Google’s Android does based on the app’s manifest file. This prevents your PWA to provide native-app-like experience.</p>
<blockquote>
<p>💡 TIP — Keep an eye on WebKit Feature Status for tracking the progress of the implementation of web standards. For instance; once Web App Manifest specs is implemented on WebKit, you won’t need to use custom meta tags anymore. Track the progress here: <a target="_blank" href="https://webkit.org/status/#?search=manifest">https://webkit.org/status/#?search=manifest</a></p>
</blockquote>
<p>Fortunately, there is a workaround to configure app icons, splash screens and status bar of your PWA on iOS platform. The workaround is adding custom meta tags to your app’s index.html file!</p>
<h4 id="heading-41-adding-meta-tags-for-app-icons-on-ios">4.1 Adding meta tags for app icons on iOS</h4>
<p>As discussed above, iOS does not recognize the app icons that you’ve put in your manifest file. That’s why you need to add meta tags to your index.html file to optimise your app and its icons for iOS.</p>
<p>The following example demonstrates specifying various sizes for most common iOS devices:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"touch-icon-iphone.png"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"152x152"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"touch-icon-ipad.png"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"180x180"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"touch-icon-iphone-retina.png"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"167x167"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"touch-icon-ipad-retina.png"</span>&gt;</span>
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/onderceylan/status/1090229929126621184">https://twitter.com/onderceylan/status/1090229929126621184</a></div>
<p>You can read more about this topic on Apple’s <a target="_blank" href="https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html">Safari Web Content Guide</a>.</p>
<h4 id="heading-42-adding-meta-tags-for-splash-screens-on-ios">4.2 Adding meta tags for splash screens on iOS</h4>
<p>In order to add splash screen to your PWA on iOS, you must add a meta tag that points out an image for a specific resolution. If the size of an image in the meta tag matches with the device’s resolution, iOS will show the image as a splash screen.</p>
<p>Apple uses a custom link with a rel called <code>apple-touch-startup-image</code> to support splash screens for an app added to iOS home screen, as you can <a target="_blank" href="https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html">see it here</a>. You also need to set <code>apple-mobile-web-app-capable</code> meta in order to bring the support for splash screens.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-capable"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"yes"</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-startup-image"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/launch.png"</span>&gt;</span>
</code></pre>
<p>You need to create static images in different sizes for different devices. Here’s a list of devices and their resolutions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039500908/udkTyZJU0.png" alt /></p>
<p>Static launch screen images — <a target="_blank" href="https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/launch-screen/">Apple Human Interface Guidelines</a></p>
<p>This is an example of some of the meta tags I have in my PWAs index.html file:</p>
<pre><code><span class="hljs-operator">&lt;</span>link href<span class="hljs-operator">=</span><span class="hljs-string">"/assets/splash/iphone5\_splash.png"</span> media<span class="hljs-operator">=</span><span class="hljs-string">"(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)"</span> rel<span class="hljs-operator">=</span><span class="hljs-string">"apple-touch-startup-image"</span><span class="hljs-operator">&gt;</span>
<span class="hljs-operator">&lt;</span>link href<span class="hljs-operator">=</span><span class="hljs-string">"/assets/splash/iphone6\_splash.png"</span> media<span class="hljs-operator">=</span><span class="hljs-string">"(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)"</span> rel<span class="hljs-operator">=</span><span class="hljs-string">"apple-touch-startup-image"</span><span class="hljs-operator">&gt;</span>
<span class="hljs-operator">&lt;</span>link href<span class="hljs-operator">=</span><span class="hljs-string">"/assets/splash/iphonex\_splash.png"</span> media<span class="hljs-operator">=</span><span class="hljs-string">"(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)"</span> rel<span class="hljs-operator">=</span><span class="hljs-string">"apple-touch-startup-image"</span><span class="hljs-operator">&gt;</span>
</code></pre><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/onderceylan/status/1090230489816928257">https://twitter.com/onderceylan/status/1090230489816928257</a></div>
<blockquote>
<p>💡 TIP — Use an automated tool to generate your images and their corresponding html codes. Here’s an open source library that I built just for this purpose: <a target="_blank" href="https://github.com/onderceylan/pwa-asset-generator">https://github.com/onderceylan/pwa-asset-generator</a></p>
</blockquote>
<pre><code class="lang-bash">npx `pwa-asset-generator https://github.com/onderceylan/pwa-asset-generator/blob/master/static/logo.png -b <span class="hljs-string">"linear-gradient(to right, #fa709a 0%, #fee140 100%)"</span>`
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039503753/8dFh_g4Ju.gif" alt /></p>
<p>Generate splash screen and icon images and corresponding HTML tags automatically via <strong>pwa-asset-generator</strong></p>
<h4 id="heading-43-adding-meta-tags-for-the-status-bar-on-ios">4.3 Adding meta tags for the status bar on iOS</h4>
<p>You can customize iOS status bar of your PWA by using <code>apple-mobile-web-app-status-bar-style</code> meta tag in your index.html file. This meta tag has no effect unless you specify full-screen mode aka standalone for your PWA.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-status-bar-style"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"white"</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039506547/rKOj7C7fS.png" alt /></p>
<p><em>View of the status bars;</em> <strong><em>black-translucent*</em></strong>,<em> **</em>white<strong><em>*, and</em> *</strong>black<em>*</em></p>
<p>Apple only supports 3 options for the <code>content</code> attribute of the meta tag with no further customisation possibility. The options are;</p>
<ul>
<li><code>white</code> — displays a white status bar with black text and symbols. <code>default</code> value has the same effect as <code>white</code> but I <em>prefer white over that.</em> It’s less confusing as <code>default</code> being non-default behaviour! 🤯</li>
<li><code>black</code> — displays a black status bar and white text and symbols. This is the default behaviour of a PWA on iOS, when this meta tag is not in place.</li>
<li><code>black-translucent</code> — displays a white text and symbols, with status bar using the same background color of your app’s body element.</li>
</ul>
<h4 id="heading-44-meta-tags-for-social-share">4.4 Meta tags for social share</h4>
<p>Progressive Web Apps are accessible via web by nature. This means that you can just share a link to your PWA on social media or instant messaging services.</p>
<p>For the convenience of your users who shares your app with their network, I recommend creating a nice preview of your app by adding <a target="_blank" href="http://ogp.me/">Open Graph Protocol meta tags</a> to your PWA.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039508411/1-r4PyeYU.png" alt /></p>
<p>A preview of your app’s link on Twitter with the Open Graph meta tags provided</p>
<p>Here’s an example of required meta tags to provide a nice user experience:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"ITNEXT Summit 2018 PWA"</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Check out the PWA of ITNEXT Summit 2018. It rocks!"</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://itnext-summit-2018.firebaseapp.com/assets/img/social-share.png"</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:url"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://itnext-summit-2018.firebaseapp.com"</span>&gt;</span>  
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:card"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"summary_large_image"</span>&gt;</span>
</code></pre>
<p>Furthermore, you can introduce even more optimisations to your PWA to customize user experience on iOS.</p>
<p>Please visit Safari HTML reference for the full list of Apple specific meta tags: <a target="_blank" href="https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html">https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html</a></p>
<p>Apart from using the meta tags, some additional optimisations can be done by using simple CSS tweaks. Such as;</p>
<ul>
<li>Disabling selection</li>
<li>Disabling highlighting</li>
<li>Disabling callouts</li>
<li>Enabling tab effects</li>
</ul>
<p>Read more on those here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://dev.to/oskarlarsson/designing-native-like-progressive-web-apps-for-ios-510o">https://dev.to/oskarlarsson/designing-native-like-progressive-web-apps-for-ios-510o</a></div>
<h3 id="heading-5-configuring-ngsw-configjson-angular-service-worker">5. Configuring ngsw-config.json — Angular Service Worker</h3>
<p>When you add @angular/pwa schematic, Angular’s ServiceWorkerModule is injected to your <code>app.module.ts</code> file. This module registers your automatically generated service worker file — ngsw-worker.js which is built by ng cli during the production build.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039510642/MZtEZy1JF.png" alt /></p>
<p>You can refer to <a target="_blank" href="https://angular.io/guide/service-worker-config">Angular docs for service worker configuration</a> to understand the design and possible options for your configuration.</p>
<h4 id="heading-51-data-groups">5.1 Data groups</h4>
<blockquote>
<p>Unlike asset resources, data requests are not versioned along with the app. They’re cached according to manually-configured policies that are more useful for situations such as API requests and other data dependencies.</p>
</blockquote>
<p>What I would like to stress here is the caching strategies for data resources. Angular service worker is designed with brevity in mind, therefore it provides 2 caching strategies.</p>
<h5 id="heading-caching-strategy-performance">Caching strategy: performance</h5>
<blockquote>
<p>Suitable for resources that don’t change often; for example, user avatar images. — <a target="_blank" href="https://angular.io/guide/service-worker-config#strategy">Angular docs</a></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039512676/aYxK_li8B.png" alt /></p>
<p>Performance is a <strong>cache-first</strong> strategy</p>
<blockquote>
<p><code>performance</code>, the default, optimizes for responses that are as fast as possible. If a resource exists in the cache, the cached version is used. This allows for some staleness, depending on the <code>maxAge</code>, in exchange for better performance. This is suitable for resources that don't change often; for example, user avatar images.</p>
</blockquote>
<h5 id="heading-caching-strategy-freshness">Caching strategy: freshness</h5>
<blockquote>
<p>Useful for resources that change frequently; for example, account balances. — <a target="_blank" href="https://angular.io/guide/service-worker-config#strategy">Angular docs</a></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039514862/_ec1-1rKb.png" alt /></p>
<p>Freshness is a <strong>network-first</strong> strategy</p>
<blockquote>
<p><code>freshness</code> optimizes for currency of data, preferentially fetching requested data from the network. Only if the network times out, according to <code>timeout</code>, does the request fall back to the cache.</p>
</blockquote>
<pre><code class="lang-json"><span class="hljs-string">"dataGroups"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"from-api"</span>,
      <span class="hljs-attr">"version"</span>: <span class="hljs-number">2</span>,
      <span class="hljs-attr">"urls"</span>: [<span class="hljs-string">"/data/data.json"</span>], <span class="hljs-comment">// or ["/dashboard", "/user"]</span>
      <span class="hljs-attr">"cacheConfig"</span>: {
        <span class="hljs-attr">"strategy"</span>: <span class="hljs-string">"performance"</span>,
        <span class="hljs-attr">"maxSize"</span>: <span class="hljs-number">10</span>,
        <span class="hljs-attr">"maxAge"</span>: <span class="hljs-string">"1h"</span>,
        <span class="hljs-attr">"timeout"</span>: <span class="hljs-string">"3s"</span>
      }
    }
  ]
</code></pre>
<p>With the example above, data from APIs or a static json file received will be cached with a freshness strategy for a maximum of 10 responses, maximum cache age of 1 hour, and a timeout of 3 seconds, after which the result will fallback to the cache. <em>Freshness</em> is a <strong>network-first strategy</strong> and alternatively, you can use <em>performance</em> as the strategy for a <strong>cache-first strategy</strong>.</p>
<blockquote>
<p>💡 TIP — Increase version field in dataGroups to invalidate your cache of data resources when your data source has been updated in backwards-incompatible way. A new version of the app may not be compatible with the old data format.</p>
</blockquote>
<h4 id="heading-52-asset-groups">5.2 Asset groups</h4>
<blockquote>
<p>Assets are resources that are part of the app version that update along with the app. They can include resources loaded from the page’s origin as well as third-party resources loaded from CDNs and other external URLs.</p>
</blockquote>
<p>Depending on the policy by which they are cached, you can introduce either <code>lazy</code> or <code>prefetch</code> strategy on each of your asset group. Resources on service worker configuration accept <a target="_blank" href="https://angular.io/guide/service-worker-config#glob-patterns">glob-like patterns</a> that match a number of files, like;</p>
<pre><code class="lang-json"><span class="hljs-string">"assetGroups"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"shell"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"updateMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"resources"</span>: {
        <span class="hljs-attr">"files"</span>: [
          <span class="hljs-string">"/favicon.ico"</span>,
          <span class="hljs-string">"/index.html"</span>,
          <span class="hljs-string">"/*.css"</span>,
          <span class="hljs-string">"/vendor.*.js"</span>,
          <span class="hljs-string">"/main.*.js"</span>,
          <span class="hljs-string">"/polyfills.*.js"</span>,
          <span class="hljs-string">"/runtime.*.js"</span>,
          <span class="hljs-string">"/*.js"</span>,
          <span class="hljs-string">"!/*-sw.js"</span>
        ]
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"assets"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"lazy"</span>,
      <span class="hljs-attr">"updateMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"resources"</span>: {
        <span class="hljs-attr">"files"</span>: [
          <span class="hljs-string">"/assets/**"</span>,
          <span class="hljs-string">"/svg/**"</span>
        ],
        <span class="hljs-attr">"urls"</span>: [
          <span class="hljs-string">"https://fonts.googleapis.com/**"</span>
        ]
      }
    }
  ]
</code></pre>
<p>For resources already in the cache, the <code>updateMode</code> determines the caching behaviour when a new version of the app is discovered. Any resources in the group that have changed since the previous version are updated in accordance with <code>updateMode</code>. Angular service worker will automatically handle updates of your assets when there’s a new service worker version for your app.</p>
<blockquote>
<p>💡 TIP — When you use <strong>a caching</strong> strategy, you must exclude the resources that you’d like to keep fresh. Such as additional service worker files that your app might use.</p>
</blockquote>
<p>Following example of a full ngsw-config.json file demonstrates asset groups, data groups, app data and a glob for excluding additional service workers:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"index"</span>: <span class="hljs-string">"/index.html"</span>,
  <span class="hljs-attr">"appData"</span>: {
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.1.0"</span>,
    <span class="hljs-attr">"changelog"</span>: <span class="hljs-string">"Added better resource caching"</span>
  },
  <span class="hljs-attr">"assetGroups"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"shell"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"updateMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"resources"</span>: {
        <span class="hljs-attr">"files"</span>: [
          <span class="hljs-string">"/favicon.ico"</span>,
          <span class="hljs-string">"/index.html"</span>,
          <span class="hljs-string">"/*.css"</span>,
          <span class="hljs-string">"/vendor.*.js"</span>,
          <span class="hljs-string">"/main.*.js"</span>,
          <span class="hljs-string">"/polyfills.*.js"</span>,
          <span class="hljs-string">"/runtime.*.js"</span>,
          <span class="hljs-string">"/*.js"</span>,
          <span class="hljs-string">"!/*-sw.js"</span>
        ]
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"assets"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"lazy"</span>,
      <span class="hljs-attr">"updateMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"resources"</span>: {
        <span class="hljs-attr">"files"</span>: [
          <span class="hljs-string">"/assets/**"</span>,
          <span class="hljs-string">"/svg/**"</span>
        ],
        <span class="hljs-attr">"urls"</span>: [
          <span class="hljs-string">"https://fonts.googleapis.com/**"</span>
        ]
      }
    }
  ],
  <span class="hljs-attr">"dataGroups"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"from-static"</span>,
      <span class="hljs-attr">"version"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"urls"</span>: [<span class="hljs-string">"/data/data.json"</span>],
      <span class="hljs-attr">"cacheConfig"</span>: {
        <span class="hljs-attr">"strategy"</span>: <span class="hljs-string">"performance"</span>,
        <span class="hljs-attr">"maxSize"</span>: <span class="hljs-number">10</span>,
        <span class="hljs-attr">"maxAge"</span>: <span class="hljs-string">"1d"</span>,
        <span class="hljs-attr">"timeout"</span>: <span class="hljs-string">"5s"</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"from-api"</span>,
      <span class="hljs-attr">"version"</span>: <span class="hljs-number">2</span>,
      <span class="hljs-attr">"urls"</span>: [<span class="hljs-string">"/dashboard"</span>, <span class="hljs-string">"/user"</span>],
      <span class="hljs-attr">"cacheConfig"</span>: {
        <span class="hljs-attr">"strategy"</span>: <span class="hljs-string">"freshness"</span>,
        <span class="hljs-attr">"maxSize"</span>: <span class="hljs-number">15</span>,
        <span class="hljs-attr">"maxAge"</span>: <span class="hljs-string">"1h"</span>,
        <span class="hljs-attr">"timeout"</span>: <span class="hljs-string">"3s"</span>
      }
    }
  ]
}
</code></pre>
<h3 id="heading-6-configuring-angularjson-for-static-assets">6. Configuring angular.json for static assets</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039516926/1v6v78Qn8.png" alt /></p>
<p>Angular Console logo, a GUI for Angular CLI — <a target="_blank" href="https://angularconsole.com/">https://angularconsole.com</a></p>
<p>One of the most important points to pay attention while building a PWA with Angular is the configuration of static assets. You need to be aware of your build configuration and its output on production build in order to properly cache and serve your static resources.</p>
<p>Adding @angular/pwa schematic to your Angular application automatically registers <code>manifest.json</code> file to your static resource configuration on <code>angular.json</code> configuration. But if you’re using additional files on your root, like <code>robots.txt</code>, <code>browserconfig.xml</code>, or a custom icon set, you need to add those files to your build configuration.</p>
<p>This is the default asset configuration on your <code>angular.json</code> file after adding @angular/pwa schematic:</p>
<pre><code class="lang-json"><span class="hljs-string">"assets"</span>: [
  <span class="hljs-string">"src/favicon.ico"</span>,
  <span class="hljs-string">"src/assets"</span>,
  <span class="hljs-string">"src/manifest.json"</span>
]
</code></pre>
<p>You may want to extend this configuration as you add static files to your root, or introduce an external plugin. Even more interesting in our case, you may want to introduce an additional service worker to your app on top of your automatically generated <code>ngsw-worker.js</code>.</p>
<p>This is an example asset configuration on <code>angular.json</code> from one of my PWAs:</p>
<pre><code class="lang-json"><span class="hljs-string">"assets"</span>: [
  <span class="hljs-string">"src/manifest.json"</span>,
  <span class="hljs-string">"src/robots.txt"</span>,
  <span class="hljs-string">"src/browserconfig.xml"</span>,
  <span class="hljs-string">"src/favicon.ico"</span>,
  {
    <span class="hljs-attr">"glob"</span>: <span class="hljs-string">"**/*"</span>,
    <span class="hljs-attr">"input"</span>: <span class="hljs-string">"src/assets"</span>,
    <span class="hljs-attr">"output"</span>: <span class="hljs-string">"assets"</span>
  },
  {
    <span class="hljs-attr">"glob"</span>: <span class="hljs-string">"**/*.svg"</span>,
    <span class="hljs-attr">"input"</span>: <span class="hljs-string">"node_modules/@ionic/angular/dist/ionic/svg"</span>,
    <span class="hljs-attr">"output"</span>: <span class="hljs-string">"./svg"</span>
  },
  {
    <span class="hljs-attr">"glob"</span>: <span class="hljs-string">"**/*-sw.js"</span>,
    <span class="hljs-attr">"input"</span>: <span class="hljs-string">"src/app/sw"</span>,
    <span class="hljs-attr">"output"</span>: <span class="hljs-string">"/"</span>
  },
  {
    <span class="hljs-attr">"glob"</span>: <span class="hljs-string">"**/idb-keyval-iife.min.js"</span>,
    <span class="hljs-attr">"input"</span>: <span class="hljs-string">"node_modules/idb-keyval/dist/"</span>,
    <span class="hljs-attr">"output"</span>: <span class="hljs-string">"/"</span>
  }
]
</code></pre>
<h3 id="heading-7-extending-angular-service-worker">7. Extending Angular service worker</h3>
<p>As of February 2019, Angular service worker — aka ngsw doesn’t support the composition of another service worker. This means that you cannot extend Angular service worker’s default behaviour and add your flavour to it.</p>
<p>You can try and edit automatically generated <code>ngsw-worker.js</code> service worker file but every time you build your application for production, this file will be overridden by Angular, based on your configuration on <code>ngsw-config.json</code>.</p>
<p>This architecture may introduce a problem if you plan to do more than what Angular service worker offers to you. No worries though, there is a nice workaround to tackle this issue.</p>
<h4 id="heading-introducing-your-own-service-worker-file">Introducing your own service worker file</h4>
<p>You can introduce a new service worker file, say <code>main-sw.js</code> to your Angular app and import automatically generated ngsw <em>within</em> your new service worker.</p>
<p>Assuming you configured your sw files to be copied to your apps root folder — please refer to <code>"**/*-sw.js"</code> glob on the chapter “Configuring angular.json for static assets”, the following <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts">importScripts</a> imports on your worker should resolve.</p>
<p>Create a new <code>main-sw.js</code> file in src/app/sw folder with the following content:</p>
<pre><code class="lang-javascript">importScripts(<span class="hljs-string">'ngsw-worker.js'</span>); <span class="hljs-comment">// automatically generated ngsw  </span>
importScripts(<span class="hljs-string">'messaging-sw.js'</span>); <span class="hljs-comment">// custom service worker #1  </span>
importScripts(<span class="hljs-string">'notifications-sw.js'</span>); <span class="hljs-comment">// custom service worker #2</span>
</code></pre>
<p>After introducing your own service worker wrapper, you must change the service worker reference on your <code>app.module.ts</code> file.</p>
<pre><code class="lang-typescript">ServiceWorkerModule.register(<span class="hljs-string">'/main-sw.js'</span>, { enabled: environment.production })
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039519016/YvROEcopQO.png" alt /></p>
<p>Use Chrome DevTools’ Application tab to inspect your app’s service workers. A demonstration of extended service worker of the ITNEXT 2018 Conference PWA — <a target="_blank" href="https://itnext-summit-2018.firebaseapp.com">https://itnext-summit-2018.firebaseapp.com</a></p>
<blockquote>
<p>💡 TIP — By default, service workers are enabled for production environment. Angular recommends <a target="_blank" href="https://angular.io/guide/service-worker-getting-started">building for production and running http-server</a> for testing service workers of your app.</p>
</blockquote>
<h3 id="heading-8-updating-your-pwa">8. Updating your PWA</h3>
<p>A service worker is the backbone of a Progressive Web App. And, updating a PWA always starts at its service worker. Browsers regularly check attached service worker of a client app for a byte difference and once service worker file is updated, the browser automatically initializes an update process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039520978/AnVarEdHN.jpeg" alt /></p>
<p>Source: <a target="_blank" href="https://deanhume.com/displaying-a-new-version-available-progressive-web-app/">https://deanhume.com/displaying-a-new-version-available-progressive-web-app/</a></p>
<h4 id="heading-81-app-versions-and-update-checks">8.1 App versions and update checks</h4>
<blockquote>
<p>In the context of an Angular service worker, a “version” is a collection of resources that represent a specific build of the Angular app. Whenever a new build of the app is deployed, the service worker treats that build as a new version of the app. This is true even if only a single file is updated. At any given time, the service worker may have multiple versions of the app in its cache and it may be serving them simultaneously.</p>
<p>Every time the user opens or refreshes the application, the Angular service worker checks for updates to the app by looking for updates to the <code>ngsw.json</code> manifest. This manifest file represents the “version” of an Angular build, along with all the files associated with ngsw-config file. If an update is found, it is downloaded and cached automatically, and will be served the next time the application is loaded.</p>
<p>— <a target="_blank" href="https://angular.io/guide/service-worker-devops">Angular docs</a></p>
</blockquote>
<p>The ngsw.json manifest file, representing a build aka “version” of an Angular PWA:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"configVersion"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">"appData"</span>: {
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.1.0"</span>,
    <span class="hljs-attr">"changelog"</span>: <span class="hljs-string">"Added better resource caching"</span>
  },
  <span class="hljs-attr">"index"</span>: <span class="hljs-string">"/index.html"</span>,
  <span class="hljs-attr">"assetGroups"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"shell"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"updateMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"urls"</span>: [
        <span class="hljs-string">"/favicon.ico"</span>,
        <span class="hljs-string">"/index.html"</span>,
        <span class="hljs-string">"/main.cb67e635642476004207.js"</span>,
        <span class="hljs-string">"/polyfills.1840410d8aa59aab644c.js"</span>,
        <span class="hljs-string">"/runtime.a66f828dca56eeb90e02.js"</span>,
        <span class="hljs-string">"/styles.3ff695c00d717f2d2a11.css"</span>,
        <span class="hljs-string">"/vendor.3335969a60ee384a24da.js"</span>
      ],
      <span class="hljs-attr">"patterns"</span>: []
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"assets"</span>,
      <span class="hljs-attr">"installMode"</span>: <span class="hljs-string">"lazy"</span>,
      <span class="hljs-attr">"updateMode"</span>: <span class="hljs-string">"prefetch"</span>,
      <span class="hljs-attr">"urls"</span>: [
        <span class="hljs-string">"/assets/icons/icon-128x128.png"</span>,
        <span class="hljs-string">"/assets/icons/icon-144x144.png"</span>,
        <span class="hljs-string">"/assets/icons/icon-152x152.png"</span>,
        <span class="hljs-string">"/assets/icons/icon-192x192.png"</span>,
        <span class="hljs-string">"/assets/icons/icon-384x384.png"</span>,
        <span class="hljs-string">"/assets/icons/icon-512x512.png"</span>,
        <span class="hljs-string">"/assets/icons/icon-72x72.png"</span>,
        <span class="hljs-string">"/assets/icons/icon-96x96.png"</span>
      ],
      <span class="hljs-attr">"patterns"</span>: [
        <span class="hljs-string">"https:\\/\\/fonts\\.googleapis\\.com\\/.*"</span>
      ]
    }
  ],
  <span class="hljs-attr">"dataGroups"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"from-static"</span>,
      <span class="hljs-attr">"patterns"</span>: [
        <span class="hljs-string">"\\/data\\/data\\.json"</span>
      ],
      <span class="hljs-attr">"strategy"</span>: <span class="hljs-string">"performance"</span>,
      <span class="hljs-attr">"maxSize"</span>: <span class="hljs-number">10</span>,
      <span class="hljs-attr">"maxAge"</span>: <span class="hljs-number">86400000</span>,
      <span class="hljs-attr">"timeoutMs"</span>: <span class="hljs-number">5000</span>,
      <span class="hljs-attr">"version"</span>: <span class="hljs-number">1</span>
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"from-api"</span>,
      <span class="hljs-attr">"patterns"</span>: [
        <span class="hljs-string">"\\/dashboard"</span>,
        <span class="hljs-string">"\\/user"</span>
      ],
      <span class="hljs-attr">"strategy"</span>: <span class="hljs-string">"freshness"</span>,
      <span class="hljs-attr">"maxSize"</span>: <span class="hljs-number">15</span>,
      <span class="hljs-attr">"maxAge"</span>: <span class="hljs-number">3600000</span>,
      <span class="hljs-attr">"timeoutMs"</span>: <span class="hljs-number">3000</span>,
      <span class="hljs-attr">"version"</span>: <span class="hljs-number">2</span>
    }
  ],
  <span class="hljs-attr">"hashTable"</span>: {
    <span class="hljs-attr">"/assets/icons/icon-128x128.png"</span>: <span class="hljs-string">"dae3b6ed49bdaf4327b92531d4b5b4a5d30c7532"</span>,
    <span class="hljs-attr">"/assets/icons/icon-144x144.png"</span>: <span class="hljs-string">"b0bd89982e08f9bd2b642928f5391915b74799a7"</span>,
    <span class="hljs-attr">"/assets/icons/icon-152x152.png"</span>: <span class="hljs-string">"7479a9477815dfd9668d60f8b3b2fba709b91310"</span>,
    <span class="hljs-attr">"/assets/icons/icon-192x192.png"</span>: <span class="hljs-string">"1abd80d431a237a853ce38147d8c63752f10933b"</span>,
    <span class="hljs-attr">"/assets/icons/icon-384x384.png"</span>: <span class="hljs-string">"329749cd6393768d3131ed6304c136b1ca05f2fd"</span>,
    <span class="hljs-attr">"/assets/icons/icon-512x512.png"</span>: <span class="hljs-string">"559d9c4318b45a1f2b10596bbb4c960fe521dbcc"</span>,
    <span class="hljs-attr">"/assets/icons/icon-72x72.png"</span>: <span class="hljs-string">"c457e56089a36952cd67156f9996bc4ce54a5ed9"</span>,
    <span class="hljs-attr">"/assets/icons/icon-96x96.png"</span>: <span class="hljs-string">"3914125a4b445bf111c5627875fc190f560daa41"</span>,
    <span class="hljs-attr">"/favicon.ico"</span>: <span class="hljs-string">"84161b857f5c547e3699ddfbffc6d8d737542e01"</span>,
    <span class="hljs-attr">"/index.html"</span>: <span class="hljs-string">"40b6fc0e0fae9194170df9397b48487bcd139bcd"</span>,
    <span class="hljs-attr">"/main.cb67e635642476004207.js"</span>: <span class="hljs-string">"622827b74fd2511ca3293cc9e7bc0873e40b38a0"</span>,
    <span class="hljs-attr">"/polyfills.1840410d8aa59aab644c.js"</span>: <span class="hljs-string">"c59ac0163bbb63bf9a602a38fa41aaa3da1dae99"</span>,
    <span class="hljs-attr">"/runtime.a66f828dca56eeb90e02.js"</span>: <span class="hljs-string">"078e320cc6fdaf355836c3b1c52b059cdd33fc7e"</span>,
    <span class="hljs-attr">"/styles.3ff695c00d717f2d2a11.css"</span>: <span class="hljs-string">"da39a3ee5e6b4b0d3255bfef95601890afd80709"</span>,
    <span class="hljs-attr">"/vendor.3335969a60ee384a24da.js"</span>: <span class="hljs-string">"e06c17d4e5b96102d8bb7828bc473c1a4fce27ea"</span>
  },
  <span class="hljs-attr">"navigationUrls"</span>: [
    {
      <span class="hljs-attr">"positive"</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">"regex"</span>: <span class="hljs-string">"^\\/.*$"</span>
    },
    {
      <span class="hljs-attr">"positive"</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">"regex"</span>: <span class="hljs-string">"^\\/(?:.+\\/)?[^/]*\\.[^/]*$"</span>
    },
    {
      <span class="hljs-attr">"positive"</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">"regex"</span>: <span class="hljs-string">"^\\/(?:.+\\/)?[^/]*__[^/]*$"</span>
    },
    {
      <span class="hljs-attr">"positive"</span>: <span class="hljs-literal">false</span>,
      <span class="hljs-attr">"regex"</span>: <span class="hljs-string">"^\\/(?:.+\\/)?[^/]*__[^/]*\\/.*$"</span>
    }
  ]
}
</code></pre>
<p>Read more about ngsw app updates: <a target="_blank" href="https://angular.io/guide/service-worker-devops">https://angular.io/guide/service-worker-devops</a></p>
<h4 id="heading-82-appdata-on-ngsw-configjson">8.2 AppData on ngsw-config.json</h4>
<p><code>appData</code> field in ngsw-config.json file enables you to pass any data (you want) that describes this particular version of the app. The <code>[SwUpdate](https://angular.io/api/service-worker/SwUpdate)</code> service includes that data in the update notifications. Many apps make use of this field to provide some additional info for the display of any popups, notifying users of the available update.</p>
<pre><code class="lang-json"><span class="hljs-string">"index"</span>: <span class="hljs-string">"/index.html"</span>,  
<span class="hljs-string">"appData"</span>: {  
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.1.0"</span>,  
  <span class="hljs-attr">"changelog"</span>: <span class="hljs-string">"Added better resource caching"</span>  
}
</code></pre>
<h4 id="heading-83-swupdate-service">8.3 SwUpdate service</h4>
<p>Angular handles its service worker updates via its own service called <code>[SwUpdate](https://angular.io/api/service-worker/SwUpdate)</code> from <em>@angular/service-worker</em> module. You can subscribe to <em>UpdateAvailableEvent</em> and <em>UpdateActivatedEvent</em> events on this service to be notified on service worker updates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039523168/eejjrzl2Y.png" alt /></p>
<p>ITNEXT 2018 Conference PWA, app update demonstration — See source code on <a target="_blank" href="https://github.com/LINKIT-Group/itnext-summit-app">https://github.com/LINKIT-Group/itnext-summit-app</a></p>
<p>Following example demonstrates a prompt for users, which will be displayed when a new service worker version becomes available. By doing this, we provide an option for users to immediately apply the update.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { SwUpdate } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/service-worker'</span>;
<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-root'</span>,
  templateUrl: <span class="hljs-string">'./app.component.html'</span>,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppComponent <span class="hljs-keyword">implements</span> OnInit {
ngOnInit() {
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.swUpdate.isEnabled) {
    <span class="hljs-built_in">this</span>.swUpdate.available.subscribe(<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">const</span> alert = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.alertController.create({
        header: <span class="hljs-string">`App update!`</span>,
        message: <span class="hljs-string">`Newer version of the app is available. It's a quick refresh away!`</span>,
        buttons: [
          {
            text: <span class="hljs-string">'Cancel'</span>,
            role: <span class="hljs-string">'cancel'</span>,
            cssClass: <span class="hljs-string">'secondary'</span>,
          }, {
            text: <span class="hljs-string">'Refresh'</span>,
            handler: <span class="hljs-function">() =&gt;</span> {
              <span class="hljs-built_in">window</span>.location.reload();
            },
          },
        ],
      });

      <span class="hljs-keyword">await</span> alert.present();
    });
  }
}
</code></pre>
<p>Please note that you don’t have to provide this manual update functionality. Service workers are automatically updated by the browser on the time of disconnection of all clients or pages attached to it. However, I recommend providing this immediate update option for the convenience of your users.</p>
<blockquote>
<p>💡 TIP — Browsers automatically check byte differences of the attached main service worker file, which would be main-sw.js if you’ve extended ngsw. You should make sure that other sw files that you might import to your main service worker are not cached. This will be explained in detail on Firebase hosting configuration on next chapter.</p>
</blockquote>
<h3 id="heading-9-hosting-your-app-on-firebase">9. Hosting your app on Firebase</h3>
<p>Firebase is built around simplicity. It’s quite easy to set up a hosting and configure deployments for your application.</p>
<p>Once you’ve logged in to <a target="_blank" href="https://console.firebase.google.com">Firebase console</a>, you can click on <em>Add Project</em> button and fill in the form below with your preference<em>.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039525422/pvfMoO49T.png" alt /></p>
<p>Creating a new project for your app on Firebase</p>
<p>The easiest way of using Firebase services is to make use of Firebase CLI through a console. To access Firebase CLI on your command line, run one of the following;</p>
<p>npm i -g firebase-tools<br />npx firebase // or use npx to avoid global installation</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039527574/iE6MlC2Al.png" alt /></p>
<p>Setting up hosting on Firebase</p>
<p>After creating your project, navigate to <em>Hosting</em> tab under develop category. Click Finish in order to make your project available on CLI.</p>
<h4 id="heading-initializing-firebase-config">Initializing Firebase config</h4>
<p>You can deploy your PWA to Firebase by simply executing 3 commands via Firebase CLI; <em>login</em>, <em>init</em> and <em>deploy</em>. We’ll only make use of hosting service of Firebase for now. In order to do that, you should execute <code>firebase init</code> on your project root and then check that with <em>Space</em> key and press <em>Enter</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039529743/oCw2afGd_.png" alt /></p>
<p>Options for initialising Firebase on your project</p>
<p>On the next step, it will show your existing projects to you to set up a hosting on. Select <em>[create a new project]</em> option to create a default project for your app. After that, answer the steps like below;</p>
<ul>
<li><em>What do you want to use as your public directory?</em> <code>dist/my-pwa</code> — This is the path for your production build that is configured on angular.json file under <em>outputPath</em>.</li>
<li><em>Configure as a single-page app (rewrite all urls to /index.html)</em>? <code>y</code></li>
<li><em>File dist/my-pwa/index.html already exists. Overwrite?</em> <code>n</code></li>
</ul>
<p>As an output of this setup, Firebase CLI creates 2 files in your project root: <code>firebase.json</code> and <code>.firebaserc</code></p>
<h3 id="heading-10-configuring-a-firebase-hosting-for-your-pwa">10. Configuring a Firebase hosting for your PWA</h3>
<p>It’s possible to configure your hosting on Firebase via <code>firebase.json</code> file in your project. You can see the default configuration of which Firebase CLI created for your project below;</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"hosting"</span>: {
    <span class="hljs-attr">"public"</span>: <span class="hljs-string">"dist/my-pwa"</span>,
    <span class="hljs-attr">"ignore"</span>: [
      <span class="hljs-string">"firebase.json"</span>,
      <span class="hljs-string">"**/.*"</span>,
      <span class="hljs-string">"**/node_modules/**"</span>
    ],
    <span class="hljs-attr">"rewrites"</span>: [
      {
        <span class="hljs-attr">"source"</span>: <span class="hljs-string">"**"</span>,
        <span class="hljs-attr">"destination"</span>: <span class="hljs-string">"/index.html"</span>
      }
    ]
  }
}
</code></pre>
<p>It’s important to be aware of a couple of points on this configuration.</p>
<ul>
<li>Since Angular is a SPA (Single Page Application), you must configure rewrite as above to point all sources to your index.html file.</li>
<li>Do not deploy any file that keeps your credentials <strong>under no circumstances</strong>. Keys, secrets, and such confidential information on your configuration files that relies on the public folder you pointed out, might be exposed to the world immediately. Make sure to ignore any configuration file you have in your public folder, or else;</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/svblxyz/status/1045013939904532482">https://twitter.com/svblxyz/status/1045013939904532482</a></div>
<h4 id="heading-101-adding-custom-http-headers-for-caching">10.1 Adding custom HTTP headers for caching</h4>
<p>Firebase hosting configuration allows you to add <a target="_blank" href="https://firebase.google.com/docs/hosting/full-config#headers">custom HTTP headers</a> for your project.</p>
<p>This configuration is especially important for your PWA where you might want to cache your static assets and gzip them to improve your app’s loading performance. A long cache lifetime can speed up repeat visits to your page.</p>
<blockquote>
<p>When a browser requests a resource, the server providing the resource can tell the browser how long it should temporarily store or <strong>cache</strong> the resource. For any subsequent request for that resource, the browser uses its local copy, rather than going to the network to get it. — <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/cache-policy">Cache policy on Google Developers portal</a></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039532265/G76nuMWrb.png" alt /></p>
<p>Verifying cached responses in Chrome DevTools — <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/cache-policy">Cache policy on Google Developers portal</a></p>
<p>Since Angular creates your static resources with a hash postfix in the filename, you can take advantage of introducing a long cache lifetime for those resources by adding <em>Cache-Control</em> HTTP header<em>.</em> When you do that, you should also consider <a target="_blank" href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#invalidating_and_updating_cached_responses">invalidating your caches</a><em>.</em> However<em>, invalidating caches of static resources is not required for your Angular app</em> as Angular’s build system automatically changes the postfix hash in your resources’ file names when they’re updated.</p>
<p>Moreover, Firebase automatically compresses static resources with gzip and returns a response with <em>Content-Encoding</em> gzip header where it applies. But I’d like putting that explicit header anyway to make the intent visible.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039534535/qAvRAEiQ9.png" alt /></p>
<p>Production build output of Angular where vendor chunk is enabled. Demonstrates hashes in filenames as well.</p>
<blockquote>
<p>💡 TIP — Introduce vendor chunk to your production build configuration for your Angular app — see vendorChunk option in angular.json. This will help on your PWA’s cache strategy as you most likely will not update vendor libraries too often.</p>
</blockquote>
<p>The following example demonstrates adding long cache lifetime and providing compressed static resources;</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"headers"</span>: [
  {
    <span class="hljs-attr">"source"</span>: <span class="hljs-string">"**/*.@(js|css)"</span>,
    <span class="hljs-attr">"headers"</span>: [
      {
        <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Cache-Control"</span>,
        <span class="hljs-attr">"value"</span>: <span class="hljs-string">"max-age=31536000"</span>
      },
      {
        <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Content-Encoding"</span>,
        <span class="hljs-attr">"value"</span>: <span class="hljs-string">"gzip"</span>
      }
    ]
  }
}
</code></pre>
<p>You might have noticed above that Firebase configuration accepts <a target="_blank" href="https://firebase.google.com/docs/hosting/full-config#glob_pattern_matching">glob-like pattern</a> for source files.</p>
<h4 id="heading-102-keeping-your-additional-service-worker-files-fresh">10.2 Keeping your additional service worker files fresh</h4>
<p>If you make use of such cache configuration above for all your js and css files, you must create an exception rule for your additional service worker files. Here’s an example of <em>no-cache</em> exception;</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"headers"</span>: [
  {
    <span class="hljs-attr">"source"</span>: <span class="hljs-string">"**/*-sw.js"</span>,
    <span class="hljs-attr">"headers"</span>: [
      {
        <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Cache-Control"</span>,
        <span class="hljs-attr">"value"</span>: <span class="hljs-string">"no-cache"</span>
      }
    ]
  }
}
</code></pre>
<p>This rule will make sure that we’ll keep all our service worker files matching with the file name pattern <em>\</em>*/*-sw.js* fresh.</p>
<h4 id="heading-103-adding-http2-server-push-with-link-headers">10.3 Adding HTTP/2 server push with link headers</h4>
<p>HTTP/2 is currently enabled for all <code>*.firebaseapp.com</code> traffic and to utilize HTTP/2 on Firebase Hosting, you don’t have to do anything! It will automatically be served if the user’s browser supports it. Read more about it here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://firebase.googleblog.com/2016/09/http2-comes-to-firebase-hosting.html">https://firebase.googleblog.com/2016/09/http2-comes-to-firebase-hosting.html</a></div>
<p>However, you might want to manually introduce HTTP/2 server push to your app. By using <a target="_blank" href="https://w3c.github.io/preload/#server-push-http-2">Link headers</a>, Firebase hosting introduced experimental support for HTTP/2 server push. Server push allows a server to automatically send the contents for additional resources when an initial request is made. A common use for server push is to send associated static resources — like JavaScript and CSS files when a page is loaded.</p>
<p>You need to add the Link header to your <code>firebase.json</code> config file with pointing out the path of your static resources in order to configure server push on Firebase hosting. Here’s an example;</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"headers"</span>: [
    {
      <span class="hljs-attr">"source"</span>: <span class="hljs-string">"/"</span>,
      <span class="hljs-attr">"headers"</span>: [{<span class="hljs-attr">"key"</span>: <span class="hljs-string">"Link"</span>, <span class="hljs-attr">"value"</span>: <span class="hljs-string">"&lt;/js/app.js&gt;;rel=preload;as=script,&lt;/css/app.css&gt;;rel=preload;as=style"</span>}]
    },
    {
      <span class="hljs-attr">"source"</span>: <span class="hljs-string">"/users/*"</span>,
      <span class="hljs-attr">"headers"</span>: [{<span class="hljs-attr">"key"</span>: <span class="hljs-string">"Link"</span>, <span class="hljs-attr">"value"</span>: <span class="hljs-string">"&lt;/js/app.js&gt;;rel=preload;as=script,&lt;/css/app.css&gt;;rel=preload;as=style;nopush,&lt;/css/users.css&gt;;rel=preload;as=style"</span>}]
    }
  ]
}
</code></pre>
<h4 id="heading-104-a-basic-hosting-configuration-example">10.4 A basic hosting configuration example</h4>
<p>Following example demonstrates a configuration example of one of my PWAs. I recommend you to read the <a target="_blank" href="https://firebase.google.com/docs/hosting/full-config">documentation of hosting configuration on Firebase</a> for you to understand all possible configuration options.</p>
<p>Example of a full firebase.json configuration file that I use on one of my PWAs:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"hosting"</span>: {
    <span class="hljs-attr">"public"</span>: <span class="hljs-string">"www"</span>,
    <span class="hljs-attr">"ignore"</span>: [
      <span class="hljs-string">"firebase.json"</span>,
      <span class="hljs-string">"3rdpartylicenses.txt"</span>,
      <span class="hljs-string">"**/.*"</span>,
      <span class="hljs-string">"**/node_modules/**"</span>
    ],
    <span class="hljs-attr">"rewrites"</span>: [
      {
        <span class="hljs-attr">"source"</span>: <span class="hljs-string">"**"</span>,
        <span class="hljs-attr">"destination"</span>: <span class="hljs-string">"/index.html"</span>
      }
    ],
    <span class="hljs-attr">"headers"</span>: [
      {
        <span class="hljs-attr">"source"</span> : <span class="hljs-string">"**/*.@(jpg|jpeg|gif|png|svg)"</span>,
        <span class="hljs-attr">"headers"</span> : [
          {
            <span class="hljs-attr">"key"</span> : <span class="hljs-string">"Cache-Control"</span>,
            <span class="hljs-attr">"value"</span> : <span class="hljs-string">"max-age=86400"</span>
          },
          {
            <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Content-Encoding"</span>,
            <span class="hljs-attr">"value"</span>: <span class="hljs-string">"gzip"</span>
          }
        ]
      },
      {
        <span class="hljs-attr">"source"</span> : <span class="hljs-string">"**/*.@(js|css)"</span>,
        <span class="hljs-attr">"headers"</span> : [
          {
            <span class="hljs-attr">"key"</span> : <span class="hljs-string">"Cache-Control"</span>,
            <span class="hljs-attr">"value"</span> : <span class="hljs-string">"max-age=31536000"</span>
          },
          {
            <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Content-Encoding"</span>,
            <span class="hljs-attr">"value"</span>: <span class="hljs-string">"gzip"</span>
          }
        ]
      },
      {
        <span class="hljs-attr">"source"</span>: <span class="hljs-string">"**/*-sw.js"</span>,
        <span class="hljs-attr">"headers"</span>: [
          {
            <span class="hljs-attr">"key"</span>: <span class="hljs-string">"Cache-Control"</span>,
            <span class="hljs-attr">"value"</span>: <span class="hljs-string">"no-cache"</span>
          }
        ]
      }
    ]
  }
}
</code></pre>
<h3 id="heading-11-deploying-your-pwa-to-firebase">11. Deploying your PWA to Firebase</h3>
<p>Deploying an app to Firebase is simple as it can be. Just execute <code>firebase deploy</code> via Firebase CLI in your project root and you’re good to go!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039536855/xokykNBZY.png" alt /></p>
<p>You might need to add a project if you haven’t done already</p>
<h3 id="heading-12-auditing-your-pwa-with-lighthouse">12. Auditing your PWA with Lighthouse</h3>
<p>Lighthouse is an <a target="_blank" href="https://github.com/GoogleChrome/lighthouse">open-source</a>, automated tool for improving the quality of web pages. You can run it against any web page, public or requiring authentication. It has audits for performance, accessibility, progressive web apps, and more.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039539978/1bVIqHUvW.png" alt /></p>
<p>Auditing a PWA can be done on online audit runner, on Chrome DevTools and through <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/#cli">Lighthouse CLI</a>.</p>
<h4 id="heading-121-using-online-audit-runner">12.1 Using online audit runner</h4>
<p>You can run Lighthouse audits by navigating to the link below and entering a URL of which application you’d like to run the audits on.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://developers.google.com/web/tools/lighthouse/run">https://developers.google.com/web/tools/lighthouse/run</a></div>
<h4 id="heading-122-using-chrome-devtools">12.2 Using Chrome DevTools</h4>
<p>Once you configure your PWA by following the tips on this article and deploy your app to firebase with recommended configuration, your app is ready for an audit. You can <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/#devtools">run an audit on Chrome DevTools</a> and see a score of 100 for the Progressive Web App audit.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039542056/bkkMtVjMl.png" alt /></p>
<p>💯score on Lighthouse PWA audit — Chrome DevTools Audit</p>
<h4 id="heading-123-using-lighthouse-cli">12.3 Using Lighthouse CLI</h4>
<p>An audit with Lighthouse can be automated on your continuous integration environment via Lighthouse CLI. This will make sure that not any of your changes will impact the score on <a target="_blank" href="https://github.com/GoogleChrome/lighthouse#cli-options">any of the audits you set — see CLI options</a> within your Lighthouse regression.</p>
<blockquote>
<p>💡 TIP — You can compare the Lighthouse reports of your current and previous audits to make sure there’s no impact to your app related to your changes.</p>
</blockquote>
<p>The following command will execute a headless Chrome and run audits against the url and then will display a report.</p>
<pre><code class="lang-bash">npx lighthouse https://my-fancy-pwa.firebaseapp.com --chrome-flags=<span class="hljs-string">"--headless"</span> --view
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039544184/hBed6Ervb.png" alt /></p>
<p>Lighthouse CLI report, viewed as html</p>
<blockquote>
<p>💡 TIP — Executing nightly regressions on your production app to monitor it against updated PWA audits will help you to align with future PWA best practices as the list of audits get updated in time.</p>
</blockquote>
<h3 id="heading-what-else-is-there-for-your-pwa">What else is there for your PWA?</h3>
<p>With the ever-growing power of the web, anything you can introduce to a web app is an option for your PWA. Make sure to check out <a target="_blank" href="https://webkit.org/status">WebKit Feature Status</a> for iOS and <a target="_blank" href="https://www.chromestatus.com">Chrome Platform Status</a> for Android platform compatibility for the features and Web APIs you’d like to adapt.</p>
<p>One year after the first beta of iOS 11.3, Apple recently released the first beta of iOS 12.2. This is the first version since PWA support was introduced and there are quite good improvements in it — focusing biggest issues of PWAs on iOS. Read more about it here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://medium.com/@firt/pwas-on-ios-12-2-beta-the-good-the-bad-and-the-not-sure-yet-if-good-a37b6fa6afbf">https://medium.com/@firt/pwas-on-ios-12-2-beta-the-good-the-bad-and-the-not-sure-yet-if-good-a37b6fa6afbf</a></div>
<p>Furthermore, you can get inspired by the capabilities of your browser by simply browsing <a target="_blank" href="https://whatwebcando.today">https://whatwebcando.today</a> and <a target="_blank" href="https://tomayac.github.io/pwa-feature-detector/">https://tomayac.github.io/pwa-feature-detector/</a> on your mobile or desktop browser.</p>
<p>If you’d like to add push notifications functionality to your PWA, you can follow the guide below of my colleague <a target="_blank" href="https://medium.com/u/b4ee91751c76">Ural</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://itnext.io/push-notification-with-angular-net-core-a2280d18eda1">https://itnext.io/push-notification-with-angular-net-core-a2280d18eda1</a></div>
<p>Follow me on <a target="_blank" href="https://twitter.com/onderceylan"><strong>Twitter</strong></a> for more tips like the ones in this post.</p>
<p>Please don’t forget to <strong>like</strong> and <strong>follow</strong> if you find this article useful. And, feel free to write comments about your PWA development journey. I’d love to hear all about it!</p>
]]></content:encoded></item><item><title><![CDATA[Level up your NgRx game]]></title><description><![CDATA[Since you’re here, it’s very likely that you’ve already been using NgRx in your Angular application. In any case, I’d like to give a short introduction of it with quoting from its official website.

@ngrx provides a set of clean, well-tested librarie...]]></description><link>https://onderceylan.com/level-up-your-ngrx-game-42652afc25bd</link><guid isPermaLink="true">https://onderceylan.com/level-up-your-ngrx-game-42652afc25bd</guid><category><![CDATA[Angular]]></category><category><![CDATA[Redux]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Önder Ceylan]]></dc:creator><pubDate>Fri, 21 Sep 2018 10:24:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039406888/fUOUObNnp.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Since you’re here, it’s very likely that you’ve already been using NgRx in your Angular application. In any case, I’d like to give a short introduction of it with quoting from its official website.</p>
<blockquote>
<p>@ngrx provides a set of clean, well-tested libraries for reactive programming in Angular applications. — <a target="_blank" href="http://ngrx.github.io/">http://ngrx.github.io/</a></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039395317/DU24r4nXx.png" alt /></p>
<p>I’ve been using NgRx for building fairly complex applications for one of our enterprise clients since last year.</p>
<p>My journey through NgRx includes building a brand new application with the latest version of NgRx, migrating a stateful Angular application to a reactive one, and also migrating an old NgRx application to the latest version.</p>
<blockquote>
<p>I’d like to share my experiences with it so far, by giving some tips and tricks to both existing users and complete beginners of NgRx.</p>
</blockquote>
<p>The examples in this article are based on NgRx v6 which uses classes for actions, rather than creators. To see updated version — v8 examples of the best practices on this blog post, you can watch my talk at <a target="_blank" href="https://2019.angularday.it/">angularday conference</a> in Verona from May 2019:</p>
<iframe src="https://www.youtube.com/embed/hxUUQCPN8k4?feature=oembed" width="700" height="393"></iframe>

<h3 id="heading-use-schematics">Use schematics</h3>
<p>If you’re not familiar with what schematics is in Angular world, please take a look at <a target="_blank" href="https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2">this post</a> first.</p>
<p>NgRx team has introduced @ngrx/schematics with version 5. If you’d like to reduce the <strong>boilerplate</strong>, you’ll definitely love this. It will also help you to align with non-officially existing style guide.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039397518/b6EucOfvP.png" alt /></p>
<p>NgRx team loves complaints on boilerplate. <a target="_blank" href="https://medium.com/u/637b8a3ce256">Brandon</a> don’t block me, bro. Seriously.</p>
<p>A good trick on NgRx schematics is to set it as default collection for your Angular application. After installing @ngrx/schematics, execute following to set it as default collection for your Angular application.</p>
<pre><code class="lang-bash">ng config cli.defaultCollection @ngrx/schematics
</code></pre>
<p>With 3 simple CLI commands, you can generate a boilerplate code for the basis of your new reactive feature, including TestBed configurations. It’s structured exactly as it’s in <a target="_blank" href="https://github.com/ngrx/platform/tree/master/projects/example-app">NgRx example-app</a>.</p>
<pre><code class="lang-bash">ng g module user --flat <span class="hljs-literal">false</span>  
ng g feature user/store/user --module ./user/user.module.ts --group  
ng g container user/containers/UsersPage
</code></pre>
<blockquote>
<p>It’s a pretty big deal actually!</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039400117/9JiPijCG3.png" alt /></p>
<p>Same folder structure as it’s in the example-app — <a target="_blank" href="https://github.com/ngrx/platform/tree/master/projects/example-app">https://github.com/ngrx/platform/tree/master/projects/example-app</a></p>
<p>You can take a look at the documentation for further usage instructions — <a target="_blank" href="https://github.com/ngrx/platform/tree/master/docs/schematics">https://github.com/ngrx/platform/tree/master/docs/schematics</a>.</p>
<h3 id="heading-maintain-a-good-action-hygiene">Maintain a good action hygiene</h3>
<p>Actions are the foundation of an NgRx app. It’s so important that a good action hygiene can help both business development and software engineers at the same time.</p>
<blockquote>
<p>Good actions are actions you can read after a year and tell where they are being dispatched. — Mike Ryan</p>
</blockquote>
<p>As <a target="_blank" href="https://medium.com/u/d6ba4dcb6838">Mike Ryan</a> states on his <a target="_blank" href="https://www.youtube.com/watch?v=JmnsEvoy-gY"><em>Good Action Hygiene with NgRx</em> talk</a>, poor processes lead to poor quality applications. I’d like to stress the common pitfalls from his talk as a reference, hoping that it will help more people avoid them.</p>
<h4 id="heading-dont-reuse-actions">Don’t reuse actions</h4>
<blockquote>
<p>Actions should capture events, not commands.</p>
</blockquote>
<p>Try to model your actions as unique events on your system so that you can easily understand where it comes from a year later.</p>
<blockquote>
<p>Reducers and effects should be the decision makers</p>
</blockquote>
<p>Separate the description of an event and the way it’s handled in your app. Leave the decision making to your reducers and effects, your components should not decide how state changes.</p>
<h4 id="heading-avoid-using-generic-action-types">Avoid using generic action types</h4>
<p>When you take a look at your code or your store devtools logs, it should be clear for everyone how your application behaves and what’s the origin of an event.</p>
<p>Especially for the development team, pinpointing any of the actions in a big application helps everyone more than you can imagine.</p>
<p>A good formula to accomplish this is using <strong>[Source] Event</strong> format.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039402401/sacqi5uVX.png" alt /></p>
<p>Generic actions vs exclusive actions — An example from Mike’s presentation</p>
<h4 id="heading-dont-do-action-subtyping">Don’t do action subtyping</h4>
<p>This pitfall is strongly related to action reusing. Chances are, you won’t fall into this if you avoid the first pitfall. Don’t push it to build generic actions with sub types and be as unique as possible with your actions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039404530/lLjyu4GUJ.png" alt /></p>
<p>Nested conditional in a reducer — An example from Mike’s presentation</p>
<p>Falling into this pitfall may bring conditional branches all throughout your application. It will be a pain in the neck to maintain them when they add up by your application scales up. Avoid nested conditionals in reducers and effects!</p>
<h3 id="heading-simplify-your-reducers-with-ngrxentity">Simplify your reducers with @ngrx/entity</h3>
<p>NgRx team works hard on reducing the boilerplate. That’s one of the reasons of building such a library. But there are more!</p>
<p>You possibly deal with collections in your store, and maintaining your store data with large and complex collections of entities might give you a headache, not to mention the boilerplate code it brings to your reducers.</p>
<p>@ngrx/entity is there for the rescue to simplify your reducers and domain models.</p>
<h4 id="heading-entity-state">Entity State</h4>
<p>After setting up your entity adapter, your entity state will look like the following in your store.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> EntityState&lt;T&gt; {  
  ids: <span class="hljs-built_in">string</span>\[\];  
  entities: { \[id: <span class="hljs-built_in">string</span>\]: T };  
}
</code></pre>
<p>There are a couple of reasons for maintaining a dictionary of entities and a list of ids in your entity state:</p>
<ol>
<li>In order to keep your collection sorted along with having a dictionary, you need an ordered list. That’s why ids array is part of the entity state.</li>
<li>Lookup actions are expensive operations most of the times if you maintain your data as a list. Using a dictionary for your entities is much faster compared to searching through an array.</li>
</ol>
<h4 id="heading-cud-operations-create-update-delete">CUD operations (Create, Update, Delete)</h4>
<p>Entity adapter brings many useful methods for your CUD needs in the store.</p>
<p>Take a look at the following in-app events example of mine, to see available operations, as well as different use cases with partial changes object.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> fromEvents <span class="hljs-keyword">from</span> <span class="hljs-string">'../actions/events.action'</span>;
<span class="hljs-keyword">import</span> { createEntityAdapter, EntityAdapter, EntityState } <span class="hljs-keyword">from</span> <span class="hljs-string">'@ngrx/entity'</span>;
<span class="hljs-keyword">import</span> { Event } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../models/event.model'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> EventsState <span class="hljs-keyword">extends</span> EntityState&lt;EventEntity&gt; {
  loaded: <span class="hljs-built_in">boolean</span>;
  loading: <span class="hljs-built_in">boolean</span>;
  error: <span class="hljs-built_in">Error</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sortDescByUpdatedDateTime</span>(<span class="hljs-params">a: EventEntity, b: EventEntity</span>) </span>{
  <span class="hljs-keyword">return</span> b.data.updatedDateTime - a.data.updatedDateTime;
}

<span class="hljs-comment">// Note that you can build your custom sort function as above and pass it down</span>
<span class="hljs-comment">// to your entity adapter to automatically sort ids array based on your sort logic</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> eventEntityAdapter: EntityAdapter&lt;EventEntity&gt; = createEntityAdapter&lt;EventEntity&gt;({
  selectId: <span class="hljs-function">(<span class="hljs-params">event: EventEntity</span>) =&gt;</span> event.data.eventId,
  sortComparer: sortDescByUpdatedDateTime,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> initialState: EventsState = eventEntityAdapter.getInitialState({
  loaded: <span class="hljs-literal">false</span>,
  loading: <span class="hljs-literal">false</span>,
  error: <span class="hljs-literal">null</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> EventEntity {
  data: Event;
  read: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">eventsReducer</span>(<span class="hljs-params">state = initialState, action: fromEvents.EventsActionsUnion</span>): <span class="hljs-title">EventsState</span> </span>{
  <span class="hljs-keyword">switch</span> (action.type) {
    <span class="hljs-keyword">case</span> fromEvents.EventsActionTypes.LoadAllEvents: {
      <span class="hljs-keyword">return</span> {
        ...state,
        error: <span class="hljs-literal">null</span>,
        loading: <span class="hljs-literal">true</span>,
      };
    }

    <span class="hljs-comment">// You can use one of the ...many operations here, based on your business requirements</span>
    <span class="hljs-comment">// addMany, updateMany and upsertMany are available</span>
    <span class="hljs-keyword">case</span> fromEvents.EventsActionTypes.LoadAllEventsSuccess: {
      <span class="hljs-keyword">return</span> eventEntityAdapter.upsertMany(action.payload, {
        ...state,
        loading: <span class="hljs-literal">false</span>,
        loaded: <span class="hljs-literal">true</span>,
      });
    }

    <span class="hljs-keyword">case</span> fromEvents.EventsActionTypes.LoadAllEventsFail: {
      <span class="hljs-keyword">return</span> {
        ...state,
        loading: <span class="hljs-literal">false</span>,
        error: action.payload,
      };
    }

    <span class="hljs-keyword">case</span> fromEvents.EventsActionTypes.ClearOldEvents: {
      <span class="hljs-keyword">const</span> idsOfexpiredEntities = <span class="hljs-comment">/* your logic here */</span>;
      <span class="hljs-keyword">return</span> eventEntityAdapter.removeMany(idsOfexpiredEntities, state);
    }

    <span class="hljs-comment">// Changes object is an object with Partial&lt;T&gt; type of your entity interface</span>
    <span class="hljs-comment">// You can consider it as an object which will be deep merged to your entity</span>
    <span class="hljs-keyword">case</span> fromEvents.EventsActionTypes.ReadEvent: {
      <span class="hljs-keyword">return</span> eventEntityAdapter.updateOne(
        {
          id: action.eventId,
          changes: {
            read: <span class="hljs-literal">true</span>,
          },
        },
        state,
      );
    }

    <span class="hljs-comment">// Note that you need to provide an array of Update&lt;T&gt; to the updateMany operation</span>
    <span class="hljs-comment">// UpdateStr&lt;T&gt; = {</span>
    <span class="hljs-comment">//   id: string;</span>
    <span class="hljs-comment">//   changes: Partial&lt;T&gt;;</span>
    <span class="hljs-comment">// };</span>
    <span class="hljs-keyword">case</span> fromEvents.EventsActionTypes.ReadAllEvents: {
      <span class="hljs-keyword">return</span> eventEntityAdapter.updateMany(
        [...state.ids].map(<span class="hljs-function">(<span class="hljs-params">eventId: <span class="hljs-built_in">string</span></span>) =&gt;</span> ({
          id: eventId,
          changes: {
            read: <span class="hljs-literal">true</span>,
          },
        })),
        state,
      );
    }
  }
  <span class="hljs-keyword">return</span> state;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getEventsLoading = <span class="hljs-function">(<span class="hljs-params">state: EventsState</span>) =&gt;</span> state.loading;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getEventsLoaded = <span class="hljs-function">(<span class="hljs-params">state: EventsState</span>) =&gt;</span> state.loaded;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getEventsError = <span class="hljs-function">(<span class="hljs-params">state: EventsState</span>) =&gt;</span> state.error;
</code></pre>
<p>Entity adapter and some of the CUD operations — reducer</p>
<h4 id="heading-default-selectors">Default Selectors</h4>
<p>Your entity adapter instance exports 4 useful selectors for you. You can export them in your selectors file, along with your custom ones if needed.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> {  
  selectIds,  
  selectEntities,  
  selectAll,  
  selectTotal,  
} = yourEntityAdapter.getSelectors();
</code></pre>
<h4 id="heading-use-ngrx-data-if-youve-too-many-domain-models">Use ngrx-data if you’ve too many domain models</h4>
<p>If you want to take <em>reducing the boilerplate code of your domain models</em> to another level, you might want to take a look at <a target="_blank" href="https://github.com/johnpapa/angular-ngrx-data">ngrx-data library</a>. It’s not part of official @ngrx/platform just yet, but they announced that it will be soon.</p>
<p>It’s not an alternative for ngrx, but instead it’s an additional flavour for it to extend managing domain models. As it’s stated on their documentation:</p>
<blockquote>
<p>Many applications have substantial <em>domain models</em> with 10s or 100s of <a target="_blank" href="https://github.com/johnpapa/angular-ngrx-data/blob/master/docs/faq.md/#entity">entity types</a> such as <em>Customer</em>, <em>Order</em>, <em>LineItem</em>, <em>Product</em>, and <em>User</em>.</p>
<p>In plain <em>ngrx</em>, to create, retrieve, update, and delete (CRUD) data for every entity type is an overwhelming task. You’re writing <em>actions</em>, <em>action-creators</em>, <em>reducers</em>, <em>effects</em>, <em>dispatchers</em>, and <em>selectors</em> as well as the HTTP GET, PUT, POST, and DELETE methods <em>for each entity type</em>.</p>
<p>In even a small model, this is a ton of repetitive code to create, maintain, and test.</p>
<p>The <em>ngrx-data</em> library is <em>one</em> way to stay on the <em>ngrx</em> path while radically reducing the “boilerplate” necessary to manage entities with <em>ngrx</em>.</p>
<p>If you haven’t used @ngrx/entity in your NgRx application yet, I strongly advise you to do so. You can read more about it on the following introduction post - <a target="_blank" href="https://medium.com/ngrx/introducing-ngrx-entity-598176456e15">Introducing @ngrx/entity</a>.</p>
</blockquote>
<h3 id="heading-use-selectors-seriously">Use selectors, seriously</h3>
<p>A selector is a pure function that slices a part of your state. They’re small query functions that filter out your state object. Other than utilizing selectors inside your view layer, it is additionally conceivable to compose selector functions on other selectors.</p>
<p>You should start using selectors for both compliance with <a target="_blank" href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY principle</a> and performance concerns. Pure selector functions will help you avoid slicing through the state, every time you need a piece of it, eventually saving you from repeating yourself. For the performance benefits it brings, let’s have a look at the following quote from NgRx selector docs:</p>
<blockquote>
<p>Because selectors are <a target="_blank" href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a>, the last result can be returned when the arguments match without reinvoking your selector function. This can provide performance benefits, particularly with selectors that perform expensive computation. This practice is known as <a target="_blank" href="https://en.wikipedia.org/wiki/Memoization">memoization</a>.</p>
<p>— NgRx selector docs</p>
</blockquote>
<p>Here’s an example usage of selectors, including composing selectors from different features:</p>
<p>Composing selectors — selector</p>
<p>Take a look at official docs for advanced use cases of selectors, it will definitely help you to solve complex problems in your app: <a target="_blank" href="https://github.com/ngrx/platform/blob/master/docs/store/selectors.md">https://github.com/ngrx/platform/blob/master/docs/store/selectors.md</a></p>
<h3 id="heading-keep-your-store-clean">Keep your store clean</h3>
<p>A common debate on NgRx store is about which state actually belongs to the store. When I watched <a target="_blank" href="https://medium.com/u/d6ba4dcb6838">Mike Ryan</a> and <a target="_blank" href="https://medium.com/u/637b8a3ce256">Brandon</a>’s <a target="_blank" href="https://www.youtube.com/watch?v=t3jx0EC-Y3c"><em>Reducing the Boilerplate with NgRx</em> talk on youtube</a>, it immediately took place in my head. I was already thinking through this question and evaluating my application’s state without a structure in place which they introduced clearly during the talk.</p>
<p>The structure that they pitched definitely helps on deciding which state belongs to the store or not.</p>
<p>The answer is <strong>SHARI</strong>! If your state fits in any of the categories below, you should keep it in your store.</p>
<p>Shared<br /> — <em>Shared state is accessed</em> by many components and services</p>
<p>Hydrated<br /> — <em>State that is persisted and hydrated from storage</em></p>
<p>Available<br /> — State that needs to be available when re-entering routes</p>
<p>Retrieved<br /> — State that needs to be retrieved with a side effect</p>
<p>Impacted<br /> — State that is impacted by actions from other sources</p>
<h3 id="heading-serialization-and-hydration"><strong>Serialization and hydration</strong></h3>
<p>If you’re like me, you probably prefer to persist data for part of your application store — like app settings, in user’s browser storage and hydrate it. This can be done through meta reducers.</p>
<h4 id="heading-serialization">Serialization</h4>
<p>Just be careful about serialization when you put a data in your store. Your data must be serializable, which means you cannot put your Map() or Set() object instances in the store.</p>
<p>An easy way of checking whether your object is serializable or not is, simply comparing your original object with the output of the following:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">JSON</span>.stringify(obj))
</code></pre>
<h4 id="heading-persistent-storage-and-hydration">Persistent storage and hydration</h4>
<p>I use <a target="_blank" href="https://www.npmjs.com/package/ngrx-store-localstorage">ngrx-store-localstorage package</a> to persist store data in localStorage. Here’s an example of how it’s done, using a meta reducer:</p>
<p>Persistent storage of your store — reducer</p>
<p>Persistent storage of your store — feature module</p>
<p>By the way, NgRx team has announced that there will be a built-in support for hydration and serialization with the next version — 7. So, looking forward to it!</p>
<p>Follow me on <a target="_blank" href="https://twitter.com/onderceylan"><strong>Twitter</strong></a> for more tips like the ones in this post.</p>
<p>Please don’t forget to <strong>like</strong> and <strong>follow</strong> if you find this article useful. <em>May the purity be with your functions and components!</em></p>
]]></content:encoded></item><item><title><![CDATA[Continuous integration and deployment for your enterprise hybrid mobile apps]]></title><description><![CDATA[Recently, I spent some time for setting up Continuous Integration (CI) and Continuous Delivery (CD) for an enterprise client. I automated build, sign/resign and distribution operations that we have to do for our hybrid mobile applications on a daily ...]]></description><link>https://onderceylan.com/continuous-integration-and-deployment-for-your-enterprise-hybrid-mobile-apps-51a57501abf0</link><guid isPermaLink="true">https://onderceylan.com/continuous-integration-and-deployment-for-your-enterprise-hybrid-mobile-apps-51a57501abf0</guid><category><![CDATA[hybrid apps]]></category><category><![CDATA[Ionic Framework]]></category><category><![CDATA[ionic framework]]></category><category><![CDATA[Angular]]></category><category><![CDATA[Bitbucket]]></category><dc:creator><![CDATA[Önder Ceylan]]></dc:creator><pubDate>Thu, 15 Mar 2018 16:05:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039439322/TyOW2ITd-.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I spent some time for setting up Continuous Integration (CI) and Continuous Delivery (CD) for an enterprise client. I automated build, sign/resign and distribution operations that we have to do for our hybrid mobile applications on a daily basis. No matter what platform you use for building your mobile app, <strong>ionic</strong>, <strong>cordova</strong>, <strong>nativescript, flutter</strong> or <strong>react native</strong>, they all share common practises and concerns when it comes to the build and distribution phase.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039411967/v6z3GWUN6.jpeg" alt /></p>
<p>I want to share my experience and hopefully inspire anyone those who are in the process of setting up or maintaining CI/CD. Although it’s a fairly technical article, I’ll also touch business values for luring business owners and managers. So, please bear with me.</p>
<h3 id="heading-why-should-you-start-using-continuous-delivery-pipeline"><strong>Why should you start using continuous delivery pipeline?</strong></h3>
<p>You may have too many manual processes and a possible bottleneck on your software deliveries. On daily basis, it can decrease your efficiency on delivering features because of the time you lose and the effort you make on context switching, manual building, fixing the builds and dependency issues, dealing with iOS code signing issues, manual archiving, manual deployment, manual re-packaging, code re-signing, and so on and so forth.</p>
<p>By introducing CI/CD pipeline into your organisation, you can improve the quality of your hybrid mobile apps, accelerate deliveries and automate your processes.</p>
<p>Before going forward, I’d like to go over Continuous Integration, Continuous Delivery, and Continuous Deployment terms. I think it’s important to clarify what they are and how they’re connected. In fact, I’d like to stress what it actually means for your business.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039416139/Ga7TM_jS3.png" alt /></p>
<h4 id="heading-continuous-integration-ci">Continuous Integration (CI)</h4>
<blockquote>
<p>Continuous integration — the practice of frequently integrating one’s new or changed code with the existing code repository — should occur frequently enough that no intervening window remains between commit and build, and such that no errors can arise without developers noticing them and correcting them immediately.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039418225/apnWvzKU9.png" alt /></p>
<p>What it could mean for your app and your team</p>
<ul>
<li>Assuring code quality by testing unit functions</li>
<li>Preventing compilation errors</li>
<li>Code linting</li>
<li>Early detection of possible issues with external dependencies, such as npm packages and plugins</li>
<li>Increasing your code coverage</li>
<li>Faster build times in time</li>
<li>Guarding a stable, working development version</li>
<li>Transparency with displaying the results of the builds to everyone</li>
<li>Deployment automation</li>
</ul>
<h4 id="heading-continuous-delivery-deployment-cd">Continuous Delivery / Deployment (CD)</h4>
<blockquote>
<p><strong>Continuous delivery</strong> is an extension of continuous integration to make sure that you can release new changes to your customers quickly in a sustainable way. This means that on top of having automated your testing, you also have automated your release process and you can deploy your application at any point of time by clicking on a button.</p>
<p><strong>Continuous deployment</strong> is the next step of continuous delivery: Every change that passes the automated tests is deployed to production automatically. Continuous deployment should be the goal of most companies that are not constrained by regulatory or other requirements.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039420336/s9GXfwTW0.png" alt /></p>
<p>What it could mean for your iOS app and your team</p>
<ul>
<li>No more xcode codesigning issues on development and distribution certificates anymore since you don’t need to build on our local computers</li>
<li>No more context switching for developers to be able to provide a QA build for testers, users, and stakeholders</li>
<li>No more build errors due to mismatch between system tools and dependencies</li>
<li>No more waiting times for testers to have a specific build/revision installed on a device</li>
<li>No more manual release preparation for production with re-building, re-packaging and re-signing with production enterprise certificates</li>
<li>No more necessity for our customers and stakeholders to stop by the office in order to get a build/version — thanks to beta app distribution</li>
<li>Any developer or tester is able to trigger a build remotely out of a branch/revision and deploy it for QA or PROD (if you follow continuous delivery approach)</li>
<li>Any developer or tester is able to trigger a build remotely out of pushing a new code or merging a PR and deploy it for QA or PROD (if you follow continuous deployment approach)</li>
</ul>
<p>From now on, I’d like to focus on tips and tricks on setting up a CI/CD for your hybrid applications. To avoid complexity, I just refer to <strong>ionic app on iOS platform</strong> in my examples.</p>
<h3 id="heading-find-bottlenecks-and-automate-manual-processes">Find bottlenecks and automate manual processes</h3>
<p>One of the blockers you might have for setting up your CI environment might be something you have to automate. It can be codesign operation, resign operation, repackaging or a web page that you need to upload a file or submit a form.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039422730/OrrRRjZ97.png" alt /></p>
<h4 id="heading-analyse">Analyse</h4>
<p>Start with analysing your manual processes on your delivery pipeline. Is it a change request you have to submit? Is it a distribution for QA? Is it a resigning operation? Define them and think about the ways to automate them.</p>
<h4 id="heading-automate-with-fastlane">Automate with fastlane</h4>
<p>Integrate <a target="_blank" href="https://fastlane.tools/">fastlane</a>! Fastlane is a great platform which focuses on solving the problems that existed for years in mobile development environment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039424736/1e6JbNnAY.png" alt /></p>
<p><em>“fastlane</em> handles tedious tasks so you don’t have to” — fastlane.tools</p>
<p>It helps you to organise and maintain your provisioning profiles and certificates for the codesigning, integrates beta deployment and so on.</p>
<h4 id="heading-automate-with-your-own-scripts">Automate with your own scripts</h4>
<p>For the processes that you can not automate through fastlane or any other helpful platform or library, you may need to write your own scripts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039426705/EgdiE2_Vx.png" alt /></p>
<p>Always browse <a target="_blank" href="https://www.npmjs.com/">npmjs.com</a> before writing your own node.js script</p>
<p>You may require to repackage your application, resign it, upload it to a web page or an enterprise MDM provider. In that case, you should create your own bash executables in order to automate those of your daily operations. Browsing npm packages and building your scripts with node.js might be wise since it’s a large community and there could already be a package for your needs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039428651/GoM_5Hqld.png" alt /></p>
<p>“Puppeteer is a Node library which provides a high-level API to control <a target="_blank" href="https://developers.google.com/web/updates/2017/04/headless-chrome">headless</a> Chrome or Chromium over the <a target="_blank" href="https://chromedevtools.github.io/devtools-protocol/">DevTools Protocol</a>. It can also be configured to use full (non-headless) Chrome or Chromium.”</p>
<p><a target="_blank" href="https://github.com/GoogleChrome/puppeteer/blob/master/README.md">Puppeteer</a> is a very powerful library and a great toolset if you need to deal with web pages for some of your manual processes. It can be your best friend for automating browser based processes.</p>
<p>I used puppeteer to automate a resigning operation where I had to upload my repackaged application build to a web form and download a resigned one from Nexus servers.</p>
<h3 id="heading-set-up-ci-configuration">Set up CI configuration</h3>
<p>Compiling and building an application package for iOS platform requires an iOS device, xcode, and code signing as we all know. No matter which CI solution you use, you need to identify and point the CI to the right agents for your build configuration.</p>
<p>Make sure that you have build agents with right capabilities for your automated processes</p>
<h4 id="heading-prerequisites-for-your-build-agents"><strong>Prerequisites for your build agents</strong></h4>
<p>Define your requirements and set prerequisites and capabilities for your CI agent. For instance, let’s say you’re building an ionic app and you have automated some tasks with fastlane and puppeteer. You may need brew, nvm, cordova, xcode, yarn, ionic, ios-deploy, fastlane and google-chrome installed for your agents.</p>
<p>You need to make sure those tools are installed on you CI agent. And as the first step on your build configuration, it’s wise to check if those tools are installed on CI system and if not, just install them with command line scripts!</p>
<p>Assert and install the tools you require for your scripts</p>
<h4 id="heading-manage-developer-and-enterprise-distribution-certificates-and-codesigning-on-ci-agent">Manage developer and enterprise distribution certificates and codesigning on CI agent</h4>
<p>If you’re building mobile apps for iOS, you might already know that codesigning is a challenging task. It’s hard to understand and even harder to maintain if you’re working in a large organisation with multiple product teams.</p>
<p>I recommend you to use <a target="_blank" href="https://docs.fastlane.tools/actions/match/">fastlane match</a> to manage your development certificates for your teams. You should also use <em>match</em> to install your distribution certificates on your CI agent.</p>
<p>You need to introduce a new step in your build configuration to download provisioning profiles and certificates from your certificate repository if you’re using match. Otherwise, you can use sigh &amp; cert to download it right from your Apple developer account. Following code snippet is an example of how you can handle iOS code signing on macOS build agent.</p>
<p>Download your provisioning profiles and certificates, set them to your custom keychain on the CI</p>
<p><em>I’d like to point to an important detail here. In order to have xcode get your certificates from the keychain during the build, your keychain should be unlocked — see line 7 on the above snippet. Otherwise, your build will be hanging on unlock keychain prompt.</em></p>
<h4 id="heading-maintaining-your-ci-scripts">Maintaining your CI scripts</h4>
<p>Although you can add command line scripts to your build configuration of your CI, I would advise keeping your build scripts on your source control system. It will be easier to develop, review and maintain your scripts and it will make it possible to see the revision history.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039430528/ChEdQIhD_.png" alt /></p>
<p>Folder structure example for maintaining your CI scripts</p>
<h4 id="heading-setting-up-steps-on-your-build-configuration">Setting up steps on your build configuration</h4>
<p>After building your custom scripts, you need to assign them to your build configuration of steps. Here’s an example of build steps for creating app artifact which is built on Bamboo.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039432566/6Z7ZLwluQ.png" alt /></p>
<p>Once you have your artifact (IPA file in our case), you may want to use it for beta deployment. To accomplish that, you can introduce a new stage for deployment and setup your build configuration similar to the one above to deploy the artifact you just built.</p>
<h3 id="heading-beta-app-distribution-for-continuous-deployment">Beta app distribution for continuous deployment</h3>
<p>Beta app distribution is a very nice way to distribute your app to QA, key users and stakeholders before publishing them to production users. Apple introduces <a target="_blank" href="https://developer.apple.com/testflight/">TestFlight</a> for commercial apps which is unfortunately not available for enterprise developer accounts.</p>
<p>However, there are other great options for enterprises which serve the same objective. See <a target="_blank" href="https://hockeyapp.net/">HockeyApp</a>, <a target="_blank" href="https://docs.fabric.io/apple/beta/overview.html">Fabric Beta</a> and <a target="_blank" href="https://testfairy.com/">TestFairy</a> for third party beta distribution services.</p>
<p>If you’re using <em>fastlane</em>, you can get advantage of using prebuilt scripts per beta service — see <a target="_blank" href="https://docs.fastlane.tools/getting-started/ios/beta-deployment/"><em>fastlane beta</em></a>. It has built-in actions for TestFlight, Fabric Beta, HockeyApp and TestFairy. Otherwise, you can also easily communicate with their APIs to upload your package and distribute it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039434552/qupLSUrOj.png" alt /></p>
<p>Distribution flow of beta app builds</p>
<p>You can setup an automated distribution stage on your CI as an additional step on your builds or use deployment pipeline of your CI.</p>
<p>Down below, you see a simple bash script which uploads your app artifacts to HockeyApp. HockeyApp automatically distributes this deployment to selected user group and sends email/slack notifications accordingly with your app’s installation link in it.</p>
<p>In case of you don’t use fastlane, it’s not that complicated to write your own beta app deployment script</p>
<h3 id="heading-in-a-nutshell">In a nutshell</h3>
<p>Although this is a longer article than I planned, it’s still an overview of the whole pipeline with some in-depth topics such as code signing, CI configuration and automating processes.</p>
<p>I hope it helps you to automate your processes and enable CI/CD for your organisation if it’s not there yet. Otherwise, you might feel the emotional cycle of manual delivery.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646039436841/riwoM79GV.png" alt /></p>
<p>We all fell down the cliff of urgency. I feel you.</p>
<p>Please leave comments if you have any questions or remarks! I’d be glad to hear your experience on your journey. Don’t forget to like and follow if you find my article valuable. Cheers!</p>
]]></content:encoded></item></channel></rss>