Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
smalluban committed May 29, 2019
1 parent ea735ce commit 3fcc9f4
Show file tree
Hide file tree
Showing 19 changed files with 628 additions and 567 deletions.
52 changes: 40 additions & 12 deletions src/cache.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { stringifyElement } from './utils';
import * as emitter from './emitter';

const entries = new WeakMap();
export function getEntry(target, key) {
Expand All @@ -15,27 +16,36 @@ export function getEntry(target, key) {
target,
key,
value: undefined,
deps: new Set(),
contexts: undefined,
deps: undefined,
state: 1,
checksum: 0,
observed: false,
};
targetMap.set(key, entry);
}

return entry;
}

function calculateChecksum({ state, deps }) {
let checksum = state;
deps.forEach((entry) => {
// eslint-disable-next-line no-unused-expressions
entry.target[entry.key];
checksum += entry.state;
});
function calculateChecksum(entry) {
let checksum = entry.state;
if (entry.deps) {
entry.deps.forEach((depEntry) => {
// eslint-disable-next-line no-unused-expressions
depEntry.target[depEntry.key];
checksum += depEntry.state;
});
}

return checksum;
}

function dispatchDeep(entry) {
if (entry.observed) emitter.dispatch(entry);
if (entry.contexts) entry.contexts.forEach(dispatchDeep);
}

let context = null;
export function get(target, key, getter) {
const entry = getEntry(target, key);
Expand All @@ -46,9 +56,15 @@ export function get(target, key, getter) {
}

if (context) {
context.deps = context.deps || new Set();
context.deps.add(entry);
}

if (context && (context.observed || (context.contexts && context.contexts.size))) {
entry.contexts = entry.contexts || new Set();
entry.contexts.add(context);
}

const parentContext = context;
context = entry;

Expand All @@ -57,8 +73,11 @@ export function get(target, key, getter) {
return entry.value;
}

if (entry.deps.size) {
entry.deps.clear();
if (entry.deps && entry.deps.size) {
entry.deps.forEach((depEntry) => {
if (depEntry.contexts) depEntry.contexts.delete(entry);
});
entry.deps = undefined;
}

try {
Expand All @@ -67,6 +86,8 @@ export function get(target, key, getter) {
if (nextValue !== entry.value) {
entry.state += 1;
entry.value = nextValue;

dispatchDeep(entry);
}

entry.checksum = calculateChecksum(entry);
Expand All @@ -79,7 +100,7 @@ export function get(target, key, getter) {
return entry.value;
}

export function set(target, key, setter, value, callback) {
export function set(target, key, setter, value) {
if (context) {
context = null;
throw Error(`Try to set '${key}' of '${stringifyElement(target)}' in get call`);
Expand All @@ -92,7 +113,7 @@ export function set(target, key, setter, value, callback) {
entry.state += 1;
entry.value = newValue;

callback();
dispatchDeep(entry);
}
}

Expand All @@ -105,8 +126,15 @@ export function invalidate(target, key, clearValue) {
const entry = getEntry(target, key);

entry.checksum = 0;
dispatchDeep(entry);

if (clearValue) {
entry.value = undefined;
}
}

export function observe(target, key, fn) {
const entry = getEntry(target, key);
entry.observed = true;
emitter.subscribe(entry, fn);
}
26 changes: 1 addition & 25 deletions src/children.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { deferred } from './utils';

function walk(node, fn, options, items = []) {
Array.from(node.children).forEach((child) => {
const hybrids = child.constructor.hybrids;
Expand All @@ -22,34 +20,12 @@ export default function children(hybridsOrFn, options = { deep: false, nested: f
get(host) { return walk(host, fn, options); },
connect(host, key, invalidate) {
const observer = new MutationObserver(invalidate);
const set = new Set();

const childEventListener = ({ target }) => {
if (!set.size) {
deferred.then(() => {
const list = host[key];
for (let i = 0; i < list.length; i += 1) {
if (set.has(list[i])) {
invalidate(false);
break;
}
}
set.clear();
});
}
set.add(target);
};

observer.observe(host, {
childList: true, subtree: !!options.deep,
});

host.addEventListener('@invalidate', childEventListener);

return () => {
observer.disconnect();
host.removeEventListener('@invalidate', childEventListener);
};
return () => { observer.disconnect(); };
},
};
}
94 changes: 53 additions & 41 deletions src/define.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,34 @@ import property from './property';
import render from './render';

import * as cache from './cache';
import { dispatch, pascalToDash, deferred } from './utils';
import { pascalToDash, deferred } from './utils';

/* istanbul ignore next */
try { process.env.NODE_ENV } catch(e) { var process = { env: { NODE_ENV: 'production' } }; } // eslint-disable-line

const dispatchSet = new Set();

function dispatchInvalidate(host) {
if (!dispatchSet.size) {
deferred.then(() => {
dispatchSet.forEach(target => dispatch(target, '@invalidate', { bubbles: true }));
dispatchSet.clear();
});
}

dispatchSet.add(host);
}

const defaultMethod = (host, value) => value;

function compile(Hybrid, hybrids) {
Hybrid.hybrids = hybrids;
function compile(Hybrid, descriptors) {
Hybrid.hybrids = descriptors;
Hybrid.connects = [];
Hybrid.observers = [];

Object.keys(hybrids).forEach((key) => {
const value = hybrids[key];
const type = typeof value;
Object.keys(descriptors).forEach((key) => {
const desc = descriptors[key];
const type = typeof desc;

let config;

if (type === 'function') {
config = key === 'render' ? render(value) : { get: value };
} else if (value === null || type !== 'object' || (type === 'object' && !value.get && !value.set && !value.connect)) {
config = property(value);
config = key === 'render' ? render(desc) : { get: desc };
} else if (desc === null || type !== 'object' || (type === 'object' && !desc.get && !desc.set && !desc.connect && !desc.observe)) {
config = property(desc);
} else {
config = {
get: value.get || defaultMethod,
set: value.set || (!value.get && defaultMethod) || undefined,
connect: value.connect,
get: desc.get || defaultMethod,
set: desc.set || (!desc.get && defaultMethod) || undefined,
connect: desc.connect,
observe: desc.observe,
};
}

Expand All @@ -49,18 +38,30 @@ function compile(Hybrid, hybrids) {
return cache.get(this, key, config.get);
},
set: config.set && function set(newValue) {
cache.set(this, key, config.set, newValue, () => dispatchInvalidate(this));
cache.set(this, key, config.set, newValue);
},
enumerable: true,
configurable: process.env.NODE_ENV !== 'production',
});

if (config.connect) {
Hybrid.connects.push(host => config.connect(host, key, (clearCache = true) => {
if (clearCache) cache.invalidate(host, key);
dispatchInvalidate(host);
Hybrid.connects.push(host => config.connect(host, key, () => {
cache.invalidate(host, key);
}));
}

if (config.observe) {
Hybrid.observers.push((host) => {
let lastValue;
cache.observe(host, key, () => {
const value = host[key];
if (value !== lastValue) {
config.observe(host, value, lastValue);
}
lastValue = value;
});
});
}
});
}

Expand Down Expand Up @@ -93,7 +94,6 @@ if (process.env.NODE_ENV !== 'production') {
});

node.connectedCallback();
dispatchInvalidate(node);
}
});
updateQueue.clear();
Expand All @@ -103,7 +103,7 @@ if (process.env.NODE_ENV !== 'production') {
};
}

const connects = new WeakMap();
const disconnects = new WeakMap();

function defineElement(tagName, hybridsOrConstructor) {
const type = typeof hybridsOrConstructor;
Expand Down Expand Up @@ -143,20 +143,32 @@ function defineElement(tagName, hybridsOrConstructor) {
class Hybrid extends HTMLElement {
static get name() { return tagName; }

constructor() {
super();
const { observers } = this.constructor;

for (let index = 0; index < observers.length; index += 1) {
observers[index](this);
}
}

connectedCallback() {
const list = this.constructor.connects.reduce((acc, fn) => {
const result = fn(this);
if (result) acc.add(result);
return acc;
}, new Set());

connects.set(this, list);
dispatchInvalidate(this);
const { connects } = this.constructor;
const list = [];

for (let index = 0; index < connects.length; index += 1) {
const disconnect = connects[index](this);
if (disconnect) list.push(disconnect);
}

disconnects.set(this, list);
}

disconnectedCallback() {
const list = connects.get(this);
list.forEach(fn => fn());
const list = disconnects.get(this);
for (let index = 0; index < list.length; index += 1) {
list[index]();
}
}
}

Expand Down
42 changes: 42 additions & 0 deletions src/emitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const targets = new WeakMap();

function getListeners(target) {
let listeners = targets.get(target);
if (!listeners) {
listeners = new Set();
targets.set(target, listeners);
}
return listeners;
}

const queue = new Set();
const run = fn => fn();

function execute() {
try {
queue.forEach((target) => {
try {
getListeners(target).forEach(run);
queue.delete(target);
} catch (e) {
queue.delete(target);
throw e;
}
});
} catch (e) {
if (queue.size) execute();
throw e;
}
}

export function dispatch(target) {
if (!queue.size) {
requestAnimationFrame(execute);
}
queue.add(target);
}

export function subscribe(target, cb) {
getListeners(target).add(cb);
dispatch(target);
}
12 changes: 1 addition & 11 deletions src/parent.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,9 @@ export default function parent(hybridsOrFn) {
get: host => walk(host, fn),
connect(host, key, invalidate) {
const target = host[key];
const cb = (event) => {
if (event.target === target) invalidate(false);
};

if (target) {
target.addEventListener('@invalidate', cb);

return () => {
target.removeEventListener('@invalidate', cb);
invalidate();
};
return invalidate;
}

return false;
},
};
Expand Down

0 comments on commit 3fcc9f4

Please sign in to comment.