Hemanth.HM

A Computer Polyglot, CLI + WEB ♥'r.

Reactive Context in Svelte

| Comments

The context API in svelte provides a way for components to communicate to each other without passing around data, functions props, or dispatching events!

Let us see a quick example:

1
2
3
4
5
6
7
8
// Parent has something like:
<script>
import { setContext } from 'svelte'

const state = {}

setContext('state', state)
</script>
1
2
3
4
5
6
// In the descendants/child components
<script>
import { getContext } from 'svelte'

const state = getContext('state')
</script>

That was easy, but say you have sistuation where the context need to be update later in time, to emulate we could setTimeout(() => {setContext('state', {})},1000) this would not reflect either in the parent or the child component.

store comes to our resuse in these cases:

1
2
3
4
5
6
7
8
9
// In the parent component:
import {setContext, getContext} from 'svelte';
import {writable} from 'svelte/store';

let state = writable({color: 'hotpink'});
setContext('state', state);

setTimeout(() => {$state = {color: 'red'}},2000);
// ^ Setting the store value after a timeout with assigment operation.
1
2
3
4
5
// In the child component:
import {getContext} from 'svelte';
const state = getContext('state')

// Access it like any other state variable, in this case `$state`

Here is a quick example:


This is explictily mentoined in the API docs too:

Context is not inherently reactive. If you need reactive values in context then you can pass a store into context, which will be reactive.

Handling Multiple Inputs FrontEnd Frameworks

| Comments

I just wanted to look into few frameworks on how they are handling multiple inputs particularly relating to the same value, if picked React, Vue and Svelte for this case study:

Say, you were to pick up some veggies from your salad today:

1
let veggies = ["🥬","🌽","🌶","🥦","🍠"];

It would be better to have array of objects for topping like [{id: '', label: '', value: ''}] but for the ease of the demo, let us stick with an array for the toppings. We need to create a simple group of inputs of type checkbox.

First up React with 🎣:

Crux of handling those multiple checkboxs:

1
2
3
4
5
6
7
8
 const [checkedItems, setCheckedItems] = useState({});

  const handleChange = event => {
    setCheckedItems({
      ...checkedItems,
      [event.target.name]: event.target.checked
    });
  };

It's Vue's trun:

This the best I could do, thanks to GreenJello for the quick review.

The key component here is v-model:

1
<input type="checkbox" v-model="veggie.selected"> 

Finally, Svelte:

The key factor here is bind:group:

1
<input type=checkbox bind:group={selectedveggies} value={veggie}>

Contoller vs Magic vs Concise code I shall leave it to the reader discretion and choice!

Top Level Await Node REPL

| Comments

One can use await keyword outside of async functions with Top-level await and the happy news is you can use it today in node and REPL.

As of today, if we execute the bleow peice of code in node env:

1
await Promise.resolve(42);

We would see a: SyntaxError: await is only valid in async function

So, we would normally mitigate this by wrapping it in an async function:

1
2
3
4
(async function() {
  const answer = await Promise.resolve(42);
  // answer would be 42
}());

But with top level await it get's easier we could just do const answer = await Promise.resolve(42); note that this will only work at the top level of modules only.

Well, Node.js v14.3.0 was released with the support of top-level await! But we always had a flag to enabled top-level await on the REPL, ever since node v10!

The --experimental-repl-await enables experimental top-level await keyword support in REPL, so all you have to do is start the REPL with the flag node --experimental-repl-await and that's it.

And within the script you could do something like:

1
2
3
4
// home.mjs
export function home() {
  return 'Hello home!';
}
1
2
3
// index.mjs
const home = await import('./foo.mjs');
console.log(home());

You could execute this using node --experimental-top-level-await.

TLDR: For REPL we would need --experimental-repl-await and for mjs scripts --experimental-top-level-await as of today.

Contact Picker API

| Comments

Native mobile apps always had this privilege of using the contact picker with ease and now web can do that too!

First up, Check if the browser support this API:

1
const hasContactPicker = "contacts" in navigator && "ContactsManager" in window;

invoke select method:

1
2
3
4
5
6
7
8
9
10
11
12
13
const properties = "name, tel, email, address, icon".split(",");
const options = { multiple: true };

const fetchContact = async evn => {
  try {
    const contacts = await navigator.contacts.select(properties, options);
    // ^ [{},{}]
  } catch (err) {
    //bombed
  }
};

contactButton.addEventListener("click", evn => fetchContact(evn));

contacts would be an array of objects with keys being the requested properties and values being the corresponding contact details.

Note:

  • This API works only on top level window.

  • User gesture is required before invoking the select method!

View the demo here and the source below:

WebSocketStream API

| Comments

WebSocketStream API came into play to make things easier with WebScoket API, which has the below limitations:

  • Ergonomics isn't great.
  • The important feature of backpressure is missing.
  • The onmessage event will keep firing until the page becomes completely unresponsive.
  • To find out when it is safe to start sending messages again, it is necessary to poll bufferedAmount.

Goals of WebSocketStream API:

  • Provide a WebSocket API that supports backpressure
  • Provide a modern, ergonomic, easy-to-use WebSocket API
  • Allow for future extension

^ From the explainer.

Sample code:

1
2
3
4
5
6
7
8
(async () => {
  const wss = new WebSocketStream("wss://echo.websocket.org/");
  const { readable, writable } = await wss.connection;
  const reader = readable.getReader();
  await writable.getWriter().write("echo");
  const { value, done } = await reader.read();
  console.log(value, done);
})();

DEMO:

DOM Element Timing API

| Comments

Measuring and monitoring the rendering timestamps of DOM elements is an important measure to improve and avoid regressions in page loads.

The Element Timing API, helps us to measure this with ease and it supports the below elements:

  • img elements.

  • image elements inside an svg.

  • video element's poster image.

  • Elements with a background-image.

  • Groups of text nodes.

Make sure the DOM element of interest has elementtiming content attribute:

1
2
3
<img src="" elementtiming="cat-pic" />
<p elementtiming="imp-doc">I need to watch this</p>
<!-- So on -->

Observe the element:

1
2
3
4
5
6
7
8
9
10
11
12
const observer = new PerformanceObserver(list => {
  let perfEntries = list.getEntries().forEach(function(entry) {
    if (entry.identifier) {
      if (entry.renderTime) {
        console.log(`${entry.renderTime}ms to render!`);
      } else {
        console.log(`${entry.loadTime}ms to load!`);
      }
    }
  });
});
observer.observe({ type: "element", buffered: true });

renderTime and loadTime will be DOMHighResTimeStamp time stamp in milliseconds.

Note: renderTime may not be available if the element is an image and it's loaded cross-origin without the Timing-Allow-Origin header.

DEMO:

Detect Dark Mode

| Comments

Detecting if the user preference is light or dark color theme is pretty easy with the prefers-color-scheme CSS media feature, below code gives more details on the same.

Detecting the prefered color scheme in JS:

1
2
const isDarkMode = window.matchMedia
    && window.matchMedia('(prefers-color-scheme: dark)').matches;

Or maybe a more generic method:

1
2
3
4
5
6
7
8
9
const checkType = type => window.matchMedia
    && window.matchMedia(`(prefers-color-scheme: ${type})`).matches);

const mode = {
    is : {
        dark: checkType('dark'),
        light: checkType('light')
    }
};
1
2
mode.is.dark; // true
mode.is.light; // false

matchMedia(query) returns a new MediaQueryList object with the parsed results of the specified media query string.

Detecting the prefered color scheme in CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Light mode */
@media (prefers-color-scheme: light) {
    body {
        background-color: #000;
        color: white;
    }
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
    body {
        background-color: #000;
        color: white;
    }
}

Note for the spec:

User preferences can also vary by medium. For example, a user may prefer dark themes on a glowing screen, but light themes when printing (to save ink and/or because inked text on blank paper prints better than blank letterforms knocked out of an inked background). UAs are expected to take such variances into consideration so that prefers-color-scheme reflects preferences appropriate to the medium rather than preferences taken out of context.

Handling prefered color changes:

1
2
3
4
5
window.matchMedia('(prefers-color-scheme: dark)')
    .addListener((e) => {
        const isDarkMode = e.matches;
        console.log(`${isDarkMode ? '🌚' : '🌞'}.`);
    });

Force setting dark mode:

1
2
3
4
5
6
7
8
9
10
11
12
13
const forceDarkMode = () => [...document.styleSheets[0].rules].forEach(rule => {
     const mediaText = rule.media && rule.media.mediaText || [];
     const hasColorScheme = mediaText.includes("prefers-color-scheme");
     const hasLightScheme = hasColorScheme && mediaText.includes("light");

     // Remove light mode if.
     if (hasLightScheme) {
         rule.media.deleteMedium("(prefers-color-scheme: light)");
     }

     // Append Dark mdoe.
     rule.media && rule.media.appendMedium("(prefers-color-scheme: dark)");
 });

Tracking DOM Element Visibility

| Comments

We would normally track if a DOM element is visible in the viewport or not to understand user behaviour or to operate on the elment based on visibilty (animation, ajax calls etc).

We would normally apply a function like below to detect if an element is visible in the viewport:

1
2
const isInView = (elm, {top, height} = elm.getBoundingClientRect()) =>
    top <= innerHeight && top + height >= 0;

Later we would mostly add it on scroll events to trigger the checks as the user scroll, which in turn is a costly operation.

That's where the Intersection Observer API comes into rescue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// From the spec
const observer = new IntersectionObserver(changes => {
  for (const change of changes) {
    console.log(change.time);               // Timestamp when the change occurred
    console.log(change.rootBounds);         // Unclipped area of root
    console.log(change.boundingClientRect); // target.boundingClientRect()
    console.log(change.intersectionRect);   // boundingClientRect, clipped by its containing block ancestors, and intersected with rootBounds
    console.log(change.intersectionRatio);  // Ratio of intersectionRect area to boundingClientRect area
    console.log(change.target);             // the Element target
  }
}, {});

// Watch for intersection.
observer.observe(target);

// Stop watching.
observer.unobserve(target);

// Stop observing.
observer.disconnect();

Say, if we were to observe this element, #catBox for visiblity, we would end up with:

1
2
3
4
5
6
7
let options = {
  root: document.querySelector('#catBox'),
  rootMargin: '0px',
  threshold: 1.0
}

let observer = new IntersectionObserver(callback, options);

Portal Communication

| Comments

Portals takes the best from SPA and MPA to provide users with a top level portal browsing context which can embedded in an HTML document.

A portal is similar to an iframe, in that it allows another browsing context to be embedded. However, the portal browsing context hosted by a portal is part of a separate unit of related browsing contexts. The user agent is thus free to use a separate event loop for the browsing contexts,even if they are same origin-domain.

At times we would want to communicate with the intial host and the portal and vice versa, it can be easily achived with the below:

In the host:

1
2
3
4
5
6
const portal = document.querySelector('portal');
portal.activate({data: {foo: "bar"}}).then(() => {
  window.portalHost.onmessage = evn => {
     console.log(e.data); // data from the portal
  }
});

We can also get other information from the event:

1
2
3
4
5
6
7
8
9
10
11
12
{
      origin: evn.origin,
      data: evn.data,
      sourceIsPortalHost: evn.source === window.portalHost,
      gotUserActivation: !!evn.userActivation,
      userActivation: {
        isActive: evn.userActivation && evn.userActivation.isActive,
        hasBeenActive: evn.userActivation && evn.userActivation.hasBeenActive
      }
};

// ^ from WPT

In the portal:

1
2
3
4
5
6
window.addEventListener('portalactivate', (evt) => {
    let predecessor = evt.adoptPredecessor();
    evt.data
    predecessor.postMessage("data", "*");
  // ^ predecessor would be an instanceof HTMLPortalElement
});

With portal.activate we can basically pass the {data} to the portal on activation and the portal shall receive this data in the event object of the portalactivate callback.

evt.adoptPredecessor() will provide context to the originator of the portal and portalHost is exposed if the window has a portal browsing context.

createRequireFromPath in Node

| Comments

Let us consider an hypothetical scenario where we have project foo with the below directory structure:

1
2
3
4
5
6
7
.
├── index.js
├── lib
│   └── util.js
└── src
    └── component
        └── index.js

Let us say that src/component in dependent on lib/util.js, in current world one would have to do the below to require the util in their component

1
const util = require('../../lib/uitl.js');

The nesting for those realtive paths ../../ gets more complex as the directory structure gets more deeper.

For our rescue the module object of node got an addition of createRequireFromPath method in v10.12.0, which accepts a filenameto be used to construct the relative require function. and return a require function.

This can help in doing the below in our src/component, rather than dealing with relative paths.

1
2
3
const requireLib = require('module').createRequireFromPath('../../lib');

requireLib("util.js");

So, any files in ../../lib can now be just required as requireLib(<absPath>) instead of dealing with relative paths.

The heart of createRequireFromPath looks like:

1
2
3
4
5
6
Module.createRequireFromPath = (filename) => {
  const m = new Module(filename);
  m.filename = filename;
  m.paths = Module._nodeModulePaths(path.dirname(filename));
  return makeRequireFunction(m);
};

Meanwhile, MylesBorins has a proTip for us: