rxflare-js 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/index.html +33 -0
- package/main.js +183 -0
- package/package.json +28 -0
- package/src/compiler.js +322 -0
- package/src/core.js +299 -0
- package/src/dom.js +394 -0
- package/src/index.js +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# RxFlare
|
|
2
|
+
|
|
3
|
+
Fine-grained reactive framework inspired by:
|
|
4
|
+
|
|
5
|
+
- SolidJS
|
|
6
|
+
- Vue Vapor
|
|
7
|
+
- Svelte
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install rxflare
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import {
|
|
19
|
+
state,
|
|
20
|
+
derived,
|
|
21
|
+
compile,
|
|
22
|
+
createComponent,
|
|
23
|
+
render
|
|
24
|
+
} from 'rxflare';
|
|
25
|
+
|
|
26
|
+
const count = state(0);
|
|
27
|
+
|
|
28
|
+
const App =
|
|
29
|
+
compile(`
|
|
30
|
+
<div>
|
|
31
|
+
{{ count() }}
|
|
32
|
+
</div>
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
render(
|
|
36
|
+
() =>
|
|
37
|
+
createComponent(
|
|
38
|
+
App,
|
|
39
|
+
{ count }
|
|
40
|
+
),
|
|
41
|
+
document.body
|
|
42
|
+
);
|
|
43
|
+
```
|
package/index.html
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<title>RxFlare Demo</title>
|
|
7
|
+
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: sans-serif;
|
|
11
|
+
padding: 40px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.done {
|
|
15
|
+
text-decoration: line-through;
|
|
16
|
+
font-size: 50px;
|
|
17
|
+
opacity: 0.5;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
button {
|
|
21
|
+
margin-left: 8px;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
|
|
28
|
+
<div id="app"></div>
|
|
29
|
+
|
|
30
|
+
<script type="module" src="./main.js"></script>
|
|
31
|
+
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
package/main.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
state,
|
|
3
|
+
derived,
|
|
4
|
+
render,
|
|
5
|
+
compile,
|
|
6
|
+
createComponent,
|
|
7
|
+
reconcile
|
|
8
|
+
} from './src/index.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// ======================================================
|
|
13
|
+
// STATE
|
|
14
|
+
// ======================================================
|
|
15
|
+
|
|
16
|
+
const count = state(0);
|
|
17
|
+
|
|
18
|
+
const todos = state([
|
|
19
|
+
{
|
|
20
|
+
id: 1,
|
|
21
|
+
text: 'Build compiler'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 2,
|
|
25
|
+
text: 'Build vapor'
|
|
26
|
+
}
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const double = derived(() => {
|
|
30
|
+
return count() * 2;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// ======================================================
|
|
36
|
+
// TEMPLATES
|
|
37
|
+
// ======================================================
|
|
38
|
+
|
|
39
|
+
const AppTemplate =
|
|
40
|
+
compile(
|
|
41
|
+
`
|
|
42
|
+
<div class="app">
|
|
43
|
+
|
|
44
|
+
<h1>
|
|
45
|
+
Count:
|
|
46
|
+
{{ count() }}
|
|
47
|
+
</h1>
|
|
48
|
+
|
|
49
|
+
<p>
|
|
50
|
+
Double:
|
|
51
|
+
{{ double() }}
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<button>
|
|
55
|
+
INC
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
<button :class="count() % 2 ? 'red' : 'blue'">
|
|
59
|
+
COLOR
|
|
60
|
+
</button>
|
|
61
|
+
|
|
62
|
+
<div id="list"></div>
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
`
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const TodoTemplate =
|
|
69
|
+
compile(
|
|
70
|
+
`
|
|
71
|
+
<div class="todo">
|
|
72
|
+
|
|
73
|
+
{{ item.text }}
|
|
74
|
+
|
|
75
|
+
</div>
|
|
76
|
+
`
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
// ======================================================
|
|
82
|
+
// APP
|
|
83
|
+
// ======================================================
|
|
84
|
+
|
|
85
|
+
function App() {
|
|
86
|
+
|
|
87
|
+
const root =
|
|
88
|
+
createComponent(
|
|
89
|
+
AppTemplate,
|
|
90
|
+
{
|
|
91
|
+
count,
|
|
92
|
+
double
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
// ======================================================
|
|
99
|
+
// BUTTONS
|
|
100
|
+
// ======================================================
|
|
101
|
+
|
|
102
|
+
const buttons =
|
|
103
|
+
root.querySelectorAll('button');
|
|
104
|
+
|
|
105
|
+
buttons[0].onclick = () => {
|
|
106
|
+
|
|
107
|
+
count.set(
|
|
108
|
+
count() + 1
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
buttons[1].onclick = () => {
|
|
113
|
+
|
|
114
|
+
count.set(
|
|
115
|
+
count() + 1
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
// ======================================================
|
|
122
|
+
// LIST
|
|
123
|
+
// ======================================================
|
|
124
|
+
|
|
125
|
+
const list =
|
|
126
|
+
root.querySelector('#list');
|
|
127
|
+
|
|
128
|
+
reconcile(
|
|
129
|
+
|
|
130
|
+
list,
|
|
131
|
+
|
|
132
|
+
todos,
|
|
133
|
+
|
|
134
|
+
item => {
|
|
135
|
+
|
|
136
|
+
return createComponent(
|
|
137
|
+
TodoTemplate,
|
|
138
|
+
{
|
|
139
|
+
item
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
// ======================================================
|
|
148
|
+
// ADD TODO
|
|
149
|
+
// ======================================================
|
|
150
|
+
|
|
151
|
+
setInterval(() => {
|
|
152
|
+
|
|
153
|
+
todos.set([
|
|
154
|
+
|
|
155
|
+
...todos(),
|
|
156
|
+
|
|
157
|
+
{
|
|
158
|
+
id: Date.now(),
|
|
159
|
+
|
|
160
|
+
text:
|
|
161
|
+
'TODO ' +
|
|
162
|
+
todos().length
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
}, 3000);
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
return root;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
// ======================================================
|
|
177
|
+
// RENDER
|
|
178
|
+
// ======================================================
|
|
179
|
+
|
|
180
|
+
render(
|
|
181
|
+
App,
|
|
182
|
+
document.getElementById('app')
|
|
183
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rxflare-js",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"module": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./core": "./src/core.js",
|
|
10
|
+
"./dom": "./src/dom.js",
|
|
11
|
+
"./compiler": "./src/compiler.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"reactive",
|
|
15
|
+
"signals",
|
|
16
|
+
"solidjs",
|
|
17
|
+
"vue",
|
|
18
|
+
"compiler",
|
|
19
|
+
"framework"
|
|
20
|
+
],
|
|
21
|
+
"author": "msfm2018",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"description": "Fine-grained reactive UI framework",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/msfm2018/rxflare-js"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/compiler.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// Fixed version of rxflare-compiler.js
|
|
2
|
+
// Key fixes:
|
|
3
|
+
// 1. Path calculation bug during mutations (sibling index shifts)
|
|
4
|
+
// 2. Better handling using markers for dynamic parts
|
|
5
|
+
// 3. Attach instructions via a registry using node references (with clone mapping)
|
|
6
|
+
|
|
7
|
+
import { root } from './core.js';
|
|
8
|
+
import { insert, prop, show, reconcile } from './dom.js';
|
|
9
|
+
|
|
10
|
+
export function compile(htmlString) {
|
|
11
|
+
const parser = new DOMParser();
|
|
12
|
+
const doc = parser.parseFromString(htmlString, 'text/html');
|
|
13
|
+
const fragment = document.createDocumentFragment();
|
|
14
|
+
|
|
15
|
+
while (doc.body.firstChild) {
|
|
16
|
+
fragment.appendChild(doc.body.firstChild);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const instructions = [];
|
|
20
|
+
const nodeToIns = new Map(); // For elements/markers
|
|
21
|
+
|
|
22
|
+
// 深度优先遍历,处理指令并插入 marker
|
|
23
|
+
function traverse(node) {
|
|
24
|
+
if (!node) return;
|
|
25
|
+
|
|
26
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
27
|
+
// 处理 :if
|
|
28
|
+
if (node.hasAttribute(':if')) {
|
|
29
|
+
const expr = node.getAttribute(':if');
|
|
30
|
+
const subNode = node.cloneNode(true);
|
|
31
|
+
subNode.removeAttribute(':if');
|
|
32
|
+
|
|
33
|
+
const marker = document.createComment(`rx-if:${expr}`);
|
|
34
|
+
node.parentNode.replaceChild(marker, node);
|
|
35
|
+
|
|
36
|
+
instructions.push({
|
|
37
|
+
type: 'if',
|
|
38
|
+
marker,
|
|
39
|
+
getter: compileExpression(expr),
|
|
40
|
+
subTemplate: compile(subNode.outerHTML)
|
|
41
|
+
});
|
|
42
|
+
nodeToIns.set(marker, instructions[instructions.length - 1]);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 处理 :for
|
|
47
|
+
if (node.hasAttribute(':for')) {
|
|
48
|
+
const rawExpr = node.getAttribute(':for');
|
|
49
|
+
const match = rawExpr.match(/^\s*(?:([\w\s,]+)|(?:\(([\w\s,]+)\)))\s+in\s+(.+)$/);
|
|
50
|
+
if (!match) throw new Error(`RxFlare 语法错误: :for 格式必须为 'item in list'`);
|
|
51
|
+
|
|
52
|
+
const iterators = (match[1] || match[2]).split(',').map(s => s.trim());
|
|
53
|
+
const itemParam = iterators[0];
|
|
54
|
+
const indexParam = iterators[1] || '$index';
|
|
55
|
+
const listExpr = match[3].trim();
|
|
56
|
+
|
|
57
|
+
const subNode = node.cloneNode(true);
|
|
58
|
+
subNode.removeAttribute(':for');
|
|
59
|
+
|
|
60
|
+
const marker = document.createComment(`rx-for:${listExpr}`);
|
|
61
|
+
node.parentNode.replaceChild(marker, node);
|
|
62
|
+
|
|
63
|
+
instructions.push({
|
|
64
|
+
type: 'for',
|
|
65
|
+
marker,
|
|
66
|
+
getter: compileExpression(listExpr),
|
|
67
|
+
itemParam,
|
|
68
|
+
indexParam,
|
|
69
|
+
subTemplate: compile(subNode.outerHTML)
|
|
70
|
+
});
|
|
71
|
+
nodeToIns.set(marker, instructions[instructions.length - 1]);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 处理属性绑定 (包括 :class, :style 等)
|
|
76
|
+
const attrs = Array.from(node.attributes);
|
|
77
|
+
attrs.forEach(attr => {
|
|
78
|
+
const name = attr.name;
|
|
79
|
+
const expr = attr.value;
|
|
80
|
+
|
|
81
|
+
if (name.startsWith('@') || name.startsWith(':on')) {
|
|
82
|
+
const eventName = name.startsWith('@') ? name.slice(1) : name.slice(3);
|
|
83
|
+
node.removeAttribute(name);
|
|
84
|
+
instructions.push({
|
|
85
|
+
type: 'event',
|
|
86
|
+
target: node, // element itself
|
|
87
|
+
eventName,
|
|
88
|
+
getter: compileExpression(expr)
|
|
89
|
+
});
|
|
90
|
+
nodeToIns.set(node, instructions[instructions.length - 1]); // last one, but better collect list
|
|
91
|
+
} else if (name === ':model') {
|
|
92
|
+
node.removeAttribute(name);
|
|
93
|
+
instructions.push({
|
|
94
|
+
type: 'model',
|
|
95
|
+
target: node,
|
|
96
|
+
propName: expr.trim()
|
|
97
|
+
});
|
|
98
|
+
nodeToIns.set(node, instructions[instructions.length - 1]);
|
|
99
|
+
} else if (name.startsWith(':')) {
|
|
100
|
+
const propName = name.slice(1);
|
|
101
|
+
node.removeAttribute(name);
|
|
102
|
+
instructions.push({
|
|
103
|
+
type: 'prop',
|
|
104
|
+
target: node,
|
|
105
|
+
propName,
|
|
106
|
+
getter: compileExpression(expr)
|
|
107
|
+
});
|
|
108
|
+
nodeToIns.set(node, instructions[instructions.length - 1]);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// 处理 TEXT 插值
|
|
113
|
+
else if (node.nodeType === Node.TEXT_NODE) {
|
|
114
|
+
const text = node.nodeValue;
|
|
115
|
+
const regex = /\{\{(.*?)\}\}/g;
|
|
116
|
+
|
|
117
|
+
if (regex.test(text)) {
|
|
118
|
+
const parent = node.parentNode;
|
|
119
|
+
const container = document.createDocumentFragment();
|
|
120
|
+
const segments = text.split(/\{\{(.*?)\}\}/g);
|
|
121
|
+
|
|
122
|
+
segments.forEach((seg, index) => {
|
|
123
|
+
if (index % 2 === 0) {
|
|
124
|
+
if (seg) container.appendChild(document.createTextNode(seg));
|
|
125
|
+
} else {
|
|
126
|
+
const marker = document.createComment('rx-text');
|
|
127
|
+
container.appendChild(marker);
|
|
128
|
+
|
|
129
|
+
instructions.push({
|
|
130
|
+
type: 'text',
|
|
131
|
+
marker,
|
|
132
|
+
getter: compileExpression(seg.trim())
|
|
133
|
+
});
|
|
134
|
+
nodeToIns.set(marker, instructions[instructions.length - 1]);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
parent.replaceChild(container, node);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 递归子节点
|
|
144
|
+
let child = node.firstChild;
|
|
145
|
+
while (child) {
|
|
146
|
+
const next = child.nextSibling;
|
|
147
|
+
traverse(child);
|
|
148
|
+
child = next;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Array.from(fragment.childNodes).forEach(child => traverse(child));
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
template: fragment,
|
|
156
|
+
instructions,
|
|
157
|
+
// nodeToIns not needed for runtime
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function compileExpression(expr) {
|
|
162
|
+
try {
|
|
163
|
+
return new Function('scope', `
|
|
164
|
+
try {
|
|
165
|
+
with(scope) { return ${expr}; }
|
|
166
|
+
} catch(e) {
|
|
167
|
+
console.error('Expr error:', e);
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
`);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error(`RxFlare 表达式解析错误: ${expr}`);
|
|
173
|
+
return () => '';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// New createComponent using marker/target references + clone mapping
|
|
180
|
+
export function createComponent(compiledResult, scope = {}) {
|
|
181
|
+
return root(dispose => {
|
|
182
|
+
const clone = compiledResult.template.cloneNode(true);
|
|
183
|
+
|
|
184
|
+
// Create mapping from original nodes to cloned nodes by traversing both in parallel
|
|
185
|
+
const nodeMap = new Map();
|
|
186
|
+
|
|
187
|
+
function mapNodes(original, cloned) {
|
|
188
|
+
if (!original || !cloned) return;
|
|
189
|
+
nodeMap.set(original, cloned);
|
|
190
|
+
|
|
191
|
+
// Map children
|
|
192
|
+
let oChild = original.firstChild;
|
|
193
|
+
let cChild = cloned.firstChild;
|
|
194
|
+
while (oChild && cChild) {
|
|
195
|
+
mapNodes(oChild, cChild);
|
|
196
|
+
oChild = oChild.nextSibling;
|
|
197
|
+
cChild = cChild.nextSibling;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
mapNodes(compiledResult.template, clone);
|
|
202
|
+
|
|
203
|
+
// Apply all instructions using mapped nodes
|
|
204
|
+
compiledResult.instructions.forEach(ins => {
|
|
205
|
+
let target;
|
|
206
|
+
if (ins.marker) {
|
|
207
|
+
target = nodeMap.get(ins.marker);
|
|
208
|
+
} else if (ins.target) {
|
|
209
|
+
target = nodeMap.get(ins.target);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!target) {
|
|
213
|
+
console.warn('Target not found for instruction', ins);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
switch (ins.type) {
|
|
218
|
+
case 'text':
|
|
219
|
+
insert(target, () => ins.getter(scope));
|
|
220
|
+
break;
|
|
221
|
+
case 'prop':
|
|
222
|
+
prop(target, ins.propName, () => ins.getter(scope));
|
|
223
|
+
break;
|
|
224
|
+
case 'event': {
|
|
225
|
+
prop(
|
|
226
|
+
target,
|
|
227
|
+
`on${ins.eventName}`,
|
|
228
|
+
e => {
|
|
229
|
+
|
|
230
|
+
const eventScope =
|
|
231
|
+
Object.create(scope);
|
|
232
|
+
|
|
233
|
+
eventScope.$event = e;
|
|
234
|
+
|
|
235
|
+
return ins.getter(eventScope);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
case 'model':
|
|
246
|
+
if (target.tagName === 'INPUT' && (target.type === 'checkbox' || target.type === 'radio')) {
|
|
247
|
+
prop(target, 'checked', () => scope[ins.propName]);
|
|
248
|
+
target.addEventListener('change', e => {
|
|
249
|
+
if (typeof scope[ins.propName] !== 'undefined') scope[ins.propName] = e.target.checked;
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
prop(target, 'value', () => scope[ins.propName]);
|
|
253
|
+
target.addEventListener('input', e => {
|
|
254
|
+
if (typeof scope[ins.propName] !== 'undefined') scope[ins.propName] = e.target.value;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
case 'if':
|
|
259
|
+
show(target, () => !!ins.getter(scope), () => {
|
|
260
|
+
return createComponent(ins.subTemplate, scope);
|
|
261
|
+
});
|
|
262
|
+
break;
|
|
263
|
+
case 'for': {
|
|
264
|
+
const endMarker =
|
|
265
|
+
document.createComment('rx-for-end');
|
|
266
|
+
|
|
267
|
+
target.parentNode.insertBefore(
|
|
268
|
+
endMarker,
|
|
269
|
+
target.nextSibling
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
reconcile(
|
|
273
|
+
target,
|
|
274
|
+
endMarker,
|
|
275
|
+
() => ins.getter(scope) || [],
|
|
276
|
+
(item, index) => {
|
|
277
|
+
const itemScope =
|
|
278
|
+
Object.create(scope);
|
|
279
|
+
|
|
280
|
+
itemScope[ins.itemParam] = item;
|
|
281
|
+
itemScope[ins.indexParam] = index;
|
|
282
|
+
|
|
283
|
+
return createComponent(
|
|
284
|
+
ins.subTemplate,
|
|
285
|
+
itemScope
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
const first =
|
|
299
|
+
clone.firstElementChild ||
|
|
300
|
+
clone.firstChild;
|
|
301
|
+
|
|
302
|
+
if (first) {
|
|
303
|
+
first.dispose = dispose;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return first;
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
// const first = clone.firstElementChild || clone.firstChild;
|
|
310
|
+
// if (first) first.dispose = dispose;
|
|
311
|
+
|
|
312
|
+
// const wrapper =
|
|
313
|
+
// document.createElement('div');
|
|
314
|
+
|
|
315
|
+
// wrapper.appendChild(clone);
|
|
316
|
+
|
|
317
|
+
// wrapper.dispose = dispose;
|
|
318
|
+
|
|
319
|
+
// return wrapper;
|
|
320
|
+
|
|
321
|
+
});
|
|
322
|
+
}
|
package/src/core.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// ======================================================
|
|
2
|
+
// RXFLARE CORE NEXT++
|
|
3
|
+
// ======================================================
|
|
4
|
+
|
|
5
|
+
let activeEffect = null;
|
|
6
|
+
|
|
7
|
+
const effectStack = [];
|
|
8
|
+
|
|
9
|
+
const scheduled = new Set();
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// ======================================================
|
|
14
|
+
// OWNER TREE
|
|
15
|
+
// ======================================================
|
|
16
|
+
|
|
17
|
+
let currentOwner = null;
|
|
18
|
+
|
|
19
|
+
function createOwner(parent = currentOwner) {
|
|
20
|
+
|
|
21
|
+
const owner = {
|
|
22
|
+
parent,
|
|
23
|
+
cleanups: [],
|
|
24
|
+
effects: []
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return owner;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function onCleanup(fn) {
|
|
31
|
+
|
|
32
|
+
if (currentOwner) {
|
|
33
|
+
currentOwner.cleanups.push(fn);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function cleanupOwner(owner) {
|
|
38
|
+
|
|
39
|
+
owner.cleanups.forEach(fn => fn());
|
|
40
|
+
|
|
41
|
+
owner.cleanups.length = 0;
|
|
42
|
+
|
|
43
|
+
owner.effects.forEach(e => e.stop());
|
|
44
|
+
|
|
45
|
+
owner.effects.length = 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// ======================================================
|
|
51
|
+
// SIGNAL
|
|
52
|
+
// ======================================================
|
|
53
|
+
|
|
54
|
+
export function state(initial) {
|
|
55
|
+
|
|
56
|
+
let value = initial;
|
|
57
|
+
|
|
58
|
+
const subscribers = new Set();
|
|
59
|
+
|
|
60
|
+
function signal() {
|
|
61
|
+
|
|
62
|
+
if (activeEffect) {
|
|
63
|
+
|
|
64
|
+
subscribers.add(activeEffect);
|
|
65
|
+
|
|
66
|
+
activeEffect.deps.add(subscribers);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
signal.set = next => {
|
|
73
|
+
|
|
74
|
+
if (Object.is(value, next)) return;
|
|
75
|
+
|
|
76
|
+
value = next;
|
|
77
|
+
|
|
78
|
+
subscribers.forEach(schedule);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
signal.peek = () => value;
|
|
82
|
+
|
|
83
|
+
return signal;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
// ======================================================
|
|
89
|
+
// EFFECT
|
|
90
|
+
// ======================================================
|
|
91
|
+
|
|
92
|
+
export function effect(fn) {
|
|
93
|
+
|
|
94
|
+
const owner = currentOwner;
|
|
95
|
+
|
|
96
|
+
const effectFn = {
|
|
97
|
+
|
|
98
|
+
active: true,
|
|
99
|
+
|
|
100
|
+
deps: new Set(),
|
|
101
|
+
|
|
102
|
+
cleanups: [],
|
|
103
|
+
|
|
104
|
+
run() {
|
|
105
|
+
|
|
106
|
+
if (!effectFn.active) return;
|
|
107
|
+
|
|
108
|
+
cleanup(effectFn);
|
|
109
|
+
|
|
110
|
+
effectStack.push(effectFn);
|
|
111
|
+
|
|
112
|
+
activeEffect = effectFn;
|
|
113
|
+
|
|
114
|
+
const prevOwner = currentOwner;
|
|
115
|
+
|
|
116
|
+
currentOwner = owner;
|
|
117
|
+
|
|
118
|
+
fn();
|
|
119
|
+
|
|
120
|
+
currentOwner = prevOwner;
|
|
121
|
+
|
|
122
|
+
effectStack.pop();
|
|
123
|
+
|
|
124
|
+
activeEffect =
|
|
125
|
+
effectStack[
|
|
126
|
+
effectStack.length - 1
|
|
127
|
+
] || null;
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
stop() {
|
|
131
|
+
|
|
132
|
+
if (!effectFn.active) return;
|
|
133
|
+
|
|
134
|
+
effectFn.active = false;
|
|
135
|
+
|
|
136
|
+
cleanup(effectFn);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (owner) {
|
|
141
|
+
owner.effects.push(effectFn);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
effectFn.run();
|
|
145
|
+
|
|
146
|
+
return effectFn;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function cleanup(effectFn) {
|
|
150
|
+
|
|
151
|
+
effectFn.deps.forEach(dep => {
|
|
152
|
+
dep.delete(effectFn);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
effectFn.deps.clear();
|
|
156
|
+
|
|
157
|
+
effectFn.cleanups.forEach(fn => fn());
|
|
158
|
+
|
|
159
|
+
effectFn.cleanups.length = 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function schedule(effectFn) {
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
!effectFn.active ||
|
|
166
|
+
scheduled.has(effectFn)
|
|
167
|
+
) return;
|
|
168
|
+
|
|
169
|
+
scheduled.add(effectFn);
|
|
170
|
+
|
|
171
|
+
queueMicrotask(() => {
|
|
172
|
+
|
|
173
|
+
scheduled.delete(effectFn);
|
|
174
|
+
|
|
175
|
+
effectFn.run();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
// ======================================================
|
|
182
|
+
// ROOT
|
|
183
|
+
// ======================================================
|
|
184
|
+
|
|
185
|
+
export function root(fn) {
|
|
186
|
+
|
|
187
|
+
const prev = currentOwner;
|
|
188
|
+
|
|
189
|
+
const owner = createOwner(prev);
|
|
190
|
+
|
|
191
|
+
currentOwner = owner;
|
|
192
|
+
|
|
193
|
+
const result = fn(() => {
|
|
194
|
+
cleanupOwner(owner);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
currentOwner = prev;
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
// ======================================================
|
|
205
|
+
// MEMO
|
|
206
|
+
// ======================================================
|
|
207
|
+
|
|
208
|
+
export function derived(fn) {
|
|
209
|
+
|
|
210
|
+
const s = state();
|
|
211
|
+
|
|
212
|
+
effect(() => {
|
|
213
|
+
s.set(fn());
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return s;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
// ======================================================
|
|
222
|
+
// TEMPLATE CACHE
|
|
223
|
+
// ======================================================
|
|
224
|
+
|
|
225
|
+
const templateCache = new Map();
|
|
226
|
+
|
|
227
|
+
export function template(html) {
|
|
228
|
+
|
|
229
|
+
if (templateCache.has(html)) {
|
|
230
|
+
return templateCache.get(html);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const tpl =
|
|
234
|
+
document.createElement('template');
|
|
235
|
+
|
|
236
|
+
tpl.innerHTML = html;
|
|
237
|
+
|
|
238
|
+
templateCache.set(html, tpl);
|
|
239
|
+
|
|
240
|
+
return tpl;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function cloneTemplate(html) {
|
|
244
|
+
|
|
245
|
+
return template(html)
|
|
246
|
+
.content
|
|
247
|
+
.cloneNode(true);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ======================================================
|
|
251
|
+
// RENDER
|
|
252
|
+
// ======================================================
|
|
253
|
+
|
|
254
|
+
export function render(
|
|
255
|
+
component,
|
|
256
|
+
root
|
|
257
|
+
) {
|
|
258
|
+
|
|
259
|
+
const node = component();
|
|
260
|
+
|
|
261
|
+
root.appendChild(node);
|
|
262
|
+
|
|
263
|
+
return () => {
|
|
264
|
+
|
|
265
|
+
if (node.dispose) {
|
|
266
|
+
node.dispose();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
node.remove();
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
export function store(obj) {
|
|
275
|
+
const signals = {};
|
|
276
|
+
|
|
277
|
+
return new Proxy(obj, {
|
|
278
|
+
get(target, key) {
|
|
279
|
+
if (!signals[key]) {
|
|
280
|
+
signals[key] = state(target[key]);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return signals[key]();
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
set(target, key, value) {
|
|
287
|
+
target[key] = value;
|
|
288
|
+
|
|
289
|
+
if (!signals[key]) {
|
|
290
|
+
signals[key] = state(value);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
signals[key].set(value);
|
|
294
|
+
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
});
|
|
299
|
+
}
|
package/src/dom.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
// ======================================================
|
|
2
|
+
// RXFLARE DOM NEXT++
|
|
3
|
+
// ======================================================
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
effect,
|
|
7
|
+
onCleanup
|
|
8
|
+
} from './core.js';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// ======================================================
|
|
13
|
+
// INSERT
|
|
14
|
+
// ======================================================
|
|
15
|
+
|
|
16
|
+
export function insert(marker, value) {
|
|
17
|
+
const parent = marker.parentNode;
|
|
18
|
+
|
|
19
|
+
let current = null;
|
|
20
|
+
|
|
21
|
+
effect(() => {
|
|
22
|
+
const next =
|
|
23
|
+
typeof value === 'function'
|
|
24
|
+
? value()
|
|
25
|
+
: value;
|
|
26
|
+
|
|
27
|
+
patch(next);
|
|
28
|
+
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function patch(v) {
|
|
32
|
+
if (
|
|
33
|
+
typeof v === 'string' ||
|
|
34
|
+
typeof v === 'number'
|
|
35
|
+
) {
|
|
36
|
+
if (
|
|
37
|
+
current &&
|
|
38
|
+
current.nodeType === Node.TEXT_NODE
|
|
39
|
+
) {
|
|
40
|
+
current.nodeValue = v;
|
|
41
|
+
} else {
|
|
42
|
+
clear();
|
|
43
|
+
|
|
44
|
+
current =
|
|
45
|
+
document.createTextNode(v);
|
|
46
|
+
|
|
47
|
+
parent.insertBefore(
|
|
48
|
+
current,
|
|
49
|
+
marker
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (v instanceof Node) {
|
|
57
|
+
if (current !== v) {
|
|
58
|
+
clear();
|
|
59
|
+
|
|
60
|
+
current = v;
|
|
61
|
+
|
|
62
|
+
parent.insertBefore(v, marker);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (v == null || v === false) {
|
|
69
|
+
clear();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function clear() {
|
|
75
|
+
if (!current) return;
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
current.dispose?.();
|
|
79
|
+
|
|
80
|
+
current.remove?.();
|
|
81
|
+
|
|
82
|
+
current = null;
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// ======================================================
|
|
91
|
+
// PROP
|
|
92
|
+
// ======================================================
|
|
93
|
+
|
|
94
|
+
export function prop(
|
|
95
|
+
el,
|
|
96
|
+
key,
|
|
97
|
+
value
|
|
98
|
+
) {
|
|
99
|
+
|
|
100
|
+
// events
|
|
101
|
+
if (key.startsWith('on')) {
|
|
102
|
+
setProp(el, key, value);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// reactive bindings
|
|
107
|
+
if (typeof value === 'function') {
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
effect(() => {
|
|
111
|
+
setProp(el, key, value());
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
} else {
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
setProp(el, key, value);
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
function setProp(
|
|
126
|
+
el,
|
|
127
|
+
key,
|
|
128
|
+
value
|
|
129
|
+
) {
|
|
130
|
+
|
|
131
|
+
// event
|
|
132
|
+
if (key.startsWith('on')) {
|
|
133
|
+
|
|
134
|
+
const event =
|
|
135
|
+
key.slice(2).toLowerCase();
|
|
136
|
+
|
|
137
|
+
const store =
|
|
138
|
+
el._vei || (el._vei = {});
|
|
139
|
+
|
|
140
|
+
let invoker =
|
|
141
|
+
store[event];
|
|
142
|
+
|
|
143
|
+
if (!invoker) {
|
|
144
|
+
|
|
145
|
+
invoker = e => {
|
|
146
|
+
invoker.value?.(e);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
store[event] = invoker;
|
|
150
|
+
|
|
151
|
+
el.addEventListener(
|
|
152
|
+
event,
|
|
153
|
+
invoker
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
invoker.value = value;
|
|
158
|
+
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
value == null ||
|
|
164
|
+
value === false
|
|
165
|
+
) {
|
|
166
|
+
|
|
167
|
+
el.removeAttribute(key);
|
|
168
|
+
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (key === 'class') {
|
|
173
|
+
|
|
174
|
+
el.className = value;
|
|
175
|
+
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (key === 'style') {
|
|
180
|
+
|
|
181
|
+
Object.assign(el.style, value);
|
|
182
|
+
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (key in el) {
|
|
187
|
+
|
|
188
|
+
el[key] = value;
|
|
189
|
+
|
|
190
|
+
} else {
|
|
191
|
+
|
|
192
|
+
el.setAttribute(key, value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
// ======================================================
|
|
199
|
+
// SHOW
|
|
200
|
+
// ======================================================
|
|
201
|
+
|
|
202
|
+
export function show(
|
|
203
|
+
marker,
|
|
204
|
+
condition,
|
|
205
|
+
render
|
|
206
|
+
) {
|
|
207
|
+
|
|
208
|
+
insert(marker, () => {
|
|
209
|
+
|
|
210
|
+
return condition()
|
|
211
|
+
? render()
|
|
212
|
+
: null;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
// ======================================================
|
|
219
|
+
// EACH
|
|
220
|
+
// ======================================================
|
|
221
|
+
|
|
222
|
+
export function each(
|
|
223
|
+
marker,
|
|
224
|
+
list,
|
|
225
|
+
renderItem
|
|
226
|
+
) {
|
|
227
|
+
|
|
228
|
+
insert(marker, () => {
|
|
229
|
+
|
|
230
|
+
return list().map(renderItem);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ======================================================
|
|
235
|
+
// RECONCILE
|
|
236
|
+
// ======================================================
|
|
237
|
+
|
|
238
|
+
export function reconcile(
|
|
239
|
+
containerOrStart,
|
|
240
|
+
endMarkerOrList,
|
|
241
|
+
listOrRender,
|
|
242
|
+
renderItemOrKey,
|
|
243
|
+
keyFn = item => item?.id ?? item
|
|
244
|
+
) {
|
|
245
|
+
|
|
246
|
+
// ======================================================
|
|
247
|
+
// COMPAT MODE
|
|
248
|
+
// reconcile(container, list, renderItem)
|
|
249
|
+
// ======================================================
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
!(endMarkerOrList instanceof Node)
|
|
253
|
+
) {
|
|
254
|
+
|
|
255
|
+
const container =
|
|
256
|
+
containerOrStart;
|
|
257
|
+
|
|
258
|
+
const list =
|
|
259
|
+
endMarkerOrList;
|
|
260
|
+
|
|
261
|
+
const renderItem =
|
|
262
|
+
listOrRender;
|
|
263
|
+
|
|
264
|
+
const startMarker =
|
|
265
|
+
document.createComment(
|
|
266
|
+
'rx-start'
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const endMarker =
|
|
270
|
+
document.createComment(
|
|
271
|
+
'rx-end'
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
container.appendChild(
|
|
275
|
+
startMarker
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
container.appendChild(
|
|
279
|
+
endMarker
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
return reconcile(
|
|
283
|
+
startMarker,
|
|
284
|
+
endMarker,
|
|
285
|
+
list,
|
|
286
|
+
renderItem,
|
|
287
|
+
renderItemOrKey
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
// ======================================================
|
|
294
|
+
// NORMAL MODE
|
|
295
|
+
// reconcile(start, end, list, render)
|
|
296
|
+
// ======================================================
|
|
297
|
+
|
|
298
|
+
const startMarker =
|
|
299
|
+
containerOrStart;
|
|
300
|
+
|
|
301
|
+
const endMarker =
|
|
302
|
+
endMarkerOrList;
|
|
303
|
+
|
|
304
|
+
const list =
|
|
305
|
+
listOrRender;
|
|
306
|
+
|
|
307
|
+
const renderItem =
|
|
308
|
+
renderItemOrKey;
|
|
309
|
+
|
|
310
|
+
let oldMap = new Map();
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
effect(() => {
|
|
315
|
+
|
|
316
|
+
const items =
|
|
317
|
+
typeof list === 'function'
|
|
318
|
+
? list() || []
|
|
319
|
+
: [];
|
|
320
|
+
|
|
321
|
+
const newMap = new Map();
|
|
322
|
+
|
|
323
|
+
const newNodes = [];
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
items.forEach((item, index) => {
|
|
328
|
+
|
|
329
|
+
const key =
|
|
330
|
+
keyFn(item);
|
|
331
|
+
|
|
332
|
+
let node =
|
|
333
|
+
oldMap.get(key);
|
|
334
|
+
|
|
335
|
+
if (!node) {
|
|
336
|
+
|
|
337
|
+
node =
|
|
338
|
+
renderItem(
|
|
339
|
+
item,
|
|
340
|
+
index
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
newMap.set(key, node);
|
|
345
|
+
|
|
346
|
+
newNodes.push(node);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
// remove old
|
|
352
|
+
oldMap.forEach((node, key) => {
|
|
353
|
+
|
|
354
|
+
if (!newMap.has(key)) {
|
|
355
|
+
|
|
356
|
+
node.dispose?.();
|
|
357
|
+
|
|
358
|
+
node.remove?.();
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
// insert/reorder
|
|
365
|
+
let anchor =
|
|
366
|
+
endMarker;
|
|
367
|
+
|
|
368
|
+
for (
|
|
369
|
+
let i =
|
|
370
|
+
newNodes.length - 1;
|
|
371
|
+
i >= 0;
|
|
372
|
+
i--
|
|
373
|
+
) {
|
|
374
|
+
|
|
375
|
+
const node =
|
|
376
|
+
newNodes[i];
|
|
377
|
+
|
|
378
|
+
if (
|
|
379
|
+
node.nextSibling !== anchor
|
|
380
|
+
) {
|
|
381
|
+
|
|
382
|
+
endMarker.parentNode.insertBefore(
|
|
383
|
+
node,
|
|
384
|
+
anchor
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
anchor = node;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
oldMap = newMap;
|
|
392
|
+
|
|
393
|
+
});
|
|
394
|
+
}
|
package/src/index.js
ADDED