Updating all files in a directory with Node.js

const fs = require('fs');

fs.readdirSync('./path/to/directory', { 
  recursive: true, 
  withFileTypes: true 
}).filter(item => !item.isDirectory()).map(item => {
  const filePath = item.parentPath + '/' + item.name;
  try {
    const data = fs.readFileSync(filePath, 'utf8');
    if (data.indexOf('string to be replaced') >= 0) {
      const result = data.replace(/string to be replaced/g, replacement);
      fs.writeFileSync(filePath, result);
    }
  } catch (err) {
    console.error(err);
  }
});

Using .env in React Native projects

Add support for using .env in your project using: https://www.npmjs.com/package/react-native-config

Make sure you do the step "Extra step for Android": https://github.com/lugg/react-native-config?tab=readme-ov-file#extra-step-for-android

If on iOS it's not working, run pod install inside ios folder.

You can also use variables to refer to your .env variables like this:

import Config from "react-native-config";
const ENV = 'STAGE'; // 'LOCAL' or 'PROD' or 'STAGE'
export const SHOP_URL = Config[`SHOP_URL_${ENV}`];

Squashing commits

git

Before opening a pull request squash commits in your branch to have a cleaner history. Here is how to squash the last 5 commits:

git reset --soft head~5
git commit -m 'my new commit message'

Note that if you have already pushed your branch to remote you will see a warning that your branch and origin have diverged and you can use git pull to merge the remote branch into yours. Do not pull and instead use git push --force to update the remote branch. More info

Accessibility Checklist

  • All text should scale with increasing zoom level.
  • Increasing zoom level to 200% should not cause horizontal scrolling or break layout.
  • All functionality should be available via keyboard.
  • Make sure all elements have a safe contrast ratio.

Caching workflow between clients and servers

While as frontend developers we seldom do anything in regards to caching, as a full-stack developer you need a good understanding of how caching works in the browser and on the server...

Routing in Angular 17

Learn how to use directives, services, providers, and interfaces that Angular 17's Router package offers to solve common routing problems...

Error handling for fetch requests

try {
  const response = await fetch('https://restcountries.com/v4.1/all');
  if (response.ok) {
    console.log('Promise resolved and HTTP status is successful');
    // ...do something with the response
  } else {
    // Custom message for failed HTTP codes
    if (response.status === 404) throw new Error('404, Not found');
    if (response.status === 500) throw new Error('500, internal server error');
    // For any other server error
    throw new Error(response.status);
  }
} catch (error) {
  console.error('Fetch', error);
  // Output e.g.: "Fetch Error: 404, Not found"
}

Reference: https://dionarodrigues.dev/blog/fetch-api-do-you-really-know-how-to-handle-errors

Changing svg color using css filter

css

If you display svg icons using CSS background rule, one problem is that you can not change the fill color of the icon in CSS. This is now possible using CSS filter property. Simply use following tool to generate a filter rule for your desired color:

https://isotropic.co/tool/hex-color-to-css-filter/

Here is an example of displaying a black svg icon in red:

li::before {
    display: inline-block;
    content: "";
    background-image: url(../icons/caret-right-fill.svg);
    vertical-align: -.125em;
    background-repeat: no-repeat;
    background-size: 1rem 1rem;
    width: 1em;
    height: 1em;
    filter: invert(16%) sepia(100%) saturate(6866%) hue-rotate(6deg) brightness(110%) contrast(123%);
}

Converting Multi-Page apps to SPA

If you want to build a SPA (single page application) from a MPA (multi page application) you don't always need a giant framework. What you need mainly is to set an event handler on your links to disable their default behaviour...

Animated loader in CSS

css

Add animated loading icon to any HTML element by adding a "loading" class and the following css rules:

.loading::before {
    content: ' ';
    width: 1em;
    height: 1em;
    border: 0.2em solid #999;
    border-top-color: transparent;
    border-radius: 50%;
    animation: loadingspin 1s linear infinite;
}
    
@keyframes loadingspin {
    100% {
        transform: rotate(360deg)
    }
}

Demo on codepen

Tips of day

css

Various development tips from work today...

Moving from Rollup to Esbuild

I recently refactored my frontend build, switching from Rollup to Esbuild for bundling JavaScript and CSS. The change reduced the build time to less than half (from 21s to 10s). Below are a few things I learnt during this change:

  • Use time command in terminal to find out how much it takes for a command to finish, for example I used time npm run build to compare my build time before and after the change.
  • Understand what && and & work when chaining npm scripts. If you need a command to run in background insert a & after the command and if you need a command run after an earlier command has finished then insert a && after the former command, for example npm run cmd1 && npm run cmd2 & npm run cmd3 means that cmd 2 runs in background, but only after cmd1 has finished execution and that cmd3 also runs after cmd1 has finished, but parallel to cmd2. Usually you would need to run scripts that watch for files changes or start a server to run in background, specially if you want another script to run after them.
  • Esbuild not only compiles TypeScript files to JavaScript while bundling and minifying them, it can also bundle CSS modules into one CSS file while at the same time inserting CSS background images into CSS as Data URLs and hence reuding number of requests on production server.

Basic Authentication

The easiest way to password protect a resource on Web is to use Basic Authentication scheme. Bearer authentication is another common, but more complex authentication scheme which I will talk about in another article. In basic authentication two HTTP headers are used: authorization and www-authenticate. When an http request without authorization header is received by the server, it would return a response with 401 (unauthorized) status and the header www-authenticate: basic. This header tells user agent to prompt user to enter credentials required to access the resource. Credentials entered by the user are then encoded in username:password format and sent to the server in a new request's authorization header. When server receives this request it checks whether the value of authorization header matches with credentials stored on the server or not. If it does, server responds with the requested resource, otherwise it responds with 401 (unauthorized) status and the header “www-authenticate: basic”.

Example of basic authentication implemented in Node.js: https://github.com/smohadjer/vercel-basic-auth/blob/master/middleware.js

Basic Authentication demo (Use admin for both username and password)

NPM Scripts for serverless and server environments

Do not use “start” and “dev” to start a project with express.js if you wish to host it on Vercel, because then if you run project locally using “vercel dev” it will run these nom scripts. Instead use “start-express” and “dev-express” so you can test locally for both server and servereless environments.

Using Location API on anchor elements

Did you know that a HTML anchor element can be treated as a Location object? This means similar to how you can use window.location to read or write different parts of a URL, you can do the same with an anchor element. For example if you have a link like this:

<a id="myLink" href="https://mydomain/products.html?category=clothing#productName">test</a>

You can get the value of hash in this way:

const hash = document.getElementById('myLink').hash.substring(1); // returns productName

You can use different properties of Location object on an anchor element and same as with window.location you can both read and write these properties. For example to get the value of category from previous link you can use this:

const params = document.getElementById('myLink').search;
const category = new URLSearchParams(params).get('category'); // clothing

For more info see: https://developer.mozilla.org/en-US/docs/Web/API/Location

Using GitHub Actions to deploy on Vercel

When you install Vercel application on GitHub and give it access to your repository, GitHub will trigger a deployment on Vercel every time there is a new commit in your main branch, however if your API on Vercel only needs to be updated when a certain file or folder is changed, these deployments are unnecessary. You can avoid unnecessary deployments to Vercel using GitHub Actions as described here: https://vercel.com/guides/how-can-i-use-github-actions-with-vercel.

Another advantage of using GitHub Actions with Vercel is that you can uninstall Vercel application from GitHub as Vercel no longer needs access to your GitHub repository. You simply need to create a token on Vercel and store it along with your Vercel Org and Project IDs (found in .vercel/project.json in root of your project) in secrets on Github at https://github.com/your-username/your-repository/settings/secrets/actions

vercel secrets on GitHub

Responsive images that do not scale up beyond their intrinsic size

Usually all you need to make an image resize based on size of screen is to add one simple CSS rule:

img {
  width: 100%;
}

However if the screen resolution is higher than width of your image, this would result in a blurred image as there is simply not enough data in image to render it at higher resolutions. In such cases it might be more desirable to keep the image responsive, but not allow it to get larger than it's actual size. This can be achieved by the following CSS rule:

img {
  width: auto;
  max-width: 100%;
}

Fixing TypeScript error property does not exist on type EventTarget

When working with event listeners in TypeScript one often runs into error that a particular property does not exist on type EventTarget. While it's relatively easy to fix this error using type "any" or by typecasting the event target using "as", none of these solutions are optimal since they simply disable TypeScript's type checking, which is what we are using TypeScript for in the first place!

The better way to fix this error is to use JavaScript's instanceof operator which you can read about at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof.

Example of code that throws error:

document.querySelector('p').addEventListener('click', (event) => {
    alert(event.target.textContent);
});

Error:

Property 'textContent' does not exist on type 'EventTarget'.

Solution:

document.querySelector('p').addEventListener('click', (event) => {
    if (event.target instanceof Element) {
      alert(event.target.textContent);
    }
});

Handlebars 101

It is possible to iterate objects in handlebars templates. In below example countries is an object of key:value pairs where key is letter of alphabet and value is an array of countries whose name starts with that letter. @key returns key and “this” returns value for that key. It’s possible to use “../“ to access parent properties.

<dl>
    {{#each countries}}
        <dt>{{toUpperCase @key}}</dt>
            <dd>
            <ul>
            {{#each this}}
                <li><a href="{{../../url}}?country={{country_name}}">{{country_name}}</a></li>
                {{/each}}
            </ul>
        </dd>
        {{/each}}
</dl>
Handlebars.registerHelper('toUpperCase', function(str) {
    return str.toUpperCase();
});

Closing an open port via Mac's terminal

mac

Use below command in terminal to get a list of all open ports:

netstat -an | grep LISTEN

Find the process ID (PID) of the port you wish to close. You may need to use sudo.

lsof -i :[portNumber]

Then use the following command to kill the process listening on your port. You may need to use sudo with kill -9 parameter.

kill [PID]

Deploy to Vercel

In this example you will create a static HTML page that calls an api (Node.js serverless function) which responds by returning the word "Hello World" and displaying it in the browser...

List of Tools I use for Web Development

Responsive Image Breakpoints Generator Easily generate the optimal responsive image dimensions
Live Server Development server with live reload capability for fast prototyping
Thunder Client Rest Client for Testing APIs in VS Code
esbuild Bundle modules and scripts for better performance in browsers
LocaPing Traceroute From Multiple Locations. Enter your site's domain name and select a country to find out location and performance of the server hosting your domain to users visiting from that country.
iview Use to find out X and Y coordinates of any point in an image.
Image to Base64 Encode images to Base64

Sorting items in an array by an arbitrary order

Sometimes you need to display data that you fetch from backend in a custom order in frontend. The following snippet shows how to order objects in our array using a custom order defined in another array. Note that to avoid mutation we need to use WEB API's structuredClone() method instead of spread syntax (...) as spread syntax only does a shallow copy of array items and hence won't be able to clone objects inside an array.

const myArray = [
  {name: 'firstname', value: 'Tom'},
  {name: 'age', value: 61},
  {name: 'lastname', value: 'Cruise'}
];
const mySortOrder = ['firstname', 'lastname', 'age'];
const sortArray = (myArray, mySortOrder) => {
  myArray.sort((a, b) => {
    const indexA = mySortOrder.indexOf(a.name);
    const indexB = mySortOrder.indexOf(b.name);
    return indexA - indexB;
  });
  return myArray;
}
const sortedArray = sortArray(structuredClone(myArray), mySortOrder);
console.log(sortedArray);

Disabling preview deployments in Vercel

If you don't want Vercel to do preview deployments for other branches of your repository you can disable preview deployments by going to your project's settings > git > Ignored Build Step and add the following command:

if ["$VERCEL_ENV" == "production"]; then exit 1; else exit 0; fi

This is probably the easiest way. Another way would be to add a vercel.json to all branches and set "deploymentEnabled" to false for any branches that should not trigger deployment, like below:

{
  "git": {
    "deploymentEnabled": {
      "gh-pages": false
    }
  }
}

More info:

CSS best practices

css
  • When animating elements in CSS use opacity and transform properties only. This not only improves performance of your animation, but also avoids triggering layout shifts and repaints. https://web.dev/articles/animations-guide
  • Comment any rule that its purpose is not obvious.
  • Use content: none; to hide pseudo elements (::before, ::after) rather than display: none;.
  • Avoid "!important". If you have to use it, add comment to explain why.
  • Avoid negative margins. If you have to use them, add comment to explain why.
  • To target the first x children of an element, use :nth-child(-n + x).
  • To remove hover underline from a::before set a::before's display to inline-block.
  • Do not use css to hide elements and instead use HTML5 attribute "hidden".
  • Use -webkit-overflow-scrolling: touch; to apply momenutum-based scrolling to an element on touch devices. Read more
  • Check browser support before using new css properties.