Skip to content

Instantly share code, notes, and snippets.

@Neo42
Last active March 6, 2023 09:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Neo42/49abf84b6518c1fd360f794d5990db1e to your computer and use it in GitHub Desktop.
Save Neo42/49abf84b6518c1fd360f794d5990db1e to your computer and use it in GitHub Desktop.
gistpad-scratch

Rust Jargon Explained

Scope

  • Definition: Region of the program where a variable is valid.
  • A variable becomes valid when it comes into scope.
  • A variable remains valid until it goes out of scope.
  • Variable bindings are constrained to live within a block of code where it was created.
  • Blocks of code are enclosed by curly braces: {}

Variable Mutability

  • Variables in Rust are immutable by default, meaning you can't change a variable's value after its declaration.
  • But shadowing is allowed: Declare a new variable with the same name as an existing variable.
  • A shadowing variable also has its own scope. An old variable remains the same out of the shadowing variable scope.

Stack Memory vs Heap Memory

Stack

  • Values stored in sequential order
  • LIFO
  • push & pop
  • Quick reading speed
  • Data size is small, known, & fixed

CSS Fundamentals For Responsive Design

When to use margin vs padding

  • Use margin when spacing between font related elements (<h1>, <h2>, <h3>, <p>): margin-top & margin-bottom (margin-bottom = margin-top = font-size by default)
  • margin: 0 auto;: horizontally center a block element
  • Disable all top margins to avoid margin collapsing: h1, h2, h3, p { margin-top: 0; }
  • Disable the bottom margin of the last element to make box padding easier.
  • Always use padding when dealing with parent & child spacing.

Inline Elements

  • You can only nest other different inline elements within a inline elements.
  • Margins only works for inline elements on horizontal dimensions, not vertical ones.
  • Paddings works for inline elements on all dimensions, but the surrounding elements will ignore them.
  • Width & height doesn't work on inline elements.

Spans

  • no semantic meaning, used only for styling purposes: colors, font weight, etc.

Styling Links

  • a:link: default link state
  • a:visited
  • a:focus: focused with tab
  • a:hover
  • a:active: clicked or held
  • Best practise:
    • The declaration order matters: lvfha.
    • Always change color both hover and focus.
    • A link should be very obvious.
    • Leave text-decoration on.

Inline-block Elements vs Inline Elements

  • inline: doesn't take up space vertically, will probably cover other content up.
  • inline-block: stay in side by side but also takes up space like block elements and won't cover other content.

Styling Buttons

  • You can use links as buttons. Just put a "class="button"" directly on the link.
  • Always directly put 'class="button"' on <a> rather than wrapping it up with an extra <div>. (Because, with a wrapper <div>, a link won't always fill up the space within the wrapper, and the user won't be able to click the space outside of the actually link.)
  • Always use padding to create spaces within buttons and containers rather than width / height. (Padding is more flexible with layout and content size changes.)
  • Always put more padding horizontally. Horizontal padding : vertical padding = 1.6 ~ 2
  • Always remove the text-decoration underline within a button.

Units

  • px: absolute unit. Used for print styles.
  • 50%: usually used for width. Relative to the parent. (can act weird on heights but rarely used that way.)
    • percentages width usually works well with min/max-width: xxpx when you want to set a max or min width on a responsive element.
  • Block level elements are 100% wide (relative to its parent element) by default.
  • em: relative to an element's nearest parent that has a font size.
  • rem: relative to the root of the document, i.e. <html>

When to use different units?

A Rule of Thumb:

  • font-size = rem
  • padding & margin = em
  • width = em or percentage

Media Queries

Syntax: @media media-type and (media-features) {...}

media-type

  • screen
  • print
  • speech

media-condition (can be combined with the and keyword, which is more robust)

  • width: min-width / max-width (used most often)
  • orientation: landscape / portrait
  • specific features: hover

Order matters when using multiple media queries.

Navigation

Typical markup structure for a responsive navigation:

<header>
  <!-- add a container-nav to apply flex box layout on -->
  <div class="container container-nav">
    <div class="site-title">
      <h1>Site Title</h1>
      <!-- use <p> for subtitles, not h2 (for titles of other sections) -->
      <p class="subtitle">This is a subtitle</p>
    </div>
    <nav>
      <ul>
        <!-- add a current-page class to identify the link of the current page -->
        <li><a href="/" class="current-page">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/contact">Contact Us</a></li>
      </ul>
    </nav>
  </div>
  <!-- container -->
</header>

What Problems Does CSS-in-JS Solve for Traditional CSS

  1. Lack of modularization: Javascript module system
  2. No scope: function + hashed class name
  3. Unintuitive selector prioritization: styled.element direct styling
  4. No variable: styled element function takes in arguments
  5. Coupling between elements and style: unique style for each styled element

Web Worker & Service Worker Explained

Web Worker

A web worker is a JS file running in a background thread independent from the web page thread.

The worker thread and the web page thread can send data to each other with the postMessage api. The data is copied when being sent. Even objects and json are copied using the structured clone algorithm.

Usage:

const worker = new Worker('/js/worker.js') // initialization
worker.addEventListener('message', onMessage) // listen to message event
worker.postMessage({start: true}) // send message to main thread
worker.terminate() // shutdown

Service Worker

A service worker is a special type of web worker. It works like a proxy in browsers, and once it starts listening for outbound network requests, every single request that you send out will go through it and it will make requests and receive responses on behalf of the page. There is no way to tell a service worker to leave some requests out. So you have to make sure they get routed correctly.

One thing good to know is that your service worker is still bound by the rules of CORS(cross-origin resources sharing), which means the <img> tag loading some image off some CDN somewhere, which works completely normally on a normal page, won't work with a service worker setup unless the CDN publishes CORS headers.

But why?

Some use cases of service workers:

  • Caching and background sync for offline (main use case)
  • Rewriting a CDN that's down to another location and requesting from there without having to update the HTML.
  • Programmatically creating artificial response data or using the cached response for offline requests.
  • Programmatically preloading extra resources for a request for certain ones.
  • Pushing + notifications (with permissions)

Usage:

To initialize a service worker:

async function initServiceWorker() {
  // URL rewrite might be needed for sw scoping
  // register method is async
  swRegistration = await navigator.serviceWorker.register('/sw.js', {
    updateViaCache: 'none',
  })

  // default sw lifecycle: installed => waiting (optional) => active
  // only one sw can be active at the time
  // a sw is active until the user navigates to a new page or refreshes
  serviceWorker =
    swRegistration.installing || swRegistration.waiting || swRegistration.active

  // when a new worker takes control of the page
  navigator.serviceWorker.addEventListener(
    'controllerchange',
    function onControllerChange() {
      serviceWorker = navigator.serviceWorker.controller
    },
  )
}

Note: To manually reinstall a service worker, first stop the worker if it's still running, unregister it, and fire a navigate event to load a new one.

To make a service worker listen to its lifecycle events:

// won't run on restart
self.addEventListener('install', onInstall)
self.addEventListener('activate', onActivate)

// will run on restart
main().catch(console.error)

async function main() {
  console.log(`Service Worker (${version}) is starting...`)
}

async function onInstall() {
  console.log(`Service Worker (${version}) installed.`)
  // skip the waiting phase
  self.skipWaiting()
}

async function onActivate(event) {
  // if shutdown is necessary, don't do it until activation is handled
  event.waitUntil(handleActivation())
}

async function handleActivation() {
  // to fire the `controllerchange` event
  await clients.claim()
  console.log(`Service Worker (${version}) activated.`)
}

And you can send messages to your service worker as it's basically a web worker. You can send the message to the message channel port it's listening on, or let the service worker controlling the current page send the message. Here is an example of sending messages from the page to the service workers based on the the page status changes.

var isOnline = 'onLine' in navigator ? navigator.onLine : true
var isLoggedIn = /isLoggedIn=1/.test(document.cookie.toString() || '')

function sendStatusUpdate(target) {
  sendSWMessage({statusUpdate: {isOnline, isLoggedIn}}, target)
}

function onSWMessage(event) {
  var {data} = event
  // let the sw ask the page for request status change
  if (data.statusUpdate) {
    console.log(
      `Received status update request from service worker, responding...`,
    )
    // send to the message channel port that the service worker is listening on
    sendStatusUpdate(event.ports && event.ports[0])
  }
}

function sendSWMessage(message, target) {
  if (target) {
    target.postMessage(message)
  } else if (serviceWorker) {
    serviceWorker.postMessage(message)
  } else {
    navigator.serviceWorker.controller.postMessage(message)
  }
}

function ready() {
  // ...
  window.addEventListener('online', function online() {
    offlineIcon.classList.add('hidden')
    isOnline = true
    sendStatusUpdate()
  })

  window.addEventListener('offline', function offline() {
    offlineIcon.classList.remove('hidden')
    isOnline = false
    sendStatusUpdate()
  })

  async function initServiceWorker() {
    // ...
    serviceWorker =
      swRegistration.installing ||
      swRegistration.waiting ||
      swRegistration.active
    sendStatusUpdate(serviceWorker)

    navigator.serviceWorker.addEventListener(
      'controllerchange',
      function onControllerChange() {
        serviceWorker = navigator.serviceWorker.controller
        sendStatusUpdate(serviceWorker)
      },
    )
  }
}

For the service workers side, they need to listen to the message event fired by all clients, listen on their message channel port for the client message and send messages through the channel to the clients on the other port of the channel, which looks like this:

// ...
self.addEventListener('message', onMessage)

// ...

async function main() {
  await sendMessage({requestStatusUpdate: true})
}

// ...

async function sendMessage(message) {
  var allClients = await clients.matchAll({
    includeUncontrolled: true,
  })
  return Promise.all(
    allClients.map(function clientMessage(client) {
      var channel = new MessageChannel()
      channel.port1.onmessage = onMessage
      return client.postMessage(message, [channel.port2])
    }),
  )
}

function onMessage({data}) {
  if (data.statusUpdate) {
    ;({isOnline, isLoggedIn} = data.statusUpdate)
    console.log(
      `Service Worker (${version}) status update... isOnline:${isOnline}, isLoggedIn:${isLoggedIn}`,
    )
  }
}

That's basically how the clients and service workers talk to each other.

Next up, you can cache resources with service worker.

We are going to do it in a brutal-force way. First, get an id for each version of cached resources and list out the urls of all resources that need to be cached.

var cacheName = `ramblings-${version}`

var urlsToCache = {
  // resources urls that need to be cached
  loggedOut: [
    // pages urls get rewrited by the server
    '/',
    '/about',
    '/contact',
    '/login',
    '/404',
    '/offline',

    // other static files
    '/js/blog.js',
    '/js/home.js',
    '/js/login.js',
    '/js/add-post.js',
    '/css/style.css',
    '/images/logo.gif',
    '/images/offline.png',
  ],
}

And then define the function that put those resources into cache.

async function cacheLoggedOutFiles(forceReload = false) {
  var cache = await caches.open(cacheName)

  return Promise.all(
    urlsToCache.loggedOut.map(function requestFile(url) {
      try {
        let res

        if (!forceReload) {
          res = await cache.match(url)
          if (res) {
            return res
          }
        }

        let fetchOptions = {
          method: 'GET',
          cache: 'no-cache', // cache must be validated by server
          credentials: 'omit',
        }
        res = await fetch(url, fetchOptions)
        if (res.ok) {
          await cache.put(url, res.clone())
        }
      } catch (error) {}
    }),
  )
}

Then call the caching function when necessary:

async function main() {
  await sendMessage({requestStatusUpdate: true})
  // no force reload on startup: cache everything missing and ignore everything already in the cache
  await cacheLoggedOutFiles()
}

// ...
async function handleActivation() {
  // recache everything on activation
  await cacheLoggedOutFiles({forceReload: true})
  // fire `controllerchange` event
  await clients.claim()
  console.log(`Service Worker (${version}) activated.`)
}

This way the service worker will cache everything you need when it's time.

But it would be better if we clear any old caches before cache new resources. The activation time suits well for this operation as we can be sure that the old service worker has been deleted and the new one has been installed and ready to roll. If we do this during the installation, the old worker might be still lingering around and could mess things up.

async function handleActivation() {
  await clearCaches()
  // ...
}

async function clearCaches() {
  var cacheNames = await caches.keys()
  var oldCacheNames = cacheNames.filter(function matchOldCache(cacheName) {
    if (/^ramblings-\d+$/.test(cacheName)) {
      let [, cacheVersion] = cacheName.match(/^ramblings-(\d+)$/)
      cacheVersion = cacheVersion !== null ? Number(cacheVersion) : cacheVersion
      return cacheVersion > 0 && cacheVersion !== version
    }
  })
  return Promise.all(
    oldCacheNames.map(function deleteCache(cacheName) {
      return caches.delete(cacheName)
    }),
  )
}

Now that we are done with caching, we can move onto the part where we use service worker to actually intercept network requests for offline use cases. To do that, we are basically going to build a router. First we need to listen to the fetch event, and the callback should return the response back.

self.addEventListener('fetch', onFetch)

function onFetch(event) {
  event.respondWith(router(event.request))
}

For the router, we have to deal with requests to our own server. We can check the origin of the request url, try fetching the response and cache it. If the fetching fails, we will need to look into our caches and populate the response with the cache.

async function router(request) {
  var url = URL(request.url)
  var requestUrl = url.pathname
  var cache = await caches.open(cacheName)

  if (url.origin === location.origin) {
    var res
    try {
      let fetchOptions = {
        method: request.method,
        headers: request.headers,
        credentials: 'omit',
        cache: 'no-store',
      }

      res = await fetch(request.url, fetchOptions)
      if (res && res.ok) {
        await cache.put(requestUrl, res.clone())
        return res
      }
    } catch (error) {}

    res = await cache.match(requestUrl)
    if (res) {
      return res.clone()
    }
  }
}

This way, even if you are online but the server is down, the service worker will still keep your experience nice and sweet with your cached data.

Things to think about when using TypeScript with react

@types/react

Commonly used:

  • ReactNode - ReactElement, ReactFragment, ReactPortal & all the primitive data types
  • Component - all class-based components
  • PureComponent - class-based optimized components
  • FC, FunctionComponent - complete interface for function components, often used to type external function components instead of typing my own

Good to know:

  • ReactChild - JSX elements, strings & numbers. Can be used with the children prop but covers smaller ground than ReactNode
  • ReactElement - JSX elements, which is either a intrinsic element, i.e. <button />, or a value-based element, i.e. <MyComponent />. Can used with cloneElement but rarely.
  • ComponentType - used for higher order components where you don't specifically deal with the intrinsic components
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment