@tarojs/runtime 3.4.0-beta.0 → 3.4.0

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,4 @@
1
- import { isFunction, isUndefined, isObject, warn, isArray, toCamelCase, ensure, toDashed, isString, EMPTY_OBJ, internalComponents, controlledComponent, defaultReconciler, noop } from '@tarojs/shared';
1
+ import { isFunction, isUndefined, isObject, warn, isArray, toCamelCase, noop, ensure, toDashed, isString, EMPTY_OBJ, internalComponents, controlledComponent, defaultReconciler } from '@tarojs/shared';
2
2
  import { injectable, inject, ContainerModule, optional, multiInject, Container } from 'inversify';
3
3
 
4
4
  /*! *****************************************************************************
@@ -1260,7 +1260,8 @@ function shortcutAttr(key) {
1260
1260
  default:
1261
1261
  return key;
1262
1262
  }
1263
- }
1263
+ }
1264
+ const customWrapperCache = new Map();
1264
1265
 
1265
1266
  const SID_TARO_ELEMENT = '0';
1266
1267
  const SID_TARO_ELEMENT_FACTORY = '1';
@@ -1358,6 +1359,7 @@ let TaroEventTarget = class TaroEventTarget {
1358
1359
  }
1359
1360
  addEventListener(type, handler, options) {
1360
1361
  var _a, _b;
1362
+ type = type.toLowerCase();
1361
1363
  (_b = (_a = this.hooks).onAddEvent) === null || _b === void 0 ? void 0 : _b.call(_a, type, handler, options, this);
1362
1364
  if (type === 'regionchange') {
1363
1365
  // map 组件的 regionchange 事件非常特殊,详情:https://github.com/NervJS/taro/issues/5766
@@ -1365,8 +1367,6 @@ let TaroEventTarget = class TaroEventTarget {
1365
1367
  this.addEventListener('end', handler, options);
1366
1368
  return;
1367
1369
  }
1368
- type = type.toLowerCase();
1369
- const handlers = this.__handlers[type];
1370
1370
  let isCapture = Boolean(options);
1371
1371
  let isOnce = false;
1372
1372
  if (isObject(options)) {
@@ -1382,6 +1382,16 @@ let TaroEventTarget = class TaroEventTarget {
1382
1382
  return;
1383
1383
  }
1384
1384
  process.env.NODE_ENV !== 'production' && warn(isCapture, 'Taro 暂未实现 event 的 capture 特性。');
1385
+ // 某些框架,如 PReact 有委托的机制,handler 始终是同一个函数
1386
+ // 这会导致多层停止冒泡失败:view -> view(handler.stop = false) -> view(handler.stop = true)
1387
+ // 这样解决:view -> view(handlerA.stop = false) -> view(handlerB.stop = false)
1388
+ // 因此每次绑定事件都新建一个函数,如果带来了性能问题,可以把这段逻辑抽取到 PReact 插件中。
1389
+ const oldHandler = handler;
1390
+ handler = function () {
1391
+ oldHandler.apply(this, arguments); // this 指向 Element
1392
+ };
1393
+ handler.oldHandler = oldHandler;
1394
+ const handlers = this.__handlers[type];
1385
1395
  if (isArray(handlers)) {
1386
1396
  handlers.push(handler);
1387
1397
  }
@@ -1398,7 +1408,10 @@ let TaroEventTarget = class TaroEventTarget {
1398
1408
  if (!isArray(handlers)) {
1399
1409
  return;
1400
1410
  }
1401
- const index = handlers.indexOf(handler);
1411
+ const index = handlers.findIndex(item => {
1412
+ if (item === handler || item.oldHandler === handler)
1413
+ return true;
1414
+ });
1402
1415
  process.env.NODE_ENV !== 'production' && warn(index === -1, `事件: '${type}' 没有注册在 DOM 中,因此不会被移除。`);
1403
1416
  handlers.splice(index, 1);
1404
1417
  }
@@ -1430,10 +1443,13 @@ function hydrate(node) {
1430
1443
  }
1431
1444
  const data = {
1432
1445
  ["nn" /* NodeName */]: nodeName,
1433
- uid: node.uid
1446
+ sid: node.sid
1434
1447
  };
1435
1448
  const { props } = node;
1436
1449
  const SPECIAL_NODES = node.hooks.getSpecialNodes();
1450
+ if (node.uid !== node.sid) {
1451
+ data.uid = node.uid;
1452
+ }
1437
1453
  if (!node.isAnyEventBinded() && SPECIAL_NODES.indexOf(nodeName) > -1) {
1438
1454
  data["nn" /* NodeName */] = `static-${nodeName}`;
1439
1455
  if (nodeName === VIEW && !isHasExtractProp(node)) {
@@ -1472,8 +1488,158 @@ function hydrate(node) {
1472
1488
  return data;
1473
1489
  }
1474
1490
 
1475
- const eventSource = new Map();
1491
+ class EventSource extends Map {
1492
+ removeNode(child) {
1493
+ const { sid, uid } = child;
1494
+ this.delete(sid);
1495
+ if (uid !== sid && uid)
1496
+ this.delete(uid);
1497
+ }
1498
+ removeNodeTree(child) {
1499
+ this.removeNode(child);
1500
+ const { childNodes } = child;
1501
+ childNodes.forEach(node => this.removeNodeTree(node));
1502
+ }
1503
+ }
1504
+ const eventSource = new EventSource();
1505
+
1506
+ const observers = [];
1507
+ /**
1508
+ * The MutationObserver provides the ability
1509
+ * to watch for changes being made to the DOM tree.
1510
+ * It will invoke a specified callback function
1511
+ * when DOM changes occur.
1512
+ * @see https://dom.spec.whatwg.org/#mutationobserver
1513
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
1514
+ */
1515
+ class MutationObserverImpl {
1516
+ constructor(callback) {
1517
+ this.records = [];
1518
+ this.callback = callback;
1519
+ }
1520
+ /**
1521
+ * Configures the MutationObserver
1522
+ * to begin receiving notifications
1523
+ * through its callback function
1524
+ * when DOM changes matching the given options occur.
1525
+ *
1526
+ * Options matching is to be implemented.
1527
+ */
1528
+ observe(target, options) {
1529
+ this.disconnect();
1530
+ this.target = target;
1531
+ this.options = options || {};
1532
+ observers.push(this);
1533
+ }
1534
+ /**
1535
+ * Stop the MutationObserver instance
1536
+ * from receiving further notifications
1537
+ * until and unless observe() is called again.
1538
+ */
1539
+ disconnect() {
1540
+ this.target = null;
1541
+ const index = observers.indexOf(this);
1542
+ if (index >= 0) {
1543
+ observers.splice(index, 1);
1544
+ }
1545
+ }
1546
+ /**
1547
+ * Removes all pending notifications
1548
+ * from the MutationObserver's notification queue
1549
+ * and returns them in a new Array of MutationRecord objects.
1550
+ */
1551
+ takeRecords() {
1552
+ return this.records.splice(0, this.records.length);
1553
+ }
1554
+ }
1555
+ /** Match two TaroNodes by sid. */
1556
+ const sidMatches = (observerTarget, target) => {
1557
+ return !!observerTarget && observerTarget.sid === (target === null || target === void 0 ? void 0 : target.sid);
1558
+ };
1559
+ const isConcerned = (record, options) => {
1560
+ const { characterData, characterDataOldValue, attributes, attributeOldValue, childList } = options;
1561
+ switch (record.type) {
1562
+ case "characterData" /* CHARACTER_DATA */:
1563
+ if (characterData) {
1564
+ if (!characterDataOldValue)
1565
+ record.oldValue = null;
1566
+ return true;
1567
+ }
1568
+ return false;
1569
+ case "attributes" /* ATTRIBUTES */:
1570
+ if (attributes) {
1571
+ if (!attributeOldValue)
1572
+ record.oldValue = null;
1573
+ return true;
1574
+ }
1575
+ return false;
1576
+ case "childList" /* CHILD_LIST */:
1577
+ if (childList) {
1578
+ return true;
1579
+ }
1580
+ return false;
1581
+ }
1582
+ };
1583
+ let pendingMuatations = false;
1584
+ function logMutation(observer, record) {
1585
+ observer.records.push(record);
1586
+ if (!pendingMuatations) {
1587
+ pendingMuatations = true;
1588
+ Promise
1589
+ .resolve()
1590
+ .then(() => {
1591
+ pendingMuatations = false;
1592
+ observers.forEach(observer => {
1593
+ return observer.callback(observer.takeRecords());
1594
+ });
1595
+ });
1596
+ }
1597
+ }
1598
+ function recordMutation(record) {
1599
+ observers.forEach(observer => {
1600
+ const { options } = observer;
1601
+ for (let t = record.target; t; t = t.parentNode) {
1602
+ if (sidMatches(observer.target, t) && isConcerned(record, options)) {
1603
+ logMutation(observer, record);
1604
+ break;
1605
+ }
1606
+ if (!options.subtree)
1607
+ break;
1608
+ }
1609
+ });
1610
+ }
1611
+
1612
+ class MutationObserver {
1613
+ constructor(callback) {
1614
+ if (ENABLE_MUTATION_OBSERVER) {
1615
+ this.core = new MutationObserverImpl(callback);
1616
+ }
1617
+ else {
1618
+ if (process.env.NODE_ENV !== 'production') {
1619
+ console.warn('[Taro Warning] 若要使用 MutationObserver,请在 Taro 编译配置中设置 \'mini.enableMutationObserver: true\'');
1620
+ }
1621
+ this.core = {
1622
+ observe: noop,
1623
+ disconnect: noop,
1624
+ takeRecords: noop
1625
+ };
1626
+ }
1627
+ }
1628
+ observe(...args) {
1629
+ this.core.observe(...args);
1630
+ }
1631
+ disconnect() {
1632
+ this.core.disconnect();
1633
+ }
1634
+ takeRecords() {
1635
+ return this.core.takeRecords();
1636
+ }
1637
+ static record(record) {
1638
+ recordMutation(record);
1639
+ }
1640
+ }
1476
1641
 
1642
+ const CHILDNODES = "cn" /* Childnodes */;
1477
1643
  const nodeId = incrementId();
1478
1644
  let TaroNode = class TaroNode extends TaroEventTarget {
1479
1645
  constructor() {
@@ -1484,20 +1650,34 @@ let TaroNode = class TaroNode extends TaroEventTarget {
1484
1650
  this.hydrate = (node) => () => hydrate(node);
1485
1651
  const impl = getNodeImpl();
1486
1652
  impl.bind(this);
1487
- this.uid = `_n_${nodeId()}`;
1488
- eventSource.set(this.uid, this);
1653
+ this.uid = `_n_${nodeId()}`; // dom 节点 id,开发者可修改
1654
+ this.sid = this.uid; // dom 节点全局唯一 id,不可被修改
1655
+ eventSource.set(this.sid, this);
1489
1656
  }
1490
1657
  /**
1491
1658
  * like jQuery's $.empty()
1492
1659
  */
1493
1660
  _empty() {
1494
- while (this.childNodes.length > 0) {
1495
- const child = this.childNodes[0];
1661
+ while (this.firstChild) {
1662
+ // Data Structure
1663
+ const child = this.firstChild;
1496
1664
  child.parentNode = null;
1497
- eventSource.delete(child.uid);
1498
1665
  this.childNodes.shift();
1666
+ // eventSource
1667
+ eventSource.removeNodeTree(child);
1499
1668
  }
1500
1669
  }
1670
+ updateChildNodes(isClean) {
1671
+ const cleanChildNodes = () => [];
1672
+ const rerenderChildNodes = () => {
1673
+ const childNodes = this.childNodes.filter(node => !isComment(node));
1674
+ return childNodes.map(hydrate);
1675
+ };
1676
+ this.enqueueUpdate({
1677
+ path: `${this._path}.${CHILDNODES}`,
1678
+ value: isClean ? cleanChildNodes : rerenderChildNodes
1679
+ });
1680
+ }
1501
1681
  get _root() {
1502
1682
  var _a;
1503
1683
  return ((_a = this.parentNode) === null || _a === void 0 ? void 0 : _a._root) || null;
@@ -1514,7 +1694,7 @@ let TaroNode = class TaroNode extends TaroEventTarget {
1514
1694
  const list = parentNode.childNodes.filter(node => !isComment(node));
1515
1695
  const indexOfNode = list.indexOf(this);
1516
1696
  const index = this.hooks.getPathIndex(indexOfNode);
1517
- return `${parentNode._path}.${"cn" /* Childnodes */}.${index}`;
1697
+ return `${parentNode._path}.${CHILDNODES}.${index}`;
1518
1698
  }
1519
1699
  return '';
1520
1700
  }
@@ -1545,18 +1725,32 @@ let TaroNode = class TaroNode extends TaroEventTarget {
1545
1725
  * @TODO 等待完整 innerHTML 实现
1546
1726
  */
1547
1727
  set textContent(text) {
1728
+ const document = this._getElement(ElementNames.Document)();
1729
+ const newText = document.createTextNode(text);
1730
+ // @Todo: appendChild 会多触发一次
1731
+ MutationObserver.record({
1732
+ type: "childList" /* CHILD_LIST */,
1733
+ target: this,
1734
+ removedNodes: this.childNodes.slice(),
1735
+ addedNodes: text === '' ? [] : [newText]
1736
+ });
1548
1737
  this._empty();
1549
1738
  if (text === '') {
1550
- this.enqueueUpdate({
1551
- path: `${this._path}.${"cn" /* Childnodes */}`,
1552
- value: () => []
1553
- });
1739
+ this.updateChildNodes(true);
1554
1740
  }
1555
1741
  else {
1556
- const document = this._getElement(ElementNames.Document)();
1557
- this.appendChild(document.createTextNode(text));
1742
+ this.appendChild(newText);
1743
+ this.updateChildNodes();
1558
1744
  }
1559
1745
  }
1746
+ /**
1747
+ * @doc https://developer.mozilla.org/zh-CN/docs/Web/API/Node/insertBefore
1748
+ * @scenario
1749
+ * [A,B,C]
1750
+ * 1. insert D before C, D has no parent
1751
+ * 2. insert D before C, D has the same parent of C
1752
+ * 3. insert D before C, D has the different parent of C
1753
+ */
1560
1754
  insertBefore(newChild, refChild, isReplace) {
1561
1755
  if (newChild.nodeName === DOCUMENT_FRAGMENT) {
1562
1756
  newChild.childNodes.reduceRight((previousValue, currentValue) => {
@@ -1565,72 +1759,114 @@ let TaroNode = class TaroNode extends TaroEventTarget {
1565
1759
  }, refChild);
1566
1760
  return newChild;
1567
1761
  }
1568
- newChild.remove();
1762
+ // Parent release newChild
1763
+ // - cleanRef: false (No need to clean eventSource, because newChild is about to be inserted)
1764
+ // - update: true (Need to update parent.childNodes, because parent.childNodes is reordered)
1765
+ newChild.remove({ cleanRef: false });
1766
+ // Data structure
1569
1767
  newChild.parentNode = this;
1570
- let payload;
1571
1768
  if (refChild) {
1769
+ // insertBefore & replaceChild
1572
1770
  const index = this.findIndex(refChild);
1573
1771
  this.childNodes.splice(index, 0, newChild);
1574
- if (isReplace) {
1575
- payload = {
1576
- path: newChild._path,
1577
- value: this.hydrate(newChild)
1578
- };
1579
- }
1580
- else {
1581
- payload = {
1582
- path: `${this._path}.${"cn" /* Childnodes */}`,
1583
- value: () => {
1584
- const childNodes = this.childNodes.filter(node => !isComment(node));
1585
- return childNodes.map(hydrate);
1586
- }
1587
- };
1588
- }
1589
1772
  }
1590
1773
  else {
1774
+ // appendChild
1591
1775
  this.childNodes.push(newChild);
1592
- payload = {
1776
+ }
1777
+ // Serialization
1778
+ if (!refChild || isReplace) {
1779
+ // appendChild & replaceChild
1780
+ this.enqueueUpdate({
1593
1781
  path: newChild._path,
1594
1782
  value: this.hydrate(newChild)
1595
- };
1783
+ });
1596
1784
  }
1597
- this.enqueueUpdate(payload);
1598
- if (!eventSource.has(newChild.uid)) {
1599
- eventSource.set(newChild.uid, newChild);
1785
+ else {
1786
+ // insertBefore
1787
+ this.updateChildNodes();
1600
1788
  }
1789
+ MutationObserver.record({
1790
+ type: "childList" /* CHILD_LIST */,
1791
+ target: this,
1792
+ addedNodes: [newChild],
1793
+ removedNodes: isReplace
1794
+ ? [refChild] /** replaceChild */
1795
+ : [],
1796
+ nextSibling: isReplace
1797
+ ? refChild.nextSibling /** replaceChild */
1798
+ : (refChild || null),
1799
+ previousSibling: newChild.previousSibling
1800
+ });
1601
1801
  return newChild;
1602
1802
  }
1603
- appendChild(child) {
1604
- this.insertBefore(child);
1803
+ /**
1804
+ * @doc https://developer.mozilla.org/zh-CN/docs/Web/API/Node/appendChild
1805
+ * @scenario
1806
+ * [A,B,C]
1807
+ * 1. append C, C has no parent
1808
+ * 2. append C, C has the same parent of B
1809
+ * 3. append C, C has the different parent of B
1810
+ */
1811
+ appendChild(newChild) {
1812
+ return this.insertBefore(newChild);
1605
1813
  }
1814
+ /**
1815
+ * @doc https://developer.mozilla.org/zh-CN/docs/Web/API/Node/replaceChild
1816
+ * @scenario
1817
+ * [A,B,C]
1818
+ * 1. replace B with C, C has no parent
1819
+ * 2. replace B with C, C has no parent, C has the same parent of B
1820
+ * 3. replace B with C, C has no parent, C has the different parent of B
1821
+ */
1606
1822
  replaceChild(newChild, oldChild) {
1607
- if (oldChild.parentNode === this) {
1608
- this.insertBefore(newChild, oldChild, true);
1609
- oldChild.remove(true);
1610
- return oldChild;
1611
- }
1823
+ if (oldChild.parentNode !== this)
1824
+ return;
1825
+ // Insert the newChild
1826
+ this.insertBefore(newChild, oldChild, true);
1827
+ // Destroy the oldChild
1828
+ // - cleanRef: true (Need to clean eventSource, because the oldChild was detached from the DOM tree)
1829
+ // - update: false (No need to update parent.childNodes, because replace will not cause the parent.childNodes being reordered)
1830
+ oldChild.remove({ doUpdate: false });
1831
+ return oldChild;
1612
1832
  }
1613
- removeChild(child, isReplace) {
1614
- const index = this.findIndex(child);
1615
- this.childNodes.splice(index, 1);
1616
- if (!isReplace) {
1617
- this.enqueueUpdate({
1618
- path: `${this._path}.${"cn" /* Childnodes */}`,
1619
- value: () => {
1620
- const childNodes = this.childNodes.filter(node => !isComment(node));
1621
- return childNodes.map(hydrate);
1622
- }
1833
+ /**
1834
+ * @doc https://developer.mozilla.org/zh-CN/docs/Web/API/Node/removeChild
1835
+ * @scenario
1836
+ * [A,B,C]
1837
+ * 1. remove A or B
1838
+ * 2. remove C
1839
+ */
1840
+ removeChild(child, options = {}) {
1841
+ const { cleanRef, doUpdate } = options;
1842
+ if (cleanRef !== false && doUpdate !== false) {
1843
+ // appendChild/replaceChild/insertBefore 不应该触发
1844
+ // @Todo: 但其实如果 newChild 的父节点是另一颗子树的节点,应该是要触发的
1845
+ MutationObserver.record({
1846
+ type: "childList" /* CHILD_LIST */,
1847
+ target: this,
1848
+ removedNodes: [child],
1849
+ nextSibling: child.nextSibling,
1850
+ previousSibling: child.previousSibling
1623
1851
  });
1624
1852
  }
1853
+ // Data Structure
1854
+ const index = this.findIndex(child);
1855
+ this.childNodes.splice(index, 1);
1625
1856
  child.parentNode = null;
1626
- eventSource.delete(child.uid);
1627
- // @TODO: eventSource memory overflow
1628
- // child._empty()
1857
+ // Set eventSource
1858
+ if (cleanRef !== false) {
1859
+ eventSource.removeNodeTree(child);
1860
+ }
1861
+ // Serialization
1862
+ if (doUpdate !== false) {
1863
+ this.updateChildNodes();
1864
+ }
1629
1865
  return child;
1630
1866
  }
1631
- remove(isReplace) {
1867
+ remove(options) {
1632
1868
  var _a;
1633
- (_a = this.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this, isReplace);
1869
+ (_a = this.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this, options);
1634
1870
  }
1635
1871
  hasChildNodes() {
1636
1872
  return this.childNodes.length > 0;
@@ -1656,6 +1892,11 @@ let TaroText = class TaroText extends TaroNode {
1656
1892
  this.nodeName = '#text';
1657
1893
  }
1658
1894
  set textContent(text) {
1895
+ MutationObserver.record({
1896
+ target: this,
1897
+ type: "characterData" /* CHARACTER_DATA */,
1898
+ oldValue: this._value
1899
+ });
1659
1900
  this._value = text;
1660
1901
  this.enqueueUpdate({
1661
1902
  path: `${this._path}.${"v" /* Text */}`,
@@ -1869,6 +2110,7 @@ combine('box', ['DecorationBreak', 'Shadow', 'Sizing', 'Snap'], true);
1869
2110
 
1870
2111
  function setStyle(newVal, styleKey) {
1871
2112
  const old = this[styleKey];
2113
+ const oldCssTxt = this.cssText;
1872
2114
  if (newVal) {
1873
2115
  this._usedStyleProp.add(styleKey);
1874
2116
  }
@@ -1879,6 +2121,16 @@ function setStyle(newVal, styleKey) {
1879
2121
  path: `${this._element._path}.${"st" /* Style */}`,
1880
2122
  value: this.cssText
1881
2123
  });
2124
+ // @Todo:
2125
+ // el.style.cssText = 'x: y;m: n'(Bug: 触发两次)
2126
+ // el.style.cssText = 'x: y'(正常)
2127
+ // el.style.x = y(正常)
2128
+ MutationObserver.record({
2129
+ type: "attributes" /* ATTRIBUTES */,
2130
+ target: this._element,
2131
+ attributeName: 'style',
2132
+ oldValue: oldCssTxt
2133
+ });
1882
2134
  }
1883
2135
  }
1884
2136
  function initStyle(ctor) {
@@ -1918,15 +2170,15 @@ class Style {
1918
2170
  });
1919
2171
  }
1920
2172
  get cssText() {
1921
- let text = '';
2173
+ const texts = [];
1922
2174
  this._usedStyleProp.forEach(key => {
1923
2175
  const val = this[key];
1924
2176
  if (!val)
1925
2177
  return;
1926
2178
  const styleName = isCssVariable(key) ? key : toDashed(key);
1927
- text += `${styleName}: ${val};`;
2179
+ texts.push(`${styleName}: ${val};`);
1928
2180
  });
1929
- return text;
2181
+ return texts.join(' ');
1930
2182
  }
1931
2183
  set cssText(str) {
1932
2184
  if (str == null) {
@@ -2033,7 +2285,7 @@ class ClassList extends Set {
2033
2285
  this.el = el;
2034
2286
  }
2035
2287
  get value() {
2036
- return [...this].join(' ');
2288
+ return [...this].filter(v => v !== '').join(' ');
2037
2289
  }
2038
2290
  add(s) {
2039
2291
  super.add(s);
@@ -2160,12 +2412,24 @@ let TaroElement = class TaroElement extends TaroNode {
2160
2412
  var _a, _b;
2161
2413
  process.env.NODE_ENV !== 'production' && warn(isString(value) && value.length > PROPERTY_THRESHOLD, `元素 ${this.nodeName} 的 属性 ${qualifiedName} 的值数据量过大,可能会影响渲染性能。考虑降低图片转为 base64 的阈值或在 CSS 中使用 base64。`);
2162
2414
  const isPureView = this.nodeName === VIEW && !isHasExtractProp(this) && !this.isAnyEventBinded();
2415
+ if (qualifiedName !== STYLE) {
2416
+ MutationObserver.record({
2417
+ target: this,
2418
+ type: "attributes" /* ATTRIBUTES */,
2419
+ attributeName: qualifiedName,
2420
+ oldValue: this.getAttribute(qualifiedName)
2421
+ });
2422
+ }
2163
2423
  switch (qualifiedName) {
2164
2424
  case STYLE:
2165
2425
  this.style.cssText = value;
2166
2426
  break;
2167
2427
  case ID:
2168
- eventSource.delete(this.uid);
2428
+ if (this.uid !== this.sid) {
2429
+ // eventSource[sid] 永远保留,直到组件卸载
2430
+ // eventSource[uid] 可变
2431
+ eventSource.delete(this.uid);
2432
+ }
2169
2433
  value = String(value);
2170
2434
  this.props[qualifiedName] = this.uid = value;
2171
2435
  eventSource.set(value, this);
@@ -2183,7 +2447,7 @@ let TaroElement = class TaroElement extends TaroNode {
2183
2447
  qualifiedName = shortcutAttr(qualifiedName);
2184
2448
  const payload = {
2185
2449
  path: `${this._path}.${toCamelCase(qualifiedName)}`,
2186
- value
2450
+ value: isFunction(value) ? () => value : value
2187
2451
  };
2188
2452
  (_b = (_a = this.hooks).modifySetAttrPayload) === null || _b === void 0 ? void 0 : _b.call(_a, this, qualifiedName, payload);
2189
2453
  this.enqueueUpdate(payload);
@@ -2208,6 +2472,12 @@ let TaroElement = class TaroElement extends TaroNode {
2208
2472
  removeAttribute(qualifiedName) {
2209
2473
  var _a, _b, _c, _d;
2210
2474
  const isStaticView = this.nodeName === VIEW && isHasExtractProp(this) && !this.isAnyEventBinded();
2475
+ MutationObserver.record({
2476
+ target: this,
2477
+ type: "attributes" /* ATTRIBUTES */,
2478
+ attributeName: qualifiedName,
2479
+ oldValue: this.getAttribute(qualifiedName)
2480
+ });
2211
2481
  if (qualifiedName === STYLE) {
2212
2482
  this.style.cssText = '';
2213
2483
  }
@@ -2348,24 +2618,33 @@ class Performance {
2348
2618
  }
2349
2619
  const perf = new Performance();
2350
2620
 
2351
- function findCustomWrapper(ctx, dataPathArr) {
2352
- let currentData = ctx.__data__ || ctx.data || ctx._data;
2353
- let wrapper;
2354
- let index;
2355
- dataPathArr.some((item, i) => {
2356
- const key = item.replace(/^\[(.+)\]$/, '$1');
2621
+ function findCustomWrapper(root, dataPathArr) {
2622
+ // ['root', 'cn', '[0]'] remove 'root' => ['cn', '[0]']
2623
+ const list = dataPathArr.slice(1);
2624
+ let currentData = root;
2625
+ let customWrapper;
2626
+ let splitedPath = '';
2627
+ list.some((item, i) => {
2628
+ const key = item
2629
+ // '[0]' => '0'
2630
+ .replace(/^\[(.+)\]$/, '$1')
2631
+ // 'cn' => 'childNodes'
2632
+ .replace(/\bcn\b/g, 'childNodes');
2357
2633
  currentData = currentData[key];
2358
2634
  if (isUndefined(currentData))
2359
2635
  return true;
2360
- if (currentData.nn === CUSTOM_WRAPPER) {
2361
- wrapper = currentData;
2362
- index = i;
2636
+ if (currentData.nodeName === CUSTOM_WRAPPER) {
2637
+ const res = customWrapperCache.get(currentData.sid);
2638
+ if (res) {
2639
+ customWrapper = res;
2640
+ splitedPath = dataPathArr.slice(i + 2).join('.');
2641
+ }
2363
2642
  }
2364
2643
  });
2365
- if (wrapper) {
2644
+ if (customWrapper) {
2366
2645
  return {
2367
- wrapper,
2368
- index: index
2646
+ customWrapper,
2647
+ splitedPath
2369
2648
  };
2370
2649
  }
2371
2650
  }
@@ -2433,17 +2712,12 @@ let TaroRootElement = class TaroRootElement extends TaroElement {
2433
2712
  // 更新渲染,区分 CustomWrapper 与页面级别的 setData
2434
2713
  for (const p in data) {
2435
2714
  const dataPathArr = p.split('.');
2436
- const found = findCustomWrapper(ctx, dataPathArr);
2715
+ const found = findCustomWrapper(this, dataPathArr);
2437
2716
  if (found) {
2438
2717
  // 此项数据使用 CustomWrapper 去更新
2439
- const { wrapper, index } = found;
2440
- const customWrapperId = `#${wrapper.uid}`;
2441
- const customWrapper = ctx.selectComponent(customWrapperId);
2442
- if (customWrapper) {
2443
- const splitedPath = dataPathArr.slice(index + 1).join('.');
2444
- // 合并同一个 customWrapper 的相关更新到一次 setData 中
2445
- customWrapperMap.set(customWrapper, Object.assign(Object.assign({}, (customWrapperMap.get(customWrapper) || {})), { [`i.${splitedPath}`]: data[p] }));
2446
- }
2718
+ const { customWrapper, splitedPath } = found;
2719
+ // 合并同一个 customWrapper 的相关更新到一次 setData 中
2720
+ customWrapperMap.set(customWrapper, Object.assign(Object.assign({}, (customWrapperMap.get(customWrapper) || {})), { [`i.${splitedPath}`]: data[p] }));
2447
2721
  }
2448
2722
  else {
2449
2723
  // 此项数据使用页面去更新
@@ -2593,11 +2867,13 @@ function createEvent(event, node) {
2593
2867
  const eventsBatch = {};
2594
2868
  // 小程序的事件代理回调函数
2595
2869
  function eventHandler(event) {
2596
- var _a;
2870
+ var _a, _b;
2597
2871
  const hooks = getHooks();
2598
2872
  (_a = hooks.modifyMpEvent) === null || _a === void 0 ? void 0 : _a.call(hooks, event);
2599
2873
  event.currentTarget || (event.currentTarget = event.target);
2600
- const node = getDocument().getElementById(event.currentTarget.id);
2874
+ const currentTarget = event.currentTarget;
2875
+ const id = ((_b = currentTarget.dataset) === null || _b === void 0 ? void 0 : _b.sid /** sid */) || currentTarget.id /** uid */ || '';
2876
+ const node = getDocument().getElementById(id);
2601
2877
  if (node) {
2602
2878
  const dispatch = () => {
2603
2879
  var _a;
@@ -3015,8 +3291,12 @@ class StyleTagParser {
3015
3291
  // console.log('res this.styles: ', this.styles)
3016
3292
  }
3017
3293
  parseSelector(src) {
3018
- // todo: 属性选择器里可以带空格:[a = "b"],这里的 split(' ') 需要作兼容
3019
- const list = src.trim().replace(/ *([>~+]) */g, ' $1').replace(/ +/g, ' ').split(' ');
3294
+ const list = src
3295
+ .trim()
3296
+ .replace(/ *([>~+]) */g, ' $1')
3297
+ .replace(/ +/g, ' ')
3298
+ .replace(/\[\s*([^[\]=\s]+)\s*=\s*([^[\]=\s]+)\s*\]/g, '[$1=$2]')
3299
+ .split(' ');
3020
3300
  const selectors = list.map(item => {
3021
3301
  const firstChar = item.charAt(0);
3022
3302
  const selector = {
@@ -3522,7 +3802,7 @@ function bindInnerHTML(ctx, getDoc) {
3522
3802
  configurable: true,
3523
3803
  enumerable: true,
3524
3804
  set(html) {
3525
- setInnerHTML.call(ctx, ctx, html, getDoc);
3805
+ setInnerHTML.call(this, this, html, getDoc);
3526
3806
  },
3527
3807
  get() {
3528
3808
  return '';
@@ -4029,11 +4309,11 @@ if (process.env.TARO_ENV && process.env.TARO_ENV !== 'h5') {
4029
4309
  if (!(DATE in window$1)) {
4030
4310
  window$1.Date = Date;
4031
4311
  }
4032
- window$1.setTimeout = function (cb, delay) {
4033
- setTimeout(cb, delay);
4312
+ window$1.setTimeout = function (...args) {
4313
+ return setTimeout(...args);
4034
4314
  };
4035
- window$1.clearTimeout = function (seed) {
4036
- clearTimeout(seed);
4315
+ window$1.clearTimeout = function (...args) {
4316
+ return clearTimeout(...args);
4037
4317
  };
4038
4318
  document$1.defaultView = window$1;
4039
4319
  }
@@ -4170,11 +4450,8 @@ function stringify(obj) {
4170
4450
  return path === '' ? path : '?' + path;
4171
4451
  }
4172
4452
  function getPath(id, options) {
4173
- let path = id;
4174
- if (process.env.TARO_ENV !== 'h5') {
4175
- path = id + stringify(options);
4176
- }
4177
- return path;
4453
+ const idx = id.indexOf('?');
4454
+ return `${idx > -1 ? id.substring(0, idx) : id}${stringify(process.env.TARO_ENV === 'h5' ? { stamp: (options === null || options === void 0 ? void 0 : options.stamp) || '' } : options)}`;
4178
4455
  }
4179
4456
  function getOnReadyEventKey(path) {
4180
4457
  return path + '.' + ON_READY;
@@ -4207,7 +4484,7 @@ function createPageConfig(component, pageName, data, pageConfig) {
4207
4484
  let loadResolver;
4208
4485
  let hasLoaded;
4209
4486
  const config = {
4210
- [ONLOAD](options, cb) {
4487
+ [ONLOAD](options = {}, cb) {
4211
4488
  hasLoaded = new Promise(resolve => { loadResolver = resolve; });
4212
4489
  perf.start(PAGE_INIT);
4213
4490
  Current.page = this;
@@ -4216,6 +4493,9 @@ function createPageConfig(component, pageName, data, pageConfig) {
4216
4493
  // this.$taroPath 是页面唯一标识,不可变,因此页面参数 options 也不可变
4217
4494
  this.$taroPath = getPath(id, options);
4218
4495
  const $taroPath = this.$taroPath;
4496
+ if (process.env.TARO_ENV === 'h5') {
4497
+ config.path = this.$taroPath;
4498
+ }
4219
4499
  // this.$taroParams 作为暴露给开发者的页面参数对象,可以被随意修改
4220
4500
  if (this.$taroParams == null) {
4221
4501
  this.$taroParams = Object.assign({}, options);
@@ -4251,6 +4531,7 @@ function createPageConfig(component, pageName, data, pageConfig) {
4251
4531
  instances.delete($taroPath);
4252
4532
  if (pageElement) {
4253
4533
  pageElement.ctx = null;
4534
+ pageElement = null;
4254
4535
  }
4255
4536
  if (prepareMountList.length) {
4256
4537
  prepareMountList.forEach(fn => fn());
@@ -4265,13 +4546,13 @@ function createPageConfig(component, pageName, data, pageConfig) {
4265
4546
  raf(() => eventCenter.trigger(getOnReadyEventKey(id)));
4266
4547
  this.onReady.called = true;
4267
4548
  },
4268
- [ONSHOW]() {
4549
+ [ONSHOW](options = {}) {
4269
4550
  hasLoaded.then(() => {
4270
4551
  // 设置 Current 的 page 和 router
4271
4552
  Current.page = this;
4272
4553
  setCurrentRouter(this);
4273
4554
  // 触发生命周期
4274
- safeExecute(this.$taroPath, ON_SHOW);
4555
+ safeExecute(this.$taroPath, ON_SHOW, options);
4275
4556
  // 通过事件触发子组件的生命周期
4276
4557
  raf(() => eventCenter.trigger(getOnShowEventKey(id)));
4277
4558
  });
@@ -4320,9 +4601,6 @@ function createPageConfig(component, pageName, data, pageConfig) {
4320
4601
  if (!isUndefined(data)) {
4321
4602
  config.data = data;
4322
4603
  }
4323
- if (process.env.TARO_ENV === 'h5') {
4324
- config.path = id;
4325
- }
4326
4604
  (_c = hooks.modifyPageObject) === null || _c === void 0 ? void 0 : _c.call(hooks, config);
4327
4605
  return config;
4328
4606
  }
@@ -4367,8 +4645,26 @@ function createComponentConfig(component, componentName, data) {
4367
4645
  return config;
4368
4646
  }
4369
4647
  function createRecursiveComponentConfig(componentName) {
4370
- return {
4371
- properties: {
4648
+ const isCustomWrapper = componentName === CUSTOM_WRAPPER;
4649
+ const lifeCycles = isCustomWrapper
4650
+ ? {
4651
+ attached() {
4652
+ var _a;
4653
+ const componentId = (_a = this.data.i) === null || _a === void 0 ? void 0 : _a.sid;
4654
+ if (isString(componentId)) {
4655
+ customWrapperCache.set(componentId, this);
4656
+ }
4657
+ },
4658
+ detached() {
4659
+ var _a;
4660
+ const componentId = (_a = this.data.i) === null || _a === void 0 ? void 0 : _a.sid;
4661
+ if (isString(componentId)) {
4662
+ customWrapperCache.delete(componentId);
4663
+ }
4664
+ }
4665
+ }
4666
+ : EMPTY_OBJ;
4667
+ return Object.assign({ properties: {
4372
4668
  i: {
4373
4669
  type: Object,
4374
4670
  value: {
@@ -4379,15 +4675,12 @@ function createRecursiveComponentConfig(componentName) {
4379
4675
  type: String,
4380
4676
  value: ''
4381
4677
  }
4382
- },
4383
- options: {
4678
+ }, options: {
4384
4679
  addGlobalClass: true,
4385
- virtualHost: componentName !== CUSTOM_WRAPPER
4386
- },
4387
- methods: {
4680
+ virtualHost: !isCustomWrapper
4681
+ }, methods: {
4388
4682
  eh: eventHandler
4389
- }
4390
- };
4683
+ } }, lifeCycles);
4391
4684
  }
4392
4685
 
4393
4686
  function removeLeadingSlash(path) {
@@ -4428,5 +4721,5 @@ const nextTick = (cb, ctx) => {
4428
4721
  }
4429
4722
  };
4430
4723
 
4431
- export { Current, ElementNames, Events, FormElement, SERVICE_IDENTIFIER, SVGElement, Style, TaroElement, TaroEvent, TaroNode, TaroRootElement, TaroText, addLeadingSlash, caf as cancelAnimationFrame, container, createComponentConfig, createDocument, createEvent, createPageConfig, createRecursiveComponentConfig, document$1 as document, eventCenter, eventHandler, getComputedStyle, getCurrentInstance, getPageInstance, hydrate, incrementId, injectPageInstance, navigator, nextTick, now, options, processPluginHooks, raf as requestAnimationFrame, safeExecute, stringify, window$1 as window };
4724
+ export { Current, ElementNames, Events, FormElement, MutationObserver, SERVICE_IDENTIFIER, SVGElement, Style, TaroElement, TaroEvent, TaroNode, TaroRootElement, TaroText, addLeadingSlash, caf as cancelAnimationFrame, container, createComponentConfig, createDocument, createEvent, createPageConfig, createRecursiveComponentConfig, document$1 as document, eventCenter, eventHandler, eventSource, getComputedStyle, getCurrentInstance, getPageInstance, hydrate, incrementId, injectPageInstance, navigator, nextTick, now, options, processPluginHooks, raf as requestAnimationFrame, safeExecute, stringify, window$1 as window };
4432
4725
  //# sourceMappingURL=runtime.esm.js.map