~evanj/wigly

4bbf0b7623eb41d73936cad43c90011eb7984f82 — Evan M Jones 2 years ago 139fd5a
NO BUGS!
9 files changed, 147 insertions(+), 60 deletions(-)

M README.md
M example/styled-wigly.js
M package-lock.json
M package.json
M src/superfine.js
M src/wigly-types.js
M src/wigly.js
D test/main.js
A test/main.jsx
M README.md => README.md +61 -1
@@ 1,3 1,63 @@
# Wigly

Proof of concept. Building on top of Superfine. Inspired by React and CharlieXCX.
A React inspired functional UI library for the web. Built with Superfine.

## 'Hello, World!' example

```javascript
import { h, render } from "wigly";

function App(props) {
  return <div>{props.greeting}, World!</div>;
}

render(<App greeting="Hello" />, document.body);
```

## Stateful counter example

```javascript
import { h, render, useState } from "wigly";

function Counter() {
  var [count, set] = useState(0);
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onclick={set(count + 1)}>increment</button>
    </div>
  );
}

render(<Counter />, document.body);
```

## Using AJAX calls

```javascript
import { h, render, useState, useEffect } from "wigly";

function Counter(props) {
  var [username, set] = useState();

  // Optional second parameter, will only call first parameter when userId value changes.
  // If no second paramter is given the first parameter will be called after every render.
  // Operates the exact same as React's useEffect.
  useEffect(
    async function() {
      var request = await fetch(`/get/user/${props.userId}`);
      var result = await request.json();
      set(result.username);
    },
    [props.userId]
  );

  return (
    <div>
      <div>{username ? `Username: ${username}` : "loading"}</div>
    </div>
  );
}

render(<Counter userId={123} />, document.body);
```

M example/styled-wigly.js => example/styled-wigly.js +8 -5
@@ 1,10 1,13 @@
import { h } from "../";
import tags from "dom-tags";
import stringcss from "string-css/dist/string-css.module.js";

let stringcss = require("string-css").default;

export default new Proxy(
  {},
  { get: (_, name) => (...args) => props => h(name, { ...props, class: stringcss.css(...args) }) }
export default tags.reduce(
  (fns, key) => ({
    ...fns,
    [key]: style => props => h(key, { ...props, class: stringcss.css(style) })
  }),
  {}
);

export let inject = stringcss.inject;

M package-lock.json => package-lock.json +13 -22
@@ 1,6 1,6 @@
{
  "name": "wigly",
  "version": "0.3.0",
  "version": "0.3.1",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {


@@ 3591,6 3591,11 @@
        }
      }
    },
    "dom-tags": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/dom-tags/-/dom-tags-1.0.0.tgz",
      "integrity": "sha512-d1w/OgiSAxAhJ6Vwp30JGuGpAUeqsNScuw8ihRRlq5dLP/BYBoggqKZvYVn4WaCELnYH8SFXNjFgV2CGVC5CjA=="
    },
    "domain-browser": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",


@@ 4512,14 4517,12 @@
        "balanced-match": {
          "version": "1.0.0",
          "bundled": true,
          "dev": true,
          "optional": true
          "dev": true
        },
        "brace-expansion": {
          "version": "1.1.11",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "balanced-match": "^1.0.0",
            "concat-map": "0.0.1"


@@ 4534,20 4537,17 @@
        "code-point-at": {
          "version": "1.1.0",
          "bundled": true,
          "dev": true,
          "optional": true
          "dev": true
        },
        "concat-map": {
          "version": "0.0.1",
          "bundled": true,
          "dev": true,
          "optional": true
          "dev": true
        },
        "console-control-strings": {
          "version": "1.1.0",
          "bundled": true,
          "dev": true,
          "optional": true
          "dev": true
        },
        "core-util-is": {
          "version": "1.0.2",


@@ 4664,8 4664,7 @@
        "inherits": {
          "version": "2.0.3",
          "bundled": true,
          "dev": true,
          "optional": true
          "dev": true
        },
        "ini": {
          "version": "1.3.5",


@@ 4677,7 4676,6 @@
          "version": "1.0.0",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "number-is-nan": "^1.0.0"
          }


@@ 4692,7 4690,6 @@
          "version": "3.0.4",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "brace-expansion": "^1.1.7"
          }


@@ 4700,14 4697,12 @@
        "minimist": {
          "version": "0.0.8",
          "bundled": true,
          "dev": true,
          "optional": true
          "dev": true
        },
        "minipass": {
          "version": "2.2.4",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "safe-buffer": "^5.1.1",
            "yallist": "^3.0.0"


@@ 4726,7 4721,6 @@
          "version": "0.5.1",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "minimist": "0.0.8"
          }


@@ 4807,8 4801,7 @@
        "number-is-nan": {
          "version": "1.0.1",
          "bundled": true,
          "dev": true,
          "optional": true
          "dev": true
        },
        "object-assign": {
          "version": "4.1.1",


@@ 4820,7 4813,6 @@
          "version": "1.4.0",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "wrappy": "1"
          }


@@ 4942,7 4934,6 @@
          "version": "1.0.2",
          "bundled": true,
          "dev": true,
          "optional": true,
          "requires": {
            "code-point-at": "^1.0.0",
            "is-fullwidth-code-point": "^1.0.0",

M package.json => package.json +4 -1
@@ 1,6 1,6 @@
{
  "name": "wigly",
  "version": "0.3.1",
  "version": "0.3.3",
  "main": "dist/wigly.es5.js",
  "scripts": {
    "example": "parcel example/index.html",


@@ 18,5 18,8 @@
    "google-closure-compiler": "^20181008.0.0",
    "parcel-bundler": "^1.10.3",
    "string-css": "0.0.2"
  },
  "dependencies": {
    "dom-tags": "^1.0.0"
  }
}

M src/superfine.js => src/superfine.js +2 -2
@@ 111,8 111,8 @@ let createElement = function(node, lifecycle, isSvg) {
    node.type === TEXT_NODE
      ? document.createTextNode(node.name)
      : (isSvg = isSvg || node.name === "svg")
        ? document.createElementNS(SVG_NS, node.name)
        : document.createElement(node.name);
      ? document.createElementNS(SVG_NS, node.name)
      : document.createElement(node.name);

  let props = node.props;
  if (props.oncreate) {

M src/wigly-types.js => src/wigly-types.js +2 -0
@@ 38,6 38,8 @@ InternalLifecycle.prototype.ondestroy;
function VDOM() {}
/** @export @type {InternalLifecycle} */
VDOM.prototype.props;
/** @export @type {*} */
VDOM.prototype.element;

/**
 * @record

M src/wigly.js => src/wigly.js +26 -14
@@ 15,9 15,9 @@ let wigly = {
  /**
   * @export
   */
  h: (type, props, ...rest) => {
  h: (type, props, ...children) => {
    if (typeof type !== "function") {
      return superfine.h(type, props, ...rest);
      return superfine.h(type, props, ...children);
    }

    /** @type {ComponentProps} */


@@ 31,7 31,7 @@ let wigly = {

    /** @type {ComponentEnvironment} */
    let seed = originalGetSeedState(type, props) || {};
    let { isActive = true, vars = {}, effects = [], childs = [], node, lastVDOM } = seed;
    let { isActive = false, vars = {}, effects = [], childs = [], node, lastVDOM } = seed;

    let internalUseState = init => {
      let key = stateCount++;


@@ 83,6 83,7 @@ let wigly = {
          childs.push(data);
        }
      }

      save();
    };



@@ 93,7 94,7 @@ let wigly = {
      parentCallback = internalParentCallback;

      /** @type {VDOM} */
      let res = type({ ...props, ["children"]: [].concat.apply([], rest) }); // todo
      let res = type({ ...props, children });

      stateCount = 0;
      effectCount = 0;


@@ 103,29 104,35 @@ let wigly = {
      parentCallback = originalParentCallback;

      /** @type {VDOM} */
      let vdom = { ...res, props: { ...res.props, oncreate, onupdate, onremove, ondestroy } };

      return (lastVDOM = vdom);
      return { ...res, props: { ...res.props, oncreate, onupdate, onremove, ondestroy } };
    };

    let oncreate = el => {
      node = el;
      isActive = true;
      save();
      callEffects();
    };

    let onupdate = () => {
      callEffects();
    let onupdate = el => {
      node = el;
      isActive = true;
      save();
      requestAnimationFrame(callEffects);
    };

    let onremove = (_, remove) => {
    let onremove = (el, remove) => {
      node = el;
      isActive = false;
      save();
      update();
      remove();
    };

    let ondestroy = () => {
      originalParentCallback(env(), true); // reset
    let ondestroy = el => {
      node = el;
      isActive = false;
      originalParentCallback(env(), true); // remove records from parent
    };

    let save = () => {


@@ 138,7 145,9 @@ let wigly = {
    };

    let update = () => {
      superfine.patch(lastVDOM, work(), node);
      if (!isActive) return;
      let container = lastVDOM.element ? lastVDOM.element.parentElement : node.parentElement;
      lastVDOM = superfine.patch(lastVDOM, work(), container);
    };

    let callEffects = () => {


@@ 159,7 168,10 @@ let wigly = {
      }
    };

    return work();
    /** @type {VDOM} */
    let results = (lastVDOM = work());
    return results.element

  },

  /**

D test/main.js => test/main.js +0 -15
@@ 1,15 0,0 @@
import test from "ava";
import { h, render, useState, useEffect } from "../";

require("browser-env")();

test("'Hello, World'", t => {
  let App = () => {
    let [msg] = useState("Hello, World!");
    return h("div", {}, msg);
  };

  render(h(App), document.body);

  t.deepEqual(document.body.firstChild.textContent, "Hello, World!");
});

A test/main.jsx => test/main.jsx +31 -0
@@ 0,0 1,31 @@
import test from "ava";
import { h, render, useState, useEffect } from "../";

require("browser-env")();

test("'Hello, World'", t => {
  let App = () => {
    let [msg] = useState("Hello, World!");
    return <div>{msg}</div>;
  };

  let el = render(h(App), document.body);
  t.deepEqual(el.textContent, "Hello, World!");
});

test("Conditional components work as expected.", t => {
  let App = () => {
    let [active, set] = useState(true);
    return (
      <div>
        {active && <h1>hi</h1>}
        <button onclick={set(!active)} />
      </div>
    );
  };

  let el = render(<App />);
  t.deepEqual(el.textContent, "hi");
  el.querySelector("button").click();
  t.deepEqual(el.textContent, "");
});