1,311 Matching Annotations
  1. Jun 2022
    1. Creating GUID/UUID in Javascript using ES6 Crypto APIWe can use JavaScript ES6’s crypto API to generate GUID/UUID at the client side.crypto API comes up with a method called getRandomValues() which is used to generate a Universally Unique IDentifier as shown below

      js function CreateUUID() { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ) }

    1. However, if a TCO-capable engine can realize that the doA(b+1) call is in tail position meaning doB(b) is basically complete, then when calling doA(b+1), it doesn’t need to create a new stack frame, but can instead reuse the existing stack frame from doB(b). That’s faster and uses less memory.This sort of optimization becomes crucial when dealing with recursion, especially if the recursion could have resulted in thousands of stack frames. With TCO, the engine can perform all those calls in a single stack frame.
      Recursive function without TCO optimization

      ```js function factorial(n) { console.trace();

      if (n === 0) {
          return 1;
      }    // no proper tail call
      return n * factorial(n - 1);
      

      } factorial(2);VM31:4 console.trace factorial @ VM31:4VM31:4 console.trace factorial @ VM31:4 factorial @ VM31:10VM31:4 console.trace factorial @ VM31:4 factorial @ VM31:10 factorial @ VM31:10 ```

      Recursive function with TCO optimization

      js 'use strict'; function factorial(n, total = 1) { console.trace(); if (n === 0) { return total; } // proper tail call return factorial(n - 1, n * total); } factorial(2);Trace at factorial Trace at factorial Trace at factorial

    1. Implementation using readAsText()

      ```html <input type="file" id="file" /> <button id="get-time">Get Time</button>

      <script> document.getElementById('get-time').onclick = function () { let file = document.getElementById('file').files[0]; let fr = new FileReader(); fr.onload = function (e) { let startTime = getTime(e.target.result, false); let endTime = getTime(e.target.result, true); alert(`Log time range: ${startTime} ~ ${endTime}`); } fr.readAsText(file); } function getTime(text, reverse) { let timeReg = /\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}/; for (let i = reverse ? text.length - 1 : 0; reverse ? i > -1 : i < text.length; reverse ? i-- : i++) { if (text[i].charCodeAt() === 10) { let snippet = text.substr(i + 1, 19); if (timeReg.exec(snippet)) { return snippet; } } } } </script>

      ```

      Implementation using readAsArrayBuffer()

      ```html <input type="file" id="file" /> <button id="get-time">Get Time</button>

      <script> document.getElementById('get-time').onclick = function () { let file = document.getElementById('file').files[0]; let fr = new FileReader(); let CHUNK_SIZE = 10 * 1024; let startTime, endTime; let reverse = false; fr.onload = function () { let buffer = new Uint8Array(fr.result); let timeReg = /\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}/; for (let i = reverse ? buffer.length - 1 : 0; reverse ? i > -1 : i < buffer.length; reverse ? i-- : i++) { if (buffer[i] === 10) { let snippet = new TextDecoder('utf-8').decode(buffer.slice(i + 1, i + 20)); if (timeReg.exec(snippet)) { if (!reverse) { startTime = snippet; reverse = true; seek(); } else { endTime = snippet; alert(`Log time range: ${startTime} ~ ${endTime}`); } break; } } } } seek(); function seek() { let start = reverse ? file.size - CHUNK_SIZE : 0; let end = reverse ? file.size : CHUNK_SIZE; let slice = file.slice(start, end); fr.readAsArrayBuffer(slice); } } </script>

      ```

    1. ```html

      <input type="file" id="file-selector" multiple>

      <script> const fileSelector = document.getElementById('file-selector'); fileSelector.addEventListener('change', (event) => { const fileList = event.target.files; console.log(fileList); }); </script>

      ```

    1. The dataset read-only property of the HTMLElement interface provides read/write access to custom data attributes (data-*) on elements. It exposes a map of strings (DOMStringMap) with an entry for each data-* attribute.

      ```html

      <div id="user" data-id="1234567890" data-user="johndoe" data-date-of-birth>John Doe</div>

      ```

      ```js const el = document.querySelector('#user');

      // el.id === 'user' // el.dataset.id === '1234567890' // el.dataset.user === 'johndoe' // el.dataset.dateOfBirth === ''

      // set a data attribute el.dataset.dateOfBirth = '1960-10-03'; // Result on JS: el.dataset.dateOfBirth === '1960-10-03' // Result on HTML: <div id="user" data-id="1234567890" data-user="johndoe" data-date-of-birth="1960-10-03">John Doe</div>

      delete el.dataset.dateOfBirth; // Result on JS: el.dataset.dateOfBirth === undefined // Result on HTML: <div id="user" data-id="1234567890" data-user="johndoe">John Doe</div>

      if ('someDataAttr' in el.dataset === false) { el.dataset.someDataAttr = 'mydata'; // Result on JS: 'someDataAttr' in el.dataset === true // Result on HTML: <div id="user" data-id="1234567890" data-user="johndoe" data-some-data-attr="mydata">John Doe</div> } ```

    1. ```js if(window.location.href.endsWith('sitemap.xml')){

      const xmlStr = (
          `<?xml version="1.0" encoding="UTF-8"?>
              <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
                  <url><loc>https://erikmartinjordan.com</loc></url>
              </urlset>
          `
      );
      
      let blob = new Blob([xmlStr], {type: "text/xml"});
      let url = URL.createObjectURL(blob);
      window.open(url, "_self");
      URL.revokeObjectURL(url);
      

      } ```

    1. Using the download attribute to save a <canvas> as a PNGTo save a <canvas> element's contents as an image, you can create a link with a download attribute and the canvas data as a data: URL: Example painting app with save link

      ```html

      Paint by holding down the mouse button and moving it. Download my painting

      <canvas width="300" height="300"></canvas>

      ```

      css html { font-family: sans-serif; } canvas { background: #fff; border: 1px dashed; } a { display: inline-block; background: #69c; color: #fff; padding: 5px 10px; }

      ```js var canvas = document.querySelector('canvas'), c = canvas.getContext('2d'); c.fillStyle = 'hotpink';

      function draw(x, y) { if (isDrawing) { c.beginPath(); c.arc(x, y, 10, 0, Math.PI*2); c.closePath(); c.fill(); } }

      canvas.addEventListener('mousemove', event => draw(event.offsetX, event.offsetY) ); canvas.addEventListener('mousedown', () => isDrawing = true); canvas.addEventListener('mouseup', () => isDrawing = false);

      document.querySelector('a').addEventListener('click', event => event.target.href = canvas.toDataURL() ); ```

    1. Highlight part of an element This example uses the Range.setStart() and Range.setEnd() methods to add part of an address to a range. The selected range is then highlighted using Range.surroundContents(). The address contains nine nodes: five text nodes, and four <br> elements.

      ```html

      Wyatt Earp<br> 101 E. Main St.<br> Dodge City, KS<br> 67801<br> USA


      Nodes in the original address:

        ```

        ```js const address = document.getElementById('address'); const log = document.getElementById('log');

        // Log info address.childNodes.forEach(node => { const li = document.createElement('li'); li.textContent = ${node.nodeName}, ${node.nodeValue}; log.appendChild(li); });

        // Highlight the street and city const startOffset = 2; // Start at third node: 101 E. Main St. const endOffset = 5; // End at fifth node: Dodge City, KS const range = document.createRange(); range.setStart(address, startOffset); range.setEnd(address, endOffset);

        const mark = document.createElement('mark'); range.surroundContents(mark); ```

      1. ```html

        <html> <head> <br /> <script> function noMondays() { var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); var textNode = tw.nextNode(); while (textNode) { if (textNode.wholeText.match('Monday') || textNode.parentNode.getAttribute('id') == 'Monday') { textNode.parentNode.removeChild(textNode); } textNode = tw.nextNode(); } } function refresh() { window.location.reload( false ); // Reload our page. } </script>

        </head> <body>

        Monday, Joe bought a turkey.

        Tuesday, Bill bought a pound of nails.

        <div>Chuck called in sick Monday.</div>

        CALL supplier today!

        Wednesday's delivery was delayed.

        <button onclick="refresh()">Reload</button> <button onclick="noMondays()">Lose Mondays</button> </body>

        </html> ```

      1. DOMPurify - a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG
      1. If the component wrapped with memo re-renders, it means that one of its properties changes.
    2. May 2022
      1. ```html

        Choose your monster's colors:

        <div> <input type="color" id="head" name="head" value="#e66465"> <label for="head">Head</label> </div> <div> <input type="color" id="body" name="body" value="#f6b73c"> <label for="body">Body</label> </div>

        ```

        ```js colorPicker.addEventListener("input", updateFirst, false); colorPicker.addEventListener("change", watchColorPicker, false);

        function watchColorPicker(event) { document.querySelectorAll("p").forEach(function(p) { p.style.color = event.target.value; }); } ```

      1. JS is plenty fast and can be used for "large, complicated programs"[1] just fine. The problem with most JS written today is in programmer practices—the way that the community associated with NodeJS pushes one other to write code (which is, ironically, not even a good fit for the JavaScript language). It turns out that how you write code actually matters
      1. I feel like the point of the article isn't so much "how do I solve this specific issue" as "this is the general state of JS packaging", and the solution you present doesn't work in the general case of larger, less trivial dependencies

        Much of the (apparent) progress (i.e. activity—whether it constitutes busywork is another matter) in the world of "JS Prime" (that is, pre-Reformation, NodeJS/NPM-style development) is really about packaging problems.

      1. Anyway: I think the underlying problem is that it has been hidden that Node is NOT JavaScript. It uses (some) of the JavaScript syntax, but it doesn't use its standard library, and its not guaranteed that a Node package will also run on the browser.

        NodeJS development is often standards-incompatible with JS-the-language and other (actually standardized) vendor-neutral APIs.

      1. I've watched a bunch of very smart, highly-competent people bounce off JS; that's not a fault with them but with the ever-changing and overly-complicated ecosystem.

        It helps to be accurate (if we ever want to see these things fixed):

        They didn't "bounce off JS". They "bounced" after seeing NodeJS—and its community's common practices.

      1. If the media resource is embedded (for example in a iframe), Media Session API information must be set from the embedded context. See snippet below.

        ```html

        <iframe id="iframe"> <video>...</video> </iframe> <script> iframe.contentWindow.navigator.mediaSession.metadata = new MediaMetadata({ title: 'Never Gonna Give You Up', ... }); </script>

        ```

      1. React Helmet

        ```js import {Helmet} from "react-helmet";

        const Demo = props => ( <br /> <div className="application"> <Helmet> <script src="/path/to/resource.js" type="text/javascript" /> </Helmet> ... </div>

        ); ```

        DOM Method

        ```js componentDidMount () { const script = document.createElement("script"); script.src = "/path/to/resource.js"; script.async = true; document.body.appendChild(script); }

        export const appendScript = (scriptToAppend) => { const script = document.createElement("script"); script.src = scriptToAppend; script.async = true; document.body.appendChild(script); }

        class Demo extends React.Component { componentDidMount () { appendScript("/path/to/resource.js"); } } ```

        React Hooks

        js useEffect(() => { const script = document.createElement('script'); script.src = "/path/to/resource.js"; script.async = true; document.body.appendChild(script); return () => { document.body.removeChild(script); } }, []);

        ```js import { useEffect } from 'react'; const importScript = resourceUrl=> { useEffect(() => { const script = document.createElement('script'); script.src = resourceUrl; script.async = true; document.body.appendChild(script); return () => { document.body.removeChild(script); } }, [resourceUrl]); };

        const Demo = props => { importScript("/path/to/resource.js"); } ```

      1. The new lines you mention really are present in the text content of the element. HTML tags are not being replaced by new lines, they just get omitted entirely. If you look at the textContent property of the <p> element you selected in the browser console, and you'll see the same new lines. Also if you select the text and run window.getSelection().getRangeAt(0).toString() in the browser console you'll see the same new lines. In summary, this is working as it is currently expected to. What I think may have been surprising here is that the captured text is not the same as what would be copied to the clipboard. When copying to the clipboard, new lines in the source get replaced with spaces, and <br> tags get converted to new lines. Browser specifications distinguish the original text content of HTML "in the source" as returned by element.textContent from the text content "as rendered" returned by element.innerText. Hypothesis has always captured quotes from and searched for quotes in the "source" text content rather than the "rendered" text. This behavior causes issues with line breaks as well. It might make sense for us to look at capturing the rendered text (as copied to the clipboard) rather than the source text in future. We'd need to be careful to handle all the places where this distinction comes up, and also make sure that all existing annotations anchor properly. Also we should talk to other parties interested in the Web Annotations specifications to discuss how this impacts interoperability.
        What I think may have been surprising here is that the captured text is not the same as what would be copied to the clipboard. When <mark>copying to the clipboard, <mark style="background-color: #8000314f">new lines in the source</mark> get <mark style="background-color:#00800030">replaced with spaces</mark>, and <br> tags get converted to new lines</mark>. </br> <mark>Browser specifications distinguish <mark style="background-color: #00800036">the original text content of HTML "in the source"</mark> as returned by <mark style="background-color: #00800036"/>element.textContent</mark> from <mark style="background-color: #ffa500a1">the text content "as rendered" returned by element.innerText.</mark></mark> Hypothesis has always captured quotes from and searched for quotes in the "source" text content rather than the "rendered" text.
      1. Differences from innerHTML Element.innerHTML returns HTML, as its name indicates. Sometimes people use innerHTML to retrieve or write text inside an element, but textContent has better performance because its value is not parsed as HTML. Moreover, using textContent can prevent XSS attacks.
      2. Differences from innerText Don't get confused by the differences between Node.textContent and HTMLElement.innerText. Although the names seem similar, there are important differences: textContent gets the content of all elements, including <script> and <style> elements. In contrast, innerText only shows "human-readable" elements. textContent returns every element in the node. In contrast, innerText is aware of styling and won't return the text of "hidden" elements. Moreover, since innerText takes CSS styles into account, reading the value of innerText triggers a reflow to ensure up-to-date computed styles. (Reflows can be computationally expensive, and thus should be avoided when possible.) Both textContent and innerText remove child nodes when altered, but altering innerText in Internet Explorer (version 11 and below) also permanently destroys all descendant text nodes. It is impossible to insert the nodes again into any other element or into the same element after doing so.
      1. With some extra work, you can download the entire text version of Moby-Dick from Project Gutenberg using Axios.

        ```js const axios = require('axios'); const epub = require('epub-gen');

        axios.get('http://www.gutenberg.org/files/2701/2701-0.txt'). then(res => res.data). then(text => { text = text.slice(text.indexOf('EXTRACTS.')); text = text.slice(text.indexOf('CHAPTER 1.'));

        const lines = text.split('\r\n');
        const content = [];
        for (let i = 0; i < lines.length; ++i) {
          const line = lines[i];
          if (line.startsWith('CHAPTER ')) {
            if (content.length) {
              content[content.length - 1].data = content[content.length - 1].data.join('\n');
            }
            content.push({
              title: line,
              data: ['<h2>' + line + '</h2>']
            });
          } else if (line.trim() === '') {
            if (content[content.length - 1].data.length > 1) {
              content[content.length - 1].data.push('</p>');
            }
            content[content.length - 1].data.push('<p>');
          } else {
            content[content.length - 1].data.push(line);
          }
        }
        
        const options = {
          title: 'Moby-Dick',
          author: 'Herman Melville',
          output: './moby-dick.epub',
          content
        };
        
        return new epub(options).promise;
        

        }). then(() => console.log('Done')); ```

      1. Say hello to the sassy new Media Queries!

        js const html = document.getElementsByTagName('html')[0]; const toggleTheme = (theme) => { html.dataset.theme = theme; } html <script> // If `prefers-color-scheme` is not supported, fall back to light mode. // In this case, light.css will be downloaded with `highest` priority. if (window.matchMedia('(prefers-color-scheme: dark)').media === 'not all') { document.documentElement.style.display = 'none'; document.head.insertAdjacentHTML( 'beforeend', '<link rel="stylesheet" href="/light.css" onload="document.documentElement.style.display = \'\'">' ); } </script> <!-- Conditionally either load the light or the dark stylesheet. The matching file will be downloaded with `highest`, the non-matching file with `lowest` priority. If the browser doesn't support `prefers-color-scheme`, the media query is unknown and the files are downloaded with `lowest` priority (but above I already force `highest` priority for my default light experience). --> <link rel="stylesheet" href="/dark.css" media="(prefers-color-scheme: dark)"> <link rel="stylesheet" href="/light.css" media="(prefers-color-scheme: light)"> <!-- The main stylesheet --> <link rel="stylesheet" href="/style.css">

        html <img src="./sun.svg" data-light-src="./sun.svg" data-dark-src="./moon.svg" alt="light theme" id="theme-selector" onclick="switchTheme(this)">

      1. To address this issue, and to make it easier for non-web browser environments to implement fetch in a consistent way, WinterCG is working on documenting a subset of the fetch standard that deals specifically with those different requirements and constraints.
    3. Apr 2022
      1. Because promise handlers aren’t immediately executed, you might imagine that they act a little like timers — giving the browser a chance to repaint and perform other important tasks before the handler is executed.

        is this because callbacks go into the callback queue ie. lower priority than the microtask queue ?

        promises and mutation observer go into the microtask queue and have higher higher priority, are DOM mutations the "important tasks" that James is refering to ??

      2. In fact, promises aren’t like timers at all. If there are any promise handlers waiting to be executed when your script completes, they’ll be executed immediately.

        is this because promises go into the microtask queue ( higher priority than the callback queue ) ??

      3. rejection handler for then()

        this is also applicable tor .catch() . since catch() is just a special case of .then() ie. .then(null , failure handler). Check the description of promise.catch in the "A Spoonful of Sugar" chapter.

      1. ```js document.createAnnotation()

        document.getAnnotations(nodes)

        document.removeAnnotation(annotation) ```

        ```js Annotation#addTarget(target)

        Annotation#addBody(body) ```

      1. Cache using fetch

        Determine how to cache a resource by setting TTLs, custom cache keys, and cache headers in a fetch request.

        ```js async function handleRequest(request) { const url = new URL(request.url);

        // Only use the path for the cache key, removing query strings // and always store using HTTPS, for example, https://www.example.com/file-uri-here const someCustomKey = https://${url.hostname}${url.pathname};

        let response = await fetch(request, { cf: { // Always cache this fetch regardless of content type // for a max of 5 seconds before revalidating the resource cacheTtl: 5, cacheEverything: true, //Enterprise only feature, see Cache API for other plans cacheKey: someCustomKey, }, }); // Reconstruct the Response object to make its headers mutable. response = new Response(response.body, response);

        // Set cache control headers to cache on browser for 25 minutes response.headers.set('Cache-Control', 'max-age=1500'); return response; }

        addEventListener('fetch', event => { return event.respondWith(handleRequest(event.request)); }); ```


        Caching HTML resources

        Setting the cache level to Cache Everything will override the default cacheability of the asset. For time-to-live (TTL), Cloudflare will still rely on headers set by the origin.

        js // Force Cloudflare to cache an asset fetch(event.request, { cf: { cacheEverything: true } });

      1. ``js let originalHTML =

        Hello Mr. Wayne, decide what to do:

        • Call Alfred
        • Take Thalia Al Gul to the cinema
        • Save Gotham
        <span>Use the mouse to choose an option.</span> `;

        let newHTML = `

        Hello Batman, decide what to do:

        • Kill The Joker
        • Save Thalia Al Gul
        • Save Gotham

        <span>Use the batarang to choose an option.</span> `;

        // Diff HTML strings let output = htmldiff(originalHTML, newHTML);

        // Show HTML diff output as HTML (crazy right?)! document.getElementById("output").innerHTML = output; ```

        ```css ins { text-decoration: none; background-color: #d4fcbc; }

        del { text-decoration: line-through; background-color: #fbb6c2; color: #555; } ```

    4. Mar 2022
      1. The URLPattern API provides a web platform primitive for matching URLs based on a convenient pattern syntax.
      1. We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put || after it with a replacement value. If the initial value can be converted to false, you’ll get the replacement instead. The rules for converting strings and numbers to Boolean values state that 0, NaN, and the empty string ("") count as false, while all the other values count as true. So 0 || -1 produces -1, and "" || "!?" yields "!?".
      2. When an operator is applied to the “wrong” type of value, JavaScript will quietly convert that value to the type it needs, using a set of rules that often aren’t what you want or expect. This is called type coercion. The null in the first expression becomes 0, and the "5" in the second expression becomes 5 (from string to number). Yet in the third expression, + tries string concatenation before numeric addition, so the 1 is converted to "1" (from number to string).
      1. ```js

        //If you write your own code, remember hex color shortcuts (eg., #fff, #000)

        function hexToRgbA(hex){ var c; if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){ c= hex.substring(1).split(''); if(c.length== 3){ c= [c[0], c[0], c[1], c[1], c[2], c[2]]; } c= '0x'+c.join(''); return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+',1)'; } throw new Error('Bad Hex'); }

        hexToRgbA('#fbafff')

        / returned value: (String) rgba(251,175,255,1) / ```

    5. Feb 2022
      1. Records and Tuples break that convention, and allow us to compare by value. Deep comparisons of objects has been something that's been quite tricky in Javascript for a long time, but with Tuples and Records we can finally do that. As such, the following code returns true:

        js console.log(#{ a: { b : 3 }} === #{ a: { b : 3 }}) // return true console.log(#[1, 2, 3] === #[1, 2, 3]) // returns true

      1. Integrating AbortController

        js export const Timeout = (time) => { let controller = new AbortController(); setTimeout(() => controller.abort(), time * 1000); return controller; }

        ```js import { useEffect, useState } from "react"; //imports goes here

        export default function App() { //state goes here

        //update useEffect(() => { fetch("https://jsonplaceholder.typicode.com/todos/1", { signal: Timeout(10).signal }) .then((resp) => resp.json()) .then((resp) => setData(resp)) .catch((err) => setError(true)); return () => {}; }, []); return ( <div> {* JSX goes here*} </div> ); } ```

      1. 2. Timeout a fetch() request

        ```js async function fetchWithTimeout(resource, options = {}) { const { timeout = 8000 } = options;

        const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); const response = await fetch(resource, { ...options, signal: controller.signal<br /> }); clearTimeout(id); return response; }

        async function loadGames() { try { const response = await fetchWithTimeout('/games', { timeout: 6000 }); const games = await response.json(); return games; } catch (error) { // Timeouts if the request takes // longer than 6 seconds console.log(error.name === 'AbortError'); } } ```

      1. since then I hate the Node JS ecosystem

        So Gitea—or Codeberg, at least—should be amenable to the suggestion of un-messing the process of self-hosting where it brings the aspiring self-hoster into contact with NodeJS/NPM silliness. Make that part optional.

      1. Reacquiring a wake lock

        The following code reacquires the wake lock should the visibility of the document change and the wake lock is released.

        js document.addEventListener('visibilitychange', async () => { if (wakeLock !== null && document.visibilityState === 'visible') { wakeLock = await navigator.wakeLock.request('screen'); } });

    6. Jan 2022
      1. A note on setting worker-loader’s publicPath with webpack 5

        Webpack 5 introduced a mechanism to detect the publicPath that should be used automatically

        [...]

        Webpack 5 exposes a global variable called __webpack_public_path__ that allows you to do that.

        // Updates the `publicPath` at runtime, overriding whatever was set in the
        // webpack's `output` section.
        __webpack_public_path__ = "/workers/";
        
        const myWorker = new Worker(
          new URL("/workers/worker.js");
        );
        
        // Eventually, restore the `publicPath` to whatever was set in output.
        __webpack_public_path__ = "https://my-static-cdn/";
        
      1. Mermaid lets you create diagrams and visualizations using text and code.It is a Javascript based diagramming and charting tool that renders Markdown-inspired text definitions to create and modify diagrams dynamically.
      1. function parseLinkHeader( linkHeader ) {
           const linkHeadersArray = linkHeader.split( ", " ).map( header => header.split( "; " ) );
           const linkHeadersMap = linkHeadersArray.map( header => {
              const thisHeaderRel = header[1].replace( /"/g, "" ).replace( "rel=", "" );
              const thisHeaderUrl = header[0].slice( 1, -1 );
              return [ thisHeaderRel, thisHeaderUrl ]
           } );
           return Object.fromEntries( linkHeadersMap );
        }
        
    7. Dec 2021
      1. Web Workers

        As of webpack 5, you can use Web Workers without worker-loader.

        Syntax

        new Worker(new URL('./worker.js', import.meta.url));
        
      1. Instant Dates and Times

        Temporal.Instant returns an object representing a date and time to the nearest nanosecond according to an ISO 8601 formatted string:

      1. // main.js
        const { RemoteReadableStream, RemoteWritableStream } = RemoteWebStreams;
        (async () => {
          const worker = new Worker('./worker.js');
          // create a stream to send the input to the worker
          const { writable, readablePort } = new RemoteWritableStream();
          // create a stream to receive the output from the worker
          const { readable, writablePort } = new RemoteReadableStream();
          // transfer the other ends to the worker
          worker.postMessage({ readablePort, writablePort }, [readablePort, writablePort]);
        
          const response = await fetch('./some-data.txt');
          await response.body
            // send the downloaded data to the worker
            // and receive the results back
            .pipeThrough({ readable, writable })
            // show the results as they come in
            .pipeTo(new WritableStream({
              write(chunk) {
                const results = document.getElementById('results');
                results.appendChild(document.createTextNode(chunk)); // tadaa!
              }
            }));
        })();
        
        // worker.js
        const { fromReadablePort, fromWritablePort } = RemoteWebStreams;
        self.onmessage = async (event) => {
          // create the input and output streams from the transferred ports
          const { readablePort, writablePort } = event.data;
          const readable = fromReadablePort(readablePort);
          const writable = fromWritablePort(writablePort);
        
          // process data
          await readable
            .pipeThrough(new TransformStream({
              transform(chunk, controller) {
                controller.enqueue(process(chunk)); // do the actual work
              }
            }))
            .pipeTo(writable); // send the results back to main thread
        };
        
      1. 
        //getDates.js
        
        // Returns an array of dates between the two dates
        function getDates (startDate, endDate) {
          const dates = []
          let currentDate = startDate
          const addDays = function (days) {
            const date = new Date(this.valueOf())
            date.setDate(date.getDate() + days)
            return date
          }
          while (currentDate <= endDate) {
            dates.push(currentDate)
            currentDate = addDays.call(currentDate, 1)
          }
          return dates
        }
        
        // Usage
        const dates = getDates(new Date(2013, 10, 22), new Date(2013, 11, 25))
        dates.forEach(function (date) {
          console.log(date)
        })
        
      1. function getDomPath(el) {
          var stack = [];
          while ( el.parentNode != null ) {
            console.log(el.nodeName);
            var sibCount = 0;
            var sibIndex = 0;
            for ( var i = 0; i < el.parentNode.childNodes.length; i++ ) {
              var sib = el.parentNode.childNodes[i];
              if ( sib.nodeName == el.nodeName ) {
                if ( sib === el ) {
                  sibIndex = sibCount;
                }
                sibCount++;
              }
            }
            if ( el.hasAttribute('id') && el.id != '' ) {
              stack.unshift(el.nodeName.toLowerCase() + '#' + el.id);
            } else if ( sibCount > 1 ) {
              stack.unshift(el.nodeName.toLowerCase() + ':eq(' + sibIndex + ')');
            } else {
              stack.unshift(el.nodeName.toLowerCase());
            }
            el = el.parentNode;
          }
          return stack.slice(1); // removes the html element
        }
        
        //Usage:
        var path = getDomPath(document.getElementById('button'));
        console.log(path.join(' > '));
        
      1. Using Web Workers

        You can run highlighting inside a web worker to avoid freezing the browser window while dealing with very big chunks of code.

        // In your main script:
        addEventListener('load', () => {
          const code = document.querySelector('#code');
          const worker = new Worker('worker.js');
          worker.onmessage = ({data}) => { code.innerHTML = data; }
          worker.postMessage(code.textContent);
        });
        
        // In worker.js:
        
        onmessage = (event) => {
          importScripts('<path>/highlight.min.js');
          const result = self.hljs.highlightAuto(event.data);
          postMessage(result.value);
        };
        
      1. API

        In order to access the JavaScript object which provides the SoundCloud Widget API, add this script to your html page.

        This script exposes the SC.Widget(/*iframeElement|iframeElementID*/) function to the global scope. It allows you to control the widget from the parent page (the page the widget is inserted into). SC.Widget accepts the reference to the iframe element or its id.

        var iframeElement   = document.querySelector('iframe');
        var iframeElementID = iframeElement.id;
        var widget1         = SC.Widget(iframeElement);
        var widget2         = SC.Widget(iframeElementID);
        // widget1 === widget2