Full Code of luwes/little-vdom for AI

main e6db8d217fe9 cached
9 files
25.9 KB
7.5k tokens
15 symbols
1 requests
Download .txt
Repository: luwes/little-vdom
Branch: main
Commit: e6db8d217fe9
Files: 9
Total size: 25.9 KB

Directory structure:
gitextract_91a3b87y/

├── .gitignore
├── README.md
├── dist/
│   └── little-vdom.js
├── little-vdom.js
├── package.json
└── test/
    ├── _util/
    │   ├── helpers.js
    │   └── logCall.js
    ├── test.jsx
    └── web-test-runner.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/node_modules/


================================================
FILE: README.md
================================================
# 🍼 little-vdom

> Forked from developit's [little-vdom](https://gist.github.com/developit/2038b141b31287faa663f410b6649a87) gist.

**npm**: `npm i @luwes/little-vdom`  
**cdn**: [unpkg.com/@luwes/little-vdom](https://unpkg.com/@luwes/little-vdom)  

---

- 650B Virtual DOM
- Components
- State
- Diffing
- Keys
- Fragments
- Refs
- Style maps

Use reactive JSX with minimal overhead.

## Usage ([Codepen](https://codepen.io/luwes/pen/ZEXPbzE?editors=0011))

```jsx
/** @jsx h */

// Components get passed (props, state, setState)
function Counter(props, { count = 0 }, update) {
  const increment = () => update({ count: ++count });
  return <button onclick={increment}>{count}</button>
}

function Since({ time }, state, update) {
  setTimeout(update, 1000); // update every second
  const ago = (Date.now() - time) / 1000 | 0;
  return <time>{ago}s ago</time>
}

render(
  <div id="app">
    <h1>Hello</h1>
    <Since time={Date.now()} />
    <Counter />
  </div>,
  document.body
);
```


================================================
FILE: dist/little-vdom.js
================================================
const n=(n,e,...t)=>({t:n,o:e,i:t.filter((n=>!1!==n)),key:e&&e.key}),e=n=>n.children,t=(t,c,o=c.l||(c.l={}))=>r(n(e,{},[t]),c,o),r=(n,e,t,o)=>{if(n.pop)return c(e,n,t);if(n.t.call){n.u=t.u||{};const c={children:n.i,...n.o},i=n.t(c,n.u,(t=>(Object.assign(n.u,t),r(n,e,n))));return n.p=r(i,e,t&&t.p||{},o),e.l=n}{const r=t.dom||(n.t?document.createElement(n.t):new Text(n.o));if(n.o!=t.o)if(n.t){const{key:e,ref:c,...o}=n.o;c&&(c.current=r);for(let n in o){const e=o[n];if("style"!==n||e.trim)e!=(t.o&&t.o[n])&&(n in r||(n=n.toLowerCase())in r?r[n]=e:null!=e?r.setAttribute(n,e):r.removeAttribute(n));else for(const n in e)r.style[n]=e[n]}}else r.data=n.o;return c(r,n.i,t),t.dom&&null==o||e.insertBefore(n.dom=r,e.childNodes[o+1]||null),e.l=Object.assign(t,n)}},c=(e,t,c)=>{const i=c._||[];return c._=t.concat.apply([],t).map(((t,c)=>{const o=t.i?t:n("",""+t),l=i.find(((n,e)=>n&&n.t==o.t&&n.key==o.key&&(e==c&&(c=void 0),i[e]=0,n)))||{};return r(o,e,l,c)})),i.map(o),c};function o(n){const{i:e=[],p:t}=n;e.concat(t).map((n=>n&&o(n))),n.dom&&n.dom.remove()}export{n as h,e as Fragment,t as render,r as diff};

================================================
FILE: little-vdom.js
================================================
// Adapted and fixed bugs from little-vdom.js
// https://gist.github.com/developit/2038b141b31287faa663f410b6649a87
// https://gist.github.com/marvinhagemeister/8950b1032d67918d21950b3985259d78
// Added refs, style maps

const h = (type, props, ...children) => {
  return {
    _type: type,
    _props: props, // An object for components and DOM nodes, a string for text nodes.
    _children: children.filter((_) => _ !== false),
    key: props && props.key,
  };
};

const Fragment = (props) => {
  return props.children;
};

const render = (newVNode, dom, oldVNode = dom._vnode || (dom._vnode = {})) => {
  return diff(h(Fragment, {}, [newVNode]), dom, oldVNode);
};

const diff = (newVNode, dom, oldVNode, currentChildIndex) => {
  // Check if we are in fact dealing with an array of nodes. A more common
  // and faster version of this check is Array.isArray()
  if (newVNode.pop) {
    return diffChildren(dom, newVNode, oldVNode);
  }
  // Check if we have a component. Only functions have a .call() method.
  // Here components have a different signature compared to Preact or React:
  //
  // (props, state, updateFn) => VNode;
  //
  // The 3rd argument is basically similar concept-wise to setState
  else if (newVNode._type.call) {
    // Initialize state of component if necessary
    newVNode._state = oldVNode._state || {};
    // Add children to props
    const props = { children: newVNode._children, ...newVNode._props };
    const renderResult = newVNode._type(
      props,
      newVNode._state,
      // Updater function that is passed as 3rd argument to components
      (nextState) => {
        // Update state with new value
        Object.assign(newVNode._state, nextState);
        return diff(newVNode, dom, newVNode);
      }
    );

    newVNode._patched = diff(
      renderResult,
      dom,
      (oldVNode && oldVNode._patched) || {},
      currentChildIndex
    );

    return (dom._vnode = newVNode);
  }
  // Standard DOM elements
  else {
    // Create a DOM element and assign it to the vnode. If one already exists,
    // we will reuse the existing one and not create a new node.
    const newDom =
      oldVNode.dom ||
      (newVNode._type
        ? document.createElement(newVNode._type)
        : // If we have a text node, vnode.props will be a string
          new Text(newVNode._props));

    // diff props
    if (newVNode._props != oldVNode._props) {
      // If newVNode.type is truthy (=not an empty string) we have a DOM node
      if (newVNode._type) {
        const { key, ref, ...newProps } = newVNode._props;
        if (ref) ref.current = newDom;

        for (let name in newProps) {
          const value = newProps[name];
          // A string object has a trim method.
          if (name === 'style' && !value.trim) {
            for (const n in value) {
              newDom.style[n] = value[n];
            }
          } else if (value != (oldVNode._props && oldVNode._props[name])) {
            if (name in newDom || (name = name.toLowerCase()) in newDom) {
              newDom[name] = value;
            } else if (value != null) {
              newDom.setAttribute(name, value);
            } else {
              newDom.removeAttribute(name);
            }
          }
        }
      }
      // Otherwise a text node
      else {
        // Update text node content
        newDom.data = newVNode._props;
      }
    }

    // diff children (typed/keyed)
    diffChildren(newDom, newVNode._children, oldVNode);

    // insert at position
    if (!oldVNode.dom || currentChildIndex != undefined) {
      dom.insertBefore(
        (newVNode.dom = newDom),
        dom.childNodes[currentChildIndex + 1] || null
      );
    }

    return (dom._vnode = Object.assign(oldVNode, newVNode));
  }
};

const diffChildren = (parentDom, newChildren, oldVNode) => {
  const oldChildren = oldVNode._normalizedChildren || [];
  oldVNode._normalizedChildren = newChildren.concat
    .apply([], newChildren)
    .map((child, index) => {
      // If the vnode has no children we assume that we have a string and
      // convert it into a text vnode.
      const nextNewChild = child._children ? child : h('', '' + child);

      // If we have previous children we search for one that matches our
      // current vnode.
      const nextOldChild =
        oldChildren.find((oldChild, childIndex) => {
          let result =
            oldChild &&
            oldChild._type == nextNewChild._type &&
            oldChild.key == nextNewChild.key &&
            (childIndex == index && (index = undefined),
            (oldChildren[childIndex] = 0),
            oldChild);
          // if (result) console.log('found vnode', result);
          return result;
        }) || {};

      // Continue diffing recursively against the next child.
      return diff(nextNewChild, parentDom, nextOldChild, index);
    });

  // remove old children if there are any
  oldChildren.map(removePatchedChildren)

  return oldVNode;
};

function removePatchedChildren(child) {
  const { _children = [], _patched } = child
  // remove children
  _children.concat(_patched).map(c => c && removePatchedChildren(c))
  // remove dom
  child.dom && child.dom.remove()
}

export { h, Fragment, render, diff };


================================================
FILE: package.json
================================================
{
  "name": "@luwes/little-vdom",
  "version": "0.3.4",
  "description": "Fork from developit/little-vdom",
  "type": "module",
  "main": "dist/little-vdom.js",
  "scripts": {
    "lint": "eslint '*.{js,jsx}'",
    "build": "npm run minify && npm run size",
    "minify": "terser little-vdom.js -c -m toplevel=true --mangle-props regex=/^_/ -o dist/little-vdom.js",
    "size": "echo \"gzip: $(cat dist/little-vdom.js | gzip -c9 | wc -c)\" && echo \"brotli: $(cat dist/little-vdom.js | brotli | wc -c)\" && echo ''",
    "test": "web-test-runner **/*test.jsx --config test/web-test-runner.config.js",
    "test:watch": "npm run test -- --watch"
  },
  "license": "MIT",
  "devDependencies": {
    "@esm-bundle/chai": "^4.3.4-fix.0",
    "@web/dev-server": "^0.1.29",
    "@web/dev-server-esbuild": "^0.2.16",
    "@web/test-runner": "^0.13.25",
    "eslint": "^8.7.0",
    "eslint-plugin-react": "^7.28.0",
    "prettier": "^2.5.1",
    "terser": "^5.10.0"
  },
  "prettier": {
    "tabWidth": 2,
    "singleQuote": true,
    "semi": true
  },
  "eslintConfig": {
    "env": {
      "browser": true,
      "es6": true,
      "node": true,
      "mocha": true
    },
    "extends": [
      "eslint:recommended",
      "plugin:react/recommended"
    ],
    "parserOptions": {
      "ecmaVersion": 2019,
      "sourceType": "module",
      "ecmaFeatures": {
        "jsx": true
      }
    },
    "settings": {
      "react": {
        "pragma": "h"
      }
    },
    "rules": {
      "no-shadow": "error",
      "react/prop-types": 0,
      "react/no-unknown-property": [
        2,
        {
          "ignore": [
            "class"
          ]
        }
      ]
    }
  }
}


================================================
FILE: test/_util/helpers.js
================================================
import { clearLog, getLog } from './logCall';

/**
 * Setup the test environment
 * @returns {HTMLDivElement}
 */
export function setupScratch() {
	const scratch = document.createElement('div');
	scratch.id = 'scratch';
	(document.body || document.documentElement).appendChild(scratch);
	return scratch;
}

/**
 * Teardown test environment and reset preact's internal state
 * @param {HTMLDivElement} scratch
 */
export function teardown(scratch) {
	if (scratch) {
		scratch.parentNode.removeChild(scratch);
	}

	if (getLog().length > 0) {
		clearLog();
	}
}

export function serializeHtml(node) {
  let str = '';
  let child = node.firstChild;
  while (child) {
    str += serializeDomTree(child);
    child = child.nextSibling;
  }
  return str;
}

const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;

function encodeEntities(str) {
  return str.replace(/&/g, '&amp;');
}

/**
 * Normalize svg paths spacing. Some browsers insert spaces around letters,
 * others do not.
 * @param {string} str
 * @returns {string}
 */
function normalizePath(str) {
  let len = str.length;
  let out = '';
  for (let i = 0; i < len; i++) {
    const char = str[i];
    if (/[A-Za-z]/.test(char)) {
      if (i == 0) out += char + ' ';
      else
        out += (str[i - 1] == ' ' ? '' : ' ') + char + (i < len - 1 ? ' ' : '');
    } else if (char == '-' && str[i - 1] !== ' ') out += ' ' + char;
    else out += char;
  }

  return out.replace(/\s\s+/g, ' ').replace(/z/g, 'Z');
}

/**
 * Serialize a DOM tree.
 * Uses deterministic sorting where necessary to ensure consistent tests.
 * @param {Element|Node} node  The root node to serialize
 * @returns {string} html
 */
function serializeDomTree(node) {
  if (node.nodeType === 3) {
    return encodeEntities(node.data);
  } else if (node.nodeType === 8) {
    return '<!--' + encodeEntities(node.data) + '-->';
  } else if (node.nodeType === 1 || node.nodeType === 9) {
    let str = '<' + node.localName;
    const attrs = [];
    for (let i = 0; i < node.attributes.length; i++) {
      attrs.push(node.attributes[i].name);
    }
    attrs.sort();
    for (let i = 0; i < attrs.length; i++) {
      const name = attrs[i];
      let value = node.getAttribute(name);

      // don't render attributes with null or undefined values
      if (value == null) continue;

      // normalize empty class attribute
      if (!value && name === 'class') continue;

      str += ' ' + name;
      value = encodeEntities(value);

      // normalize svg <path d="value">
      if (node.localName === 'path' && name === 'd') {
        value = normalizePath(value);
      }
      str += '="' + value + '"';
    }
    str += '>';

    // For elements that don't have children (e.g. <wbr />) don't descend.
    if (!VOID_ELEMENTS.test(node.localName)) {
      // IE puts the value of a textarea as its children while other browsers don't.
      // Normalize those differences by forcing textarea to not have children.
      if (node.localName != 'textarea') {
        let child = node.firstChild;
        while (child) {
          str += serializeDomTree(child);
          child = child.nextSibling;
        }
      }

      str += '</' + node.localName + '>';
    }
    return str;
  }
}


================================================
FILE: test/_util/logCall.js
================================================
/**
 * Serialize an object
 * @param {Object} obj
 * @return {string}
 */
function serialize(obj) {
	if (obj instanceof Text) return '#text';
	if (obj instanceof Element) return `<${obj.localName}>${obj.textContent}`;
	if (obj === document) return 'document';
	if (typeof obj == 'string') return obj;
	return Object.prototype.toString.call(obj).replace(/(^\[object |\]$)/g, '');
}

/** @type {string[]} */
let log = [];

/**
 * Modify obj's original method to log calls and arguments on logger object
 * @template T
 * @param {T} obj
 * @param {keyof T} method
 */
export function logCall(obj, method) {
	let old = obj[method];
	obj[method] = function(...args) {
		let c = '';
		for (let i = 0; i < args.length; i++) {
			if (c) c += ', ';
			c += serialize(args[i]);
		}

		// Normalize removeChild -> remove to keep output clean and readable
		const operation =
			method != 'removeChild'
				? `${serialize(this)}.${method}(${c})`
				: `${serialize(c)}.remove()`;
		log.push(operation);
		return old.apply(this, args);
	};

	return () => (obj[method] = old);
}

/**
 * Return log object
 * @return {string[]} log
 */
export function getLog() {
	return log;
}

/** Clear log object */
export function clearLog() {
	log = [];
}

export function getLogSummary() {
	/** @type {{ [key: string]: number }} */
	const summary = {};

	for (let entry of log) {
		summary[entry] = (summary[entry] || 0) + 1;
	}

	return summary;
}


================================================
FILE: test/test.jsx
================================================
/**
  Cherry picked tests from Preact

  The MIT License (MIT)
  Copyright (c) 2015-present Jason Miller

  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.
 */
import { expect } from '@esm-bundle/chai';
import { h, Fragment, render } from '../little-vdom.js';
import { clearLog, getLog, logCall } from './_util/logCall.js';
import { setupScratch, teardown, serializeHtml } from './_util/helpers.js';

describe('all', () => {
  let scratch;

  let resetAppendChild;
  let resetInsertBefore;
  let resetRemoveChild;
  let resetRemove;

  beforeEach(() => {
    scratch = setupScratch();
  });

  afterEach(() => {
    teardown(scratch);
  });

  before(() => {
    resetAppendChild = logCall(Element.prototype, 'appendChild');
    resetInsertBefore = logCall(Element.prototype, 'insertBefore');
    resetRemoveChild = logCall(Element.prototype, 'removeChild');
    resetRemove = logCall(Element.prototype, 'remove');
  });

  after(() => {
    resetAppendChild();
    resetInsertBefore();
    resetRemoveChild();
    resetRemove();
  });

  /** @type {(props: {values: any[]}) => any} */
  const List = props => (
    <ol>
      {props.values.map(value => (
        <li key={value}>{value}</li>
      ))}
    </ol>
  );

  /**
   * Move an element in an array from one index to another
   * @param {any[]} values The array of values
   * @param {number} from The index to move from
   * @param {number} to The index to move to
   */
  function move(values, from, to) {
    const value = values[from];
    values.splice(from, 1);
    values.splice(to, 0, value);
  }


  it('should register on* functions as handlers', () => {
    let count = 0;
    let onclick = () => (++count);

    render(<div onClick={onclick} />, scratch);
    expect(scratch.childNodes[0].attributes.length).to.equal(0);
    scratch.childNodes[0].click();
    expect(count).to.equal(1);
  });

  // render.test.js

  it('should rerender when value from "" to 0', () => {
    render('', scratch);
    expect(scratch.innerHTML).to.equal('');

    render(0, scratch);
    expect(scratch.innerHTML).to.equal('0');
  });

  it('change content', () => {
    render(<div>Bad</div>, scratch);
    render(<div>Good</div>, scratch);
    expect(scratch.innerHTML).to.eql(`<div>Good</div>`);
  });

  it('should allow node type change with content', () => {
    render(<span>Bad</span>, scratch);
    render(<div>Good</div>, scratch);
    expect(scratch.innerHTML).to.eql(`<div>Good</div>`);
  });

  it('should nest empty nodes', () => {
    render(
      <div>
        <span />
        <foo />
        <x-bar />
      </div>,
      scratch
    );

    expect(scratch.childNodes).to.have.length(1);
    expect(scratch.childNodes[0].nodeName).to.equal('DIV');

    let c = scratch.childNodes[0].childNodes;
    expect(c).to.have.length(3);
    expect(c[0].nodeName).to.equal('SPAN');
    expect(c[1].nodeName).to.equal('FOO');
    expect(c[2].nodeName).to.equal('X-BAR');
  });

  it('should reorder child pairs', () => {
    render(
      <div>
        <a>a</a>
        <b>b</b>
      </div>,
      scratch
    );

    let a = scratch.firstChild.firstChild;
    let b = scratch.firstChild.lastChild;

    expect(a).to.have.property('nodeName', 'A');
    expect(b).to.have.property('nodeName', 'B');

    render(
      <div>
        <b>b</b>
        <a>a</a>
      </div>,
      scratch
    );

    expect(scratch.firstChild.firstChild).to.equal(b);
    expect(scratch.firstChild.lastChild).to.equal(a);
  });

  it('should remove class attributes', () => {
    const App = props => (
      <div class={props.class}>
        <span>Bye</span>
      </div>
    );

    render(<App class="hi" />, scratch);
    expect(scratch.innerHTML).to.equal(
      '<div class="hi"><span>Bye</span></div>'
    );

    render(<App />, scratch);
    expect(scratch.innerHTML).to.equal('<div><span>Bye</span></div>');
  });

  // keys.test.js

  it('should remove orphaned keyed nodes', () => {
    render(
      <div>
        <div>1</div>
        <li key="a">a</li>
        <li key="b">b</li>
      </div>,
      scratch
    );

    render(
      <div>
        <div>2</div>
        <li key="b">b</li>
        <li key="c">c</li>
      </div>,
      scratch
    );

    expect(scratch.innerHTML).to.equal(
      '<div><div>2</div><li>b</li><li>c</li></div>'
    );
  });

  it('should append new keyed elements', () => {
    const values = ['a', 'b'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('ab');

    values.push('c');
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abc');
    expect(getLog()).to.deep.equal([
      '<li>.insertBefore(#text, Null)',
      '<ol>ab.insertBefore(<li>c, Null)'
    ]);
  });

  it('should remove keyed elements from the end', () => {
    const values = ['a', 'b', 'c', 'd'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abcd');

    values.pop();
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abc');
    expect(getLog()).to.deep.equal(['<li>d.remove()']);
  });

  it('should prepend keyed elements to the beginning', () => {
    const values = ['b', 'c'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('bc');

    values.unshift('a');
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abc');

    // Comment out efficient reconciliation proof, would require a bigger diffing algo.
    // expect(getLog()).to.deep.equal([
    //   '<li>.insertBefore(#text, Null)',
    //   '<ol>bc.insertBefore(<li>a, <li>b)'
    // ]);
  });

  it('should remove keyed elements from the beginning', () => {
    const values = ['z', 'a', 'b', 'c'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('zabc');

    values.shift();
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abc');

    // Comment out efficient reconciliation proof, would require a bigger diffing algo.
    // expect(getLog()).to.deep.equal(['<li>z.remove()']);
  });

  it('should insert new keyed children in the middle', () => {
    const values = ['a', 'c'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('ac');

    values.splice(1, 0, 'b');
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abc');
    // expect(getLog()).to.deep.equal([
    //   '<li>.insertBefore(#text, Null)',
    //   '<ol>ac.insertBefore(<li>b, <li>c)'
    // ]);
  });

  it('should remove keyed children from the middle', () => {
    const values = ['a', 'b', 'x', 'y', 'z', 'c', 'd'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abxyzcd');

    values.splice(2, 3);
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abcd');
    // expect(getLog()).to.deep.equal([
    //   '<li>z.remove()',
    //   '<li>y.remove()',
    //   '<li>x.remove()'
    // ]);
  });

  it('should move keyed children to the end of the list', () => {
    const values = ['a', 'b', 'c', 'd'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abcd');

    // move to end
    move(values, 0, values.length - 1);
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('bcda', 'move to end');
    // expect(getLog()).to.deep.equal(
    //   ['<ol>abcd.insertBefore(<li>a, Null)'],
    //   'move to end'
    // );

    // move to beginning
    move(values, values.length - 1, 0);
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal('abcd', 'move to beginning');
    // expect(getLog()).to.deep.equal(
    //   ['<ol>bcda.insertBefore(<li>a, <li>b)'],
    //   'move to beginning'
    // );
  });

  it('should reverse keyed children effectively', () => {
    const values = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal(values.join(''));

    // reverse list
    values.reverse();
    clearLog();

    render(<List values={values} />, scratch);
    expect(scratch.textContent).to.equal(values.join(''));
    // expect(getLog()).to.deep.equal([
    //   '<ol>abcdefghij.insertBefore(<li>j, <li>a)',
    //   '<ol>jabcdefghi.insertBefore(<li>i, <li>a)',
    //   '<ol>jiabcdefgh.insertBefore(<li>h, <li>a)',
    //   '<ol>jihabcdefg.insertBefore(<li>g, <li>a)',
    //   '<ol>jihgabcdef.insertBefore(<li>f, <li>a)',
    //   '<ol>jihgfabcde.insertBefore(<li>e, <li>a)',
    //   '<ol>jihgfeabcd.insertBefore(<li>d, <li>a)',
    //   '<ol>jihgfedabc.insertBefore(<li>c, <li>a)',
    //   '<ol>jihgfedcab.insertBefore(<li>a, Null)'
    // ]);
  });

  // fragment.test.js

  it('should render a single child', () => {
    clearLog();
    render(
      <Fragment>
        <span>foo</span>
      </Fragment>,
      scratch
    );

    expect(scratch.innerHTML).to.equal('<span>foo</span>');
    expect(getLog()).to.deep.equal([
      '<span>.insertBefore(#text, Null)',
      '<div>.insertBefore(<span>foo, Null)'
    ]);
  });

  it('should render multiple children via noop renderer', () => {
    render(
      <Fragment>
        hello <span>world</span>
      </Fragment>,
      scratch
    );

    expect(scratch.innerHTML).to.equal('hello <span>world</span>');
  });

  it.skip('should handle reordering components that return Fragments #1325', () => {
    const X = (props) => {
      return props.children;
    }

    const App = (props) => {
      if (props.i === 0) {
        return (
          <div>
            <X key={1}>1</X>
            <X key={2}>2</X>
          </div>
        );
      }
      return (
        <div>
          <X key={2}>2</X>
          <X key={1}>1</X>
        </div>
      );
    }

    render(<App i={0} />, scratch);
    expect(scratch.textContent).to.equal('12');

    clearLog();
    console.log('----------------------------------');

    render(<App i={1} />, scratch);
    console.log(getLog());
    expect(scratch.textContent).to.equal('21');
  });

  // refs.test.js

  it('should support createRef', () => {
    const r = { current: null };
    expect(r.current).to.equal(null);

    render(<div ref={r} />, scratch);
    expect(r.current).to.equal(scratch.firstChild);
  });

  // createRoot.js

  it('should apply string attributes', () => {
    render(<div foo="bar" data-foo="databar" />, scratch);
    expect(serializeHtml(scratch)).to.equal(
      '<div data-foo="databar" foo="bar"></div>'
    );
  });

  it('should apply class as String', () => {
    render(<div class="foo" />, scratch);
    expect(scratch.childNodes[0]).to.have.property('className', 'foo');
  });

  it('should set checked attribute on custom elements without checked property', () => {
    render(<o-checkbox checked />, scratch);
    expect(scratch.innerHTML).to.equal(
      '<o-checkbox checked="true"></o-checkbox>'
    );
  });

  it('should set value attribute on custom elements without value property', () => {
    render(<o-input value="test" />, scratch);
    expect(scratch.innerHTML).to.equal('<o-input value="test"></o-input>');
  });

  it('should mask value on password input elements', () => {
    render(<input value="xyz" type="password" />, scratch);
    expect(scratch.innerHTML).to.equal('<input type="password">');
  });


});


================================================
FILE: test/web-test-runner.config.js
================================================
import { esbuildPlugin } from "@web/dev-server-esbuild";

export default {
  nodeResolve: true,
  plugins: [
    esbuildPlugin({ jsx: true, jsxFactory: "h", jsxFragment: "Fragment" }),
  ],
};
Download .txt
gitextract_91a3b87y/

├── .gitignore
├── README.md
├── dist/
│   └── little-vdom.js
├── little-vdom.js
├── package.json
└── test/
    ├── _util/
    │   ├── helpers.js
    │   └── logCall.js
    ├── test.jsx
    └── web-test-runner.config.js
Download .txt
SYMBOL INDEX (15 symbols across 5 files)

FILE: dist/little-vdom.js
  function o (line 1) | function o(n){const{i:e=[],p:t}=n;e.concat(t).map((n=>n&&o(n))),n.dom&&n...

FILE: little-vdom.js
  function removePatchedChildren (line 152) | function removePatchedChildren(child) {

FILE: test/_util/helpers.js
  function setupScratch (line 7) | function setupScratch() {
  function teardown (line 18) | function teardown(scratch) {
  function serializeHtml (line 28) | function serializeHtml(node) {
  constant VOID_ELEMENTS (line 38) | const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|p...
  function encodeEntities (line 40) | function encodeEntities(str) {
  function normalizePath (line 50) | function normalizePath(str) {
  function serializeDomTree (line 72) | function serializeDomTree(node) {

FILE: test/_util/logCall.js
  function serialize (line 6) | function serialize(obj) {
  function logCall (line 23) | function logCall(obj, method) {
  function getLog (line 48) | function getLog() {
  function clearLog (line 53) | function clearLog() {
  function getLogSummary (line 57) | function getLogSummary() {

FILE: test/test.jsx
  function move (line 75) | function move(values, from, to) {
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (28K chars).
[
  {
    "path": ".gitignore",
    "chars": 15,
    "preview": "/node_modules/\n"
  },
  {
    "path": "README.md",
    "chars": 992,
    "preview": "# 🍼 little-vdom\n\n> Forked from developit's [little-vdom](https://gist.github.com/developit/2038b141b31287faa663f410b6649"
  },
  {
    "path": "dist/little-vdom.js",
    "chars": 1107,
    "preview": "const n=(n,e,...t)=>({t:n,o:e,i:t.filter((n=>!1!==n)),key:e&&e.key}),e=n=>n.children,t=(t,c,o=c.l||(c.l={}))=>r(n(e,{},["
  },
  {
    "path": "little-vdom.js",
    "chars": 5239,
    "preview": "// Adapted and fixed bugs from little-vdom.js\n// https://gist.github.com/developit/2038b141b31287faa663f410b6649a87\n// h"
  },
  {
    "path": "package.json",
    "chars": 1676,
    "preview": "{\n  \"name\": \"@luwes/little-vdom\",\n  \"version\": \"0.3.4\",\n  \"description\": \"Fork from developit/little-vdom\",\n  \"type\": \"m"
  },
  {
    "path": "test/_util/helpers.js",
    "chars": 3259,
    "preview": "import { clearLog, getLog } from './logCall';\n\n/**\n * Setup the test environment\n * @returns {HTMLDivElement}\n */\nexport"
  },
  {
    "path": "test/_util/logCall.js",
    "chars": 1424,
    "preview": "/**\n * Serialize an object\n * @param {Object} obj\n * @return {string}\n */\nfunction serialize(obj) {\n\tif (obj instanceof "
  },
  {
    "path": "test/test.jsx",
    "chars": 12601,
    "preview": "/**\n  Cherry picked tests from Preact\n\n  The MIT License (MIT)\n  Copyright (c) 2015-present Jason Miller\n\n  Permission i"
  },
  {
    "path": "test/web-test-runner.config.js",
    "chars": 193,
    "preview": "import { esbuildPlugin } from \"@web/dev-server-esbuild\";\n\nexport default {\n  nodeResolve: true,\n  plugins: [\n    esbuild"
  }
]

About this extraction

This page contains the full source code of the luwes/little-vdom GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (25.9 KB), approximately 7.5k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!