~evanj/wigly

934e6eba760814a0ef73c862e3dd14afeeb9eb6f — Evan M Jones 2 years ago bbff9c4
base component state on parent
4 files changed, 190 insertions(+), 130 deletions(-)

M README.md
M package.json
M src/main.js
M webpack.config.js
M README.md => README.md +18 -0
@@ 69,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

M package.json => package.json +1 -1
@@ 1,6 1,6 @@
{
  "name": "wigly",
  "version": "0.5.0",
  "version": "0.5.1",
  "main": "dist/wigly.es5.js",
  "module": "src/main.js",
  "scripts": {

M src/main.js => src/main.js +170 -128
@@ 1,145 1,171 @@
import * as superfine from "superfine";

// globals
let currentlyExecutingComponent; // for hooks
let globalComponentBag = new WeakMap(); // for keeping track of lists of components
let componentBag = new Map(); // holds given component state
// let defer = f => () => Promise.resolve().then.bind(Promise.resolve())(f);

let copy = f => {
  let copied = (...args) => f(...args);
  Object.defineProperty(copied, "isCopied", {
    value: true,
    writable: false
  });
  return copied;
};
import { h as createElement, patch } from "superfine";

let getComponentEnv = ref => {
  let env = componentBag.get(ref);
  return { ...env };
};
// global
let current;

let setComponentEnv = (ref, next) => {
  let base = { effects: {}, states: {}, stateCount: 0, effectCount: 0 };
  let last = getComponentEnv(ref);
  componentBag.set(ref, { ...base, ...last, ...next });
  return getComponentEnv(ref);
};
// util
let defer = Promise.resolve().then.bind(Promise.resolve());

let traverseTree = (tree, f) => {
  let next = f(tree);
  return {
    ...next,
    children: next.children.filter(child => !!child).map(child => traverseTree(child, f))
let debounce = f => {
  let inst;
  return () => {
    clearTimeout(inst);
    inst = setTimeout(f);
  };
};

let updateComponent = ref => {
  let { props, children, vdom, el } = getComponentEnv(ref);

  if (!el || !el.parentElement) {
    return;
  }

  let latestVDOM = traverseTree(vdom, item => {
    if (item.internal) {
      if (item.internal.ref() === item.internal.ref()) {
        let { vdom } = item.internal.env();
        return vdom || item;
      }
      return item;
    } else {
      return item;
// funcs
let runEffects = (el, self) => {
  for (let key in self.effects) {
    let effect = self.effects[key];
    if (
      effect &&
      (typeof effect.prev === "undefined" || effect.args.length === 0 || effect.prev.join() !== effect.args.join())
    ) {
      if (effect.cleanup) effect.cleanup();
      self.effects[key] = { prev: effect.args, cleanup: effect.f(el) };
    }
  });

  vdom = superfine.patch(latestVDOM, h(ref, props, ...children), el.parentElement);
  setComponentEnv({ vdom });
  }
};

let runEffects = async ref => {
  let { effects, el } = getComponentEnv(ref);

  for (let key in effects) {
    let effect = effects[key];

    if (effect.opts.length < 1 || effect.prev.join() !== effect.opts.join()) {
      if (effect.cleanup) effect.cleanup();
      effects[key] = { ...effect, prev: effect.opts, cleanup: await effect.f(el) };
let traverse = (tree, f) => {
  Object.assign(tree, f(tree));
  if (tree.children) {
    for (let key in tree.children) {
      tree.children[key] = traverse(tree.children[key], f);
    }
  }

  setComponentEnv({ effects });
  return tree;
};

export let h = (f, props, ...children) => {
  let isComponent = typeof f === "function";
  props = props || {};
  props.key = props.key || 0;

  if (isComponent && !f.isCopied) {
    let possible = globalComponentBag.get(f);
    if (possible) {
      let specific = possible.get(props.key);
      if (specific) {
        f = specific;
      } else {
        f = copy(f);
        possible.set(props.key, f);
      }
    } else {
      globalComponentBag.set(f, new Map());
      return h(f, props, ...children);
    }
let transformer = async (spec, getEnv, giveEnv, giveVDOM, updateVDOM) => {
  if (typeof spec === "string" || typeof spec === "number") {
    giveVDOM(spec);
    return;
  }

  setComponentEnv(f, { effectCount: 0, stateCount: 0 });
  currentlyExecutingComponent = f;

  return (vdom => {
    return (vdom = {
      ...vdom,
      props: {
        ...vdom.props,
        ...(!isComponent
          ? {}
          : {
  let { f, props, children = [] } = spec;

  children = children.filter(item => !!item);

  if (typeof f === "function") {
    let lastvdom;

    let self = {
      f,
      states: [],
      effects: [],
      children: {},
      ...getEnv(f, props.key),
      iter: 0,
      update: debounce(() => {
        transformer(
          spec,
          getEnv,
          giveEnv,
          next => {
            if (lastvdom && lastvdom.element && lastvdom.element.parentElement) {
              lastvdom = patch(lastvdom, next, lastvdom.element.parentElement);
            }
          },
          updateVDOM
        );
      })
    };

    current = self;
    let res = f({ ...props, children });

    if (res instanceof Promise) {
      let file = await res;
      current = self;
      res = file.default({ ...props, children });
    }

    transformer(
      res,
      (component, key) => {
        return self.children[component] ? self.children[component][key] : {};
      },
      (component, key, env) => {
        self.children[component] = { ...self.children[component], [key]: env };
      },
      vdom => {
        let { oncreate, onupdate, ondestroy } = { ...vdom.props };
        giveVDOM(
          (lastvdom = Object.assign(vdom, {
            props: {
              ...vdom.props,
              oncreate: el => {
                setComponentEnv(f, { vdom, el, props, children });
                setTimeout(runEffects, 1, f);
                if (oncreate) oncreate(el);
                defer(() => {
                  runEffects(el, self);
                  giveEnv(f, props.key, self);
                });
              },
              onupdate: el => {
                setComponentEnv(f, { vdom, el, props, children });
                setTimeout(runEffects, 1, f);
                if (onupdate) onupdate(el);
                defer(() => {
                  updateVDOM(f, props.key, lastvdom);
                  runEffects(el, self);
                  giveEnv(f, props.key, self);
                });
              },
              ondestroy: () => {
                let env = getComponentEnv(f);
                setComponentEnv(f, { states: {} });
                if (Object.keys(env).length) {
                  for (let key of Object.keys(env.effects)) {
                    let { cleanup } = env.effects[key];
                    if (cleanup) cleanup();
                if (ondestroy) ondestroy();
                for (let effect of self.effects) {
                  if (effect && effect.cleanup) {
                    effect.cleanup();
                  }
                }
                giveEnv(f, props.key, {}); // reset state
              }
            })
            },
            internal: { f, self }
          }))
        );
      },
      internal: {
        ref: () => f,
        env: () => getComponentEnv(f)
      (component, key, vdom) => {
        Object.assign(
          lastvdom,
          traverse(lastvdom, item => {
            if (item.internal && item.internal.f === component && key === item.props.key) {
              return vdom;
            } else {
              return item;
            }
          })
        );
        // updateVDOM(f, props.key, lastvdom); // we probably need this yea?
      }
    });
  })(superfine.h(f, props, children));
    );
  } else {
    giveVDOM(
      createElement(
        f,
        props,
        await Promise.all(
          children.map(child => {
            return new Promise(resolve => {
              transformer(child, getEnv, giveEnv, resolve, updateVDOM);
            });
          })
        )
      )
    );
  }
};

export let render = (f, el) => superfine.patch(null, f, el).element;
export let h = (f, props, ...children) => {
  props = props || {};
  children = [].concat.apply([], children);
  return { f, props, children };
};

export let state = init => {
  let ref = currentlyExecutingComponent;
  let { states, stateCount } = getComponentEnv(ref);
  let index = stateCount++;
  let val = states[index];
  let curr = current;
  let key = curr.iter++;
  let val = curr.states[key];

  if (typeof val === "undefined") {
    if (typeof init === "function") {


@@ 149,27 175,43 @@ export let state = init => {
    }
  }

  setComponentEnv(ref, { stateCount });

  return [
    val,
    next => {
      let { states } = getComponentEnv(ref);
      states[index] = next;
      setComponentEnv(ref, { states });
      updateComponent(ref);
      curr.states[key] = next;
      defer(curr.update);
    }
  ];
};

export let effect = (f, ...opts) => {
  let ref = currentlyExecutingComponent;
  let { effects, effectCount } = getComponentEnv(ref);
  let index = effectCount++;
  let base = { prev: [], cleanup: () => {} };
  let prev = effects[index] || {};
  effects[index] = { ...base, ...prev, f, opts };
  setComponentEnv(ref, { effects, effectCount });
export let effect = (f, ...args) => {
  let key = current.iter++;
  let last = current.effects[key];
  current.effects[key] = { prev: [], ...last, f, args };
};

export let render = (f, el) => {
  let cb = () => {};

  defer(() => {
    transformer(
      f,
      () => ({}),
      () => ({}),
      vdom => {
        patch(null, vdom, el);
        cb(vdom.element);
      },
      () => {}
    );
  });

  // promise mock
  return {
    then: f => {
      cb = f;
    }
  };
};

export default { h, render, state, effect };
export default { render, state, effect, h };

M webpack.config.js => webpack.config.js +1 -1
@@ 29,7 29,7 @@ module.exports = [
                [
                  "@babel/preset-env",
                  {
                    // targets: { browsers: ["chrome 70"] },
                    targets: { browsers: ["last 2 versions"] }
                  }
                ]
              ]