Frontend Development 15 min read

Mastering H5 + Mini‑Program Development: Static Compile vs Dynamic Parse

This article explores the challenges of building H5 and mini‑program applications across multiple platforms, compares static compilation approaches like Chameleon, MPVue, Taro and Rax with dynamic parsing solutions such as Remax and Frad, and discusses performance trade‑offs, lifecycle integration, and future directions for view‑layer DSLs.

Taobao Frontend Technology
Taobao Frontend Technology
Taobao Frontend Technology
Mastering H5 + Mini‑Program Development: Static Compile vs Dynamic Parse

As closed‑loop ecosystems mature, mini‑programs have become essential for many businesses. Different platforms (Alipay, WeChat, JD, etc.) provide their own mini‑program solutions, and development teams face the difficulty of adapting H5 and multiple mini‑programs simultaneously.

Static Compilation

Static compilation solutions are abundant. Based on a Vue‑style DSL there are Chameleon ( https://cml.js.org/ ) and MPVue ( http://mpvue.com/ ); based on React JSX there are Taro ( https://nervjs.github.io/taro/ ) and Rax ( https://rax.js.org/ ).

The Vue‑like DSL aligns well with mini‑program design because mini‑program DSLs already borrow from Vue and are static languages without a runtime, making the mental cost of implementing a compiler low. JSX, however, is a high‑level JavaScript syntax; many expressions cannot be evaluated at compile time, which limits static compilation.

Examples:

<code>// DEMO 1
function DemoA({list}) {
  return (
    <div>
        {list.map(item => <div key={item.id}>{item.content}</div>)}
    </div>
  )
}

// DEMO 2
function DemoB({visible}) {
  if (!visible) {
    return null
  }
  return <div>cool</div>
}

// DEMO 3
function SomeFunctionalRender({children, ...props}) {
  return typeof children === 'function' ? children(props) : null
}
function DemoC() {
  return (
    <SomeFunctionalRender>{props => <div>{props.content}</div>}</SomeFunctionalRender>
  )
}
</code>

Demo 1 and Demo 2 can be transformed into mini‑program DSL (a:for / a:if) via AST parsing, but Demo 3 represents a nightmare for static DSL because it relies on runtime function calls, a pattern common in React libraries such as react‑spring.

How do Taro and Rax solve these problems? By trimming JSX. They restrict the usable JSX syntax to maximize compatibility with mini‑program DSLs.

Rax extends JSX with JSX+ (see https://rax.js.org/docs/guide/jsxplus ) to allow declarative conditional rendering, loops, slots, etc., limiting complex JavaScript in children while preserving rendering capabilities.

Taro takes a different route: it does not extend JSX but uses AST analysis to convert Array.map, if/else, ternary, and enum rendering into static DSL that mini‑programs understand. This approach retains the original JSX development experience at the cost of higher mental overhead and limited support for patterns like Demo 3.

Dynamic Parsing

Because JSX relies heavily on a JavaScript runtime, newer frameworks embrace React and adopt a dynamic‑parsing strategy. Representative projects are Remax ( https://remaxjs.org/ ) and Frad ( https://github.com/yisar/fard ).

React’s rendering pipeline is "State → Virtual DOM → DOM". The crucial step is Virtual DOM → DOM, performed by React Reconciler (see https://github.com/facebook/react/tree/master/packages/react-reconciler ). React Native implements its own reconciler for native views.

React rendering flow
React rendering flow

By providing a full JavaScript runtime, dynamic‑parsing frameworks can generate mini‑program DOM from Virtual DOM at runtime. Mini‑programs offer a

template

component ( https://opendocs.alipay.com/mini/framework/axml-template ) that enables dynamic component invocation, allowing the Virtual DOM to be parsed and rendered as mini‑program DOM.

The advantage is a near‑native React experience, but the downside is performance loss: no compile‑time optimizations, no dead‑code elimination, and every render must recompute every node, which is problematic on low‑end devices or long lists.

Lifecycle & Application State Management

Mini‑program lifecycle and state management map closely to React class components. The following code shows a bridge that maps mini‑program data/setData to React state, and aligns lifecycle hooks.

<code>import React from 'react'
import omit from 'lodash/omit'
import noop from 'lodash/noop'

function createComponent(comp) {
  const { data, onInit = noop, deriveDataFromProps = noop, didMount = noop, didUpdate = noop, didUnmount = noop, methods = {}, render, } = comp
  return class extends React.Component {
    constructor(props) {
      super(props)
      this.state = { ...data }
      this.setData = this.setState
      this.__init()
    }
    get data() { return this.state }
    __init() {
      for (let key in methods) { this[key] = methods[key] }
      onInit.call(this, data)
    }
    componentWillMount() { deriveDataFromProps.call(this, this.props) }
    componentDidMount() { didMount.call(this) }
    componentWillReceiveProps(nextProps) { deriveDataFromProps.call(this, nextProps) }
    componentWillUpdate(nextProps, nextState) { deriveDataFromProps.call(this, nextProps) }
    componentDidUpdate(prevProps, prevState) { didUpdate.call(this, prevProps, prevState) }
    componentWillUnmount() { didUnmount.call(this) }
    render() { return render ? render.call(this) : null }
  }
}
export default createComponent
</code>

Mini‑programs also distinguish App, Page, and Component. The code below registers pages dynamically via a global

window.__PageRegister

bridge.

<code>import React from 'react'
import ReactDOM from 'react-dom'
import { hot } from 'react-hot-loader'

export class PageRegister {
  constructor() {
    if (window.__PageRegister) return window.__PageRegister
    this.__page = () => null
    this.__handlers = []
    window.__PageRegister = this
  }
  subscribe(cb) { this.__handlers.push(cb) }
  unsubscribe(cb) { this.__handlers = this.__handlers.filter(h => h !== cb) }
  destroy() { this.__handlers = []; this.__page = function () { return null } }
  setPage(page) { this.__page = page; this.__handlers.map(cb => typeof cb === 'function' && cb(page)) }
  getPage() { return this.__page }
}

export default function createApp(app) {
  const pageRegister = new PageRegister()
  class __App extends React.Component {
    constructor(props) {
      super(props)
      this.state = { page: pageRegister.getPage() }
      pageRegister.subscribe(page => this.setState({ page }))
    }
    componentWillUnmount() { pageRegister.destroy() }
    render() { const { page: Page } = this.state; return <Page /> }
  }
  const App = __DEV__ ? hot(module)(__App) : __App
  ReactDOM.render(<App />, document.getElementById('root'))
}
</code>

View Layer DSL

After experimenting with many view‑layer co‑implementation schemes, I question whether a unified DSL is necessary. Web must compromise to fit mini‑program constraints, and static compilation relies heavily on ASTs that are hard to debug. Both Web and mini‑program models are simple: they take props and state (data) and output a view.

Separating AXML and JSX implementations does not increase mental load and actually makes rendering behavior more controllable.

Conclusion

Remax and Frad’s Virtual DOM approach opens a new door for mini‑program co‑implementation, with the potential to adapt to React Native and other view frameworks. However, performance remains a critical hurdle for dynamic‑parsing solutions. Future work will continue to explore performance‑friendly strategies and deeper topics such as data binding, dependency injection, and tree shaking for H5 + mini‑program multi‑end builds.

Cross-PlatformReactMini Programstatic compilationdynamic parsing
Taobao Frontend Technology
Written by

Taobao Frontend Technology

The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.

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.