Hummer Cross-Platform Framework

repository·master·Indexed 23 days ago

https://github.com/didi/hummer

A high-performance, lightweight (<1MB) cross-platform framework by Didi for building Android and iOS apps using a single codebase with Vue, React, TypeScript, or JavaScript. It utilizes native components for maximum rendering performance and includes the Tenon ecosystem, featuring @hummer/tenon-vue for Vue integration, @hummer/tenon-react for React components with native page lifecycles and animations, and @hummer/tenon-store for state management.

Tokens
18.4K
Snippets
42
Records
125
Agent score
80%

What's inside Hummer

  1. Overview of Hummer Cross-Platform Framework

    master

    Hummer is a high-performance, high-availability cross-platform development framework that allows developers to write a single codebase for both Android and iOS applications. It supports multiple syntaxes including Vue, React, TypeScript, and JavaScript.

    Key features include:

    • Ultra-lightweight: Compiled products are less than 1MB.
    • High Availability: Proven in large-scale production with a crash rate below 0.01%.
    • High Performance: Leverages native components and modules for maximum rendering performance.
    • Dynamic Deployment: Uses JavaScript interpretation for rapid deployment, which can be integrated with the Hummer Nest platform for cloud publishing.
  2. Avoid using container methods on leaf nodes

    master

    Leaf nodes such as Image, Text, and Button should not call container-specific methods like appendChild.

    Reason for platform discrepancy: On iOS, these components inherit from View and thus possess appendChild. On Android, they do not inherit from View and lack this method. To ensure cross-platform compatibility, treat leaf nodes as non-containers.

  3. The Base class for Tenon view nodes

    master

    The Base class is the foundation for all view nodes in Tenon. It provides built-in support for:

    • Element Reference: The element property points to the underlying native Hummer component instance.
    • Lifecycle Hooks: onMounted() and onDestoryed() (note the spelling in the source) for managing component lifecycle.
    • Attribute Management: setAttribute(key, value) handles standard props, including data- attributes and class updates. Developers can override _setAttribute(key, value) for custom logic.
    • Event Binding: addEventListener(event, func) and removeEventListener(event, func) bridge Tenon events to the native element.
    • Node Operations: Methods like _appendChild, _removeChild, and _insertBefore manage the tree structure and trigger lifecycle events on children.
    • Animations: handleAnimation(animation) supports various animation types (Keyframe, Basic, Step).
  4. Use ARGB color format for transparency

    master

    Hummer uses the ARGB color format for all color values to maintain consistency with web front-end standards. This differs from standard Android native behavior. The last two characters represent the alpha (transparency) channel.

    Example: #FF000022 where 22 is the transparency value.

  5. Handle animation state discrepancies

    master

    Be aware that animations may behave differently regarding the final view state:

    • Android: The view retains its final state after an animation completes.
    • iOS: The view typically reverts to its initial state after an animation. While some versions of iOS attempt to adjust the view frame to match the end state, the view will revert to its initial state once a Yoga layout is triggered.
  6. Intercepting sub-views in custom components

    master

    When a native component (like a Scroller or Map) expects specific sub-views to be passed via specialized properties or methods rather than standard child appending, you must intercept them in the _appendChild or _insertBefore methods.

    Implementation Pattern

    Instead of calling this.element.appendChild(child.element) for every child, check the child's properties (e.g., a type prop) to identify special sub-views. If a match is found, call the corresponding native method instead of appending it to the standard DOM tree.

    Example: Intercepting a custom marker in a Map component

    _appendChild(child: any) {
      // ... standard tree management logic ...
    
      if (this.element && child.element) {
        if (child.props.type === 'custom-marker') {
          // Intercept: Pass the element to the native method instead of appending
          this.element.addCustomMarker(child.element);
        } else {
          // Standard: Append as a normal child
          this.element.appendChild(child.element);
          child._onMounted();
          child.onMounted();
        }
      }
    }
  7. Understand Yoga layout length property conversion rules

    master

    When defining layout properties in Hummer's Yoga implementation, values are converted based on their type:

    • Undefined/Null: key: undefined or key: null results in an undefined type.
    • DP/PT (Virtual Pixels): key: 123 (number) is treated as density-independent pixels.
    • Strings (Auto/Percentage): key: 'auto' or key: '100%' are valid. Note: You must include the suffix; pure numeric strings like '100' will cause an error.
    • Invalid Types: Objects or dictionaries (e.g., key: {x: 1}) will cause errors.
  8. Define custom exported classes for JS

    master

    To expose Java objects to JavaScript, use the Hummer annotation processor.

    1. Configure HUMMER_MODULE_NAME in your module's gradle file.
    2. Add com.didi.hummer:hummer-compiler as an annotationProcessor dependency.
    3. Annotate your class with @Component("Name") and its properties/methods with @JsProperty or @JsMethod respectively.
    // 1. Module gradle configuration
    android {
        defaultConfig {
            ...
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [HUMMER_MODULE_NAME: project.getName()]
                }
            }
        }
    }
    
    // 2. Add annotation processor dependency
    dependencies {
        ...
        annotationProcessor 'com.didi.hummer:hummer-compiler:0.2.15'
    }
    // 3. Define the exported class
    @Component("TestExportModel")
    public class TestExportModel {
        @JsProperty("text")
        public String text;
     
        @JsProperty("floatValue")
        public float floatValue;
    
        public float getFloatValue() {
            return floatValue;
        }
     
        @JsProperty("style")
        private Map<String, Object> style;
    
        public void setStyle(Map<String, Object> style) {
            this.style = style;
        }
    
        public Map<String, Object> getStyle() {
            return style;
        }
     
        @JsProperty("array")
        private List<String> array;
        public void setArray(List<String> array) {
            this.array = array;
        }
     
        public List<String> getArray() {
            return array;
        }
     
        @JsMethod("getElementById")
        public String getSubview(String viewID, int index, Long test, HashMap<String, String> testMap, ArrayList<Object> testList) {
            return "getSubview func, " + viewID + ", map: " + testMap + ", list: " + testList;
        }
    }
  9. Integrate Hummer into an existing iOS application

    master

    To add Hummer to your iOS project, use CocoaPods. Depending on your requirements, you can install either the stable production version or the latest feature branch.

    Stable Production Version (Cashier Business): Use the specific tag 201907291934.

    Latest Version (iOS/Android Unification): Use the branch feature/Unify_iOS_Android.