<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mike Healy</title>
    <description>The latest articles on DEV Community by Mike Healy (@mike_hasarms).</description>
    <link>https://dev.to/mike_hasarms</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F101547%2Fd7680b42-6f97-447d-998f-1936a3b701a0.jpg</url>
      <title>DEV Community: Mike Healy</title>
      <link>https://dev.to/mike_hasarms</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mike_hasarms"/>
    <language>en</language>
    <item>
      <title>Using S3 Lifecyle Rules with Spatie Laravel Backups</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Fri, 14 Jul 2023 05:41:12 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/using-s3-lifecyle-rules-with-spatie-laravel-backups-3h15</link>
      <guid>https://dev.to/mike_hasarms/using-s3-lifecyle-rules-with-spatie-laravel-backups-3h15</guid>
      <description>&lt;p&gt;AWS S3 has a feature called Lifestyle Rules that let you define rules to run a range of transitions on your objects, such as changing their storage class or expiring (deleting) them.&lt;/p&gt;

&lt;p&gt;This can be useful to save costs and clean up your object list by removing objects you don't need to keep forever.&lt;br&gt;
You can lean on AWS for the expiry logic without having to program scheduled events to cleanup for you.&lt;/p&gt;

&lt;p&gt;Lifecyle rules can be targeted to an entire bucket, to objects with a path prefix, or to objects with a particular Tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://spatie.be/docs/laravel-backup/v8/introduction"&gt;Spatie Laravel Backup&lt;/a&gt; is a popular Laravel package for taking site/database backups.&lt;/p&gt;

&lt;p&gt;It can be configured to write to various 'disks'. If you're using the S3 disk it is possible to Tag those backups for targeting by the Lifecycle rule.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating the rules in S3 and targeting a tag
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ccyM34m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6raps0vtkw730hp39h1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ccyM34m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6raps0vtkw730hp39h1.png" alt="S3 Console screenshot showing Management lifecycle rules" width="800" height="332"&gt;&lt;/a&gt;&lt;br&gt;
From your bucket, go to &lt;strong&gt;Management&lt;/strong&gt; and create a Lifecyle rule.&lt;/p&gt;

&lt;p&gt;In my case I will be targeting a tag called &lt;code&gt;fadeout&lt;/code&gt; with a value of &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This rule will transition objects to the Standard Infrequent Access storage class after 30 days, and then delete them after 60 days. It also only applies to objects over 150kB.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tagging backups that Laravel Backup uploads
&lt;/h2&gt;

&lt;p&gt;To have your backup objects tagged in order for the lifecycle rule to take effect you'll need to an an option to your Laravel &lt;code&gt;fileysystems.php&lt;/code&gt; config file.&lt;/p&gt;

&lt;p&gt;Assuming you are using the &lt;code&gt;s3&lt;/code&gt; disk, you'll need to add a backup_options value to your S3 config array like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'s3' =&amp;gt; [
  'driver' =&amp;gt; 's3',
  'key' =&amp;gt; env('AWS_ACCESS_KEY_ID'),
  'secret' =&amp;gt; env('AWS_SECRET_ACCESS_KEY'),
  'region' =&amp;gt; env('AWS_DEFAULT_REGION'),
  'bucket' =&amp;gt; env('AWS_BUCKET'),
  'url' =&amp;gt; env('AWS_URL'),
  'backup_options' =&amp;gt;
     ['Tagging' =&amp;gt; 'fadeout=true', 'visibility'=&amp;gt;'private'],
],

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backups are private by default, but because we're overriding the options array we need to specify it again.&lt;br&gt;
The format of the &lt;code&gt;Tagging&lt;/code&gt; key is url encoded as shown.&lt;br&gt;
The &lt;code&gt;Tagging&lt;/code&gt; and &lt;code&gt;visibility&lt;/code&gt; keys are case sensitive as shown.&lt;/p&gt;

&lt;p&gt;With that in place you can keep your recent backups without cluttering your bucket :)&lt;/p&gt;

</description>
      <category>s3</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>Running Jest with a Vue2 application</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Sun, 15 Jan 2023 03:38:31 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/running-jest-with-a-vue2-application-2ff6</link>
      <guid>https://dev.to/mike_hasarms/running-jest-with-a-vue2-application-2ff6</guid>
      <description>&lt;p&gt;The Vue CLI and test utils have a 'happy path' to setup testing libraries, but they are naturally geared to the latest version of Vue and other dependencies.&lt;/p&gt;

&lt;p&gt;If you need to add tests to an existing Vue 2 application you might find the process a little tricky.&lt;/p&gt;

&lt;p&gt;You'll need to install the version of &lt;a href="https://github.com/vuejs/vue-test-utils/" rel="noopener noreferrer"&gt;Vue test utils&lt;/a&gt; and &lt;a href="https://github.com/vuejs/vue-jest#installation" rel="noopener noreferrer"&gt;vue-jest&lt;/a&gt; that correspond to your version of Vue.&lt;/p&gt;

&lt;p&gt;For example if you're running Jest 29, you'll need to install &lt;code&gt;vue2-test&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @vue/test-utils@1 @vue/vue2-jest@29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the tests run in an environment without a native DOM we'll also need to configure them to use the jsdom implementation.&lt;/p&gt;

&lt;p&gt;Install the package like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev jest-environment-jsdom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure Jest to use the virtual JS DOM. Jest can be configured either via a dedicated file, or through your &lt;code&gt;package.json&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 // other config...

 "jest": {
        "testEnvironment": "jsdom",
        "moduleFileExtensions": [
            "js",
            "json",
            "vue"
        ],
        "setupFiles": [
            "./jest-setup.js"
        ],
        "transform": {
            "^[^.]+.vue$": "@vue/vue2-jest",
            ".*\\.(js)$": "babel-jest"
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case I required some extra config that's loaded in &lt;code&gt;jest-setup.js&lt;/code&gt;. This is because my app had global dependencies for LoDash and a utilities file. To make these functions available in the test environment they are loaded like this.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;jest-setup.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import lodash from 'lodash';
import utils from './resources/js/utils.js';

global._ = lodash;
global.utils = utils;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>jest</category>
      <category>vue</category>
      <category>vue2</category>
    </item>
    <item>
      <title>Dark Mode Toggle with Alpine &amp; Tailwind</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Fri, 04 Jun 2021 22:56:45 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/dark-mode-toggle-with-alpine-tailwind-44h3</link>
      <guid>https://dev.to/mike_hasarms/dark-mode-toggle-with-alpine-tailwind-44h3</guid>
      <description>&lt;p&gt;Some users prefer darker colour themes to prevent eyestrain, while others like having the choice depending on their lighting conditions. Tailwind makes it easy to specify different colours to use depending on which mode is active.&lt;/p&gt;

&lt;p&gt;By default Tailwind responds to the &lt;code&gt;prefers-color-scheme&lt;/code&gt; media query, which passes through the users' operating system preference. You can also configure it to alter the colours by inheriting a 'dark' CSS class name from a parent element. &lt;br&gt;
This lets you to build your own UI control to switch modes, so that users can have a different preference for your site than their OS.&lt;/p&gt;

&lt;p&gt;To do this, change the darkMode property in your tailwind.config.js file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;

  &lt;span class="na"&gt;darkMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Controlling The Active Mode
&lt;/h2&gt;

&lt;p&gt;Now that Tailwind is configured to compile dark mode colours based on the .dark class name, we need a way to toggle that class name. We also need to persist the selection between sessions and page loads.&lt;/p&gt;

&lt;p&gt;I'll be using localStorage to store the user's preference and AlpineJS to handle the toggling action. Alpine is a lightweight JS framework that works with your markup and doesn't require a build step.&lt;/p&gt;

&lt;p&gt;As well as storing the user's preference we'll need to apply it as early as possible during page rendering to activate their desired mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tailwind generates classes that are applied when dark mode is activated&lt;/li&gt;
&lt;li&gt;When our page loads we see if the user has set an explicit choice of mode for our site (using the UI control we provide)&lt;/li&gt;
&lt;li&gt;If they've asked for dark mode we apply the CSS class&lt;/li&gt;
&lt;li&gt;If they have not set a preference, but their implicit preference via their OS is for dark mode we apply the CSS class&lt;/li&gt;
&lt;li&gt;Otherwise we'll fall back to the default light mode&lt;/li&gt;
&lt;li&gt;When our user makes a specific choice using the on-page UI we'll update the document class and also save their preference in localStorage for subsequent page loads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  UI Control
&lt;/h2&gt;

&lt;p&gt;Here's the code to provide light &amp;amp; dark mode buttons. Alpine uses the x-data property to store the state for our component. x-init runs on setup and syncs our local state with what was previously stored in localStorage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
  &lt;span class="na"&gt;x-data=&lt;/span&gt;&lt;span class="s"&gt;"{
      mode: '',
      setColorMode: m =&amp;gt; {
          if (m === 'dark') {
              document.documentElement.classList.add('dark')
              localStorage.setItem('colorMode', 'dark')
          } else {
              document.documentElement.classList.remove('dark')
              localStorage.setItem('colorMode', 'light')
          }
      }
  }"&lt;/span&gt;

  &lt;span class="na"&gt;x-init=&lt;/span&gt;&lt;span class="s"&gt;"() =&amp;gt; {
      const m = localStorage.getItem('colorMode');
      if (m !== 'dark' &amp;amp;&amp;amp; m !== 'light') return;
      mode = m;
  }"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"mode='light'; setColorMode('light');"&lt;/span&gt;
    &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"{'font-bold': mode === 'light', 'font-thin': mode !== 'light' }"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"underline px-2 py-1 rounded-md border-white hover:bg-gray-100 dark:hover:bg-gray-800"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    light
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
    &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"mode = 'dark'; setColorMode('dark');"&lt;/span&gt;
    &lt;span class="na"&gt;:class=&lt;/span&gt;&lt;span class="s"&gt;"{'bg-gray-700 text-gray-200 border border-solid border-gray-600 font-bold': mode === 'dark', 'font-thin': mode !== 'dark' }"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"underline px-2 py-1 rounded-md hover:bg-white dark:hover:bg-gray-800"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    dark
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Persisting The Selection
&lt;/h2&gt;

&lt;p&gt;Of course as users browse around your site you don't want them to have to re-apply their choice of colour mode every time they visit a new page. We'll use a bit of JS near the top of our document to reapply their preference as early in the render as possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- other bits --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"css/style.css"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"detect-mode.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// detect-mode.js&lt;/span&gt;
&lt;span class="c1"&gt;// set initial color scheme&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;explicitelyPreferScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;colorMode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;explicitelyPreferScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;colorMode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;explicitelyPreferScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;explicitelyPreferScheme&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(prefers-color-scheme:dark)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will apply dark mode if our visitor has selected it from the UI control above; or if they have not set any preference but their OS is set to use dark mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cdn.mikehealy.com.au/s3/darkmode/index.html"&gt;Test it out here&lt;/a&gt;. You can refresh the page and see your preference persist.&lt;/p&gt;

&lt;p&gt;This was originally published on my website &lt;a href="https://www.mikehealy.com.au/"&gt;mikehealy.com.au&lt;/a&gt;&lt;/p&gt;

</description>
      <category>darkmode</category>
      <category>tailwindcss</category>
      <category>alpinejs</category>
      <category>ui</category>
    </item>
    <item>
      <title>Lighter LoDash Builds for Laravel</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Tue, 05 Jan 2021 23:18:56 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/lighter-lodash-builds-for-laravel-4oj2</link>
      <guid>https://dev.to/mike_hasarms/lighter-lodash-builds-for-laravel-4oj2</guid>
      <description>&lt;p&gt;Laravel projects often include the &lt;a href="https://lodash.com"&gt;LoDash JavaScript utility library&lt;/a&gt;. It contains handy utilities, however if you don't need its entire suite you may be serving unnecessary JS to your users that you never run.&lt;/p&gt;

&lt;p&gt;LoDash supports &lt;a href="https://lodash.com/custom-builds"&gt;custom builds&lt;/a&gt; to avoid this problem, and it's fairly easy to modify your Laravel's bootstrap.js file trim down the features you're loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default resources/js/bootstrap.js
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window._ = require('lodash'); // 71.1K (gzipped: 24.6K)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Customizing the build
&lt;/h2&gt;

&lt;p&gt;In my case I was only using the throttle and debounce helpers from LoDash. This can be mapped to the window lodash object (typically _) like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window._ = require('lodash/core')  // 14K (gzipped: 5.3K)
window._.debounce = require('lodash/debounce')  // 3.5K (gzipped: 1.4K)
window._.throttle = require('lodash/throttle') // 3.7K (gzipped: 1.4K)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My application JS can then call &lt;code&gt;_.debounce()&lt;/code&gt; and &lt;code&gt;_.throttle()&lt;/code&gt; for those functions with a significant saving to my JS bundle size.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>lodash</category>
      <category>performance</category>
    </item>
    <item>
      <title>16GB or 32GB RAM for Web Development?</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Tue, 14 Jul 2020 03:11:09 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/16gb-or-32gb-ram-for-web-development-2475</link>
      <guid>https://dev.to/mike_hasarms/16gb-or-32gb-ram-for-web-development-2475</guid>
      <description>&lt;p&gt;My 2013 15" MBP will reach the end of its life at some point and I'm looking to replace it with a 16" ARM MBP when they're released.&lt;/p&gt;

&lt;p&gt;I do full stack web dev with PHP, and JS/Vue/static site generators on the front end. I also do some visual design, and occasional lightweight video editing (iMovie only).&lt;/p&gt;

&lt;p&gt;I'll use the new machine for at least 3 years, and maybe even 5 or 6 years. I'd like to get 32GB of RAM, but it is an expensive upgrade, it's not something I'll add just for the sake of it.&lt;/p&gt;

&lt;p&gt;How useful do you think the extra 16GB of RAM will be for this sort of work?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>discuss</category>
      <category>equipment</category>
    </item>
    <item>
      <title>Help: Should a new service worker force a refresh to update a PWA?</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Sat, 04 Jan 2020 23:39:32 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/help-should-a-new-service-worker-force-a-refresh-to-update-a-pwa-4335</link>
      <guid>https://dev.to/mike_hasarms/help-should-a-new-service-worker-force-a-refresh-to-update-a-pwa-4335</guid>
      <description>&lt;p&gt;I have a frontend app called &lt;a href="https://www.finskore.com"&gt;Finskore&lt;/a&gt; for scoring games of Finska that I want to enable offline.&lt;/p&gt;

&lt;p&gt;I'm following &lt;a href="https://developers.google.com/web/fundamentals/primers/service-workers/"&gt;Google's PWA guide about service workers&lt;/a&gt; and it seems that an updated service worker will only take effect on the following visit. The existing SW runs the current session, but the new version is installed for the next network visit.&lt;/p&gt;

&lt;p&gt;My question is, should I try and detect when a new service worker has been installed and force a refresh at that time? The reason being to get the new version of the app for the immediate session.&lt;/p&gt;

&lt;p&gt;I was wondering if I could compare cache version names on install and do a window.reload() if they are different?&lt;/p&gt;

&lt;p&gt;Thoughts?&lt;/p&gt;

</description>
      <category>help</category>
      <category>pwa</category>
      <category>serviceworkers</category>
    </item>
    <item>
      <title>How to Build an Image Carousel</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Mon, 25 Nov 2019 06:57:11 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/how-to-build-an-image-carousel-334i</link>
      <guid>https://dev.to/mike_hasarms/how-to-build-an-image-carousel-334i</guid>
      <description>&lt;p&gt;Don't.&lt;/p&gt;

</description>
      <category>html</category>
      <category>ui</category>
      <category>ux</category>
      <category>carousel</category>
    </item>
    <item>
      <title>Improving Performance for Low-Bandwidth Users with save-data</title>
      <dc:creator>Mike Healy</dc:creator>
      <pubDate>Wed, 25 Sep 2019 23:42:08 +0000</pubDate>
      <link>https://dev.to/mike_hasarms/improving-performance-for-low-bandwidth-users-with-save-data-e1g</link>
      <guid>https://dev.to/mike_hasarms/improving-performance-for-low-bandwidth-users-with-save-data-e1g</guid>
      <description>&lt;p&gt;Everyone likes a fast website, but some users have connections and data plans that make it especially critical. In some places bandwidth is expensive, and any bytes you don't need to serve can save your users money.&lt;/p&gt;

&lt;p&gt;I recently learned about the &lt;a href="https://nooshu.github.io/blog/2019/09/01/speeding-up-the-web-with-save-data-header/" rel="noopener noreferrer"&gt;'save-data' HTTP header&lt;/a&gt; that clients may send to signify that they want a lower-bandwidth experience. The header alone doesn't do much, but it makes their preferences clear to you so you can make your own optimizations.&lt;/p&gt;

&lt;p&gt;On mobile Chrome this setting is called 'Lite Mode'. Desktop users can install a browser extension to enable the header. Other browsers likely have their own way of enabling the setting.&lt;/p&gt;

&lt;p&gt;Once you've detected this setting you might choose to style elements differently (for example dropping large background images), avoid decorative background video, or perhaps skip custom fonts that might delay rendering and add to the bandwidth costs.&lt;/p&gt;

&lt;p&gt;The setting can be detected server-side by looking for a HTTP header (save-data=on) or client side in JS to set a flag for your CSS selectors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//PHP example
function saveData() {
   return (isset($_SERVER["HTTP_SAVE_DATA"]) &amp;amp;&amp;amp; strtolower($_SERVER["HTTP_SAVE_DATA"]) === 'on');
}

//JS example (courtesy of Nooshu)
//add save-data class name to document element for CSS selectors
if ("connection" in navigator) {
    if (navigator.connection.saveData === true) {
        document.documentElement.classList.add('save-data');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the detection in place you can omit non-essential elements to low-bandwidth users. For example, on my WordPress website I've skipped queuing Google custom fonts, and dropped my masthead background image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// functions.php
if( !saveData() ) {
  wp_enqueue_style( 'fonts', 'https://fonts.googleapis.com/css?family=Open+Sans|Oswald:300,400,600');
}

/*
N.B. in WP it's good practice to prefix functions to avoid naming clashes.
I've skipped that for this example */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's my site on mobile Chrome with and without Lite Mode (aka sava-data).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F81yho034jqmdzk2zmqu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F81yho034jqmdzk2zmqu7.png" alt="Default version, and save-data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was a pretty easy change to make for some quick performance gains for low-bandwidth users. The improvements could be even bigger for heavier sites, or if the header was considered during site development too.&lt;/p&gt;

&lt;p&gt;(This post was originally published at &lt;a href="https://www.mikehealy.com.au/" rel="noopener noreferrer"&gt;mikehealy.com.au&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>performance</category>
      <category>http</category>
      <category>wordpress</category>
      <category>php</category>
    </item>
  </channel>
</rss>
