HarmonyOS NEXT ArkTS 환경에서 React Native 다중 번들 로드 및 관리 전략

ArkTS 기반 React Native 번들 로딩 아키텍처

HarmonyOS NEXT의 ArkTS 환경에서 React Native(RN) 번들을 로드하는 과정은 크게 세 가지 핵심 단계로 구성됩니다.

  • RN 인스턴스(RNInstance)의 생성 및 상태 관리
  • RN 서드파티 컴포넌트 컨텍스트(RNComponentContext) 초기화
  • UI 렌더링 컨테이너(RNApp 또는 RNSurface) 바인딩

RN 애플리케이션은 단일 번들(Single Bundle) 구조와 다중 번들(Multi Bundle) 구조로 나뉩니다. 다중 번들 구조는 화면 간 이동 시 번들을 동적으로 로드하거나, 하나의 화면을 여러 번들이 조합하여 렌더링하는 마이크로서비스 형태의 아키텍처를 지원합니다. 이 경우 번들 로딩 지연으로 인한 화이트 스크린(White Screen) 현상을 방지하기 위해 사전 로딩(Pre-loading) 전략이 필수적입니다.

번들 설정 및 로딩 컨트롤러 구현

다중 번들 환경을 효율적으로 관리하기 위해 번들 구성 데이터와 로딩 로직을 분리하여 캡슐화합니다. HarmonyOS NEXT에서는 Java 지원이 완전히 배제되었으므로, 네이티브 TurboModule 구현을 위해 CAPI 아키텍처(enableCAPIArchitecture)를 반드시 활성화해야 합니다.

// RnBundleConfig.ts
export class RnBundleConfig {
  isDebugMode: boolean = false;
  jsBundlePath: string = '';
  resourcePath: string = '';
  bundleKey: string = '';
}

// BundleLoadController.ts
export class BundleLoadController {
  private static instanceMap: Map<string, any> = new Map();
  private static coreContext: any = null;

  public static async executeBundleLoad(config: RnBundleConfig): Promise<boolean> {
    if (!this.coreContext || this.instanceMap.has(config.bundleKey)) {
      return true;
    }

    const rnInstance = await this.coreContext.createAndRegisterRNInstance({
      enableCAPIArchitecture: true, // HarmonyOS NEXT 필수 옵션
      enableNDKTextMeasuring: true,
      enableBackgroundExecutor: true,
      assetsDest: config.resourcePath
    });

    const componentContext = new RNComponentContext(
      RNOHContext.fromCoreContext(this.coreContext, rnInstance),
      wrapBuilder(buildCustomComponent),
      wrapBuilder(buildRNComponentForTag),
      new Map()
    );

    const bundleProvider = config.isDebugMode
      ? new MetroJSBundleProvider()
      : new ResourceJSBundleProvider(this.coreContext.uiAbilityContext.resourceManager, config.jsBundlePath);

    await rnInstance.runJSBundle(bundleProvider);
    const executionStatus = rnInstance.getBundleExecutionStatus(bundleProvider.getURL());

    if (executionStatus === "DONE") {
      this.instanceMap.set(config.bundleKey, rnInstance);
      return true;
    }
    return false;
  }

  public static getInstance(key: string): any {
    return this.instanceMap.get(key);
  }
}

진입점 초기화 및 비동기 로딩

앱의 진입 페이지(aboutToAppear)에서 다중 번들을 비동기적으로 초기화합니다. 각 번들의 로딩 완료 상태와 현재 활성화된 번들 키를 상태 변수로 관리하여 UI 렌더링을 제어합니다.

// EntryPage.ets
@Entry @Component
struct EntryPage {
  @StorageLink('RNOHCoreContext') coreContext: any = undefined;
  @State isPrimaryLoaded: boolean = false;
  @State isSecondaryLoaded: boolean = false;
  @State activeBundleKey: string = 'primary';
  
  private currentInstance: any = null;
  private propsMap: Map<string, Record<string, Object>> = new Map();

  aboutToAppear() {
    if (!this.coreContext) return;
    this.initializeBundles();
  }

  private async initializeBundles() {
    const primaryConfig = new RnBundleConfig();
    primaryConfig.jsBundlePath = 'primary/bundle.harmony.js';
    primaryConfig.resourcePath = 'rawfile/primary/assets';
    primaryConfig.bundleKey = 'primary';

    const secondaryConfig = new RnBundleConfig();
    secondaryConfig.jsBundlePath = 'secondary/bundle.harmony.js';
    secondaryConfig.resourcePath = 'rawfile/secondary/assets';
    secondaryConfig.bundleKey = 'secondary';

    BundleLoadController.executeBundleLoad(primaryConfig).then((success) => {
      this.isPrimaryLoaded = success;
      if (success) this.currentInstance = BundleLoadController.getInstance('primary');
    });

    BundleLoadController.executeBundleLoad(secondaryConfig).then((success) => {
      this.isSecondaryLoaded = success;
    });
  }
  
  // ... build 메서드는 하단 참조
}

UI 컨테이너 선택: RNApp vs RNSurface

단일 번들 애플리케이션에서는 RNApp 컴포넌트를 사용하여 전체 UI를 호스팅할 수 있습니다. 그러나 다중 번들 환경, 특히 번들 간 동적 전환이나 복합 UI 페이징이 필요한 경우에는 RNSurface를 사용하는 것이 유연합니다. RNSurface는 상태 변수에 따라 특정 번들의 UI만 선택적으로 마운트하거나 여러 번들의 UI를 동시에 배치할 수 있습니다.

// EntryPage.ets (build 메서드)
build() {
  Column() {
    if (this.isPrimaryLoaded && this.activeBundleKey === 'primary') {
      RNSurface({
        surfaceConfig: { 
          appKey: 'primary', 
          initialProps: this.propsMap.get('primary') 
        },
        ctx: new RNComponentContext(
          RNOHContext.fromCoreContext(this.coreContext!, this.currentInstance),
          wrappedCustomComponentBuilder,
          wrapBuilder(buildRNComponentForTag),
          new Map()
        ),
      })
    }
    
    if (this.isSecondaryLoaded && this.activeBundleKey === 'secondary') {
      RNSurface({
        surfaceConfig: { 
          appKey: 'secondary', 
          initialProps: this.propsMap.get('secondary') 
        },
        ctx: new RNComponentContext(
          RNOHContext.fromCoreContext(this.coreContext!, this.currentInstance),
          wrappedCustomComponentBuilder,
          wrapBuilder(buildRNComponentForTag),
          new Map()
        ),
      })
    }
  }
  .width('100%')
  .height('100%')
}

네이티브 이벤트 기반 번들 전환

번들 간의 라우팅 및 전환은 RN에서 네이티브로 이벤트를 전달하고, 네이티브에서 @State 변수를 업데이트하여 트리거합니다. 이를 통해 현재 활성화된 번들 인스턴스와 전달할 초기 Props를 동적으로 갱신할 수 있습니다.

// EntryPage.ets (이벤트 리스너 등록)
aboutToAppear() {
  // ... 초기화 로직
  
  emitter.on('NAVIGATE_TO_SECONDARY_BUNDLE', (eventData) => {
    const rawParams = eventData?.data?.param;
    if (rawParams) {
      try {
        const parsedParams = JSON.parse(rawParams) as Record<string, Object>;
        this.propsMap.set('secondary', parsedParams);
      } catch (error) {
        console.error('Props parsing failed:', error);
      }
    }
    
    // 번들 전환 및 인스턴스 스와핑
    this.activeBundleKey = 'secondary';
    this.currentInstance = BundleLoadController.getInstance('secondary');
  });
}

리소스 경로(assetsDest) 제한 사항 및 우회 전략

HarmonyOS NEXT 환경에서 다중 번들 로딩 시 assetsDest 속성을 통해 각 번들의 리소스 경로를 별도로 지정하더라도, 애플리케이션이 실제 리소스를 로드할 때는 기본 경로인 rawfile/assets/만 참조하는 제한 사항이 존재합니다.

이러한 플랫폼 레벨의 제약을 우회하기 위해서는 각 번들의 리소스를 동일한 rawfile/assets/ 디렉토리 내에서도 서로 다른 하위 폴더(예: rawfile/assets/primary/images, rawfile/assets/secondary/images)로 물리적으로 분리해야 합니다. 이 경우 RN JavaScript 코드 내부에서 참조하는 이미지 경로 또한 하위 디렉토리 구조에 맞게 일괄적으로 수정(Refactoring)하는 작업이 선행되어야 합니다.

태그: HarmonyOS-NEXT ArkTS React-Native Multi-Bundle RNSurface

8월 21일 22:13에 게시됨