Skip to content

Commit

Permalink
Add context apis
Browse files Browse the repository at this point in the history
  • Loading branch information
ryansolid committed May 22, 2019
1 parent a5e7425 commit f79a89b
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 58 deletions.
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// babel.config.js
module.exports = {
presets: ['@babel/preset-env'],
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
plugins: [['babel-plugin-jsx-dom-expressions', {moduleName: './runtime.js'}]]
};
23 changes: 18 additions & 5 deletions dom-expressions.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
module.exports = {
output: 'test/runtime.js',
variables: {
imports: [ `import S from 's-js'` ],
computed: 'S',
sample: 'S.sample',
root: 'S.root',
cleanup: 'S.cleanup'
imports: [ `import {
comp as wrap, sample, root, cleanup, setContext, lookupContext,
getContextOwner as currentContext, makeDataNode
} from '@ryansolid/s-js'` ],
handlePromise: `p => {
const [processing, register] = lookupContext('suspense');
const s = makeDataNode();
register(processing() + 1);
p.then(v => s.next(v)).finally(() => register(processing() - 1));
return s.current.bind(s);
}`,
SuspenseContext: `{
id: 'suspense', initFn: () => {
const s = makeDataNode(0);
return [s.current.bind(s), s.next.bind(s)];
}
}`,
includeContext: true
}
}
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "dom-expressions",
"description": "A Fine-Grained Runtime for Performant DOM Rendering",
"version": "0.8.5",
"version": "0.9.0-beta.0",
"author": "Ryan Carniato",
"license": "MIT",
"repository": {
Expand All @@ -24,9 +24,9 @@
"devDependencies": {
"@babel/core": "7.4.4",
"@babel/preset-env": "^7.4.4",
"babel-plugin-jsx-dom-expressions": "0.8.0",
"babel-plugin-jsx-dom-expressions": "beta",
"coveralls": "3.0.3",
"jest": "24.8.0",
"s-js": "0.4.9"
"@ryansolid/s-js": "0.4.10"
}
}
4 changes: 3 additions & 1 deletion template/runtime.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ export function delegateEvents(eventNames: string[]): void;
export function clearDelegatedEvents(): void;
export function spread(node: HTMLElement, accessor: any): void;
export function classList(node: HTMLElement, value: { [k: string]: boolean; }): void;
export function currentContext(): any;
export function when(parent: Node, accessor: () => any, expr: (...args: any[]) => any, options: any, marker?: Node): void;
export function each(parent: Node, accessor: () => any, expr: (...args: any[]) => any, options: any, marker?: Node): void;
export function suspend(parent: Node, accessor: () => any, expr: (...args: any[]) => any, options: any, marker?: Node): void;
export function portal(parent: Node, accessor: () => any, expr: (...args: any[]) => any, options: any, marker?: Node): void;
export function portal(parent: Node, accessor: () => any, expr: (...args: any[]) => any, options: any, marker?: Node): void;
export function provide(parent: Node, accessor: () => any, expr: (...args: any[]) => any, options: any, marker?: Node): void;
83 changes: 51 additions & 32 deletions template/runtime.ejs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { Attributes } from 'dom-expressions';
<%- imports.join(';\n') %>;

const wrap = <%- computed %>,
root = <%- root %>,
sample = <%- sample %>,
cleanup = <%- cleanup %>;
<%- (function() {
const decls = ['wrap', 'root', 'sample', 'cleanup', 'setContext', 'currentContext', 'SuspenseContext', 'handlePromise']
.filter(decl => locals[decl])
.map(decl => `${decl} = ${locals[decl]}`)
return decls.length ? `const ${decls.join(',\n')};` : '';
})();
%>

const GROUPING = '__rGroup',
FORWARD = 'nextSibling',
BACKWARD = 'previousSibling';
let groupCounter = 0;

export { wrap };
export { wrap<%-locals.includeContext && ', currentContext' %> };

function normalizeIncomingArray(normalized, array) {
for (let i = 0, len = array.length; i < len; i++) {
Expand Down Expand Up @@ -84,6 +87,7 @@ function clearAll(parent, current, marker, startNode) {
function insertExpression(parent, value, current, marker) {
if (value === current) return current;
parent = (marker && marker.parentNode) || parent;
if (value instanceof Promise) value = handlePromise(value);
const t = typeof value;
if (t === 'string' || t === 'number') {
if (t === 'number') value = value.toString();
Expand Down Expand Up @@ -567,55 +571,70 @@ export function each(parent, accessor, expr, options, afterNode) {
}

export function suspend(parent, accessor, expr, options, marker) {
let beforeNode, disposable, current, first = true;
let beforeNode, disposable, current, rendered, isSuspended;
const { fallback } = options,
doc = document.implementation.createHTMLDocument(),
rendered = sample(expr);

doc = document.implementation.createHTMLDocument();
<% if(locals.includeContext) { %>
wrap(() => {
if (accessor) {
isSuspended = accessor;
} else {
const c = SuspenseContext.initFn();
isSuspended = c[0];
setContext(SuspenseContext.id, c)
}
sample(() => {
rendered = sample(expr);
insertExpression(parent, rendered, null, marker);
});
});
<% } else { %>
isSuspended = accessor;
rendered = sample(expr);
<% } %>
if (marker) beforeNode = marker.previousSibling;
for (let name of eventRegistry.keys()) doc.addEventListener(name, eventHandler);
Object.defineProperty(doc.body, 'host', { get() { return (marker && marker.parentNode) || parent; } });
cleanup(function dispose() { disposable && disposable(); });

wrap(cached => {
const value = !!accessor();
wrap((cached = false) => {
const value = !!isSuspended();
let node;
if (value === cached) return cached;
parent = (marker && marker.parentNode) || parent;
if (value) {
if (first) {
insertExpression(doc.body, rendered);
first = false;
} else {
node = beforeNode ? beforeNode.nextSibling : parent.firstChild;
while (node && node !== marker) {
const next = node.nextSibling;
doc.body.appendChild(node);
node = next;
}
node = beforeNode ? beforeNode.nextSibling : parent.firstChild;
while (node && node !== marker) {
const next = node.nextSibling;
doc.body.appendChild(node);
node = next;
}
if (fallback) {
sample(() => root(disposer => {
disposable = disposer;
current = insertExpression(parent, fallback(), null, marker)
current = insertExpression(parent, fallback(), null, marker);
}));
}
return value;
}
if (first) {
insertExpression(parent, rendered, null, marker);
first = false;
} else {
if (disposable) {
clearAll(parent, current, marker, beforeNode ? beforeNode.nextSibling : parent.firstChild);
disposable();
}
while (node = doc.body.firstChild) parent.insertBefore(node, marker);
if (disposable) {
clearAll(parent, current, marker, beforeNode ? beforeNode.nextSibling : parent.firstChild);
disposable();
}
while (node = doc.body.firstChild) parent.insertBefore(node, marker);
return value;
});
}

<% if(locals.includeContext) { %>
export function provide(parent, accessor, expr, options, marker) {
const Context = accessor(),
{ value } = options;
insertExpression(parent, () => sample(() => {
setContext(Context.id, Context.initFn ? Context.initFn(value) : value);
return expr();
}), undefined, marker);
}
<% } %>
export function portal(parent, accessor, expr, options, marker) {
const { useShadow } = options,
container = document.createElement('div'),
Expand Down
2 changes: 1 addition & 1 deletion test/createComponent.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import S from 's-js';
import * as S from '@ryansolid/s-js';

describe('create component with dynamic expressions', () => {
it('should properly create dynamic properties', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/each.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import S from 's-js';
import * as S from '@ryansolid/s-js';

describe('Testing an only child each control flow', () => {
let div, disposer;
Expand Down
2 changes: 1 addition & 1 deletion test/events.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import S from 's-js';
import * as S from '@ryansolid/s-js';

describe('Test Synthetic event bubbling', () => {
const Elements = {
Expand Down
2 changes: 1 addition & 1 deletion test/portal.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import S from 's-js';
import * as S from '@ryansolid/s-js';
import { clearDelegatedEvents } from './runtime';

describe('Testing a simple Portal', () => {
Expand Down
31 changes: 31 additions & 0 deletions test/provide.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as S from '@ryansolid/s-js';

describe('Testing providing simple value', () => {
const Context = {
id: Symbol('context')
}, divs = [];
let disposer, i = 0;
const ChildComponent = () => {
const v = S.lookupContext(Context.id);
return <div ref={divs[i++]}>{v}</div>
},
Component = () =>
<$ provide={Context} value={'hello'}>
<$ provide={Context} value={'hi'}>
<ChildComponent />
</$>
<ChildComponent />
</$>

test('Create provide control flow', () => {
S.root(dispose => {
disposer = dispose;
<Component />
});

expect(divs[0].innerHTML).toBe('hi');
expect(divs[1].innerHTML).toBe('hello');
});

test('dispose', () => disposer());
});
29 changes: 28 additions & 1 deletion test/suspend.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import S from 's-js';
import * as S from '@ryansolid/s-js';

describe('Testing an only child suspend control flow', () => {
let div, disposer;
Expand Down Expand Up @@ -86,5 +86,32 @@ describe('Testing an only child suspend control flow with DOM children and fallb
expect(div.firstChild.innerHTML).toBe('Too Low');
});

test('dispose', () => disposer());
});

describe('Testing a context suspend control flow', () => {
let div, disposer, resolver;
const AsyncComponent = async () =>
await new Promise(resolve => resolver = resolve),
Component = () =>
<div ref={div}><$ suspend><AsyncComponent/></$></div>;

test('Create suspend control flow', () => {
S.root(dispose => {
disposer = dispose;
<Component />
});

expect(div.innerHTML).toBe('');
});

test('Toggle suspend control flow', async (done) => {
resolver('Hi');
await Promise.resolve();
console.log('check')
expect(div.innerHTML).toBe('Hi');
done();
});

test('dispose', () => disposer());
});
2 changes: 1 addition & 1 deletion test/when.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import S from 's-js';
import * as S from '@ryansolid/s-js';

describe('Testing an only child when control flow', () => {
let div, disposer;
Expand Down

0 comments on commit f79a89b

Please sign in to comment.