Skip to content

Commit f3a082f

Browse files
author
xuzhengda
committed
feat: taro 支持 ttdom
1 parent 3317bb2 commit f3a082f

13 files changed

Lines changed: 334 additions & 43 deletions

File tree

packages/shared/src/constants.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,101 @@ export const PLATFORM_CONFIG_MAP = {
3131
type: PLATFORM_TYPE.QUICK
3232
},
3333
}
34+
35+
export const TT_SPECIFIC_COMPONENTS = new Set([
36+
'page-container',
37+
'slot',
38+
'custom-wrapper'
39+
])
40+
41+
export const DEFAULT_Components = new Set<string>([
42+
'view',
43+
'scroll-view',
44+
'swiper',
45+
'cover-view',
46+
'cover-image',
47+
'icon',
48+
'text',
49+
'rich-text',
50+
'progress',
51+
'button',
52+
'checkbox',
53+
'form',
54+
'input',
55+
'label',
56+
'picker',
57+
'picker-view',
58+
'picker-view-column',
59+
'radio',
60+
'radio-group',
61+
'checkbox-group',
62+
'slider',
63+
'switch',
64+
'textarea',
65+
'navigator',
66+
'audio',
67+
'image',
68+
'video',
69+
'camera',
70+
'live-player',
71+
'live-pusher',
72+
'map',
73+
'canvas',
74+
'open-data',
75+
'web-view',
76+
'swiper-item',
77+
'movable-area',
78+
'movable-view',
79+
'functional-page-navigator',
80+
'ad',
81+
'block',
82+
'import',
83+
'official-account',
84+
'editor'
85+
])
86+
87+
export const unitlessProperties = new Set([
88+
'animation-iteration-count',
89+
'border-image-outset',
90+
'border-image-slice',
91+
'border-image-width',
92+
'box-flex',
93+
'box-flex-group',
94+
'box-ordinal-group',
95+
'column-count',
96+
'columns',
97+
'flex',
98+
'flex-grow',
99+
'flex-positive',
100+
'flex-shrink',
101+
'flex-negative',
102+
'flex-order',
103+
'grid-area',
104+
'grid-row',
105+
'grid-row-end',
106+
'grid-row-span',
107+
'grid-row-start',
108+
'grid-column',
109+
'grid-column-end',
110+
'grid-column-span',
111+
'grid-column-start',
112+
'font-weight',
113+
'line-clamp',
114+
'line-height',
115+
'opacity',
116+
'order',
117+
'orphans',
118+
'tab-size',
119+
'widows',
120+
'z-index',
121+
'zoom',
122+
// SVG-related properties
123+
'fill-opacity',
124+
'flood-opacity',
125+
'stop-opacity',
126+
'stroke-dasharray',
127+
'stroke-dashoffset',
128+
'stroke-miterlimit',
129+
'stroke-opacity',
130+
'stroke-width',
131+
])

packages/shared/src/utils.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { internalComponents } from './components'
2-
import { PLATFORM_CONFIG_MAP, PLATFORM_TYPE } from './constants'
2+
import { PLATFORM_CONFIG_MAP, PLATFORM_TYPE, unitlessProperties } from './constants'
33
import { hooks } from './runtime-hooks'
44

55
export const EMPTY_OBJ: any = {}
@@ -249,3 +249,40 @@ export function indent (str: string, size: number): string {
249249
})
250250
.join('\n')
251251
}
252+
253+
export enum TTRenderType {
254+
V1 = 1,
255+
V2 = 2,
256+
}
257+
258+
declare const tt: any
259+
let ttUseV2TTDom: boolean | undefined
260+
261+
export function isEnableTTDom() {
262+
// 目前仅对于 react 支持 ttdom
263+
if (process.env.TARO_ENV !== 'tt' || process.env.FRAMEWORK !== 'react' || typeof tt === 'undefined') {
264+
return false
265+
}
266+
if (ttUseV2TTDom !== undefined) return ttUseV2TTDom
267+
268+
const ttMode = tt.getRenderMode ? tt.getRenderMode() : TTRenderType.V1
269+
270+
ttMode === TTRenderType.V2 && tt.__$enableTTDom$__ ? (ttUseV2TTDom = true) : (ttUseV2TTDom = false)
271+
272+
return ttUseV2TTDom
273+
}
274+
275+
export function styleObjectToCss(style: { [s: string]: unknown } | ArrayLike<unknown>) {
276+
return Object.entries(style)
277+
.map(([key, value]) => {
278+
const kebabCaseKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
279+
const cssValue = typeof value === 'number' && !isUnitlessProperty(kebabCaseKey) ? `${value}px` : value
280+
return `${kebabCaseKey}: ${cssValue};`
281+
})
282+
.join(' ')
283+
}
284+
285+
// Helper function to check if the property is unitless
286+
function isUnitlessProperty(property: string) {
287+
return unitlessProperties.has(property)
288+
}

packages/taro-react/src/props.ts

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { convertNumber2PX, FormElement } from '@tarojs/runtime'
2-
import { capitalize, internalComponents, isFunction, isNumber, isObject, isString, PLATFORM_TYPE, toCamelCase } from '@tarojs/shared'
1+
import { convertNumber2PX, eventHandlerTTDom, FormElement, setInnerHTML } from '@tarojs/runtime'
2+
import { capitalize, internalComponents, isEnableTTDom, isFunction, isNumber, isObject, isString, PLATFORM_TYPE, styleObjectToCss, toCamelCase } from '@tarojs/shared'
33

44
import type { Style, TaroElement } from '@tarojs/runtime'
55

@@ -8,6 +8,7 @@ import type { Style, TaroElement } from '@tarojs/runtime'
88
export type Props = Record<string, unknown>
99

1010
const IS_NON_DIMENSIONAL = /aspect|acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i
11+
const IS_DATASET_OR_ARIA = /^(data|aria)-/
1112

1213
function isEventName (s: string) {
1314
return s[0] === 'o' && s[1] === 'n'
@@ -125,6 +126,17 @@ function setEvent (dom: TaroElement, name: string, value: unknown, oldValue?: un
125126
eventName = 'tap'
126127
}
127128

129+
if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) {
130+
if (isFunction(oldValue)) {
131+
(dom as any).removeEventListener(`bind${eventName}`, dom[`__${eventName}__`])
132+
}
133+
if (isFunction(value)) {
134+
dom[`__${eventName}__`] = eventHandlerTTDom.bind(this, dom, value)
135+
dom.addEventListener(`bind${eventName}`, dom[`__${eventName}__`], isCapture)
136+
}
137+
return
138+
}
139+
128140
if (isFunction(value)) {
129141
if (oldValue) {
130142
dom.removeEventListener(eventName, oldValue as any, process.env.TARO_PLATFORM !== PLATFORM_TYPE.HARMONY ? false : undefined)
@@ -228,42 +240,50 @@ function setProperty (dom: TaroElement, name: string, value: unknown, oldValue?:
228240
) {
229241
// skip
230242
} else if (name === 'style') {
231-
if (/harmony.*cpp/.test(process.env.TARO_ENV || '')) {
232-
return dom.setAttribute('_style4cpp', value)
233-
}
234-
const style = dom.style
235-
if (isString(value)) {
236-
style.cssText = value
243+
if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) {
244+
if (isString(value)) {
245+
dom.setAttribute('style', value)
246+
} else if (isObject(value)) {
247+
dom.setAttribute('style', styleObjectToCss(value as StyleValue))
248+
}
237249
} else {
238-
if (isString(oldValue)) {
239-
style.cssText = ''
240-
oldValue = null
250+
if (/harmony.*cpp/.test(process.env.TARO_ENV || '')) {
251+
return dom.setAttribute('_style4cpp', value)
241252
}
253+
const style = dom.style
254+
if (isString(value)) {
255+
style.cssText = value
256+
} else {
257+
if (isString(oldValue)) {
258+
style.cssText = ''
259+
oldValue = null
260+
}
242261

243-
if (isObject<StyleValue>(oldValue)) {
244-
for (const i in oldValue) {
245-
if (!(value && i in (value as StyleValue))) {
246-
// Harmony特殊处理
247-
if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position' && oldValue[i] === 'fixed') {
248-
// @ts-ignore
249-
dom.setLayer(0)
262+
if (isObject<StyleValue>(oldValue)) {
263+
for (const i in oldValue) {
264+
if (!(value && i in (value as StyleValue))) {
265+
// Harmony特殊处理
266+
if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position' && oldValue[i] === 'fixed') {
267+
// @ts-ignore
268+
dom.setLayer(0)
269+
}
270+
setStyle(style, i, '')
250271
}
251-
setStyle(style, i, '')
252272
}
253273
}
254-
}
255274

256-
if (isObject<StyleValue>(value)) {
257-
for (const i in value) {
258-
if (!oldValue || !isEqual(value[i], (oldValue as StyleValue)[i])) {
259-
// Harmony特殊处理
260-
if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position') {
261-
if (value[i] === 'fixed' || (value[i] !== 'fixed' && oldValue?.[i])) {
262-
// @ts-ignore
263-
dom.setLayer(value[i] === 'fixed' ? 1 : 0)
275+
if (isObject<StyleValue>(value)) {
276+
for (const i in value) {
277+
if (!oldValue || !isEqual(value[i], (oldValue as StyleValue)[i])) {
278+
// Harmony特殊处理
279+
if (process.env.TARO_PLATFORM === PLATFORM_TYPE.HARMONY && i === 'position') {
280+
if (value[i] === 'fixed' || (value[i] !== 'fixed' && oldValue?.[i])) {
281+
// @ts-ignore
282+
dom.setLayer(value[i] === 'fixed' ? 1 : 0)
283+
}
264284
}
285+
setStyle(style, i, value[i])
265286
}
266-
setStyle(style, i, value[i])
267287
}
268288
}
269289
}
@@ -275,10 +295,17 @@ function setProperty (dom: TaroElement, name: string, value: unknown, oldValue?:
275295
const oldHtml = (oldValue as DangerouslySetInnerHTML)?.__html ?? ''
276296
if (newHtml || oldHtml) {
277297
if (oldHtml !== newHtml) {
278-
dom.innerHTML = newHtml
298+
if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) {
299+
setInnerHTML(dom, newHtml)
300+
} else {
301+
dom.innerHTML = newHtml
302+
}
279303
}
280304
}
281305
} else if (!isFunction(value)) {
306+
if (process.env.TARO_ENV === 'tt' && isEnableTTDom() && !IS_DATASET_OR_ARIA.test(name)) {
307+
name = toCamelCase(name)
308+
}
282309
if (value == null) {
283310
dom.removeAttribute(name)
284311
} else {

packages/taro-runtime/src/bom/document.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DEFAULT_Components, isEnableTTDom, TT_SPECIFIC_COMPONENTS } from '@tarojs/shared'
2+
13
import {
24
APP,
35
BODY,
@@ -43,5 +45,71 @@ function createDocument (): TaroDocument {
4345
return doc
4446
}
4547

48+
declare const tt: any
49+
50+
export function createTTDomDocument(): TaroDocument {
51+
const document = tt?.appDocument
52+
if (!document) {
53+
throw new Error('tt.appDocument is not found')
54+
}
55+
const html = document.createElement(HTML)
56+
const head = document.createElement(HEAD)
57+
const body = document.createElement(BODY)
58+
const app = document.createElement(APP)
59+
app.id = APP
60+
const container = document.createElement(CONTAINER)
61+
62+
document.childNodes.push(html)
63+
html.childNodes.push(head, body)
64+
body.childNodes.push(container)
65+
container.childNodes.push(app)
66+
67+
document.documentElement = html
68+
document.head = head
69+
document.body = body
70+
document.appElement = app
71+
72+
let builtInComponents = tt?.getBuiltInComponents?.()
73+
if (Array.isArray(builtInComponents)) {
74+
builtInComponents = new Set(builtInComponents)
75+
} else if (!(builtInComponents instanceof Set)) {
76+
builtInComponents = new Set([...DEFAULT_Components, ...TT_SPECIFIC_COMPONENTS])
77+
}
78+
79+
document.getElementById = function getElementById(id: string) {
80+
if (id === 'app') {
81+
return app
82+
} else {
83+
// eslint-disable-next-line no-proto
84+
return this.__proto__.getElementById.call(this, id)
85+
}
86+
}
87+
88+
document.getLastPage = function getLastPage() {
89+
return [...this._pageDocumentMap.values()][this._pageDocumentMap.size - 1]
90+
}
91+
92+
document.createElement = function (type: string, ...args) {
93+
if (type === 'root') {
94+
return this.getLastPage()
95+
} else {
96+
const el = builtInComponents.has(type)
97+
// eslint-disable-next-line no-proto
98+
? this.__proto__.createElement.call(this, type, ...args)
99+
// eslint-disable-next-line no-proto
100+
: this.__proto__.createNativeComponent.call(this, type, {
101+
__tt__inner__options__: {
102+
name: type,
103+
},
104+
})
105+
// 给元素加上 scopeId
106+
el.setAttribute('class', '')
107+
return el
108+
}
109+
}
110+
return document
111+
}
112+
46113
// Note: 小程序端 vite 打包成 commonjs,const document = xxx 会报错,所以把 document 改为 taroDocumentProvider
47-
export const taroDocumentProvider: TaroDocument = process.env.TARO_PLATFORM === 'web' ? env.document : (env.document = createDocument())
114+
export const taroDocumentProvider: TaroDocument = process.env.TARO_PLATFORM === 'web' ? env.document : (env.document =
115+
isEnableTTDom() ? createTTDomDocument() : createDocument())

packages/taro-runtime/src/dom-external/inner-html/html.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isEnableTTDom } from '@tarojs/shared'
2+
13
import { options } from '../../options'
24
import { parser } from './parser'
35

@@ -17,11 +19,21 @@ options.html = {
1719
renderHTMLTag: false
1820
}
1921

22+
declare const tt: any
23+
2024
export function setInnerHTML (element: TaroNode, html: string) {
2125
while (element.firstChild) {
2226
element.removeChild(element.firstChild)
2327
}
24-
const children = parser(html, element.ownerDocument)
28+
let { ownerDocument } = element
29+
30+
if (process.env.TARO_ENV === 'tt' && isEnableTTDom()) {
31+
if (typeof tt !== 'undefined' && 'appDocument' in tt) {
32+
ownerDocument = tt.appDocument
33+
}
34+
}
35+
36+
const children = parser(html, ownerDocument)
2537

2638
for (let i = 0; i < children.length; i++) {
2739
element.appendChild(children[i])

0 commit comments

Comments
 (0)