Cross-platform runtime for Taro framework providing DOM/BOM polyfills and mini-program bridge functionality
—
Complete virtual DOM implementation optimized for mini-program platforms while maintaining web compatibility. The DOM module provides familiar web APIs including nodes, elements, events, and style management.
The DOM implementation creates a standardized document structure that compiles efficiently to mini-program data structures. Key features include:
The DOM follows standard web patterns with optimizations for mini-programs:
TaroEventTarget (base)
↳ TaroNode (abstract base)
↳ TaroElement (general elements)
↳ FormElement (form inputs)
↳ SVGElement (SVG elements)
↳ TaroRootElement (container root)
↳ TaroText (text nodes)import { TaroNode } from '@tarojs/runtime'
abstract class TaroNode extends TaroEventTarget {
// Identity
readonly uid: string // Unique identifier
readonly sid: string // Scoped identifier
readonly nodeType: number // Node type constant
readonly nodeName: string // Tag name (uppercase)
// Tree structure
parentNode: TaroNode | null
childNodes: TaroNode[]
// Computed properties
get nextSibling(): TaroNode | null
get previousSibling(): TaroNode | null
get parentElement(): TaroElement | null
get firstChild(): TaroNode | null
get lastChild(): TaroNode | null
// Path properties (for updates)
get _path(): string // Dot-notation path from root
get _root(): TaroRootElement | null // Root container
// Tree manipulation
appendChild(child: TaroNode): TaroNode
insertBefore(newChild: TaroNode, referenceChild: TaroNode | null): TaroNode
replaceChild(newChild: TaroNode, oldChild: TaroNode): TaroNode
removeChild(child: TaroNode): TaroNode
remove(): void
// Utilities
hasChildNodes(): boolean
enqueueUpdate(): void // Trigger update batching
}
// Node type constants
const ELEMENT_NODE = 1
const TEXT_NODE = 3
const COMMENT_NODE = 8
const DOCUMENT_NODE = 9uid and scoped sidenqueueUpdate()childNodes by default// Create node tree
const parent = document.createElement('view')
const child1 = document.createElement('text')
const child2 = document.createTextNode('Hello')
// Build tree structure
parent.appendChild(child1)
parent.appendChild(child2)
// Navigate tree
console.log('Parent:', child1.parentNode) // parent element
console.log('Next sibling:', child1.nextSibling) // child2
console.log('First child:', parent.firstChild) // child1
console.log('Has children:', parent.hasChildNodes()) // true
// Get update path
console.log('Node path:', child1._path) // "root.cn.[0].cn.[0]"
console.log('Root element:', child1._root) // TaroRootElement
// Tree manipulation
const newChild = document.createElement('image')
parent.insertBefore(newChild, child2) // Insert between child1 and child2
parent.removeChild(child1) // Remove child1
child2.remove() // Remove child2 directlyimport { TaroElement } from '@tarojs/runtime'
class TaroElement extends TaroNode {
// Element properties
tagName: string // Tag name (lowercase)
props: Record<string, any> // Element properties/attributes
// Style and classes
style: Style // CSS style object
classList: ClassList // CSS class management
dataset: Record<string, string> // Data attributes
// Content properties
id: string // Element ID
className: string // CSS classes
innerHTML: string // Inner HTML content
textContent: string // Text content
cssText: string // Inline styles
// Collections
children: TaroElement[] // Child elements (no text nodes)
attributes: Record<string, string> // All attributes
// Attribute methods
hasAttribute(name: string): boolean
hasAttributes(): boolean
getAttribute(name: string): string | null
setAttribute(name: string, value: string | number | boolean): void
removeAttribute(name: string): void
// Element selection
getElementsByTagName(tagName: string): TaroElement[]
getElementsByClassName(className: string): TaroElement[]
// Focus and interaction
focus(): void
blur(): void
// Event handling (inherited from TaroEventTarget)
addEventListener(type: string, handler: EventListener, options?: AddEventListenerOptions): void
removeEventListener(type: string, handler: EventListener): void
dispatchEvent(event: TaroEvent): boolean
}pure-view, static-view, catch-viewinnerHTML and textContent// Create and configure element
const view = document.createElement('view')
view.id = 'main-container'
view.className = 'container active'
view.setAttribute('data-test', 'value')
// Style management
view.style.setProperty('background-color', '#ffffff')
view.style.setProperty('padding', '16px')
view.cssText = 'color: red; font-size: 14px;'
// Class management
view.classList.add('highlighted')
view.classList.remove('active')
view.classList.toggle('visible')
// Content manipulation
view.textContent = 'Hello World'
view.innerHTML = '<text>Formatted <text class="bold">content</text></text>'
// Attribute access
console.log('ID:', view.getAttribute('id')) // "main-container"
console.log('Has data attr:', view.hasAttribute('data-test')) // true
console.log('All attributes:', view.attributes) // { id: "main-container", ... }
// Element selection
const textElements = view.getElementsByTagName('text')
const boldElements = view.getElementsByClassName('bold')
// Dataset access (data-* attributes)
view.setAttribute('data-user-id', '123')
console.log('User ID:', view.dataset.userId) // "123"
// Event handling
view.addEventListener('tap', (event) => {
console.log('Element tapped:', event.target)
})
// Focus management
view.focus() // Set focus to element
view.blur() // Remove focusimport { TaroText } from '@tarojs/runtime'
class TaroText extends TaroNode {
// Text content
private _value: string // Internal text value
// Text properties (all reference _value)
get textContent(): string
set textContent(value: string)
get nodeValue(): string
set nodeValue(value: string)
get data(): string
set data(value: string)
// Node type
nodeType: 3 // TEXT_NODE constant
nodeName: '#text' // Standard text node name
}textContent, nodeValue, data)// Create text node
const textNode = document.createTextNode('Initial text')
// Modify content (all equivalent)
textNode.textContent = 'Updated text'
textNode.nodeValue = 'Updated text'
textNode.data = 'Updated text'
// Access content
console.log('Text:', textNode.textContent) // "Updated text"
console.log('Value:', textNode.nodeValue) // "Updated text"
console.log('Data:', textNode.data) // "Updated text"
// Add to element
const element = document.createElement('text')
element.appendChild(textNode)
// Text content propagates to parent
console.log('Element text:', element.textContent) // "Updated text"import { TaroRootElement } from '@tarojs/runtime'
class TaroRootElement extends TaroElement {
// Update management
pendingUpdate: boolean // Whether update is queued
updatePayloads: UpdatePayload[] // Queued updates
// Mini-program context
ctx: MpInstance | null // Page/component instance
// Update lifecycle
enqueueUpdate(): void // Queue update for next tick
performUpdate(): void // Execute pending updates
scheduleTask(task: () => void): void // Schedule async task
// Update callbacks
enqueueUpdateCallback(callback: () => void): void // Add update callback
flushUpdateCallback(): void // Execute all callbacks
// Custom wrapper handling
customWrapperCache: Map<string, TaroElement>
}
interface UpdatePayload {
path: string // Update path (dot notation)
value: any // New value
}
interface MpInstance {
setData(data: Record<string, any>, callback?: () => void): void
}setData call// Root element is created automatically by document
const root = document.getElementById('app')._root
// Manual update triggering (usually automatic)
root.enqueueUpdate()
// Add update callback
root.enqueueUpdateCallback(() => {
console.log('Update completed')
})
// Schedule async task
root.scheduleTask(() => {
// Task executed after current update cycle
console.log('Async task executed')
})
// Access mini-program context
if (root.ctx) {
root.ctx.setData({ customData: 'value' })
}import { Style } from '@tarojs/runtime'
class Style {
// Properties
cssText: string // Complete CSS text
// Property methods
setProperty(property: string, value: string | null, priority?: string): void
removeProperty(property: string): void
getPropertyValue(property: string): string
// Direct property access (camelCase)
[property: string]: string
}const element = document.createElement('view')
// Set individual properties
element.style.setProperty('background-color', '#ffffff')
element.style.setProperty('font-size', '16px')
element.style.setProperty('--custom-color', '#ff0000')
// Direct property access (camelCase)
element.style.backgroundColor = '#000000'
element.style.fontSize = '18px'
element.style.marginTop = '10px'
// Get property values
console.log('Background:', element.style.getPropertyValue('background-color'))
console.log('Font size:', element.style.fontSize)
// Remove properties
element.style.removeProperty('margin-top')
// Set multiple properties via cssText
element.style.cssText = 'color: red; padding: 8px; border: 1px solid #ccc;'
// Get complete CSS
console.log('All styles:', element.style.cssText)import { ClassList } from '@tarojs/runtime'
class ClassList {
// Properties
readonly length: number // Number of classes
// Methods
add(...classNames: string[]): void
remove(...classNames: string[]): void
toggle(className: string, force?: boolean): boolean
contains(className: string): boolean
// Iteration
item(index: number): string | null
toString(): string
// Array-like access
[index: number]: string
}const element = document.createElement('view')
// Add classes
element.classList.add('container')
element.classList.add('active', 'visible')
// Remove classes
element.classList.remove('active')
// Toggle classes
element.classList.toggle('hidden') // Add if not present
element.classList.toggle('visible', false) // Force remove
// Check for classes
if (element.classList.contains('container')) {
console.log('Element is a container')
}
// Access by index
console.log('First class:', element.classList[0]) // "container"
console.log('Class count:', element.classList.length) // 2
// Convert to string
console.log('Classes:', element.classList.toString()) // "container visible"import { TaroEvent, createEvent } from '@tarojs/runtime'
interface TaroEvent extends Event {
// Standard Event properties
type: string // Event type
bubbles: boolean // Whether event bubbles
cancelable: boolean // Whether event can be canceled
target: TaroElement // Event target
currentTarget: TaroElement // Current event handler element
timeStamp: number // Event timestamp
// Mini-program integration
mpEvent?: any // Original mini-program event
// Event control
stopPropagation(): void
stopImmediatePropagation(): void
preventDefault(): void
// State
readonly defaultPrevented: boolean
readonly eventPhase: number
}
// Event creation
function createEvent(event: any, node?: TaroElement): TaroEventdataset attributes into event// Event handler
function handleTap(event: TaroEvent) {
console.log('Event type:', event.type) // "tap"
console.log('Target element:', event.target) // Tapped element
console.log('Current target:', event.currentTarget) // Handler element
console.log('Timestamp:', event.timeStamp) // Event time
// Access mini-program event data
if (event.mpEvent) {
console.log('Touch data:', event.mpEvent.touches)
}
// Control event propagation
event.stopPropagation() // Stop bubbling
event.preventDefault() // Cancel default action
}
// Add event listeners
element.addEventListener('tap', handleTap)
element.addEventListener('longpress', (event) => {
console.log('Long press detected')
})
// Create custom events
const customEvent = createEvent({
type: 'custom',
detail: { customData: 'value' }
}, element)
// Dispatch events
element.dispatchEvent(customEvent)import { eventHandler } from '@tarojs/runtime'
function eventHandler(handler: (event: TaroEvent) => void): (mpEvent: any) => voidThe eventHandler function wraps standard event handlers to work with mini-program events:
// Standard handler (receives TaroEvent)
function onTap(event: TaroEvent) {
console.log('Tapped:', event.target.tagName)
}
// Wrap for mini-program use
const mpHandler = eventHandler(onTap)
// Use in mini-program component
{
data: {},
onTap: mpHandler // Automatically converts mpEvent to TaroEvent
}import { eventSource } from '@tarojs/runtime'
interface EventSource {
// Global event registry for DOM nodes
set(node: TaroNode, event: TaroEvent): void
get(node: TaroNode): TaroEvent | null
delete(node: TaroNode): void
}import { FormElement } from '@tarojs/runtime'
class FormElement extends TaroElement {
// Form-specific properties
type: string // Input type
value: string // Current value
// Automatic value synchronization on input/change events
}// Create form elements
const input = document.createElement('input')
input.type = 'text'
input.value = 'Initial value'
const textarea = document.createElement('textarea')
textarea.value = 'Multi-line text'
// Value updates automatically sync with mini-program
input.addEventListener('input', (event) => {
console.log('New value:', input.value)
})
// Form submission
const form = document.createElement('form')
form.appendChild(input)
form.addEventListener('submit', (event) => {
event.preventDefault()
console.log('Form data:', input.value)
})import { SVGElement } from '@tarojs/runtime'
class SVGElement extends TaroElement {
// SVG-specific implementation
// Inherits all TaroElement functionality with SVG namespace handling
}// Create SVG elements
const svg = document.createElement('svg')
svg.setAttribute('width', '100')
svg.setAttribute('height', '100')
const circle = document.createElement('circle')
circle.setAttribute('cx', '50')
circle.setAttribute('cy', '50')
circle.setAttribute('r', '25')
circle.setAttribute('fill', 'blue')
svg.appendChild(circle)import { MutationObserver } from '@tarojs/runtime'
class MutationObserver {
constructor(callback: MutationCallback)
// Observation control
observe(target: Node, options?: MutationObserverInit): void
disconnect(): void
takeRecords(): MutationRecord[]
}
interface MutationRecord {
type: 'attributes' | 'characterData' | 'childList'
target: Node
addedNodes: NodeList
removedNodes: NodeList
previousSibling: Node | null
nextSibling: Node | null
attributeName: string | null
attributeNamespace: string | null
oldValue: string | null
}
type MutationCallback = (mutations: MutationRecord[], observer: MutationObserver) => void// Create observer
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
console.log('Mutation type:', mutation.type)
console.log('Target:', mutation.target)
if (mutation.type === 'childList') {
console.log('Added nodes:', mutation.addedNodes.length)
console.log('Removed nodes:', mutation.removedNodes.length)
}
if (mutation.type === 'attributes') {
console.log('Attribute changed:', mutation.attributeName)
}
})
})
// Start observing
observer.observe(document.body, {
childList: true, // Watch for child additions/removals
attributes: true, // Watch for attribute changes
characterData: true, // Watch for text content changes
subtree: true // Watch entire subtree
})
// Stop observing
observer.disconnect()
// Get pending mutations
const mutations = observer.takeRecords()Elements are automatically mapped to platform-specific components:
// Generic element
const view = document.createElement('view')
// Platform-specific mapping:
// Web: <div>
// WeChat: <view>
// Alipay: <view>
// etc.Special view types for performance optimization:
// Performance-optimized views
const pureView = document.createElement('pure-view') // Static content
const staticView = document.createElement('static-view') // No event handling
const catchView = document.createElement('catch-view') // Catch touch events
const clickView = document.createElement('click-view') // Click-only eventsThe DOM uses path-based updates for efficient mini-program rendering:
// Automatic path-based updates
const element = document.createElement('view')
element.textContent = 'Hello' // Updates: { "root.cn.[0].v": "Hello" }
element.style.color = 'red' // Updates: { "root.cn.[0].st.color": "red" }The DOM implementation provides complete web compatibility while optimizing for mini-program performance, enabling seamless cross-platform development.
Install with Tessl CLI
npx tessl i tessl/npm-tarojs--runtime