Hemanth.HM

A Computer Polyglot, CLI + WEB ♥'r.

Declarative Shadow DOM

| Comments

Shadow DOM plays an important role in wold of web components as they provide an easy way to encapsulate, buu they were created imperatively using JavaScript, with something like:

1
2
3
4
5
6
7
8
9
10
class FancyElement extends HTMLElement {
    constructor() {
        super();
        this.ShadowRoot = this.attachShadow({ mode: "open" });
        this.ShadowRoot.innerHTML = "Shadow DOM imperative";
    }
}

customElements.get("imperative-fancy-element") ||
    customElements.define("imperative-fancy-element", FancyElement);

Well, this works fine for client-side only application, but has lot of challenges to express and build them on the server (SSR), that when declarative shadow comes into picture!

Declarative Shadow DOM (DSD) helps us to have Shadow DOM to the server with ease, the sample imperative example as above would look like:

1
2
3
4
5
6
<fancy-element>
    <template shadowroot="open">
        <slot></slot>
    </template>
    <h2>Declarative Shadow DOM</h2>
</fancy-element>

Here is codesandbox:

P.S: chrome://flags/#enable-experimental-web-platform-features should be enabled as of today to use this feature.

Promisified Node Builtins

| Comments

At any point in time if you were to promisify a function you can use util.promisify, for example:

1
2
3
4
import util from "util";
import fs from "fs";

const readFile = util.promisify(fs.readFile);
1
2
3
4
5
6
7
readFile("/tmp/foo.txt", "utf-8")
    .then((buffer) => {
        // we have the content
    })
    .catch((error) => {
        // Handle the error
    });
1
2
3
4
// Maybe
(async () => {
    const content = await readFile("/tmp/foo.txt", "utf-8").catch((err) => {});
})();

But interesting there are few builtins that are already promisifed for us:

1
2
3
4
require("module")
.builtinModules
.filter((module) => /promises/.test(module));
// -> [ 'dns/promises', 'fs/promises', 'stream/promises', 'timers/promises' ]

In the latest version of node v16 we have the below:

Sample usage:

1
2
3
4
// mjs
import { setTimeout, setImmediate, setInterval } from "timers/promises";

const res = await setTimeout(100, "42");
1
2
3
4
const dnsPromises = require("dns/promises"); // require('dns').promises
const lookup = dnsPromises.lookup;
const lookupResp = await lookup("127.0.1.1");
// lookupResp -> { address: '127.0.1.1', family: 4 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { pipeline } = require("stream/promises");
const fs = require("fs");

async function convert() {
    await pipeline(
        fs.createReadStream("enlgish.txt"),
        async function* (source) {
            source.setEncoding("utf8");
            for await (const chunk of source) {
                yield translate(chunk, "EN2FR"); // imaginary translate method
            }
        },
        fs.createWriteStream("french.txt")
    );
    console.log("Pipeline ✅.");
}

run().catch(console.error);
1
2
3
4
const readFile = require("fs/promises").readFile;
(async () => {
    const content = await readFile("/tmp/foo.txt", "utf-8").catch((err) => {});
})();

Hope to see more such built-in promise APIs!

V8 Inspector From Node

| Comments

The inspector module provides an API that makes it easy to interact with the V8 inspector.

One can easily open a inspector like:

1
2
3
4
5
6
7
8
9
10
11
12
13
const inspector = require("inspector");

inspector.open(); // <- inspector.open([port[, host[, wait]]])

/*
Debugger listening on ws://127.0.0.1:9229/ab01a630-90ad-4c87-91c9-a665632c1d8b
For help see https://nodejs.org/en/docs/inspector
*/

inspector.url();
// 'ws://127.0.0.1:9229/ab01a630-90ad-4c87-91c9-a665632c1d8b'

inspector.console.log("This should be visible on the debugger console");

But, most of times we would want to wait until the debugger is connected to our process, that's where inspector.waitForDebugger() will be useful, here is a quick example.

1
2
3
4
5
6
// foo.js
(() => {
    const inspector = require("inspector");
    inspector.open();
    inspector.waitForDebugger();
})();
1
2
3
4
5
6
7
8
9
> node foo.js

^ This would make the process wait till the debugger is connected.
/*
  Debugger listening on ws://127.0.0.1:9229/b4b0e48d-2e4c-4cde-8177-6924c662aef4
  For help, see: https://nodejs.org/en/docs/inspector
  Debugger attached.
  Waiting for the debugger to disconnect...
  */

Dispatching and receiving message responses and notifications is possible with the session API:

1
2
3
4
5
6
7
8
9
10
11
12
const inspector = require("inspector");
inspector.open();
inspector.waitForDebugger();

const session = new inspsector.Session();
session.on("inspectorNotification", (message) => console.log(message.method));
// Debugger.paused
// Debugger.resumed

session.post("Runtime.evaluate", { expression: "5 + 4" }, (error, { result }) =>
    console.log(result)
); // 9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> new inspector.Session()
Session {
  domain:
   Domain {
     domain: null,
     _events: { error: [Function: debugDomainError] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] },
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined,
  [Symbol(connectionProperty)]: null,
  [Symbol(nextId)]: 1,
  [Symbol(messageCallbacks)]: Map {} }

session.on(<inspector-protocol-method>) where the methods are from devtools-protocol.

P.S: This is an experimental API with Stability of 1 meaning is not subject to Semantic Versioning rules. Non-backward compatible changes or removal may occur in any future release. Use of the feature is not recommended in production environments.

URLPattern API

| Comments

The URLPattern web API is an URL pattern matching web-platform primitive as a generic javascript code. Also helps in controlling service worker to the subset of a site that it owns and maintains.

URLPattern() constructor accepts an Object as in input param as in the sample usage and and has test() and exec() methods.

Currently there is a partial implementation of URLPattern available in Chrome Canary under the "Experimental Web Platform features" flag.

Sample usage:

1
2
3
4
5
6
7
8
9
10
11
// Takes an Object as a param with the below options
new URLPattern({
    protocol: "https",
    username: "",
    password: "",
    hostname: "example.com",
    port: "",
    pathname: "/foo/bar/:image.jpg",
    search: "(.*)",
    hash: "(.*)",
});
1
2
3
4
// Say you want to match the pathname of `/products/(.*)`
const productsPattern = new URLPattern({ "pathname": "/products/(.*)" })

productsPattern.exec({ "pathname": "/products/cars" }))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Would return
{
    "hash": {
        "groups": {
            "0": ""
        },
        "input": ""
    },
    "hostname": {
        "groups": {
            "0": ""
        },
        "input": ""
    },
    "input": {
        "pathname": "/products/cars"
    },
    "password": {
        "groups": {
            "0": ""
        },
        "input": ""
    },
    "pathname": {
        "groups": {
            "0": "cars"
        },
        "input": "/products/cars"
    },
    "port": {
        "groups": {
            "0": ""
        },
        "input": ""
    },
    "protocol": {
        "groups": {
            "0": ""
        },
        "input": ""
    },
    "search": {
        "groups": {
            "0": ""
        },
        "input": ""
    },
    "username": {
        "groups": {
            "0": ""
        },
        "input": ""
    }
}
1
2
3
4
5
6
7
// imagePattern sample from the sepc
const imagePattern = new URLPattern({
  pathname: '/*.:imagetype(jpg|png|gif)'
});
if (let result = imagePattern.exec(url)) {
  return handleImage(result.pathname.groups['imagetype']);
}
1
2
3
4
5
6
7
8
// apiPattern from the sepc
const apiPattern = new URLPattern({
  pathname: '/api/:product/:param?'
});
if (let result = apiPattern.exec(url)) {
  return handleAPI(result.pathname.groups['product'],
                   result.pathname.groups['param']);
}
1
2
3
4
5
6
7
8
9
10
11
12
// input, pathname, and groups
const p = new URLPattern({ pathname: "(.*)/:image.jpg" });
const r = p.exec("https://example.com/foo/bar/cat.jpg?q=v");

// outputs 'https://example.com/foo/bar/cat.jpg?q=v'
console.log(r.input);

// outputs '/foo/bar/cat.jpg'
console.log(r.pathname.input);

// outputs 'cat'
console.log(r.pathname.groups.image);

In serviceworker:

1
2
3
4
5
6
7
8
// Service worker controlling /cars and everything under /cars/.
// Does not control /carsearch.
navigator.serviceWorker.register("/maps/maps-sw.js", {
    scope: new URLPattern({
        baseURL: self.location,
        pathname: `/cars/*?`,
    }),
});

We also have the URLPatternList API in the proposals, that would help to do something like:

1
2
3
4
5
// Match any URL ending with 'jpg' or 'png'.
const p = new URLPatternList([
    { pathname: "/*.:filetype(jpg)" },
    { pathname: "/*.:filetype(png)" },
]);

URLPattern API is highly influenced by path-to-regexp node module and support many of the features including:

  • "*" wildcards
  • named matching groups
  • regexp groups
  • group modifiers such as "?", "+", and "*"

Don't miss read the Design Document for URLPattern API.

CSS Highlight Pseudo-elements

| Comments

The CSS Pseudo-Elements Level 4 (CSS4) provides an ability to select highlighted content, these represent a portions of a document that needs particular status and are typically styled differently to indicate that status to the user.

We have the below Highlight Pseudo-elements:

  • ::selection: the portion of a document that has been selected as the target

  • ::target-text: represents text directly targetted by the document URL’s fragment

  • ::spelling-error: content that has been flagged by the user agent as misspelled.

  • ::grammar-error: content that has been flagged by the user agent as grammatically incorrect.

Note: A future level of CSS may introduce ways to create custom highlight pseudo-elements.

Let us see few quick examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
::selection {
  background-color: #ff69b4;
}

::target-text {
  background-color: #ff69b4;
}

::spelling-error {
  text-decoration: underline wavy red;
}

::grammer-error {
  text-decoration: underline wavy green;
}

P.S: Modern browsers are yet to implement the last 3 of these pesduo elements.

Nullish Coalescing vs Logical Or

| Comments

Whenever I talk about ?? people have asked me the difference between Logical OR (||) vs Nullish coalescing (??) operators, so here is a quick post.

Logical Assignment Operators in JavaScript

| Comments

Logical assignment operators in JavaScript combine Logical Operators and Assignment Expressions.

1
2
3
//"Or Or Equals"
x ||= y;
x || (y = z);
1
2
3
// "And And Equals"
x &&= y;
x && (y = z);
1
2
3
// "QQ Equals"
x ??= y;
x ?? (y = z);

So, say you have a function updateID it can vary in the below ways:

1
2
3
4
5
6
7
8
9
10
11
const updateID = user => {

  // We can do this
  if (!user.id) user.id = 1

  // Or this
  user.id = user.id || 1

  // Or use logical assignment operator.
  user.id ||= 1
}

You could also use it with ??

1
2
3
4
5
6
function setOpts(opts) {
  opts.cat ??= 'meow'
  opts.dog ??= 'bow';
}

setOpts({ cat: 'meow' })

This is on stage-4 and you should be able to use it today!

I am happy that I was able to co-champion this proposal.

7 Years ago it was just a thought!

history

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.