Save the (DOM) trees! Why direct DOM manipulation is not a bad idea

A generic, and so non-binary, unsorted, some labels duplicated, arbitrary diagram of a tree.
A generic, and so non-binary, unsorted, some labels duplicated, arbitrary diagram of a tree. CC BY-SA 4.0

Direct DOM manipulation has gotten a bad reputation in the last decade of web development. From Ruby on Rails to React, the DOM was seen as something to gloriously destroy and re-render from the server or even from the browser. Never mind that the browser already exerted a lot of effort parsing HTML and constructing this tree! Mind-numbingly complex HTML string regular expression tests and manipulations had to deal with low-level details of the HTML syntax to insert, delete and change elements, sometimes on every keystroke! Contrasting to that, functions like createElement, remove and insertBefore from the DOM world were largely unknown and unused, except perhaps in jQuery.

Processing of HTML is destructive: The original DOM is destroyed and garbage collected with a certain time delay. Attached event handlers are detached and garbage collected. A completely new DOM is created from parsing new HTML set via .innerHTML =. Event listeners will have to be re-attached from the user-land (this is no issue when using on* HTML attributes, but this has disadvantages as well).

It doesn’t have to be this way. Do not eliminate, but manipulate!

Save the (DOM) trees!

sanitize-dom crawls a DOM sub-tree (beginning from a given node, all the way down to its ancestral leaves) and filters and manipulates it non-destructively. This is very efficient: The browser doesn’t have to re-render everything; it only re-renders what has been changed (sound familiar from React?).

The benefits of direct DOM manipulation:

  • Nodes stay alive.
  • References to nodes (i.e. stored in a Map or WeakMap) stay alive.
  • Already attached event handlers stay alive.
  • The browser doesn’t have to re-render entire sections of a page; thus no flickering, no scroll jumping, no big CPU spikes.
  • CPU cycles for repeatedly parsing and dumping of HTML are eliminated.

sanitize-dom

I just released version 4 of my sanitize-dom library which is my tool of choice to sanitize user-generated content. As mentioned in this post, my project takes a different approach: It doesn’t work with HTML, it operates only on DOM nodes.

sanitize-doms further advantages:

  • No dependencies.
  • Small footprint (only about 7 kB minimized).
  • Faster than other HTML sanitizers because there is no HTML parsing and serialization.

Check out sanitize-dom here on Github!

Friction-less login to a tmux session on a suspended host

The Skating Minister by Henry Raeburn – Friction-less! (public domain)

I recently found that more and more I’m using technical solutions only when they are friction-less (I guess that is not that unusual generally, but a bit more unusual for me). This includes my work on a remote machine, which is suspended most of the time, and for which I’ve enabled WOL (Wake-on-LAN, see my related blog post on how I accomplished this).

I wanted to create a way to log into this machine regardless if it is suspended or not, and continue working where I left off (i.e. not losing any state even when it goes to sleep or if the network is interrupted). I’m usually doing complex work in several shell sessions with several files open at once, several processes running at once, etc.

Below, you’ll find a shell script to accomplish this. You can customize the host name and the host’s MAC address directly in the script, and then call it without arguments.

What does the script do?

It will first wake the machine up using wakeonlan . If it is already awake, then wakeonlan will not have any effect, and all is fine. If it is sleeping, then it will come online in a few seconds. Using an exponential back-off strategy, I’m polling the availability of the SSH port 22. If the port comes online, we know that SSH is ready to receive the login. I’m then trying to attach to an already existing tmux session where I can continue to work where I left off after the last ssh disconnection (simulate this by typing sequentially Enter ~ .) or tmux detachment (Ctrl+b d), and if there is no running tmux session, then it will start a new tmux session, which will also keep me logged in using the shell configured for my user (bash or zsh).

And without further ado, here is the script:

https://gist.github.com/michaelfranzl/5a4dc6b6e450577c39522a7deca51358

#!/bin/bash

# Copyright 2020 Michael Karl Franzl

# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
# associated documentation files (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
# OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

set -e

host_name="example.com"
host_mac="00:00:00:00:00:00"

echoerr() { printf "%s\n" "$*" >&2; }

wakeonlan ${host_mac}

# exponential backoff strategy for the login
for i in 1 2 4 8 16; do
  if [ $i -ge 16 ]; then
    echoerr "Host ${host_name} did not wake up. Giving up."
    exit 1
  fi

  echo "Waiting until host ${host_name} is awake..."
  nc -z ${host_name} 22 && break
  sleep $i
done

echo "Host ${host_name} is now awake. Logging in..."
ssh -t ${host_name} "tmux a || tmux"

Negative niceness is supanice!

Did you ever want to effortlessly start a process with highest (negative) priority?

Like here, where I don’t want the high CPU load of other processes on my system degrade my developing experience (i.e. zippy nvim):

supanice nvim

Here is the shell function accomplishing this:

function supanice {
  sudo --preserve-env=PATH nice -n -20 su -c $@ `whoami`
}