How Taro 3 Redefined Cross‑Platform Frontend Architecture
This article explains Taro 3's shift from a compile‑time heavy architecture to an interpretive runtime, detailing its JSX compilation drawbacks, the new runtime design that supports multiple frameworks, the implementation of a custom React reconciler, Web Components via Stencil, and how these changes improve cross‑platform development performance and flexibility.
Background
In our project we used Taro 3 for cross‑platform development and share its underlying principles to deepen our understanding and speed up issue location.
The previous overall architecture consisted of two parts: a compile‑time phase that transformed user React code into code runnable on each mini‑program, and a runtime framework on each platform that adapted the code.
Taro 3 Before (Heavy Compile‑time, Light Runtime)
During compilation Taro used babel‑parser to parse the code into an AST, then modified the AST with babel‑types and finally generated target code with babel‑generate .
This implementation had three major drawbacks:
JSX support was imperfect because Taro relied on exhaustive compile‑time adaptations for the highly flexible JSX syntax, resulting in huge maintenance effort.
Source‑maps were not supported after the series of transformations, making debugging inconvenient.
The compiler code was complex and fragmented, making maintenance and iteration very difficult.
Runtime issues were similar: each mini‑program platform provided its own runtime framework, so fixing a bug or adding a feature required changes in multiple runtime implementations.
Taro 3 After (Heavy Runtime)
Taro 3 can be understood as an interpretive architecture (compared with Taro 1/2). It simulates DOM and BOM APIs on the mini‑program side so that front‑end frameworks can run directly in the mini‑program environment, achieving H5‑mini‑program unification. Lifecycle, component libraries, APIs, and routing differences are smoothed by defining a unified standard while each platform implements its own adapters.
Because of this design, Taro 3 supports React, Vue, jQuery, and even custom frameworks like Angular. The overall architecture is:
Taro Mini‑Program
User React or Vue code is first bundled by the CLI with Webpack, then runtime adapters for React and Vue are applied, calling Taro’s DOM and BOM APIs, and finally the program is rendered on all mini‑program platforms.
React’s React‑DOM contains a lot of browser‑specific code, making the bundle large. Taro customizes and optimizes this part.
In React 16+, the architecture is:
The top layer is the core react‑core, the middle is react‑reconciler which maintains the VirtualDOM tree and implements the Diff/Fiber algorithm, and the Renderer (e.g., React‑DOM) handles platform‑specific rendering.
Taro implements a taro‑react package that connects react‑reconciler with @tarojs/runtime ’s BOM/DOM API, providing a mini‑program‑specific React renderer.
Creating a custom renderer involves two steps:
Step 1 – Host Config
Implement the host configuration required by react‑reconciler, defining how to create instances, update properties, and commit changes using Taro’s BOM/DOM API.
const Reconciler = require('react-reconciler');
const HostConfig = {
// ... implement adapter methods and config items
};
export const TaroReconciler = Reconciler(HostConfig);Step 2 – Render Function
Provide a render function similar to ReactDOM.render() that creates a root container and updates it.
// Create Reconciler instance and pass HostConfig
const MyRenderer = Reconciler(HostConfig);
/**
* render(element, container, callback)
*/
export function render(element, container, callback) {
if (!container._rootContainer) {
container._rootContainer = ReactReconcilerInst.createContainer(container, false);
}
return ReactReconcilerInst.updateContainer(element, container._rootContainer, null, callback);
}After these steps, React code can run in the mini‑program runtime, producing a Taro DOM tree that is then rendered via template stitching because mini‑programs cannot create nodes dynamically.
Templates are generated for each component, listing all possible attributes, and the runtime recursively composes the tree using these templates.
Taro H5
Taro follows a WeChat‑mini‑program‑first component and API spec, but browsers lack these components. Therefore Taro implements a component library and API layer for H5 using Stencil to generate Web Components that follow the mini‑program spec.
Stencil builds a Web Component library that can be used by React, Vue, etc.
Web Components Basics
Custom Elements : define new HTML tags with JavaScript.
Shadow DOM : encapsulate DOM and styles.
HTML Templates : <template> and <slot> for reusable markup.
Stencil Component Example
import { Component, Prop, State, h } from '@stencil/core';
@Component({ tag: 'my-component' })
export class MyComponent {
@Prop() first = '';
@State() last = 'JS';
componentDidLoad() {
console.log('load');
}
render() {
return (
<div>Hello, my name is {this.first} {this.last}</div>
);
}
}Using the component:
<my-component first="Taro" />React‑ify Web Components (reactify‑wc)
Because React’s synthetic event system cannot listen to custom events and props are passed as attributes, a higher‑order component wraps a Web Component, forwarding props as DOM properties and manually attaching event listeners.
const reactifyWebComponent = WC => {
return class Index extends React.Component {
ref = React.createRef();
eventHandlers = [];
update() {
this.clearEventHandlers();
Object.entries(this.props).forEach(([prop, val]) => {
if (typeof val === 'function' && prop.match(/^on[A-Z]/)) {
const event = prop.substr(2).toLowerCase();
this.eventHandlers.push([event, val]);
this.ref.current.addEventListener(event, val);
} else {
// update property
this.ref.current[prop] = val;
}
});
}
clearEventHandlers() {
this.eventHandlers.forEach(([event, handler]) => {
this.ref.current.removeEventListener(event, handler);
});
this.eventHandlers = [];
}
componentDidMount() { this.update(); }
componentDidUpdate() { this.update(); }
componentWillUnmount() { this.clearEventHandlers(); }
render() {
const { children, dangerouslySetInnerHTML } = this.props;
return React.createElement(WC, { ref: this.ref, dangerouslySetInnerHTML }, children);
}
};
};To forward refs, the HOC is wrapped with React.forwardRef and the ref is passed to the underlying Web Component.
return React.forwardRef((props, ref) => (
React.createElement(Index, { ...props, forwardRef: ref })
));Class name handling merges the component’s built‑in classes with user‑provided classes to keep the hydrated class that Stencil adds.
function getClassName(wc, prevProps, props) {
const classList = Array.from(wc.classList);
const oldClassNames = (prevProps.className || prevProps.class || '').split(' ');
let incomingClassNames = (props.className || props.class || '').split(' ');
let finalClassNames = [];
classList.forEach(classname => {
if (incomingClassNames.indexOf(classname) > -1) {
finalClassNames.push(classname);
incomingClassNames = incomingClassNames.filter(name => name !== classname);
} else if (oldClassNames.indexOf(classname) === -1) {
finalClassNames.push(classname);
}
});
finalClassNames = [...finalClassNames, ...incomingClassNames];
return finalClassNames.join(' ');
}Finally, Taro’s H5 component library exports many components created with reactifyWc, e.g.,
export const View = reactifyWc('taro-view-core');
export const Button = reactifyWc('taro-button-core');
// ... many othersPackage Overview
The following NPM packages are used by Taro 3 (name – description):
babel-preset-taro : Babel preset for Taro projects.
@tarojs/taro : Core API exposed to developers.
@tarojs/shared : Internal utilities.
@tarojs/api : Public API for all platforms.
@tarojs/taro-h5 : H5‑specific API.
@tarojs/router : H5 routing.
@tarojs/react : React renderer based on react‑reconciler.
@tarojs/cli : Development tools.
@tarojs/extend : Extensions (e.g., jQuery API).
@tarojs/helper : CLI and runner helpers.
@tarojs/service : Plugin core.
@tarojs/taro-loader : Webpack loader.
@tarojs/runner-utils : Shared utilities for runners.
@tarojs/webpack-runner : H5 Webpack runner.
@tarojs/mini-runner : Mini‑program Webpack runner.
@tarojs/components : Standard component library (H5).
@tarojs/taroize : Mini‑program reverse compiler.
@tarojs/with-weapp : Runtime adapter for reverse conversion.
eslint-config-taro & eslint-plugin-taro : Linting rules.
Conclusion
Taro 3’s refactor solves architectural problems and adds multi‑framework support by moving work from runtime to compile‑time, improving performance while providing greater flexibility and a better developer experience.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
