在分析 Taro 4.0.7 的源码时,我们可以从其核心的 monorepo 结构出发,理解不同子包的作用,以及 Taro 的多端执行过程和插件机制。

1. 核心 Monorepo 子包及对应作用

Taro 采用了 monorepo 结构,将多个功能模块拆分为不同的子包,便于管理和开发。以下是 Taro 4.0.7 中的一些核心子包,按照编译时和运行时进行分类:

2. Taro 多端执行的全过程

Taro 的多端执行过程包括项目的编译和执行两个阶段。

以下是从编译到执行的全过程,结合子包进行讲解:

2.1. 编译阶段

1. 项目初始化

  • 使用 @tarojs/cli 创建项目,生成基本目录结构和配置文件。

2. 代码编译

  • 根据配置文件,选择相应的编译插件(如 @tarojs/plugin-platform-weapp 或 @tarojs/plugin-platform-h5)进行编译。

  • 编译过程中,使用 Babel 等工具将 ES6+ 代码转化为兼容的 JavaScript 代码。

  • 根据 process.env.TARO_ENV 进行条件编译,移除不适用于当前平台的代码。

3. 生成产物

  • 编译完成后,生成的代码文件会被放置在 /dist 目录下,供后续使用。

2.2. 执行阶段

1. 应用启动

  • 用户通过相应的平台(如微信小程序、H5)访问应用。

2. 运行时解析

  • 运行时库(如 @tarojs/taro)加载并解析应用的入口文件。
  • 初始化路由管理(通过 @tarojs/router),处理页面的跳转和状态管理。

3. 组件渲染

  • 通过 @tarojs/components 进行组件的创建和渲染,确保组件在各个平台上呈现一致的效果。

  • 运行时执行与平台相关的特定逻辑,如事件绑定、样式处理等。

4. 用户交互

  • 处理用户的交互事件,通过 API 与底层平台进行数据交换,更新视图。

    2.3. 执行过程时序图

    编译执行过程:

    3. 核心原理代码说明

    3.1. 编译时处理机制

    3.1.1. 代码解析阶段

    1. AST解析与处理

    // 源代码
    function Index() {
      return (
        
          Hello World!
        
      )
    }
    // 解析为AST后的核心节点
    {
      type: "Program",
      body: [{
        type: "FunctionDeclaration",
        id: { type: "Identifier", name: "Index" },
        body: {
          type: "ReturnStatement",
          argument: {
            type: "JSXElement",
            // ... JSX节点信息
          }
        }
      }]
    }

    2. 条件编译处理

    // 源代码
    if (process.env.TARO_ENV === 'weapp') {
      require('./weapp.js')
    } else if (process.env.TARO_ENV === 'h5') {
      require('./h5.js')
    }
    // 编译后(以微信小程序为例)
    require('./weapp.js')

    3. 依赖分析

    • 建立模块依赖图

    • 识别循环依赖

    • 分析未使用代码

    • 处理动态导入

    3.1.2. 代码转换阶段

    1. 组件转换

    // React组件
    
      Hello
    
    // 转换后的微信小程序
    
      Hello
    

    2. API转换

    // Taro API
    Taro.showToast({ title: 'Hello' })
    // 转换后(微信小程序)
    wx.showToast({ title: 'Hello' })
    // 转换后(H5)
    Toast.show({ content: 'Hello' })

    3. 样式转换

    // 源样式
    .container {
      display: flex;
      transform: scale(0.5);
      .title {
        font-size: 14px;
      }
    }
    // 转换后
    .container {
      display: -webkit-flex;
      display: flex;
      -webkit-transform: scale(0.5);
      transform: scale(0.5);
    }
    .container .title {
      font-size: 28rpx; // 小程序单位转换
    }

    3.1.3. 代码生成阶段

    1. 目标平台代码生成

    // 生成配置文件 (app.json)
    {
      "pages": [
        "pages/index/index"
      ],
      "window": {
        "backgroundTextStyle": "light",
        "navigationBarBackgroundColor": "#fff"
      }
    }
    // 生成页面配置 (index.json)
    {
      "usingComponents": {
        "custom-component": "../../components/custom-component/index"
      }
    }

    2. 静态资源处理

    • 图片资源转换与优化

    • 字体文件处理

    • 媒体资源处理

    3.2. 运行时处理机制

    3.2.1. 框架运行时

    1. 生命周期映射表

    const LIFECYCLE_MAPPING = {
      // React生命周期 -> 小程序生命周期
      componentDidMount: 'onLoad',
      componentDidShow: 'onShow',
      componentDidHide: 'onHide',
      componentWillUnmount: 'onUnload'
    }

    2. 事件系统处理

    // 事件对象标准化
    function createEvent(event) {
      const { type, target, currentTarget, detail } = event
      return {
        type,
        target: {
          id: target.id,
          dataset: target.dataset
        },
        currentTarget: {
          id: currentTarget.id,
          dataset: currentTarget.dataset
        },
        detail,
        timestamp: Date.now()
      }
    }

    3.2.2. 组件运行时

    1. 组件映射机制

    const ComponentsMapping = {
      // React组件 -> 小程序组件
      View: 'view',
      Text: 'text',
      Button: 'button',
      Image: 'image'
    }

    2. 属性转换处理

    function processProps(props) {
      const newProps = {}
      Object.keys(props).forEach(key => {
        // className -> class
        if (key === 'className') {
          newProps.class = props[key]
        }
        // onClick -> bindtap
        else if (key === 'onClick') {
          newProps.bindtap = props[key]
        }
        // style对象处理
        else if (key === 'style' && typeof props[key] === 'object') {
          newProps.style = processStyle(props[key])
        }
        else {
          newProps[key] = props[key]
        }
      })
      return newProps
    }

    3.2.3. 状态管理运行时

    1. 状态更新机制

    class Store {
      constructor() {
        this.state = {}
        this.observers = new Set()
      }
      setState(newState) {
        this.state = { ...this.state, ...newState }
        this.notify()
      }
      notify() {
        this.observers.forEach(observer => observer(this.state))
      }
    }

    2. 数据流管理

    // Redux集成示例
    function connectComponent(Component) {
      return class Connected extends Taro.Component {
        componentDidMount() {
          store.subscribe(this.handleStoreChange)
        }
        handleStoreChange = (state) => {
          this.setState(state)
        }
        render() {
          return 
        }
      }
    }

    3.3. 平台差异化处理

    3.3.1. API适配层

    https://github.com/NervJS/taro/blob/d936b773d2c05f50ca1e7edc8f421514b6c2028a/packages/taro-platform-tt/src/apis.ts#L9

    // API适配示例
    const apiDiff = {
      weapp: {
        showToast: wx.showToast,
        getStorage: wx.getStorage
      },
      alipay: {
        showToast: my.showToast,
        getStorage: my.getStorage
      },
      h5: {
        showToast: (options) => Toast.show(options),
        getStorage: (options) => localStorage.getItem(options.key)
      }
    }
    // 统一调用接口
    function callApi(name, options) {
      const api = apiDiff[process.env.TARO_ENV][name]
      return api(options)
    }

    3.3.2. 样式适配处理

    // 样式处理函数
    function processStyle(style) {
      const { platform } = process.env
      // 单位转换
      function transformUnit(value) {
        if (typeof value === 'number') {
          return platform === 'h5' ? `${value}px` : `${value}rpx`
        }
        return value
      }
      // 前缀处理
      function addVendorPrefix(property) {
        const needPrefix = ['transform', 'transition', 'animation']
        if (needPrefix.includes(property)) {
          return [
            `-webkit-${property}`,
            `-moz-${property}`,
            `-ms-${property}`,
            property
          ]
        }
        return [property]
      }
      return Object.entries(style).reduce((acc, [key, value]) => {
        const properties = addVendorPrefix(key)
        properties.forEach(prop => {
          acc[prop] = transformUnit(value)
        })
        return acc
      }, {})
    }

    3.4. 运行整体流程图

    4. Taro 插件与端支持插件机制

    4.1. 插件机制

    Taro 的插件机制使得开发者可以通过插件的方式扩展 Taro 的功能。这包括编译时的支持插件和运行时的功能扩展。插件的设计遵循以下原则:

    • 独立性: 插件之间独立,彼此不影响,便于维护和扩展。

    • 灵活性: 开发者可以选择性地添加或移除插件,适应不同项目需求。

    4.2. 端支持插件

    Taro 提供了多种端支持插件,如 @tarojs/plugin-platform-weapp 和 @tarojs/plugin-platform-h5,这些插件负责处理特定平台的编译逻辑。

    开发者也可以基于现有的插件进行扩展,支持更多平台。

    5. 补充资料