Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active January 23, 2026 15:03
Show Gist options
  • Select an option

  • Save WebReflection/291357aa6bbbd97d54a971ce5f5aae4e to your computer and use it in GitHub Desktop.

Select an option

Save WebReflection/291357aa6bbbd97d54a971ce5f5aae4e to your computer and use it in GitHub Desktop.
DOM Marker Interface
//@ts-check
// ⚠️ UPDATED https://gist.github.com/WebReflection/291357aa6bbbd97d54a971ce5f5aae4e?permalink_comment_id=5952616#gistcomment-5952616
//@ts-ignore
import custom from 'https://cdn.jsdelivr.net/npm/custom-function/esm/factory.js';
/** @typedef {'marker' | 'start' | 'end'} MarkerType */
if (!('MARKER_NODE' in Node)) {
const { COMMENT_NODE, ELEMENT_NODE } = Node;
const MARKER_NODE = 13;
const re = /(\w+)([\s>]|=(['"])?(.*)?\3)?/g;
/**
* @param {string} data
* @returns
*/
const parse = data => {
const attributes = {};
let match;
while (match = re.exec(data)) {
const [, name, _, __, value] = match;
attributes[name] = value ?? true;
}
return attributes;
};
/**
* @param {Comment} node
* @param {MarkerType} type
* @returns {Marker}
*/
const promote = (node, type) => {
promoted = node;
try {
return new Marker(type, '', parse(node.data.slice(type.length + 1)));
}
finally {
promoted = void 0;
}
};
let promoted;
class Marker extends custom(Node) {
#attributes;
/** @type {string} */
#name;
/** @type {MarkerType} */
#type;
/**
* @param {MarkerType} type
* @param {string} [name]
*/
constructor(type, name = '', attributes = {}) {
super(promoted ?? document.createComment(type));
this.#attributes = attributes;
this.#name = name;
this.#type = type;
}
get attributes() {
return this.#attributes;
}
/** @returns {number} */
get nodeType() {
return MARKER_NODE;
}
/** @returns {string} */
get name() {
return this.#name;
}
/** @returns {MarkerType} */
get type() {
return this.#type;
}
/**
* @param {string} name
* @returns {boolean}
*/
hasAttribute(name) {
return !!this.#attributes.hasOwnProperty(name);
}
/**
* @param {string} name
* @returns {string | null}
*/
getAttribute(name) {
return this.#attributes[name] ?? null;
}
/**
* @param {string} name
* @param {string | boolean} value
*/
setAttribute(name, value) {
this.#attributes[name] = value;
}
removeAttribute(name) {
delete this.#attributes[name];
}
}
//@ts-ignore
Node.MARKER_NODE = MARKER_NODE;
/**
* @param {Comment} node
*/
const check = node => {
if (/^(start|end|marker)(?:$|\s+)/.test(node.data))
promote(node, /** @type {MarkerType} */ (RegExp.$1));
};
/**
* @param {Element} parent
*/
const walk = parent => {
const tw = parent.ownerDocument.createTreeWalker(parent, NodeFilter.SHOW_COMMENT);
let node;
while (node = tw.nextNode()) check(/** @type {Comment} */ (node));
};
const mo = new MutationObserver(records => {
for (const record of records) {
for (const node of record.addedNodes) {
switch (node.nodeType) {
case COMMENT_NODE:
check(/** @type {Comment} */ (node));
break;
case ELEMENT_NODE:
walk(/** @type {Element} */ (node));
break;
}
}
}
});
const { documentElement } = document;
mo.observe(documentElement, { childList: true, subtree: true });
walk(documentElement);
}
@WebReflection
Copy link
Author

WebReflection commented Jan 23, 2026

OK, this looks like a better approach:

//@ts-check

//@ts-ignore
import custom from 'https://cdn.jsdelivr.net/npm/custom-function/esm/factory.js';

/** @typedef {'marker' | 'start' | 'end'} MarkerType */

if (!('MARKER_NODE' in Node)) {
  const { COMMENT_NODE, ELEMENT_NODE } = Node;
  const MARKER_NODE = 13;

  /**
   * @param {string} data
   * @returns
   */
  const name = data => / name=(['"])?(.+)?\1/.test(data) ? RegExp.$2 : '';

  /**
   * @param {Comment} node
   * @param {MarkerType} type
   * @returns {Marker}
   */
  const promote = (node, type) => {
    promoted = node;
    try {
      return new Marker(type, name(node.data.slice(type.length)));
    }
    finally {
      promoted = void 0;
    }
  };

  let promoted;

  class Marker extends custom(Node) {
    /** @type {string} */
    #name;

    /** @type {MarkerType} */
    #type;

    /**
     * @param {MarkerType} type
     * @param {string} [name]
     */
    constructor(type, name = '') {
      super(promoted ?? document.createComment(type));
      this.#name = name;
      this.#type = type;
    }

    /** @type {string} */
    get nodeName() {
      return '#marker';
    }

    /** @type {number} */
    get nodeType() {
      return MARKER_NODE;
    }

    /** @type {string} */
    get name() {
      return this.#name;
    }

    /** @type {MarkerType} */
    get type() {
      return this.#type;
    }
  }

  //@ts-ignore
  Node.MARKER_NODE = MARKER_NODE;

  /**
   * @param {Comment} node
   */
  const check = node => {
    if (/^(start|end|marker)(?:$|\s+)/.test(node.data))
      promote(node, /** @type {MarkerType} */ (RegExp.$1));
  };

  /**
   * @param {Element} parent
   */
  const walk = parent => {
    const tw = document.createTreeWalker(parent, NodeFilter.SHOW_COMMENT);
    let node;
    while (node = tw.nextNode()) check(/** @type {Comment} */(node));
  };

  const { attachShadow: $ } = Element.prototype;
  const mode = { childList: true, subtree: true };

  /**
   * 
   * @param {ShadowRootInit} init
   * @returns {ShadowRoot}
   */
  Element.prototype.attachShadow = function attachShadow(init) {
    const sr = $.call(this, init);
    mo.observe(sr, mode);
    return sr;
  };

  const { documentElement } = document;
  const mo = new MutationObserver(records => {
    for (const record of records) {
      for (const node of record.addedNodes) {
        switch (node.nodeType) {
          case COMMENT_NODE:
            check(/** @type {Comment} */(node));
            break;
          case ELEMENT_NODE:
            walk(/** @type {Element} */(node));
            break;
        }
      }
    }
  });
  mo.observe(documentElement, mode);
  walk(documentElement);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment