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