vite-plugin-taro-runtime 0.7.13 → 0.8.0-beta.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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/plugin-framework-react/runtime.js +3 -3
  3. package/dist/plugin-html/html-elements.d.ts +4 -0
  4. package/dist/plugin-html/html-elements.js +94 -0
  5. package/dist/plugin-html/runtime.js +1 -12
  6. package/dist/react/index.d.ts +3 -1
  7. package/dist/react/react.esm.js +56 -48
  8. package/dist/react/renderer-host.d.ts +4 -0
  9. package/dist/runtime/dom/element.d.ts +21 -3
  10. package/dist/runtime/dom/element.js +58 -29
  11. package/dist/runtime/dom/event-source.js +5 -1
  12. package/dist/runtime/dom/event-target.d.ts +11 -1
  13. package/dist/runtime/dom/event-target.js +35 -7
  14. package/dist/runtime/dom/event.d.ts +8 -1
  15. package/dist/runtime/dom/event.js +45 -14
  16. package/dist/runtime/dom/nearest-ctx.d.ts +3 -1
  17. package/dist/runtime/dom/nearest-ctx.js +10 -1
  18. package/dist/runtime/dom/node.d.ts +15 -7
  19. package/dist/runtime/dom/node.js +88 -44
  20. package/dist/runtime/dom/style.d.ts +7 -2
  21. package/dist/runtime/dom/style.js +19 -5
  22. package/dist/runtime/dom/text.d.ts +3 -2
  23. package/dist/runtime/dom/text.js +13 -6
  24. package/dist/runtime/dom-external/mutation-observer/implements.d.ts +3 -1
  25. package/dist/runtime/dom-external/mutation-observer/implements.js +5 -1
  26. package/dist/runtime/dom-external/mutation-observer/index.d.ts +2 -0
  27. package/dist/runtime/dom-external/mutation-observer/index.js +5 -1
  28. package/dist/runtime/dom-external/node.js +56 -18
  29. package/dist/runtime/dsl/common.d.ts +1 -0
  30. package/dist/runtime/dsl/common.js +22 -74
  31. package/dist/runtime/dsl/instance.d.ts +2 -3
  32. package/dist/runtime/index.d.ts +1 -1
  33. package/dist/runtime/index.js +1 -1
  34. package/dist/runtime/utils/index.js +1 -1
  35. package/package.json +7 -1
  36. package/types/tt-document.d.ts +21 -2
package/README.md CHANGED
@@ -14,6 +14,8 @@ Output directories and exports mirror the upstream Taro package names (`runtime`
14
14
 
15
15
  The `api`, `taro`, `components`, `router`, and `taro-h5` directories retain the runtime files and declarations used by VPT. Alternate bundles and the components' unused lazy-loader JavaScript are omitted. The generic Taro facade retains its CommonJS package boundary, shared mutable API object, and bulk native initialization. VPT's Mini facade attaches framework hooks to that same object; it does not generate a separate API inventory or promise per-API tree-shaking. H5 instead uses a read-only ESM namespace over its modular APIs and static framework hooks. The framework runtime retains Taro's original hook registration, so hooks remain grouped rather than being individually tree-shaken. Copied declarations redirect imports and module augmentations to this package's type exports, so consumer type checking does not depend on Vite aliases or upstream development dependencies. CSS remains side-effectful, and required third-party runtime dependencies are declared directly. None of these upstream Taro packages—and therefore none of their `@tarojs/helper` compiler dependencies—are installed in consumer projects.
16
16
 
17
+ The `plugin-html/runtime` entry installs upstream HTML hooks. Its side-effect-free `plugin-html/html-elements` companion shares the inline/block classifications and semantic `h5-*` class handling with native DOM hosts.
18
+
17
19
  ## Development
18
20
 
19
21
  Build the runtime package from the repository root:
@@ -250,7 +250,7 @@ function connectReactPage(R, id) {
250
250
  return h$1('div', { id, className: 'taro_page' }, children);
251
251
  }
252
252
  else {
253
- return h$1('root', { id }, children);
253
+ return h$1(ReactDOM$1.getPageRootType(id), { id }, children);
254
254
  }
255
255
  }
256
256
  };
@@ -636,7 +636,7 @@ function createNativePageConfig(Component, pageName, data, react, reactDOM, page
636
636
  }
637
637
  const pageObj = {
638
638
  options: pageConfig,
639
- [ONLOAD](options = {}, cb) {
639
+ [ONLOAD](options) {
640
640
  hasLoaded = new Promise(resolve => { loadResolver = resolve; });
641
641
  Current.page = this;
642
642
  this.config = pageConfig || {};
@@ -655,7 +655,7 @@ function createNativePageConfig(Component, pageName, data, react, reactDOM, page
655
655
  safeExecute($taroPath, ONLOAD, this.$taroParams);
656
656
  loadResolver();
657
657
  pageElement.ctx = this;
658
- pageElement.performUpdate(true, cb);
658
+ pageElement.performUpdate(true);
659
659
  };
660
660
  const mount = () => {
661
661
  if (!Current.app) {
@@ -0,0 +1,4 @@
1
+ /** Upstream HTML classifications shared by templates and native DOM hosts. */
2
+ export declare const inlineElements: ReadonlySet<string>
3
+ export declare const blockElements: ReadonlySet<string>
4
+ export declare function ensureHtmlClass(tagName: string, className: string | undefined): string
@@ -0,0 +1,94 @@
1
+ // Upstream HTML display defaults shared by VPT's template and native DOM hosts.
2
+ export const inlineElements = new Set([
3
+ 'i',
4
+ 'abbr',
5
+ 'select',
6
+ 'acronym',
7
+ 'small',
8
+ 'bdi',
9
+ 'kbd',
10
+ 'strong',
11
+ 'big',
12
+ 'sub',
13
+ 'sup',
14
+ 'br',
15
+ 'mark',
16
+ 'meter',
17
+ 'template',
18
+ 'cite',
19
+ 'object',
20
+ 'time',
21
+ 'code',
22
+ 'output',
23
+ 'u',
24
+ 'data',
25
+ 'picture',
26
+ 'tt',
27
+ 'datalist',
28
+ 'var',
29
+ 'dfn',
30
+ 'del',
31
+ 'q',
32
+ 'em',
33
+ 's',
34
+ 'embed',
35
+ 'samp',
36
+ 'b'
37
+ ])
38
+ export const blockElements = new Set([
39
+ 'body',
40
+ 'svg',
41
+ 'address',
42
+ 'fieldset',
43
+ 'li',
44
+ 'span',
45
+ 'article',
46
+ 'figcaption',
47
+ 'main',
48
+ 'aside',
49
+ 'figure',
50
+ 'nav',
51
+ 'blockquote',
52
+ 'footer',
53
+ 'ol',
54
+ 'details',
55
+ 'p',
56
+ 'dialog',
57
+ 'h1',
58
+ 'h2',
59
+ 'h3',
60
+ 'h4',
61
+ 'h5',
62
+ 'h6',
63
+ 'pre',
64
+ 'dd',
65
+ 'header',
66
+ 'section',
67
+ 'div',
68
+ 'hgroup',
69
+ 'table',
70
+ 'dl',
71
+ 'hr',
72
+ 'ul',
73
+ 'dt',
74
+ 'view',
75
+ 'view-block'
76
+ ])
77
+
78
+ export function ensureHtmlClass(tagName, className) {
79
+ const htmlClass = `h5-${tagName}`
80
+ const prefix = `${htmlClass} `
81
+ if (className === undefined || className === '') {
82
+ return prefix
83
+ }
84
+ // Taro uses literal-space token boundaries and preserves the caller's whitespace.
85
+ if (
86
+ className === htmlClass ||
87
+ className.startsWith(prefix) ||
88
+ className.endsWith(` ${htmlClass}`) ||
89
+ className.includes(` ${prefix}`)
90
+ ) {
91
+ return className
92
+ }
93
+ return prefix + className
94
+ }
@@ -1,5 +1,6 @@
1
1
  import { isHasExtractProp } from '@tarojs/runtime';
2
2
  import { isString, isFunction, toCamelCase, hooks, warn } from '@tarojs/shared';
3
+ import { inlineElements, blockElements, ensureHtmlClass } from './html-elements.js';
3
4
 
4
5
  function genAttrMapFnFromDir(dir) {
5
6
  const fn = function (key, value) {
@@ -18,10 +19,6 @@ function genAttrMapFnFromDir(dir) {
18
19
  };
19
20
  return fn;
20
21
  }
21
- // Taro normally fills this set by rewriting runtime.js from its compiler plugin.
22
- // VPT pins the upstream defaults at install time instead: no CLI, Babel, or per-build file mutation.
23
- const inlineElements = new Set(['i', 'abbr', 'select', 'acronym', 'small', 'bdi', 'kbd', 'strong', 'big', 'sub', 'sup', 'br', 'mark', 'meter', 'template', 'cite', 'object', 'time', 'code', 'output', 'u', 'data', 'picture', 'tt', 'datalist', 'var', 'dfn', 'del', 'q', 'em', 's', 'embed', 'samp', 'b']);
24
- const blockElements = new Set(['body', 'svg', 'address', 'fieldset', 'li', 'span', 'article', 'figcaption', 'main', 'aside', 'figure', 'nav', 'blockquote', 'footer', 'ol', 'details', 'p', 'dialog', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'dd', 'header', 'section', 'div', 'hgroup', 'table', 'dl', 'hr', 'ul', 'dt', 'view', 'view-block']);
25
22
  const specialElements = new Map([
26
23
  ['slot', 'slot'],
27
24
  ['form', 'form'],
@@ -193,14 +190,6 @@ function mapNameByContion(nodeName, key, element, componentsAlias) {
193
190
  });
194
191
  }
195
192
  }
196
- function ensureHtmlClass(tagName, className = '') {
197
- const classList = className.split(' ');
198
- const htmlClass = `h5-${tagName}`;
199
- if (classList.indexOf(htmlClass) === -1) {
200
- classList.unshift(htmlClass);
201
- }
202
- return classList.join(' ');
203
- }
204
193
  function ensureRect(props, style = '') {
205
194
  let cssText = style;
206
195
  const { width, height } = props;
@@ -2,6 +2,7 @@ import { internalInstanceKey } from './constant';
2
2
  import { createRoot } from './render';
3
3
  import type { TaroElement } from 'vite-plugin-taro-runtime/runtime/mini';
4
4
  import type { ReactNode } from 'react';
5
+ declare function getPageRootType(id: string): string;
5
6
  declare const unstable_batchedUpdates: (fn: any, a: any) => any;
6
7
  declare function unmountComponentAtNode(dom: TaroElement): boolean;
7
8
  declare function createPortal(children: ReactNode, containerInfo: TaroElement, key?: string): {
@@ -16,13 +17,14 @@ declare const flushSync: {
16
17
  <R>(fn: () => R): R;
17
18
  };
18
19
  export type { RendererHost } from './renderer-host';
19
- export { createPortal, createRoot, flushSync, internalInstanceKey, unmountComponentAtNode, unstable_batchedUpdates };
20
+ export { createPortal, createRoot, flushSync, getPageRootType, internalInstanceKey, unmountComponentAtNode, unstable_batchedUpdates };
20
21
  declare const _default: {
21
22
  flushSync: {
22
23
  (): void;
23
24
  <R>(fn: () => R): R;
24
25
  };
25
26
  createRoot: typeof createRoot;
27
+ getPageRootType: typeof getPageRootType;
26
28
  unstable_batchedUpdates: (fn: any, a: any) => any;
27
29
  unmountComponentAtNode: typeof unmountComponentAtNode;
28
30
  createPortal: typeof createPortal;
@@ -403,8 +403,9 @@ function setEvent(dom, name, value, oldValue) {
403
403
  }
404
404
  if (isFunction(value)) {
405
405
  if (oldValue) {
406
- dom.removeEventListener(eventName, oldValue, process.env.TARO_PLATFORM !== PLATFORM_TYPE.HARMONY ? false : undefined);
406
+ // Keep the host subscription active while replacing the logical callback.
407
407
  dom.addEventListener(eventName, value, process.env.TARO_PLATFORM !== PLATFORM_TYPE.HARMONY ? { isCapture, sideEffect: false } : undefined);
408
+ dom.removeEventListener(eventName, oldValue, process.env.TARO_PLATFORM !== PLATFORM_TYPE.HARMONY ? false : undefined);
408
409
  }
409
410
  else {
410
411
  dom.addEventListener(eventName, value, process.env.TARO_PLATFORM !== PLATFORM_TYPE.HARMONY ? isCapture : undefined);
@@ -495,63 +496,66 @@ function setHarmonyStyle(dom, value, oldValue) {
495
496
  }
496
497
  dom.setAttribute('__hmStyle', value);
497
498
  }
498
- function setProperty(dom, name, value, oldValue) {
499
- var _a, _b;
500
- name = name === 'className' ? 'class' : name;
501
- if (name === 'key' ||
502
- name === 'children' ||
503
- name === 'ref') ;
504
- else if (name === 'style') {
505
- if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) {
506
- if (isString(value)) {
507
- dom.setAttribute('style', value);
508
- }
509
- else if (isObject(value)) {
510
- dom.setAttribute('style', styleObjectToCss(value));
511
- }
499
+ function setStyleProperty(dom, value, oldValue) {
500
+ if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) {
501
+ if (isString(value)) {
502
+ dom.setAttribute('style', value);
503
+ }
504
+ else if (isObject(value)) {
505
+ dom.setAttribute('style', styleObjectToCss(value));
506
+ }
507
+ }
508
+ else {
509
+ if (/harmony.*cpp/.test(process.env.TARO_ENV || '')) {
510
+ return dom.setAttribute('_style4cpp', value);
511
+ }
512
+ const style = dom.style;
513
+ if (isString(value)) {
514
+ style.cssText = value;
512
515
  }
513
516
  else {
514
- if (/harmony.*cpp/.test(process.env.TARO_ENV || '')) {
515
- return dom.setAttribute('_style4cpp', value);
517
+ if (isString(oldValue)) {
518
+ style.cssText = '';
519
+ oldValue = null;
516
520
  }
517
- const style = dom.style;
518
- if (isString(value)) {
519
- style.cssText = value;
520
- }
521
- else {
522
- if (isString(oldValue)) {
523
- style.cssText = '';
524
- oldValue = null;
525
- }
526
- if (isObject(oldValue)) {
527
- for (const i in oldValue) {
528
- if (!(value && i in value)) {
529
- // Harmony特殊处理
530
- if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position' && oldValue[i] === 'fixed') {
531
- // @ts-ignore
532
- dom.setLayer(0);
533
- }
534
- setStyle(style, i, '');
521
+ if (isObject(oldValue)) {
522
+ for (const i in oldValue) {
523
+ if (!(value && i in value)) {
524
+ // Harmony特殊处理
525
+ if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position' && oldValue[i] === 'fixed') {
526
+ // @ts-ignore
527
+ dom.setLayer(0);
535
528
  }
529
+ setStyle(style, i, '');
536
530
  }
537
531
  }
538
- if (isObject(value)) {
539
- for (const i in value) {
540
- if (!oldValue || !isEqual(value[i], oldValue[i])) {
541
- // Harmony特殊处理
542
- if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position') {
543
- if (value[i] === 'fixed' || (value[i] !== 'fixed' && (oldValue === null || oldValue === void 0 ? void 0 : oldValue[i]))) {
544
- // @ts-ignore
545
- dom.setLayer(value[i] === 'fixed' ? 1 : 0);
546
- }
532
+ }
533
+ if (isObject(value)) {
534
+ for (const i in value) {
535
+ if (!oldValue || !isEqual(value[i], oldValue[i])) {
536
+ // Harmony特殊处理
537
+ if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position') {
538
+ if (value[i] === 'fixed' || (value[i] !== 'fixed' && (oldValue === null || oldValue === void 0 ? void 0 : oldValue[i]))) {
539
+ // @ts-ignore
540
+ dom.setLayer(value[i] === 'fixed' ? 1 : 0);
547
541
  }
548
- setStyle(style, i, value[i]);
549
542
  }
543
+ setStyle(style, i, value[i]);
550
544
  }
551
545
  }
552
546
  }
553
547
  }
554
548
  }
549
+ }
550
+ function setProperty(dom, name, value, oldValue) {
551
+ var _a, _b;
552
+ name = name === 'className' ? 'class' : name;
553
+ if (name === 'key' ||
554
+ name === 'children' ||
555
+ name === 'ref') ;
556
+ else if (name === 'style') {
557
+ host.batchStyleUpdate(dom, () => setStyleProperty(dom, value, oldValue))
558
+ }
555
559
  else if (isEventName(name)) {
556
560
  setEvent(dom, name, value, oldValue);
557
561
  }
@@ -658,8 +662,8 @@ const hostConfig = {
658
662
  getRootHostContext() {
659
663
  return {};
660
664
  },
661
- getChildHostContext(parentHostContext) {
662
- return parentHostContext;
665
+ getChildHostContext(parentHostContext, type) {
666
+ return host.getChildHostContext(parentHostContext, type);
663
667
  },
664
668
  prepareForCommit(container) {
665
669
  host.beforeCommit?.(container);
@@ -995,14 +999,18 @@ function createPortal(children, containerInfo, key) {
995
999
  }
996
1000
  // Public flushSync runs a callback at synchronous priority; controlled-input restoration only flushes queued sync work.
997
1001
  const flushSync = TaroReconciler.flushSyncFromReconciler;
1002
+ function getPageRootType(id) {
1003
+ return host.getPageRootType(id);
1004
+ }
998
1005
  var index = {
999
1006
  flushSync,
1000
1007
  createRoot,
1008
+ getPageRootType,
1001
1009
  unstable_batchedUpdates,
1002
1010
  unmountComponentAtNode,
1003
1011
  createPortal,
1004
1012
  internalInstanceKey
1005
1013
  };
1006
1014
 
1007
- export { createPortal, createRoot, index as default, flushSync, internalInstanceKey, unmountComponentAtNode, unstable_batchedUpdates };
1015
+ export { createPortal, createRoot, index as default, flushSync, getPageRootType, internalInstanceKey, unmountComponentAtNode, unstable_batchedUpdates };
1008
1016
  //# sourceMappingURL=react.esm.js.map
@@ -1,8 +1,12 @@
1
1
  /** Host-owned creation, form classification and commit boundaries for the shared React renderer. */
2
2
  export type RendererHost<Container, Context, Element, Text> = Readonly<{
3
+ getPageRootType(id: string): string
4
+ getChildHostContext(parentContext: Context, type: string): Context
3
5
  createElement(container: Container, context: Context, type: string): Element
4
6
  createText(container: Container, context: Context, text: string): Text
5
7
  isFormElement(element: Element): boolean
8
+ /** Synchronously scope one style-prop application; logical setters and mutation records still run. */
9
+ batchStyleUpdate(element: Element, update: () => void): void
6
10
  beforeCommit?(container: Container): void
7
11
  afterCommit(container: Container): void
8
12
  }>
@@ -9,11 +9,13 @@ declare class TaroElement extends TaroNode {
9
9
  ctx?: any;
10
10
  tagName: string;
11
11
  props: Record<string, any>;
12
- style: Style;
12
+ private _style?: Style;
13
13
  dataset: Record<string, unknown>;
14
14
  innerHTML: string;
15
15
  constructor();
16
16
  private _stopPropagation;
17
+ get style(): Style;
18
+ set style(value: Style);
17
19
  get id(): string;
18
20
  set id(val: string);
19
21
  get className(): string;
@@ -29,11 +31,21 @@ declare class TaroElement extends TaroNode {
29
31
  get focus(): () => void;
30
32
  set focus(value: () => void);
31
33
  blur(): void;
34
+ /** Pre-mutation template classification: pure view for writes, static view for removals. Native hosts return false. */
35
+ prepareAttributeMutation(removing: boolean): boolean;
32
36
  setAttribute(qualifiedName: string, value: any): void;
33
- /** Delegates after logical storage and ID/dataset updates; the flag records the prior template view classification. */
37
+ /**
38
+ * 逻辑属性、ID/dataset 和 mutation 记录更新后调用,尚未转换模板属性别名。
39
+ * value 为逻辑分支处理后的属性值,isPureView 记录修改前的模板分类。
40
+ * style 属性先触发 Style 委托,再进入此入口;默认准备模板属性及类型更新。
41
+ */
34
42
  delegateAttributeUpdate(qualifiedName: string, value: unknown, isPureView: boolean): void;
35
43
  removeAttribute(qualifiedName: string): void;
36
- /** Delegates after successful logical removal; the flag records the prior template view classification. */
44
+ /**
45
+ * 逻辑移除完成后调用,isStaticView 记录移除前的模板分类。
46
+ * 普通属性缺失或被 hook 拦截时提前返回;style 先经 Style 委托清空。
47
+ * 默认准备模板属性及类型更新,host 可接管原生属性移除。
48
+ */
37
49
  delegateAttributeRemoval(qualifiedName: string, isStaticView: boolean): void;
38
50
  getAttribute(qualifiedName: string): string;
39
51
  getElementsByTagName(tagName: string): TaroElement[];
@@ -41,6 +53,12 @@ declare class TaroElement extends TaroNode {
41
53
  dispatchEvent(event: TaroEvent): boolean;
42
54
  addEventListener(type: any, handler: any, options: any): void;
43
55
  removeEventListener(type: any, handler: any, sideEffect?: boolean): void;
56
+ /**
57
+ * added=true 在添加处理器前调用,false 在移除后调用,负责模板节点类型副作用。
58
+ * sideEffect=false 只抑制模板更新;原生订阅通知由 delegateEventListener 独立处理。
59
+ * 默认实现按 added 选择上游添加/移除 hook,并按需查询组件别名。
60
+ */
61
+ delegateEventUpdate(added: boolean, sideEffect: boolean): void;
44
62
  static extend(methodName: string, options: TFunc | Record<string, any>): void;
45
63
  }
46
64
 
@@ -14,7 +14,6 @@ class TaroElement extends TaroNode {
14
14
  this.props = {};
15
15
  this.dataset = EMPTY_OBJ;
16
16
  this.nodeType = 1 /* NodeType.ELEMENT_NODE */;
17
- this.style = new Style(this);
18
17
  hooks.call('patchElement', this);
19
18
  }
20
19
  _stopPropagation(event) {
@@ -22,7 +21,7 @@ class TaroElement extends TaroNode {
22
21
  let target = this;
23
22
  // eslint-disable-next-line no-cond-assign
24
23
  while ((target = target.parentNode)) {
25
- const listeners = target.__handlers[event.type];
24
+ const listeners = target.getEventHandlers(event.type);
26
25
  if (!isArray(listeners)) {
27
26
  continue;
28
27
  }
@@ -32,6 +31,13 @@ class TaroElement extends TaroNode {
32
31
  }
33
32
  }
34
33
  }
34
+ // Each element retains one mutable declaration, initialized on first style access.
35
+ get style() {
36
+ return this._style ??= new Style(this);
37
+ }
38
+ set style(value) {
39
+ this._style = value;
40
+ }
35
41
  get id() {
36
42
  return this.getAttribute(ID);
37
43
  }
@@ -56,7 +62,7 @@ class TaroElement extends TaroNode {
56
62
  get attributes() {
57
63
  const props = this.props;
58
64
  const propKeys = Object.keys(props);
59
- const style = this.style.cssText;
65
+ const style = this.getAttribute(STYLE);
60
66
  const attrs = propKeys.map(key => ({ name: key, value: props[key] }));
61
67
  return attrs.concat(style ? { name: STYLE, value: style } : []);
62
68
  }
@@ -89,10 +95,14 @@ class TaroElement extends TaroNode {
89
95
  blur() {
90
96
  this.setAttribute(FOCUS, false);
91
97
  }
98
+ /** Capture template classification before logical mutation, observer records and removal hooks. */
99
+ prepareAttributeMutation(removing) {
100
+ return this.nodeName === VIEW && isHasExtractProp(this) === removing && !this.isAnyEventBinded();
101
+ }
92
102
  setAttribute(qualifiedName, value) {
93
103
  process.env.NODE_ENV !== 'production' && warn(isString(value) && value.length > PROPERTY_THRESHOLD, `元素 ${this.nodeName} 的 ${qualifiedName} 属性值数据量过大,可能会影响渲染性能。考虑降低图片转为 base64 的阈值或在 CSS 中使用 base64。`);
94
- const isPureView = this.nodeName === VIEW && !isHasExtractProp(this) && !this.isAnyEventBinded();
95
- if (qualifiedName !== STYLE) {
104
+ const isPureView = this.prepareAttributeMutation(false);
105
+ if (qualifiedName !== STYLE && MutationObserver.hasObservers) {
96
106
  MutationObserver.record({
97
107
  target: this,
98
108
  type: "attributes" /* MutationRecordType.ATTRIBUTES */,
@@ -126,7 +136,15 @@ class TaroElement extends TaroNode {
126
136
  }
127
137
  this.delegateAttributeUpdate(qualifiedName, value, isPureView);
128
138
  }
129
- /** Delegates the stored attribute value; isPureView preserves the pre-mutation template classification. */
139
+ /**
140
+ * Taro 已完成逻辑属性写入、相应的 ID/dataset 更新及 mutation 记录,再通知 host。
141
+ * qualifiedName 保留属性名;value 是逻辑分支处理后的值,例如 id 已转为字符串,
142
+ * 此时尚未执行模板短名/组件别名转换。
143
+ * isPureView 保存修改前的模板分类,用于判断属性变化是否要求切换模板节点类型。
144
+ * 默认实现为已挂载节点准备属性和节点类型 payload;host 可接管原生属性写入。
145
+ * style 属性会先经 Style.delegateStyleUpdate,再进入此处;host 应按属性名
146
+ * 区分这两个通知的职责,复用 Style 已完成的逻辑样式处理。
147
+ */
130
148
  delegateAttributeUpdate(qualifiedName, value, isPureView) {
131
149
  // Serialization
132
150
  if (!this._root)
@@ -169,13 +187,15 @@ class TaroElement extends TaroNode {
169
187
  }
170
188
  }
171
189
  removeAttribute(qualifiedName) {
172
- const isStaticView = this.nodeName === VIEW && isHasExtractProp(this) && !this.isAnyEventBinded();
173
- MutationObserver.record({
174
- target: this,
175
- type: "attributes" /* MutationRecordType.ATTRIBUTES */,
176
- attributeName: qualifiedName,
177
- oldValue: this.getAttribute(qualifiedName)
178
- });
190
+ const isStaticView = this.prepareAttributeMutation(true);
191
+ if (MutationObserver.hasObservers) {
192
+ MutationObserver.record({
193
+ target: this,
194
+ type: "attributes" /* MutationRecordType.ATTRIBUTES */,
195
+ attributeName: qualifiedName,
196
+ oldValue: this.getAttribute(qualifiedName)
197
+ });
198
+ }
179
199
  if (qualifiedName === STYLE) {
180
200
  this.style.cssText = '';
181
201
  }
@@ -191,7 +211,13 @@ class TaroElement extends TaroNode {
191
211
  }
192
212
  this.delegateAttributeRemoval(qualifiedName, isStaticView);
193
213
  }
194
- /** Delegates a completed removal; interrupted and absent attributes retain their original early returns. */
214
+ /**
215
+ * Taro 执行原有属性移除逻辑后通知 host;普通属性不存在或被移除 hook 拦截时提前返回。
216
+ * qualifiedName 是被移除的属性名,isStaticView 保存移除前的模板分类。
217
+ * 默认实现准备属性清空及相应的模板类型更新;原生 host 接管原生属性移除。
218
+ * style 的清空先经过 Style.delegateStyleUpdate,再通知此方法,与设置 style 的顺序一致。
219
+ * 逻辑 props、移除拦截和 mutation 记录仍由 Taro 的公开方法维护。
220
+ */
195
221
  delegateAttributeRemoval(qualifiedName, isStaticView) {
196
222
  // Serialization
197
223
  if (!this._root)
@@ -233,7 +259,7 @@ class TaroElement extends TaroNode {
233
259
  }
234
260
  }
235
261
  getAttribute(qualifiedName) {
236
- const attr = qualifiedName === STYLE ? this.style.cssText : this.props[qualifiedName];
262
+ const attr = qualifiedName === STYLE ? this._style?.cssText : this.props[qualifiedName];
237
263
  return attr !== null && attr !== void 0 ? attr : '';
238
264
  }
239
265
  getElementsByTagName(tagName) {
@@ -250,7 +276,7 @@ class TaroElement extends TaroNode {
250
276
  }
251
277
  dispatchEvent(event) {
252
278
  const cancelable = event.cancelable;
253
- const listeners = this.__handlers[event.type];
279
+ const listeners = this.getEventHandlers(event.type);
254
280
  if (!isArray(listeners)) {
255
281
  return false;
256
282
  }
@@ -283,32 +309,35 @@ class TaroElement extends TaroNode {
283
309
  return listeners != null;
284
310
  }
285
311
  addEventListener(type, handler, options) {
286
- const name = this.nodeName;
287
- const SPECIAL_NODES = hooks.call('getSpecialNodes');
288
312
  let sideEffect = true;
289
313
  if (isObject(options) && options.sideEffect === false) {
290
314
  sideEffect = false;
291
315
  delete options.sideEffect;
292
316
  }
293
- hooks.call('modifyAddEventListener', this, sideEffect, getComponentsAlias);
294
- if (sideEffect !== false && !this.isAnyEventBinded() && SPECIAL_NODES.indexOf(name) > -1) {
295
- const componentsAlias = getComponentsAlias();
296
- const alias = componentsAlias[name]._num;
297
- this.enqueueUpdate({
298
- path: `${this._path}.${"nn" /* Shortcuts.NodeName */}`,
299
- value: alias
300
- });
301
- }
317
+ this.delegateEventUpdate(true, sideEffect);
302
318
  super.addEventListener(type, handler, options);
303
319
  }
304
320
  removeEventListener(type, handler, sideEffect = true) {
305
321
  super.removeEventListener(type, handler);
322
+ this.delegateEventUpdate(false, sideEffect);
323
+ }
324
+ /**
325
+ * 事件监听变化对应的模板副作用,与原生订阅开关 delegateEventListener 分开。
326
+ * added=true 时在添加处理器之前调用,观察旧监听数组;added=false 时在移除之后调用,
327
+ * 观察剩余监听数组。默认实现据此把节点切换为可交互或 pure/static 模板。
328
+ * sideEffect=false 只抑制模板更新,逻辑监听器变更和原生订阅通知照常执行。
329
+ * 原生 host 可独立替换此模板副作用;Taro 的公开方法继续负责处理器语义。
330
+ */
331
+ delegateEventUpdate(added, sideEffect) {
306
332
  const name = this.nodeName;
307
333
  const SPECIAL_NODES = hooks.call('getSpecialNodes');
308
- hooks.call('modifyRemoveEventListener', this, sideEffect, getComponentsAlias);
334
+ // 上游添加/移除方法各有一个模板扩展 hook;合并副作用实现后按 added 选择原 hook。
335
+ // 参数依次是当前逻辑节点、模板副作用开关和别名查询函数。函数保持延迟调用,
336
+ // 由 hook 按需取得组件别名;这一步处理模板类型,不注册或取消原生事件。
337
+ hooks.call(added ? 'modifyAddEventListener' : 'modifyRemoveEventListener', this, sideEffect, getComponentsAlias);
309
338
  if (sideEffect !== false && !this.isAnyEventBinded() && SPECIAL_NODES.indexOf(name) > -1) {
310
339
  const componentsAlias = getComponentsAlias();
311
- const value = name === VIEW && !isHasExtractProp(this) ? PURE_VIEW : `static-${name}`;
340
+ const value = added ? name : (name === VIEW && !isHasExtractProp(this) ? PURE_VIEW : `static-${name}`);
312
341
  const valueAlias = componentsAlias[value]._num;
313
342
  this.enqueueUpdate({
314
343
  path: `${this._path}.${"nn" /* Shortcuts.NodeName */}`,
@@ -8,7 +8,11 @@ class EventSource extends Map {
8
8
  removeNodeTree(child) {
9
9
  this.removeNode(child);
10
10
  const { childNodes } = child;
11
- childNodes.forEach(node => this.removeNodeTree(node));
11
+ const length = childNodes.length;
12
+ // A local index visits the existing children in sibling order.
13
+ for (let index = 0; index < length; index++) {
14
+ this.removeNodeTree(childNodes[index]);
15
+ }
12
16
  }
13
17
  }
14
18
  const eventSource = new EventSource();
@@ -1,9 +1,19 @@
1
1
  import { EventHandler, AddEventListenerOptions } from '../interface/event-target.js';
2
2
 
3
3
  declare class TaroEventTarget {
4
- __handlers: Record<string, EventHandler[]>;
4
+ private _handlers?: Record<string, EventHandler[]>;
5
+ get __handlers(): Record<string, EventHandler[]>;
6
+ set __handlers(value: Record<string, EventHandler[]>);
7
+ /** Read an existing event array without materializing the handler table. */
8
+ getEventHandlers(type: string): EventHandler[] | undefined;
5
9
  addEventListener(type: string, handler: EventHandler, options?: boolean | AddEventListenerOptions): void;
6
10
  removeEventListener(type: string, handler: EventHandler): void;
11
+ /**
12
+ * 首个处理器存入后 subscribed=true,末个移除后为 false;复用现有处理器数组判断。
13
+ * type 为小写逻辑事件名,regionchange 已展开为 begin/end;host 负责平台订阅关系。
14
+ * 默认实现为空,处理器、once 和移除匹配由 Taro 维护。
15
+ */
16
+ delegateEventListener(type: string, subscribed: boolean): void;
7
17
  isAnyEventBinded(): boolean;
8
18
  isOnlyClickBinded(): boolean;
9
19
  }