Frontend Development 15 min read

How to Enable Zero-Tracking in Taro3 Mini‑Programs with a Custom Babel Plugin

This article explains how to adapt GrowingIO's no‑embed SDK for Taro 3 by intercepting runtime events and using a Babel plugin to preserve user method names, achieving a stable zero‑tracking solution for mini‑programs.

GrowingIO Tech Team
GrowingIO Tech Team
GrowingIO Tech Team
How to Enable Zero-Tracking in Taro3 Mini‑Programs with a Custom Babel Plugin

Introduction

Most mini‑program developers are familiar with the Taro framework, which lets you write React code once and compile it to multiple mini‑program platforms, reducing development cost.

Background

GrowingIO’s mini‑program SDK requires two core capabilities for a zero‑tracking (no‑embed) solution: intercepting user event triggers and generating a unique, stable identifier for each node. In Taro 2 this was achieved by overriding

createComponent

and preserving method names during compilation. After Taro 3’s architectural changes,

createApp

and

createComponent

disappeared, and method names are minified, breaking the original SDK.

Problem Analysis

In Taro 3 each page receives a relatively stable

id

and all event listeners are stored in an

eh

method on the page instance. However, node

id

s change when the DOM order changes, so they cannot serve as permanent identifiers. The two remaining tasks are to intercept runtime user methods and retain their names in production.

Step‑by‑Step Solution

Getting User Methods

All page configurations are returned by

createPageConfig

. The runtime’s

eventHandler

and

dispatchEvent

functions expose the

__handlers

object that stores bound listeners. By hooking

dispatchEvent

we can capture the original method reference.

<code>export function eventHandler (event) {
  if (event.currentTarget == null) {
    event.currentTarget = event.target
  }
  const node = document.getElementById(event.currentTarget.id)
  if (node != null) {
    node.dispatchEvent(createEvent(event, node))
  }
}
class TaroElement extends TaroNode {
  public dispatchEvent (event) {
    const listeners = this.__handlers[event.type]
    return listeners != null
  }
}
</code>

Hooking

dispatchEvent

:

<code>function hookDispatchEvent(dispatch) {
  return function() {
    const event = arguments[0]
    let node = document.getElementById(event.currentTarget.id)
    let handlers = node.__handlers
    // …process handlers…
    return dispatch.apply(this, arguments)
  }
}
if (document?.tagName === '#DOCUMENT' && !!document.getElementById) {
  const TaroNode = document.__proto__.__proto__
  const dispatchEvent = TaroNode.dispatchEvent
  Object.defineProperty(TaroNode, 'dispatchEvent', {
    value: hookDispatchEvent(dispatchEvent),
    enumerable: false,
    configurable: false
  })
}
</code>

Preserving Method Names

Method names can be obtained directly for named functions and class methods via

Function.name

. Anonymous functions, arrow functions, and inline handlers lose their names after production minification. The solution is to create a Babel plugin that injects a stable name at compile time.

The plugin traverses

JSXAttribute

nodes whose name matches

on[A-Z][a-zA-Z]+

, extracts the value expression, and wraps it with a helper

_GIO_DI_NAME_

that defines the function’s

name

property.

<code>function visitorComponent(path, state) {
  path.traverse({
    JSXAttribute(path) {
      let attrName = path.get('name').node.name
      if (!/^on[A-Z][a-zA-Z]+/.test(attrName)) return
      let valueExpression = path.get('value.expression')
      replaceWithCallStatement(valueExpression)
    }
  })
}
module.exports = function({ template }) {
  return {
    name: 'babel-plugin-setname',
    visitor: {
      Function: visitorComponent,
      Class: visitorComponent
    }
  }
}
</code>

The plugin handles various binding forms:

Simple identifier:

&lt;Button onClick={handler}&gt;

→ name =

handler

Member expression:

&lt;Button onClick={parent.props.handle}&gt;

→ name =

parent_props_handle

Call expression (higher‑order functions): the plugin concatenates the callee name with argument values, e.g.

getHandler('tab1')

getHandler$tab1

.

Anonymous functions: a prefix such as

HomeFunc0

is generated per component using an incrementing counter.

Babel Plugin Features

Intercepts JSX attributes that bind event handlers.

Generates a stable identifier for the handler, preserving it through production minification.

Supports identifier, member expression, call expression, and anonymous function cases.

Example of transformed JSX:

<code>_reactJsxRuntime.jsx(Button, {
  onClick: _GIO_DI_NAME_("parent_props_arrowsFunction", parent.props.arrowsFunction)
})
</code>

Conclusion

By combining runtime event interception with a compile‑time Babel plugin that retains method names, GrowingIO’s SDK can provide a stable zero‑tracking capability for Taro 3 mini‑programs. The plugin is open‑source (github.com/growingio/growing-babel-plugin-setname) and can be extended for future optimizations.

FrontendSDKBabelmini-programTaroNo-Tracking
GrowingIO Tech Team
Written by

GrowingIO Tech Team

The official technical account of GrowingIO, showcasing our tech innovations, experience summaries, and cutting‑edge black‑tech.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.