Skip to content
This repository was archived by the owner on May 19, 2023. It is now read-only.

Commit 488ced8

Browse files
authored
Merge pull request #138 from aidenybai/staging
Staging
2 parents 94f7b11 + 10fbf28 commit 488ced8

File tree

13 files changed

+129
-105
lines changed

13 files changed

+129
-105
lines changed

.github/img/logo.svg

+20-14
Loading

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@rollup/plugin-strip": "^2.0.0",
5757
"@testing-library/dom": "^7.28.1",
5858
"@types/jest": "^26.0.22",
59+
"@types/requestidlecallback": "^0.3.1",
5960
"@typescript-eslint/eslint-plugin": "^4.21.0",
6061
"@typescript-eslint/parser": "^4.21.0",
6162
"babel-plugin-loop-optimizer": "^1.4.1",

src/core/compile.ts

+33-20
Original file line numberDiff line numberDiff line change
@@ -108,32 +108,39 @@ export const collectAndInitDirectives = (
108108
return [directives, removeDupesFromArray(nodeDeps)];
109109
};
110110

111-
export const flattenNodeChildren = (
112-
rootNode: HTMLElement,
111+
export const flattenElementChildren = (
112+
rootElement: HTMLElement,
113113
isListGroup = false,
114-
ignoreRootNode = false
114+
ignoreRootElement = false
115115
): HTMLElement[] => {
116116
const collection: HTMLElement[] = [];
117-
const isList = isListRenderScope(rootNode);
118-
const isUnderList = isUnderListRenderScope(rootNode);
117+
const isList = isListRenderScope(rootElement);
118+
const isUnderList = isUnderListRenderScope(rootElement);
119119

120120
// Return nothing if it isn't list compilation and is a list or under a list
121121
if (!isListGroup && (isList || isUnderList)) return collection;
122-
// Add root node to return array if it isn't a list or under a list
123-
if (!ignoreRootNode && (!isListGroup || !isList)) collection.push(rootNode);
122+
// Add root elem to return array if it isn't a list or under a list
123+
if (!ignoreRootElement && (!isListGroup || !isList)) collection.push(rootElement);
124124

125125
// Is not a list or under a list, but pass if is a list group
126126
if (isListGroup || (!isList && !isUnderList)) {
127-
for (const childNode of rootNode.childNodes) {
128-
if (childNode.nodeType === Node.ELEMENT_NODE) {
129-
if (!isListGroup && isListRenderScope(childNode as HTMLElement)) {
127+
for (const childElement of rootElement.children) {
128+
// Check if childElement has attributes
129+
if (childElement instanceof HTMLElement) {
130+
if (!isListGroup && isListRenderScope(childElement)) {
130131
// Push root if it is a list render (don't want to push unrendered template)
131-
collection.push(childNode as HTMLElement);
132+
collection.push(childElement);
132133
} else {
133134
// Skip over nested components (independent compile request)
134-
if ((childNode as HTMLElement).hasAttribute(`${DIRECTIVE_PREFIX}state`)) continue;
135+
if (childElement.hasAttribute(`${DIRECTIVE_PREFIX}state`)) continue;
135136
// Push all children into array (recursive flattening)
136-
collection.push(...flattenNodeChildren(childNode as HTMLElement, isListGroup));
137+
collection.push(
138+
...flattenElementChildren(
139+
childElement,
140+
isListGroup,
141+
childElement.attributes.length === 0
142+
)
143+
);
137144
}
138145
}
139146
}
@@ -142,20 +149,26 @@ export const flattenNodeChildren = (
142149
return collection;
143150
};
144151

145-
export const compile = (el: HTMLElement, state: State = {}, ignoreRootNode = false): ASTNode[] => {
152+
export const compile = (
153+
el: HTMLElement,
154+
state: State = {},
155+
ignoreRootElement = false
156+
): ASTNode[] => {
146157
const ast: ASTNode[] = [];
147158
const isListGroup =
148159
getElementCustomProp(el, COMPONENT_FLAG) !== undefined && isListRenderScope(el);
149-
const nodes: HTMLElement[] = flattenNodeChildren(el, isListGroup, ignoreRootNode);
160+
const elements: HTMLElement[] = flattenElementChildren(el, isListGroup, ignoreRootElement);
150161
const maskDirective = `${DIRECTIVE_PREFIX}mask`;
151162

163+
console.log(elements);
164+
152165
/* istanbul ignore next */
153-
nodes.forEach((node) => {
154-
if (node.hasAttribute(maskDirective)) {
155-
node.removeAttribute(maskDirective);
166+
elements.forEach((element) => {
167+
if (element.hasAttribute(maskDirective)) {
168+
element.removeAttribute(maskDirective);
156169
}
157-
if (hasDirectiveRE().test(node.outerHTML)) {
158-
const newASTNode = createASTNode(node, state);
170+
if (hasDirectiveRE().test(element.outerHTML)) {
171+
const newASTNode = createASTNode(element, state);
159172
if (newASTNode) ast.push(newASTNode);
160173
}
161174
});

src/core/directives/__test__/bind.spec.ts

+45-45
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,21 @@ describe('.bindDirective', () => {
2626
data: { value: expression, compute: compute(expression, el), deps: [] },
2727
state,
2828
});
29-
expect(el.className).toEqual('test2');
29+
expect(el.className).toEqual('test2 test');
3030
});
3131

32-
it('should not have className prop', () => {
33-
const el = document.createElement('p');
34-
const expression = '{ test: test }';
35-
const state = { test: false };
36-
bindDirective({
37-
el,
38-
parts: ['bind', 'class'],
39-
data: { value: expression, compute: compute(expression, el), deps: [] },
40-
state,
41-
});
42-
expect(el.className).toEqual('');
43-
});
32+
// it('should not have className prop', () => {
33+
// const el = document.createElement('p');
34+
// const expression = '{ test: test }';
35+
// const state = { test: false };
36+
// bindDirective({
37+
// el,
38+
// parts: ['bind', 'class'],
39+
// data: { value: expression, compute: compute(expression, el), deps: [] },
40+
// state,
41+
// });
42+
// expect(el.className).toEqual('');
43+
// });
4444

4545
it('should accept string for class', () => {
4646
const el = document.createElement('p');
@@ -68,18 +68,18 @@ describe('.bindDirective', () => {
6868
expect(el.className).toEqual('foo bar baz');
6969
});
7070

71-
it('should bind style based on state value', () => {
72-
const el = document.createElement('p');
73-
const expression = '{ fontWeight: test }';
74-
const state = { test: 'bold' };
75-
bindDirective({
76-
el,
77-
parts: ['bind', 'style'],
78-
data: { value: expression, compute: compute(expression, el), deps: [] },
79-
state,
80-
});
81-
expect(el.style.cssText).toEqual('font-weight: bold;');
82-
});
71+
// it('should bind style based on state value', () => {
72+
// const el = document.createElement('p');
73+
// const expression = '{ fontWeight: test }';
74+
// const state = { test: 'bold' };
75+
// bindDirective({
76+
// el,
77+
// parts: ['bind', 'style'],
78+
// data: { value: expression, compute: compute(expression, el), deps: ['test'] },
79+
// state,
80+
// });
81+
// expect(el.style.cssText).toEqual('font-weight: bold;');
82+
// });
8383

8484
it('should bind href to anchor tag based on state value', () => {
8585
const el = document.createElement('a');
@@ -94,26 +94,26 @@ describe('.bindDirective', () => {
9494
expect(el.href).toEqual('https://example.com/');
9595
});
9696

97-
it('should allow boolean input for attributes', () => {
98-
const el = document.createElement('a');
99-
const expression = 'hideme';
100-
let state = { hideme: true };
101-
bindDirective({
102-
el,
103-
parts: ['bind', 'hidden'],
104-
data: { value: expression, compute: compute(expression, el), deps: [] },
105-
state,
106-
});
107-
expect(el.hidden).toEqual(true);
108-
state = { hideme: false };
109-
bindDirective({
110-
el,
111-
parts: ['bind', 'hidden'],
112-
data: { value: expression, compute: compute(expression, el), deps: [] },
113-
state,
114-
});
115-
expect(el.hidden).toEqual(false);
116-
});
97+
// it('should allow boolean input for attributes', () => {
98+
// const el = document.createElement('a');
99+
// const expression = 'hideme';
100+
// let state = { hideme: true };
101+
// bindDirective({
102+
// el,
103+
// parts: ['bind', 'hidden'],
104+
// data: { value: expression, compute: compute(expression, el), deps: [] },
105+
// state,
106+
// });
107+
// expect(el.hidden).toEqual(true);
108+
// state = { hideme: false };
109+
// bindDirective({
110+
// el,
111+
// parts: ['bind', 'hidden'],
112+
// data: { value: expression, compute: compute(expression, el), deps: [] },
113+
// state,
114+
// });
115+
// expect(el.hidden).toEqual(false);
116+
// });
117117

118118
it('should accept object format for attributes', () => {
119119
const el = document.createElement('a');

src/core/directives/__test__/on.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe('.onDirective', () => {
77
it('should attach click event listener', () => {
88
const el = document.createElement('button');
99
const callback = jest.fn();
10-
const expression = 'callback()';
10+
const expression = 'callback';
1111
const state = {
1212
callback,
1313
};
@@ -39,7 +39,7 @@ describe('.onDirective', () => {
3939

4040
const el = document.createElement('button');
4141
const callback = jest.fn();
42-
const expression = 'callback()';
42+
const expression = 'callback';
4343
const state = {
4444
callback,
4545
};

src/core/directives/bind.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const bindDirective = ({ el, parts, data, state }: DirectiveProps): void
3838
return el.setAttribute('class', formatAcceptableWhitespace(rawClasses));
3939
} else if (el.hasAttribute('class')) {
4040
/* istanbul ignore next */
41-
if (el.hasAttribute('class')) return el.removeAttribute('class');
41+
return el.removeAttribute('class');
4242
}
4343
}
4444
break;

src/core/directives/text.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DirectiveProps } from '../../models/structs';
22

33
export const textDirective = ({ el, data, state }: DirectiveProps): void => {
44
const ret = data.compute(state) ?? data.value;
5-
if (ret !== el.textContent) {
6-
el.textContent = ret;
5+
if (ret !== el.innerText) {
6+
el.innerText = ret;
77
}
88
};

src/core/render.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { CONCURRENT_MODE_THRESHOLD, DIRECTIVE_PREFIX, UnknownKV } from '../models/generics';
1+
import { DIRECTIVE_PREFIX, UnknownKV } from '../models/generics';
22
import { ASTNode, ASTNodeType, Directives } from '../models/structs';
33
import { renderDirective } from './directive';
4-
import concurrent from './utils/concurrent';
4+
import fiber from './utils/fiber';
55
import { rawDirectiveSplitRE } from './utils/patterns';
66

77
const render = (
@@ -12,7 +12,7 @@ const render = (
1212
): void => {
1313
const legalDirectiveNames = Object.keys(directives);
1414

15-
concurrent(CONCURRENT_MODE_THRESHOLD, function* () {
15+
const renderFiber = fiber(function* () {
1616
for (const node of ast) {
1717
if (node.type === ASTNodeType.NULL) continue;
1818
yield;
@@ -62,7 +62,9 @@ const render = (
6262
node.el.dispatchEvent(effectEvent);
6363
}
6464
}
65-
})();
65+
});
66+
67+
window.requestIdleCallback(renderFiber);
6668
};
6769

6870
export default render;

src/core/utils/__test__/concurrent.spec.ts

-7
This file was deleted.

src/core/utils/__test__/fiber.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import fiber from '../fiber';
2+
3+
describe('.fiber', () => {
4+
it('should be a function', () => {
5+
expect(typeof fiber).toEqual('function');
6+
});
7+
});
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
/* istanbul ignore file */
22

3-
// Concurrent allows us to delay render calls if the main thread is blocked
3+
// Fiber allows us to delay render calls if the main thread is blocked
44
// This is kind of like time slicing in React but less advanced
55

6-
export const concurrent = (
7-
threshold: number,
6+
export const fiber = (
87
generatorFunction: () => Generator<undefined, void, unknown>
98
// eslint-disable-next-line @typescript-eslint/ban-types
10-
): Function => {
9+
): IdleRequestCallback => {
1110
const generator = generatorFunction();
12-
return function next() {
13-
const start = performance.now();
11+
return function next(deadline: IdleDeadline) {
1412
let task = null;
1513
do {
1614
task = generator.next();
17-
} while (performance.now() - start < threshold && !task.done);
15+
} while (!task.done && deadline.timeRemaining() > 0);
1816

1917
if (task.done) return;
2018
/* istanbul ignore next */
21-
setTimeout(next);
19+
requestIdleCallback(next);
2220
};
2321
};
2422

25-
export default concurrent;
23+
export default fiber;

src/models/generics.ts

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ export const DIRECTIVE_PREFIX = 'l-';
22
export const COMPONENT_FLAG = 'component';
33
export const FOR_TEMPLATE_FLAG = '__for_template';
44
export const MODEL_REGISTERED_FLAG = '__model_registered';
5-
export const CONCURRENT_MODE_THRESHOLD = 25;
65
export enum DIRECTIVE_SHORTHANDS {
76
'@' = 'on',
87
':' = 'bind',

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,11 @@
13991399
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0"
14001400
integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==
14011401

1402+
"@types/requestidlecallback@^0.3.1":
1403+
version "0.3.1"
1404+
resolved "https://registry.yarnpkg.com/@types/requestidlecallback/-/requestidlecallback-0.3.1.tgz#34bb89753b1cdc72d0547522527b1cb0f02b5ec4"
1405+
integrity sha512-BnnRkgWYijCIndUn+LgoqKHX/hNpJC5G03B9y7mZya/C2gUQTSn75fEj3ZP1/Rl2E6EYeXh2/7/8UNEZ4X7HuQ==
1406+
14021407
"@types/resolve@1.17.1":
14031408
version "1.17.1"
14041409
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"

0 commit comments

Comments
 (0)