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