~evanj/wigly

51ccbe90a839826b8e7cc47bf27f3acda2117604 — Evan M Jones 2 years ago 7609104
async components built in
8 files changed, 150 insertions(+), 49 deletions(-)

M README.md
M example/main.jsx
D example/styled-wigly.js
M package-lock.json
M package.json
M src/wigly-types.js
M src/wigly.js
M test/main.js
M README.md => README.md +25 -3
@@ 2,7 2,7 @@

# Wigly

A React inspired, component-based UI library for the web. Built with Superfine. Built to be lean.
A React inspired, component-based UI library for the web. Built with [Superfine](https://github.com/jorgebucaran/superfine/). Built to be lean.

## Live example



@@ 26,7 26,8 @@ wigly.render(<App greeting="Hello" />, document.body);
import wigly, { useState } from "wigly";

function Counter() {
  var [count, set] = wigly.useState(0);
  var [count, set] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>


@@ 68,6 69,24 @@ function App(props) {
wigly.render(<App userId={123} />, document.body);
```

## Advanced, lazy/async components

```javascript
import wigly from "wigly";

// A function that returns a promise that will resolve to a
// component can be used as a valid wigly component.
let LazyChild = () => import("./components/child");

let App = () => (
  <div>
    <LazyChild />
  </div>
);

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

## Advanced, creating a 'styled-components' package

```javascript


@@ 81,7 100,10 @@ let styled = tags.reduce(
    [key]: style => props => {
      let [classes] = useState(stringcss.css(style));
      useEffect(stringcss.inject, 0);
      return wigly.h(key, { ...props, class: props.class ? `${classes} ${props.class}` : classes });
      return wigly.h(key, {
        ...props,
        class: props.class ? `${classes} ${props.class}` : classes
      });
    }
  }),
  {}

M example/main.jsx => example/main.jsx +56 -29
@@ 1,41 1,68 @@
// @jsx wigly.h
import wigly, { useState, useEffect } from "../";
import navaid from "navaid";
import "babel-polyfill";

function App(props) {
  var [username, setUsername] = useState();
  var [error, setError] = useState();

  useEffect(
    async function() {
      try {
        var request = await fetch(`/get/user/${props.userId}`);
        var result = await request.json();
        setUsername(result.username);
      } catch (e) {
        setError(e.toString());
      }
    },
    [props.userId]
let def = () => <div />;

function Home() {
  return <div>home</div>;
}

function Blog() {
  return <div>blog</div>;
}

function useRouter(router, routes) {
  let [[comp, params], set] = useState([def, {}]);

  useEffect(() => {
    for (let route of routes) {
      router.on(route.href, params => {
        set([route.component, params]);
      });
    }
  }, 0);

  useEffect(router.listen, 0);

  return [comp, params];
}

function Nav() {
  return (
    <nav>
      <a href="/">home</a>
      <a href="/blog">blog</a>
    </nav>
  );
}

let AsyncNav = () => Promise.resolve({ default: Nav });

let AsyncHome = () =>
  new Promise(resolve => {
    setTimeout(resolve, 250, { default: Home });
  });

let AsyncBlog = () =>
  new Promise(resolve => {
    setTimeout(resolve, 250, { default: Blog });
  });

function App(props) {
  var [Comp, props] = useRouter(new navaid("/"), [
    { href: "/", component: AsyncHome },
    { href: "/err", component: AsyncHome },
    { href: "/blog", component: AsyncBlog }
  ]);

  return (
    <div>
      {(() => {
        switch (true) {
          case !!error: {
            return error;
          }
          case !!username: {
            return <h1>Username: {username}</h1>;
          }
          default: {
            return "loading...";
          }
        }
      })()}
      <AsyncNav />
      <Comp {...props} />
    </div>
  );
}

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

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

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 +15 -0
@@ 8104,6 8104,15 @@
      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
      "dev": true
    },
    "navaid": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/navaid/-/navaid-1.0.0.tgz",
      "integrity": "sha512-K+gfVdiO36xjlv0dDDscX2zN2x+lsxHGgfQqAvKJ11uMxCFVA9MgSVXbAqW1AIq0OXVtPRTUmEHCysSFOa83rA==",
      "dev": true,
      "requires": {
        "regexparam": "^0.0.1"
      }
    },
    "nice-try": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",


@@ 11210,6 11219,12 @@
        "safe-regex": "^1.1.0"
      }
    },
    "regexparam": {
      "version": "0.0.1",
      "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-0.0.1.tgz",
      "integrity": "sha512-ZAWSvAXXL3WaoF9wVCo+PWzIaiQ81jk87OaFOJ2CQ0ld/seonv9kbCmWdEEA4fEY0SQNiSBV0zidWUAw77cryw==",
      "dev": true
    },
    "regexpu-core": {
      "version": "4.2.0",
      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz",

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


@@ 21,6 21,7 @@
    "dom-tags": "^1.0.0",
    "google-closure-compiler": "^20181008.0.0",
    "jest": "^23.6.0",
    "navaid": "^1.0.0",
    "parcel-bundler": "^1.10.3",
    "string-css": "0.0.2"
  },

M src/wigly-types.js => src/wigly-types.js +9 -0
@@ 18,6 18,8 @@ ComponentEnvironment.prototype.lastVDOM;
ComponentEnvironment.prototype.effects;
/** @type {?} */
ComponentEnvironment.prototype.isActive;
/** @type {?} */
ComponentEnvironment.prototype.f;

/**
 * @record


@@ 60,3 62,10 @@ Effect.prototype.cb;
function ComponentProps() {}
/** @export @type {?} */
ComponentProps.prototype.key;

/**
 * @record
 */
function SubPromise() {}
/** @export @type {?} */
ComponentProps.prototype.then;

M src/wigly.js => src/wigly.js +25 -3
@@ 7,6 7,7 @@ let actualUseState = NOOP;
let actualUseEffect = NOOP;
let getSeedState = NOOP;
let parentCallback = NOOP;
let rerenderParent = NOOP; // async components rerender parent to keep vdom consistent

/**
 * @export


@@ 25,13 26,14 @@ let wigly = {

    let originalGetSeedState = getSeedState;
    let originalParentCallback = parentCallback;
    let originalRerenderParent = rerenderParent;

    let stateCount = 0;
    let effectCount = 0;

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

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


@@ 92,9 94,28 @@ let wigly = {
      actualUseEffect = internalUseEffect;
      getSeedState = internalGetSeedState;
      parentCallback = internalParentCallback;
      rerenderParent = update;

      /** @type {VDOM} */
      let res = type({ ...props, children });
      let res = f({ ...props, children });

      if (res instanceof Promise) {
        /** @type {SubPromise} */
        let promise = res;

        /** @type {InternalLifecycle} */
        let asyncLifecycle = {
          oncreate: () => {
            promise.then(file => {
              f = file.default;
              save();
              originalRerenderParent();
            });
          }
        };

        res = superfine.h("template", asyncLifecycle);
      }

      stateCount = 0;
      effectCount = 0;


@@ 102,6 123,7 @@ let wigly = {
      actualUseEffect = NOOP;
      getSeedState = originalGetSeedState;
      parentCallback = originalParentCallback;
      rerenderParent = originalRerenderParent;

      let lc = ["oncreate", "onupdate", "onremove", "ondestroy"].reduce(
        (fns, key) => ({


@@ 159,7 181,7 @@ let wigly = {

    let env = () => {
      /** @type {ComponentEnvironment} */
      return { type, props, vars, childs, node, lastVDOM, effects };
      return { type, props, vars, childs, node, lastVDOM, effects, f };
    };

    let update = () => {

M test/main.js => test/main.js +18 -0
@@ 79,3 79,21 @@ test("Immediate components are removed properly.", t => {
  setter(false);
  t.deepEqual(element.textContent, "");
});

test("Basic lazy components work as expected.", async t => {
  let LazyChild = () => Promise.resolve({ default: () => <div>here we go</div> });

  let App = () => {
    return (
      <div>
        <LazyChild />
      </div>
    );
  };

  var { element } = render(<App />, document.body);
  t.deepEqual(element.textContent, "");

  await new Promise(r => setTimeout(r, 0));
  t.deepEqual(element.textContent, "here we go");
});