Cross-platform runtime for Taro framework providing DOM/BOM polyfills and mini-program bridge functionality
—
Comprehensive utility functions, performance monitoring tools, event management, and optimization helpers for cross-platform Taro development.
The utilities module provides essential tools for building performant cross-platform applications:
import { perf } from '@tarojs/runtime'
interface Performance {
// Timing methods
start(id: string): void
stop(id: string, now?: number): void
delayStop(id: string, delay?: number): void
// Internal timing storage
timers: Map<string, number>
}
const perf: Performanceperformance.now() when available, falls back to Date.now()// Basic performance timing
perf.start('component-render')
// ... component rendering logic
perf.stop('component-render') // Logs: "component-render took X ms"
// Manual timing with custom timestamp
const startTime = performance.now()
// ... some operation
perf.stop('custom-operation', startTime)
// Delayed timing (debounced)
perf.start('frequent-operation')
perf.delayStop('frequent-operation', 100) // Waits 100ms before logging
// Multiple timing sessions
perf.start('data-fetch')
const data = await fetchData()
perf.stop('data-fetch')
perf.start('data-process')
const processed = processData(data)
perf.stop('data-process')
// Nested timing
perf.start('full-operation')
perf.start('sub-operation-1')
// ... work
perf.stop('sub-operation-1')
perf.start('sub-operation-2')
// ... more work
perf.stop('sub-operation-2')
perf.stop('full-operation')import { throttle } from '@tarojs/runtime'
function throttle<T extends (...args: any[]) => any>(
fn: T,
threshold?: number,
scope?: any
): T
// Default threshold: 250msthis context when scope provided// Basic throttling
const expensiveFunction = (data: string) => {
console.log('Processing:', data)
// Expensive computation
}
const throttledFn = throttle(expensiveFunction, 1000)
// Multiple rapid calls - only executes every 1000ms
throttledFn('call 1') // Executes immediately
throttledFn('call 2') // Ignored
throttledFn('call 3') // Ignored
// ... 1000ms later, executes with last arguments
// Scroll event throttling
const handleScroll = throttle((event: Event) => {
const scrollTop = (event.target as Element).scrollTop
console.log('Scroll position:', scrollTop)
}, 100)
window.addEventListener('scroll', handleScroll)
// Resize handler with context
class ResponsiveComponent {
threshold = 768
handleResize = throttle(function(this: ResponsiveComponent) {
if (window.innerWidth < this.threshold) {
this.enterMobileMode()
} else {
this.enterDesktopMode()
}
}, 250, this)
enterMobileMode() { /* ... */ }
enterDesktopMode() { /* ... */ }
}import { debounce } from '@tarojs/runtime'
function debounce<T extends (...args: any[]) => any>(
fn: T,
ms?: number,
scope?: any
): T
// Default delay: 250msthis context when scope provided// Basic debouncing
const searchFunction = (query: string) => {
console.log('Searching for:', query)
// API call
}
const debouncedSearch = debounce(searchFunction, 500)
// Rapid typing - only searches after 500ms of no input
debouncedSearch('a') // Cancelled
debouncedSearch('ap') // Cancelled
debouncedSearch('app') // Cancelled
debouncedSearch('apple') // Executes after 500ms
// Form validation
const validateForm = debounce((formData: FormData) => {
// Validate form fields
console.log('Validating form...')
}, 300)
// API call debouncing
const saveData = debounce(async (data: any) => {
try {
await api.saveUserData(data)
console.log('Data saved successfully')
} catch (error) {
console.error('Save failed:', error)
}
}, 1000)
// Method debouncing with context
class AutoSave {
data: any = {}
updateData(key: string, value: any) {
this.data[key] = value
this.debouncedSave()
}
debouncedSave = debounce(function(this: AutoSave) {
console.log('Auto-saving data:', this.data)
// Save to storage
}, 2000, this)
}import { isElement, isText, isComment } from '@tarojs/runtime'
// Element type checks
function isElement(node: TaroNode): node is TaroElement
function isText(node: TaroNode): node is TaroText
function isComment(node: TaroNode): boolean// Type-safe DOM manipulation
function processNode(node: TaroNode) {
if (isElement(node)) {
// TypeScript knows this is TaroElement
console.log('Element tag:', node.tagName)
console.log('Element attributes:', node.attributes)
node.setAttribute('processed', 'true')
} else if (isText(node)) {
// TypeScript knows this is TaroText
console.log('Text content:', node.textContent)
node.textContent = node.textContent.trim()
} else if (isComment(node)) {
console.log('Comment node found')
// Handle comment node
}
}
// Filter node collections
function getElements(nodes: TaroNode[]): TaroElement[] {
return nodes.filter(isElement)
}
function getTextNodes(nodes: TaroNode[]): TaroText[] {
return nodes.filter(isText)
}
// DOM traversal
function walkDOM(node: TaroNode, callback: (node: TaroNode) => void) {
callback(node)
if (isElement(node)) {
node.childNodes.forEach(child => walkDOM(child, callback))
}
}import {
isHasExtractProp,
isParentBinded,
shortcutAttr
} from '@tarojs/runtime'
// Property extraction check
function isHasExtractProp(element: TaroElement): boolean
// Event binding check
function isParentBinded(element: TaroElement, eventType: string): boolean
// Attribute shortcuts
function shortcutAttr(key: string): string// Check if element has extractable properties
const element = document.createElement('custom-component')
element.setAttribute('custom-prop', 'value')
if (isHasExtractProp(element)) {
console.log('Element has non-standard properties')
// Handle custom property extraction
}
// Check event delegation
const child = document.createElement('button')
const parent = document.createElement('view')
parent.addEventListener('tap', handleTap)
parent.appendChild(child)
if (isParentBinded(child, 'tap')) {
console.log('Parent handles tap events')
// Skip adding duplicate listener
} else {
child.addEventListener('tap', handleTap)
}
// Attribute shortcuts
console.log(shortcutAttr('className')) // 'class'
console.log(shortcutAttr('htmlFor')) // 'for'import { incrementId } from '@tarojs/runtime'
function incrementId(): () => string// Create ID generator
const getId = incrementId()
// Generate unique IDs
const id1 = getId() // 'A'
const id2 = getId() // 'B'
const id3 = getId() // 'C'
// ... continues through alphabet
// Use in component creation
function createUniqueElement(tagName: string): TaroElement {
const element = document.createElement(tagName)
element.id = getId()
return element
}
// Multiple generators for different purposes
const nodeIdGen = incrementId()
const componentIdGen = incrementId()
const nodeId = nodeIdGen() // Independent sequence
const componentId = componentIdGen() // Independent sequenceimport { eventCenter, Events } from '@tarojs/runtime'
interface EventsType {
// Event registration
on(eventName: string, listener: Function, context?: any): EventsType
once(eventName: string, listener: Function, context?: any): EventsType
// Event emission
trigger(eventName: string, ...args: any[]): EventsType
emit(eventName: string, ...args: any[]): EventsType
// Event removal
off(eventName?: string, listener?: Function, context?: any): EventsType
}
const eventCenter: EventsTypethis binding for event handlers// Basic event communication
eventCenter.on('user-login', (user: User) => {
console.log('User logged in:', user.name)
updateUserInterface(user)
})
eventCenter.trigger('user-login', { name: 'John', id: '123' })
// Cross-component communication
// Component A
class ComponentA extends React.Component {
sendMessage = () => {
eventCenter.trigger('message-sent', {
from: 'ComponentA',
message: 'Hello from A'
})
}
}
// Component B
class ComponentB extends React.Component {
componentDidMount() {
eventCenter.on('message-sent', this.handleMessage, this)
}
componentWillUnmount() {
eventCenter.off('message-sent', this.handleMessage, this)
}
handleMessage = (data: { from: string, message: string }) => {
console.log(`Message from ${data.from}: ${data.message}`)
}
}
// One-time events
eventCenter.once('app-initialized', () => {
console.log('App initialization complete')
// This handler will only run once
})
// Event cleanup
class EventfulComponent {
listeners = new Map()
componentDidMount() {
// Store listeners for cleanup
this.listeners.set('data-update', this.handleDataUpdate)
this.listeners.set('user-action', this.handleUserAction)
// Register events
eventCenter.on('data-update', this.handleDataUpdate, this)
eventCenter.on('user-action', this.handleUserAction, this)
}
componentWillUnmount() {
// Clean up all listeners
this.listeners.forEach((handler, eventName) => {
eventCenter.off(eventName, handler, this)
})
}
handleDataUpdate = (data: any) => { /* ... */ }
handleUserAction = (action: any) => { /* ... */ }
}import { nextTick } from '@tarojs/runtime'
function nextTick(
callback: Function,
context?: Record<string, any>
): voidsetTimeout if update queue unavailable// Wait for DOM updates
function updateComponent() {
// Modify DOM
element.textContent = 'Updated content'
element.className = 'new-class'
// Execute after DOM update
nextTick(() => {
console.log('DOM updates completed')
// Safe to read computed styles, dimensions, etc.
const rect = element.getBoundingClientRect()
console.log('Element dimensions:', rect)
})
}
// React integration
class ReactComponent extends React.Component {
updateData = () => {
this.setState({ data: newData }, () => {
// Execute after React update
nextTick(() => {
// Execute after Taro DOM update
console.log('Both React and Taro updates complete')
})
})
}
}
// Custom context
const context = { name: 'CustomContext' }
nextTick(function() {
console.log('Context:', this.name) // 'CustomContext'
}, context)
// Chained operations
function performComplexUpdate() {
// Step 1: Update data
updateApplicationData()
nextTick(() => {
// Step 2: Update DOM after data update
updateDOMElements()
nextTick(() => {
// Step 3: Final operations after DOM update
performFinalOperations()
})
})
}import { hydrate } from '@tarojs/runtime'
function hydrate(node: TaroElement | TaroText): MiniData
interface MiniData {
[key: string]: any
// Converted mini-program data structure
}// Hydrate DOM tree for mini-program
const element = document.createElement('view')
element.className = 'container'
element.textContent = 'Hello World'
element.style.color = 'red'
const miniData = hydrate(element)
console.log('Mini-program data:', miniData)
// Output: { cn: [{ v: 'Hello World' }], st: { color: 'red' }, ... }
// Hydrate complex component tree
const complexElement = document.createElement('scroll-view')
complexElement.setAttribute('scroll-y', 'true')
const listItem = document.createElement('view')
listItem.className = 'list-item'
listItem.textContent = 'Item 1'
complexElement.appendChild(listItem)
const hydratedData = hydrate(complexElement)
console.log('Complex hydration:', hydratedData)
// Use in custom rendering
function customRender(virtualDOM: TaroElement) {
const miniData = hydrate(virtualDOM)
// Send to mini-program
if (getCurrentInstance().page) {
getCurrentInstance().page.setData({
customComponent: miniData
})
}
}import {
addLeadingSlash,
hasBasename,
stripBasename,
stripTrailing,
stripSuffix
} from '@tarojs/runtime'
// URL formatting
function addLeadingSlash(path: string): string
function stripTrailing(path: string): string
function stripSuffix(path: string, suffix: string): string
// Basename handling
function hasBasename(path: string, basename: string): boolean
function stripBasename(path: string, basename: string): string// URL formatting
console.log(addLeadingSlash('path/to/page')) // '/path/to/page'
console.log(addLeadingSlash('/path/to/page')) // '/path/to/page'
console.log(stripTrailing('/path/to/page/')) // '/path/to/page'
console.log(stripTrailing('/path/to/page')) // '/path/to/page'
console.log(stripSuffix('/page.html', '.html')) // '/page'
console.log(stripSuffix('/page', '.html')) // '/page'
// Basename handling
const basename = '/app'
const fullPath = '/app/pages/home'
console.log(hasBasename(fullPath, basename)) // true
console.log(stripBasename(fullPath, basename)) // '/pages/home'
// Route processing
function processRoute(rawPath: string, appBasename: string = ''): string {
let path = addLeadingSlash(rawPath)
path = stripTrailing(path)
if (appBasename && hasBasename(path, appBasename)) {
path = stripBasename(path, appBasename)
}
return path
}
console.log(processRoute('pages/home/', '/myapp')) // '/pages/home'import { getHomePage, getCurrentPage } from '@tarojs/runtime'
// Get application home page
function getHomePage(): string
// Get current page path
function getCurrentPage(): string// Navigation helpers
function navigateHome() {
const homePage = getHomePage()
wx.navigateTo({ url: homePage })
}
function refreshCurrentPage() {
const currentPage = getCurrentPage()
wx.redirectTo({ url: currentPage })
}
// Breadcrumb generation
function generateBreadcrumb(): string[] {
const current = getCurrentPage()
const home = getHomePage()
const breadcrumb = [home]
if (current !== home) {
breadcrumb.push(current)
}
return breadcrumb
}import { RuntimeCache } from '@tarojs/runtime'
class RuntimeCache<T = any> {
// Cache operations
set(key: string, value: T): void
get(key: string): T | undefined
has(key: string): boolean
delete(key: string): boolean
clear(): void
// Iteration
keys(): IterableIterator<string>
values(): IterableIterator<T>
entries(): IterableIterator<[string, T]>
// Properties
readonly size: number
}// Create cache instance
const pageCache = new RuntimeCache<PageData>()
const componentCache = new RuntimeCache<ComponentState>()
// Cache page data
interface PageData {
userInfo: User
settings: AppSettings
}
pageCache.set('user-profile', {
userInfo: { id: '123', name: 'John' },
settings: { theme: 'dark', lang: 'en' }
})
// Retrieve cached data
const cachedData = pageCache.get('user-profile')
if (cachedData) {
console.log('Cached user:', cachedData.userInfo.name)
}
// Cache management
console.log('Cache size:', pageCache.size)
console.log('Has user data:', pageCache.has('user-profile'))
// Iterate cache
for (const [key, value] of pageCache.entries()) {
console.log(`Cached ${key}:`, value)
}
// Cleanup
pageCache.delete('old-data')
pageCache.clear() // Clear all cache
// Component state caching
class CacheableComponent extends React.Component {
cache = new RuntimeCache<ComponentState>()
componentDidMount() {
const cached = this.cache.get('component-state')
if (cached) {
this.setState(cached)
}
}
componentWillUnmount() {
this.cache.set('component-state', this.state)
}
}import { options } from '@tarojs/runtime'
interface Options {
prerender: boolean // Enable prerendering (default: true)
debug: boolean // Enable debug mode (default: false)
}
const options: Options// Configure runtime behavior
options.prerender = false // Disable prerendering
options.debug = true // Enable debug logging
// Conditional behavior
if (options.debug) {
console.log('Debug mode enabled')
// Additional logging
}
if (options.prerender) {
// Prerender optimization enabled
performPrerenderOptimizations()
}
// Environment-based configuration
if (process.env.NODE_ENV === 'development') {
options.debug = true
}
if (process.env.NODE_ENV === 'production') {
options.prerender = true
options.debug = false
}import { handlePolyfill } from '@tarojs/runtime'
function handlePolyfill(): voidObject.assign, Array.find, etc.IntersectionObserver polyfill for web platforms// Apply polyfills (usually done automatically)
handlePolyfill()
// Manual polyfill check
if (typeof Object.assign !== 'function') {
handlePolyfill() // Will apply Object.assign polyfill
}
// Environment-specific polyfills
if (typeof window !== 'undefined') {
// Web environment - full polyfills applied
handlePolyfill()
}import { extend, getComponentsAlias, convertNumber2PX } from '@tarojs/runtime'
// Object extension
function extend(target: any, ...sources: any[]): any
// Component mapping
function getComponentsAlias(): Record<string, string>
// Unit conversion
function convertNumber2PX(value: number | string): string// Object extension (like Object.assign)
const base = { a: 1, b: 2 }
const extended = extend(base, { b: 3, c: 4 }, { d: 5 })
console.log(extended) // { a: 1, b: 3, c: 4, d: 5 }
// Component aliasing
const aliases = getComponentsAlias()
console.log(aliases) // { 'scroll-view': 'ScrollView', ... }
// Convert to px units
console.log(convertNumber2PX(16)) // '16px'
console.log(convertNumber2PX('16')) // '16px'
console.log(convertNumber2PX('16px')) // '16px'
// Use in style processing
function normalizeStyleValue(value: any): string {
if (typeof value === 'number') {
return convertNumber2PX(value)
}
return String(value)
}import { env } from '@tarojs/runtime'
interface Env {
window: Window | {}
document: Document | {}
}
const env: Env// Check platform capabilities
if (env.window && 'addEventListener' in env.window) {
// Web platform - use native APIs
env.window.addEventListener('resize', handleResize)
} else {
// Mini-program platform - use alternatives
// Handle resize through Taro lifecycle
}
// Safe document access
if (env.document && 'createElement' in env.document) {
// Native DOM available
const element = env.document.createElement('div')
} else {
// Use Taro DOM polyfills
const element = document.createElement('view')
}import { parseUrl } from '@tarojs/runtime'
function parseUrl(url?: string): {
href: string
origin: string
protocol: string
hostname: string
host: string
port: string
pathname: string
search: string
hash: string
}// Parse complete URLs
const parsed = parseUrl('https://example.com:8080/path?query=value#section')
console.log(parsed.hostname) // 'example.com'
console.log(parsed.port) // '8080'
console.log(parsed.pathname) // '/path'
console.log(parsed.search) // '?query=value'
console.log(parsed.hash) // '#section'
// Handle partial URLs
const relative = parseUrl('/api/users?page=1')
console.log(relative.pathname) // '/api/users'
console.log(relative.search) // '?page=1'
// Safe URL parsing
function getUrlParts(url: string) {
try {
const parts = parseUrl(url)
return parts.pathname + parts.search
} catch (error) {
return '/'
}
}import { hooks } from '@tarojs/runtime'
interface Hooks {
call<T>(hookName: string, ...args: any[]): T
tap(hookName: string, callback: Function): void
}
const hooks: Hooks// Register hook handlers
hooks.tap('beforeMount', (component) => {
console.log('Component mounting:', component)
})
hooks.tap('afterUpdate', (instance) => {
console.log('Component updated:', instance)
})
// Call hooks programmatically
const eventCenter = hooks.call('getEventCenter', Events)
const router = hooks.call('getRouter')
// Custom hook integration
hooks.tap('customEvent', (data) => {
// Handle custom events
processCustomData(data)
})
// Trigger custom hooks
hooks.call('customEvent', { type: 'user-action', payload: data })The utilities module provides essential tools for building performant, maintainable cross-platform applications with comprehensive performance monitoring, event management, and optimization capabilities.
Install with Tessl CLI
npx tessl i tessl/npm-tarojs--runtime