bobe-dom 0.0.62 → 0.0.64

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.
@@ -1,4 +1,5 @@
1
1
  import { customRender } from 'bobe';
2
+ import { Parser } from 'htmlparser2';
2
3
 
3
4
  const BOOLEAN_ATTRS$1 = new Set(['disabled', 'readonly', 'checked', 'selected', 'hidden', 'multiple', 'required', 'autofocus', 'autoplay', 'controls', 'loop', 'muted', 'defer', 'async', 'reversed', 'open', 'itemscope', 'ismap', 'nohref', 'noshade', 'nowrap', 'compact', 'default']);
4
5
  const VALUE_PROP_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT']);
@@ -116,8 +117,8 @@ const beforeIndent$1 = node => {
116
117
  return false;
117
118
  }
118
119
  };
119
- const leaveNode$1 = () => {};
120
- const leaveLogicNode$1 = () => {};
120
+ const leaveNode = () => {};
121
+ const leaveLogicNode = () => {};
121
122
  const render = customRender({
122
123
  createNode: createNode$1,
123
124
  setProp: setProp$1,
@@ -127,68 +128,98 @@ const render = customRender({
127
128
  firstChild: firstChild$1,
128
129
  nextSib: nextSib$1,
129
130
  beforeIndent: beforeIndent$1,
130
- leaveNode: leaveNode$1,
131
- leaveLogicNode: leaveLogicNode$1
131
+ leaveNode,
132
+ leaveLogicNode
132
133
  });
133
134
 
134
- let ctx = {
135
- root: null,
136
- current: null
137
- };
138
- const cleanCtx = () => Object.keys(ctx).forEach(key => {
139
- ctx[key] = null;
140
- });
141
-
142
- class SSRNode {
143
- parent = null;
144
- firstChild = null;
145
- lastChild = null;
146
- nextSibling = null;
147
- prevSibling = null;
148
- closed = false;
149
- startClosed = false;
150
- _innerHtml = null;
151
- }
152
- let SSRNodeType = function (SSRNodeType) {
135
+ (function (SSRNodeType) {
153
136
  SSRNodeType[SSRNodeType["Element"] = 0] = "Element";
154
137
  SSRNodeType[SSRNodeType["Text"] = 1] = "Text";
155
138
  SSRNodeType[SSRNodeType["Anchor"] = 2] = "Anchor";
156
139
  SSRNodeType[SSRNodeType["Root"] = 3] = "Root";
157
140
  return SSRNodeType;
158
- }({});
159
- class Element extends SSRNode {
160
- type = SSRNodeType.Element;
161
- textContent = null;
162
- attrs = {};
163
- constructor(value, parent) {
164
- super();
165
- this.value = value;
166
- this.parent = parent;
167
- }
168
- }
169
- class Text extends SSRNode {
170
- type = SSRNodeType.Text;
171
- constructor(textContent, parent) {
172
- super();
173
- this.textContent = textContent;
174
- this.parent = parent;
141
+ })({});
142
+ class SSRFiber {
143
+ parent = undefined;
144
+ next = undefined;
145
+ child = undefined;
146
+ html = undefined;
147
+ openTagEnd = undefined;
148
+ constructor(type, props = {}) {
149
+ this.type = type;
150
+ this.props = props;
151
+ }
152
+ querySelector(selector) {
153
+ const idMatch = selector.match(/#([\w-]+)/);
154
+ const id = idMatch ? idMatch[1] : null;
155
+ const classMatches = selector.match(/\.[\w-]+/g);
156
+ const classes = classMatches ? classMatches.map(c => c.slice(1)) : [];
157
+ const tag = selector.replace(/#[\w-]+/g, '').replace(/\.[\w-]+/g, '').trim() || null;
158
+ const walk = node => {
159
+ if (!node) return null;
160
+ if (node.type !== 'root' && node.type !== 'anchor' && node.type !== 'text') {
161
+ if (!tag || node.type === tag) {
162
+ if (!id || node.props['id'] === id) {
163
+ if (classes.length === 0 || classes.every(c => (node.props['class'] || '').split(/\s+/).includes(c))) {
164
+ return node;
165
+ }
166
+ }
167
+ }
168
+ }
169
+ return walk(node.child) || walk(node.next);
170
+ };
171
+ return walk(this.child);
175
172
  }
176
- }
177
- class Anchor extends SSRNode {
178
- type = SSRNodeType.Anchor;
179
- constructor(value, parent) {
180
- super();
181
- this.value = value;
182
- this.parent = parent;
173
+ getElementById(id) {
174
+ return this.querySelector(`#${id}`);
183
175
  }
184
176
  }
185
- class Root extends SSRNode {
186
- type = SSRNodeType.Root;
187
- constructor(value, parent) {
188
- super();
189
- this.value = value;
190
- this.parent = parent;
191
- }
177
+
178
+ function parseHtmlToFibers(html, root) {
179
+ const stack = [{
180
+ parent: root,
181
+ lastChild: null
182
+ }];
183
+ const append = node => {
184
+ const frame = stack[stack.length - 1];
185
+ if (!frame.parent.child) {
186
+ frame.parent.child = node;
187
+ } else if (frame.lastChild) {
188
+ frame.lastChild.next = node;
189
+ }
190
+ frame.lastChild = node;
191
+ node.parent = frame.parent;
192
+ };
193
+ let parserRef;
194
+ const parser = new Parser({
195
+ onparserinit(p) {
196
+ parserRef = p;
197
+ },
198
+ onopentag(name, attribs) {
199
+ const fiber = new SSRFiber(name, {
200
+ ...attribs
201
+ });
202
+ fiber.openTagEnd = parserRef.endIndex;
203
+ append(fiber);
204
+ stack.push({
205
+ parent: fiber,
206
+ lastChild: null
207
+ });
208
+ },
209
+ ontext(text) {
210
+ const fiber = new SSRFiber('text', {
211
+ text
212
+ });
213
+ append(fiber);
214
+ },
215
+ onclosetag() {
216
+ stack.pop();
217
+ const top = stack[stack.length - 1];
218
+ top.lastChild ?? null;
219
+ }
220
+ });
221
+ parser.write(html);
222
+ parser.end();
192
223
  }
193
224
 
194
225
  const VOID_TAGS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
@@ -215,161 +246,150 @@ const escapeText = str => {
215
246
  return TEXT_RE.test(s) ? s.replace(TEXT_RE, m => TEXT_MAP[m]) : s;
216
247
  };
217
248
  const createNode = name => {
218
- if (name === 'text') {
219
- return new Text('');
220
- }
221
- ctx.root.value += `<${name}`;
222
- return new Element(name);
249
+ return new SSRFiber(name);
223
250
  };
224
251
  const setProp = (node, key, value) => {
225
- if (node.startClosed) return;
226
- if (key.startsWith('on') || key === 'ref') return;
227
- if (key === 'text') {
228
- if (value == null) return;
229
- node.textContent = value;
230
- return;
231
- }
252
+ node.props[key] = value;
232
253
  if (key === 'html') {
233
- if (value == null) return;
234
- node._innerHtml = String(value);
235
- return;
254
+ parseHtmlToFibers(value, node);
236
255
  }
237
- if (key === 'class') {
238
- if (value == null) return;
239
- let classStr;
240
- if (typeof value === 'object' && !Array.isArray(value)) {
241
- classStr = Object.entries(value).filter(([, v]) => !!v).map(([k]) => k).join(' ');
242
- } else {
243
- classStr = typeof value === 'boolean' ? value ? 'true' : '' : String(value);
244
- }
245
- if (classStr) ctx.root.value += ` class="${escapeAttr(classStr)}"`;
246
- return;
247
- }
248
- if (key === 'style') {
249
- if (value == null) return;
250
- ctx.root.value += ` style="${escapeAttr(String(value))}"`;
251
- return;
252
- }
253
- if (BOOLEAN_ATTRS.has(key)) {
254
- if (value !== false && value !== null && value !== undefined) {
255
- ctx.root.value += ` ${key}`;
256
- }
257
- return;
258
- }
259
- if (key.startsWith('data-') || key.startsWith('aria-')) {
260
- if (value == null) return;
261
- ctx.root.value += ` ${key}="${escapeAttr(value)}"`;
262
- return;
263
- }
264
- if (value == null) return;
265
- ctx.root.value += ` ${key}="${escapeAttr(value)}"`;
266
- };
267
- const appendInnerContent = node => {
268
- if (node._innerHtml != null) {
269
- ctx.root.value += node._innerHtml;
270
- return true;
271
- }
272
- if (node.textContent != null) {
273
- ctx.root.value += escapeText(node.textContent);
274
- return true;
275
- }
276
- return false;
277
256
  };
278
257
  const beforeIndent = node => {
279
- if (node instanceof Text) return true;
280
- const hasText = node.textContent != null;
281
- const hasHtml = node._innerHtml != null;
282
- if (VOID_TAGS.has(node.value)) {
283
- ctx.root.value += `/>`;
284
- console.warn(`<${node.value}> can't have children`);
285
- node.startClosed = true;
286
- node.closed = true;
258
+ if (node.props.html != null) {
287
259
  return false;
288
260
  }
289
- if (hasText || hasHtml) {
290
- console.warn(`<${node.value}> has ${hasHtml ? 'html' : 'text'} content and child elements — children ignored`);
291
- ctx.root.value += `>`;
292
- appendInnerContent(node);
293
- ctx.root.value += `</${node.value}>`;
294
- node.startClosed = true;
295
- node.closed = true;
296
- return false;
297
- }
298
- ctx.root.value += `>`;
299
- node.startClosed = true;
300
- return true;
301
- };
302
- const leaveLogicNode = node => {
303
- const realAfter = node.realAfter;
304
- ctx.root.value += `<!--${realAfter.value}-->`;
305
- };
306
- const leaveNode = (node, isDedent) => {
307
- if (node.closed) return;
308
- if (node instanceof Text) {
309
- ctx.root.value += escapeText(node.textContent);
310
- } else {
311
- if (node.startClosed) {
312
- if (VOID_TAGS.has(node.value)) ; else {
313
- ctx.root.value += `</${node.value}>`;
314
- }
315
- } else {
316
- if (VOID_TAGS.has(node.value)) {
317
- ctx.root.value += ` />`;
318
- } else {
319
- ctx.root.value += `>`;
320
- appendInnerContent(node);
321
- ctx.root.value += `</${node.value}>`;
322
- }
323
- }
324
- }
325
261
  };
326
262
  const insertAfter = (parent, node, prev) => {
327
- const next = prev ? prev.nextSibling : parent.firstChild;
328
- node.nextSibling = next;
329
- node.prevSibling = prev;
263
+ let next;
330
264
  if (prev) {
331
- prev.nextSibling = node;
332
- } else {
333
- parent.firstChild = node;
334
- }
335
- if (next) {
336
- next.prevSibling = node;
265
+ next = prev.next;
266
+ prev.next = node;
337
267
  } else {
338
- parent.lastChild = node;
268
+ next = parent.child;
269
+ parent.child = node;
339
270
  }
271
+ node.next = next;
340
272
  node.parent = parent;
341
273
  };
342
274
  const createAnchor = (name, isBefore) => {
343
- if (isBefore) {
344
- ctx.root.value += `<!--${name}-->`;
345
- }
346
- return new Anchor(name);
275
+ return new SSRFiber('anchor', {
276
+ name,
277
+ isBefore
278
+ });
347
279
  };
348
- const remove = node => {
280
+ const remove = (node, prev) => {
349
281
  const parent = node.parent,
350
- prevSibling = node.prevSibling,
351
- nextSibling = node.nextSibling;
352
- if (prevSibling) {
353
- prevSibling.nextSibling = nextSibling;
354
- }
355
- if (nextSibling) {
356
- nextSibling.prevSibling = prevSibling;
282
+ next = node.next;
283
+ node.next = null;
284
+ if (prev) {
285
+ prev.next = next;
286
+ } else {
287
+ parent.child = next;
357
288
  }
358
- if (parent) {
359
- if (parent.firstChild === node) {
360
- parent.firstChild = nextSibling;
289
+ node.parent = null;
290
+ };
291
+ const firstChild = node => node.child;
292
+ const nextSib = node => node.next;
293
+ function walkFiber(root) {
294
+ let point = root;
295
+ let shouldSink = true;
296
+ sink: do {
297
+ if (point.type === 'root') {
298
+ point.html = '';
299
+ } else if (point.type === 'anchor') {
300
+ point.html = `<!--${point.props.name}-->`;
301
+ } else if (point.type === 'text') {
302
+ const text = point.props.text;
303
+ if (text != null) {
304
+ point.html = escapeText(point.props.text);
305
+ }
306
+ } else {
307
+ point.html = `<${point.type}`;
308
+ const props = point.props;
309
+ let text;
310
+ for (const key in props) {
311
+ const value = props[key];
312
+ if (key.startsWith('on') || key === 'ref') continue;
313
+ if (key === 'html') {
314
+ continue;
315
+ }
316
+ if (key === 'text') {
317
+ text = value;
318
+ continue;
319
+ }
320
+ if (key === 'class') {
321
+ if (value == null) continue;
322
+ let classStr;
323
+ if (typeof value === 'object' && !Array.isArray(value)) {
324
+ classStr = Object.entries(value).filter(([, v]) => !!v).map(([k]) => k).join(' ');
325
+ } else {
326
+ classStr = typeof value === 'boolean' ? value ? 'true' : '' : String(value);
327
+ }
328
+ if (classStr) {
329
+ point.html += ` class="${escapeAttr(classStr)}"`;
330
+ }
331
+ continue;
332
+ }
333
+ if (key === 'style') {
334
+ if (value == null) continue;
335
+ point.html += ` style="${escapeAttr(String(value))}"`;
336
+ continue;
337
+ }
338
+ if (BOOLEAN_ATTRS.has(key)) {
339
+ if (value !== false && value !== null && value !== undefined) {
340
+ point.html += ` ${key}`;
341
+ }
342
+ continue;
343
+ }
344
+ if (key.startsWith('data-') || key.startsWith('aria-')) {
345
+ if (value == null) continue;
346
+ point.html += ` ${key}="${escapeAttr(value)}"`;
347
+ continue;
348
+ }
349
+ if (value == null) continue;
350
+ point.html += ` ${key}="${escapeAttr(value)}"`;
351
+ }
352
+ if (text != null && props.html == null) {
353
+ const content = escapeText(text);
354
+ point.html += `>${content}</${point.type}>`;
355
+ if (point.child) console.warn(`<${point.type}> has text content and child elements — children ignored`);
356
+ shouldSink = false;
357
+ } else {
358
+ if (VOID_TAGS.has(point.type)) {
359
+ point.html += `/>`;
360
+ if (point.child) console.warn(`<${point.type}> can't have children`);
361
+ shouldSink = false;
362
+ } else {
363
+ point.html += `>`;
364
+ }
365
+ }
361
366
  }
362
- if (parent.lastChild === node) {
363
- parent.lastChild = prevSibling;
367
+ if (point.child && shouldSink) {
368
+ point = point.child;
369
+ continue;
364
370
  }
365
- }
366
- };
367
- const firstChild = node => node.firstChild;
368
- const nextSib = node => node.nextSibling;
371
+ do {
372
+ const notRoot = point !== root;
373
+ const notAnchor = point.type !== 'anchor';
374
+ const notText = point.type !== 'text';
375
+ if (shouldSink && notRoot && notAnchor && notText) {
376
+ point.html += `</${point.type}>`;
377
+ }
378
+ if (notRoot) {
379
+ point.parent.html += point.html;
380
+ }
381
+ shouldSink = true;
382
+ if (!notRoot) break sink;
383
+ if (point.next) {
384
+ point = point.next;
385
+ break;
386
+ }
387
+ point = point.parent;
388
+ } while (true);
389
+ } while (true);
390
+ }
369
391
  const renderHtmlStr = ComponentClass => {
370
- cleanCtx();
371
- const root = new Root('');
372
- ctx.root = ctx.current = root;
392
+ const root = new SSRFiber('root');
373
393
  const render = customRender({
374
394
  createNode,
375
395
  setProp,
@@ -379,34 +399,42 @@ const renderHtmlStr = ComponentClass => {
379
399
  firstChild,
380
400
  nextSib,
381
401
  beforeIndent,
382
- leaveNode,
383
- leaveLogicNode,
384
402
  noopEffect: true
385
403
  });
386
404
  render(ComponentClass, root);
387
- return {
388
- html: ctx.root.value
389
- };
405
+ walkFiber(root);
406
+ return root.html;
390
407
  };
391
408
 
392
409
  class TreeCursor {
393
410
  claimed = new WeakSet();
411
+ current = null;
394
412
  constructor(container) {
395
413
  this.parent = container;
396
414
  }
397
415
  enterChildren(node) {
398
416
  this.parent = node;
417
+ this.current = null;
418
+ }
419
+ setParent(node) {
420
+ this.parent = node;
421
+ this.current = null;
422
+ }
423
+ setCurrent(node) {
424
+ this.current = node;
399
425
  }
400
426
  leaveToParent(node) {
401
427
  if (node === this.parent && this.parent.parentNode) {
402
428
  this.parent = this.parent.parentNode;
429
+ this.current = node;
403
430
  }
404
431
  }
405
432
  findUnclaimed(predicate) {
406
- let node = this.parent.firstChild;
433
+ let node = this.current?.nextSibling || this.parent.firstChild;
407
434
  while (node) {
408
435
  if (!this.claimed.has(node) && predicate(node)) {
409
436
  this.claimed.add(node);
437
+ this.current = node;
410
438
  return node;
411
439
  }
412
440
  node = node.nextSibling;
@@ -465,7 +493,21 @@ const hydrate = (ComponentClass, rootEl) => {
465
493
  const leaveNode = node => {
466
494
  if (isFirstRender) cursor.leaveToParent(node);
467
495
  };
468
- const leaveLogicNode = node => {
496
+ const beforeLogicIndent = node => {
497
+ if (isFirstRender && node.tpData) {
498
+ const targetDom = node.tpData.node;
499
+ if (targetDom) cursor.enterChildren(targetDom);
500
+ }
501
+ };
502
+ const leaveLogicNode = (node, _isDedent) => {
503
+ if (isFirstRender && node.tpData) {
504
+ const parentDom = node.realAfter?.parentNode;
505
+ if (parentDom) {
506
+ cursor.setParent(parentDom);
507
+ if (node.realAfter) cursor.setCurrent(node.realAfter);
508
+ }
509
+ return;
510
+ }
469
511
  if (isFirstRender) cursor.leaveToParent(node);
470
512
  };
471
513
  const render = customRender({
@@ -477,6 +519,7 @@ const hydrate = (ComponentClass, rootEl) => {
477
519
  firstChild: firstChild$1,
478
520
  nextSib: nextSib$1,
479
521
  beforeIndent,
522
+ beforeLogicIndent,
480
523
  leaveNode,
481
524
  leaveLogicNode,
482
525
  onBeforeFlush: () => {
@@ -486,5 +529,5 @@ const hydrate = (ComponentClass, rootEl) => {
486
529
  return render(ComponentClass, rootEl);
487
530
  };
488
531
 
489
- export { BOOLEAN_ATTRS$1 as BOOLEAN_ATTRS, CONTENT_FLAG, beforeIndent$1 as beforeIndent, createAnchor$1 as createAnchor, createNode$1 as createNode, firstChild$1 as firstChild, hydrate, insertAfter$1 as insertAfter, leaveLogicNode$1 as leaveLogicNode, leaveNode$1 as leaveNode, nextSib$1 as nextSib, remove$1 as remove, render, renderHtmlStr, setProp$1 as setProp };
532
+ export { BOOLEAN_ATTRS$1 as BOOLEAN_ATTRS, CONTENT_FLAG, beforeIndent$1 as beforeIndent, createAnchor$1 as createAnchor, createNode$1 as createNode, firstChild$1 as firstChild, hydrate, insertAfter$1 as insertAfter, leaveLogicNode, leaveNode, nextSib$1 as nextSib, remove$1 as remove, render, renderHtmlStr, setProp$1 as setProp, walkFiber };
490
533
  //# sourceMappingURL=bobe-dom.esm.js.map