UI5

Component vs Library 프리로드 캐시 차이 #shorts #SAP #UI5

▶ YouTube에서 보기

이 글에서 다루는 것과 도달 지점

SAPUI5/OpenUI5 앱의 초기 로딩 성능을 좌우하는 두 가지 번들, Component-preload.jslibrary-preload.js를 빌드 최적화 관점에서 비교합니다. 두 파일이 각각 언제 생성되고 어떤 시점에 로드되는지, UI5 Tooling과 레거시 Grunt 빌드에서 무엇이 다른지, 캐싱 전략과 의존성 해석이 어떻게 갈리는지를 실전 설정 코드와 함께 파고듭니다. 이 글을 끝내면 아래 항목을 스스로 판단할 수 있게 됩니다.

  • 앱 코드와 재사용 라이브러리 코드 중 어느 쪽을 어떤 번들로 묶을지 결정
  • ui5.yaml로 프리로드 생성 범위를 세밀하게 제어
  • manifest.json 의존성 선언이 프리로드 로딩 순서에 미치는 영향 이해
  • 네트워크 탭에서 프리로드 적중/미적중을 진단하고 튜닝

미리 갖춰두면 좋은 배경

UI5 컴포넌트 기반 앱 구조(Component.js, manifest.json)를 만들어 본 경험, AMD 스타일 모듈 정의(sap.ui.define)에 대한 이해, Node.js 기반 CLI 사용 경험이 있으면 충분합니다. XML 뷰와 컨트롤러의 역할 분담을 알고 있다는 전제로 진행합니다.

검증 환경과 준비물

이 글의 예제는 다음 환경을 기준으로 작성했습니다.

  • SAPUI5 1.120 LTS / OpenUI5 1.120 계열 (1.71 이상이면 대부분 동일하게 동작)
  • UI5 Tooling v3 (@ui5/cli 3.x, specVersion 3.0 이상)
  • Node.js 18 LTS 이상, 배포 대상은 SAP BTP Cloud Foundry의 HTML5 Application Repository 또는 온프레미스 ABAP 프런트엔드 서버(구 Grunt 절차 비교용)

예제 시나리오는 가상의 콜드체인 물류 모니터링 앱 com.frostline.chainwatch와, 사내 여러 앱이 공유하는 재사용 라이브러리 frostline.uxkit입니다. 실제 프로젝트의 네임스페이스로 치환해서 그대로 적용할 수 있습니다.

두 프리로드의 본질 — 택배 상자와 공용 창고

UI5 모듈은 원래 한 파일당 한 번의 HTTP 요청으로 로드됩니다. 뷰 10개, 컨트롤러 10개, 프래그먼트 5개면 앱을 여는 순간 수십 번의 왕복이 발생합니다. 프리로드는 이 개별 파일들을 하나의 JSON 유사 번들로 묶어 모듈 이름 → 소스 문자열 맵 형태로 로더의 프리로드 캐시에 미리 등록해 두는 장치입니다. 등록만 될 뿐 즉시 실행되지 않고, 누군가 sap.ui.require로 해당 모듈을 요구하는 순간 캐시에서 꺼내 평가됩니다. 즉 "요청 횟수는 줄이되 실행 시점은 늦추는" 지연 평가 구조입니다.

비유하자면 Component-preload.js한 고객 앞으로 가는 택배 상자입니다. 특정 앱 컴포넌트(com.frostline.chainwatch)의 뷰·컨트롤러·i18n·manifest를 한 상자에 담아, 그 앱이 시작될 때 한 번에 배송합니다. 반면 library-preload.js여러 매장이 함께 쓰는 공용 창고입니다. sap.m, sap.ui.core 같은 표준 라이브러리든 frostline.uxkit 같은 커스텀 라이브러리든, 라이브러리 단위로 번들되어 여러 앱이 동일한 파일을 공유하고 브라우저/CDN 캐시에 한 번만 적재됩니다.

로딩 시점도 다릅니다. 라이브러리 프리로드는 부트스트랩 직후 manifest.jsonsap.ui5/dependencies/libs 선언을 근거로 컴포넌트 인스턴스화 이전에 병렬로 요청됩니다. 컴포넌트 프리로드는 Component.create() 또는 ComponentContainer가 해당 컴포넌트를 로드하는 시점에 Component.js 대신 Component-preload.js를 먼저 시도하는 방식으로 로드됩니다. 빌드 관점에서는 UI5 Tooling의 generateComponentPreload 태스크가 전자를, generateLibraryPreload 태스크가 후자를 만들며, 프로젝트 타입(type: application vs type: library)에 따라 자동으로 갈립니다. 레거시 Grunt 시절에는 grunt-openui5openui5_preload 태스크 하나에서 compress, resources 옵션으로 두 종류를 수동 구성해야 했고, 압축 품질과 소스맵 지원이 제한적이었습니다. 현재는 UI5 Tooling 사용이 일반적으로 권장됩니다.

캐싱 전략의 차이가 최적화의 핵심입니다. 라이브러리 프리로드는 UI5 버전 경로(예: /resources/1.120.x/...)에 묶여 있어 앱을 아무리 자주 배포해도 캐시가 유지됩니다. 컴포넌트 프리로드는 앱 배포마다 내용이 바뀌므로 Fiori launchpad의 AppCacheBuster나 HTML5 Repository의 버전 경로로 무효화됩니다. 따라서 변경 빈도가 낮고 여러 앱이 공유하는 코드는 라이브러리로, 앱 고유 코드는 컴포넌트로 나누는 것이 캐시 적중률을 극대화하는 기본 원칙입니다.

실전 구성 3단계 — 번들 만들기부터 프로덕션 튜닝까지

1단계: 앱 프리로드 기본 생성과 의존성 선언

애플리케이션 프로젝트의 ui5.yaml은 타입만 정확하면 프리로드가 자동 생성됩니다.

specVersion: "3.0"
metadata:
  name: com.frostline.chainwatch
type: application
framework:
  name: SAPUI5
  version: "1.120.12"
  libraries:
    - name: sap.m
    - name: sap.ui.core
    - name: sap.f

manifest.json의 라이브러리 선언은 라이브러리 프리로드의 병렬 로딩 트리거입니다. 여기 없는 라이브러리를 코드에서 갑자기 쓰면 런타임에 동기 폴백 요청이 생길 수 있습니다.

{
  "sap.ui5": {
    "dependencies": {
      "minUI5Version": "1.120.0",
      "libs": {
        "sap.ui.core": {},
        "sap.m": {},
        "sap.f": { "lazy": true }
      }
    }
  }
}

lazy: true로 표시한 sap.f는 초기 병렬 로딩에서 빠지고, 실제 사용 시점에 Lib.load()로 불러와야 합니다. ui5 build --all 실행 후 dist/Component-preload.js가 생기고, 내부는 대략 아래 구조입니다(직접 작성하는 파일이 아니라 빌드 산출물입니다).

sap.ui.require.preload({
  "com/frostline/chainwatch/Component.js": function(){ /* 원본 소스 */ },
  "com/frostline/chainwatch/controller/SensorBoard.controller.js": function(){ /* ... */ },
  "com/frostline/chainwatch/view/SensorBoard.view.xml": "",
  "com/frostline/chainwatch/manifest.json": "{ ... }"
}, "com/frostline/chainwatch/Component-preload");

2단계: 재사용 라이브러리 분리와 로딩 검증

사내 공통 컨트롤(온도 게이지, 상태 배지 등)을 frostline.uxkit 라이브러리로 분리하면 별도의 library-preload.js가 생성되어 여러 앱이 캐시를 공유합니다.

specVersion: "3.0"
metadata:
  name: frostline.uxkit
type: library
builder:
  componentPreload: {}   # 라이브러리 프로젝트에는 불필요, 명시적으로 비움
  jsdoc:
    excludes:
      - "frostline/uxkit/thirdparty/**"

앱에서 사용할 때는 manifest 선언과 함께, 지연 로딩이 필요한 화면에서는 명시적으로 로드하고 실패를 로깅합니다.

sap.ui.define([
  "sap/ui/core/Lib",
  "sap/base/Log"
], function (Lib, Log) {
  "use strict";
  return {
    loadUxkitOnDemand: async function () {
      try {
        // library-preload.js 를 비동기로 요청하고 초기화까지 대기
        await Lib.load({ name: "frostline.uxkit" });
        Log.info("uxkit library-preload loaded", null, "chainwatch.boot");
      } catch (oError) {
        Log.error("uxkit preload failed, falling back to single modules",
          oError.message, "chainwatch.boot");
        // 프리로드 실패 시에도 개별 모듈 요청으로 동작은 지속됨
      }
    }
  };
});

검증은 브라우저 네트워크 탭에서 합니다. library-preload.js들이 부트스트랩 직후 병렬로, Component-preload.js가 그 다음 단일 요청으로 내려오면 정상입니다. 개별 .controller.js 요청이 보이면 프리로드 미적중이며, URL에 sap-ui-xx-componentPreload=off를 붙여 의도적으로 끄고 전후를 비교하면 효과를 정량화할 수 있습니다.

3단계: 프로덕션 — 번들 제외, 커스텀 번들, 캐시 무효화

테스트 코드나 대형 서드파티 모듈이 프리로드에 섞여 초기 페이로드를 키우는 것이 실무에서 가장 흔한 낭비입니다. excludes로 잘라내고, 초기 화면에 꼭 필요한 모듈만 담은 커스텀 번들을 별도로 정의할 수 있습니다.

specVersion: "3.0"
metadata:
  name: com.frostline.chainwatch
type: application
builder:
  componentPreload:
    excludes:
      - "com/frostline/chainwatch/test/**"
      - "com/frostline/chainwatch/thirdparty/chart-heavy.js"
  bundles:
    - bundleDefinition:
        name: "com/frostline/chainwatch/boot-bundle.js"
        sections:
          - mode: preload
            filters:
              - "com/frostline/chainwatch/controller/SensorBoard.controller.js"
              - "com/frostline/chainwatch/view/SensorBoard.view.xml"
            resolve: false
      bundleOptions:
        optimize: true        # 프로덕션 압축(terser)
        sourceMap: true       # 배포 후 디버깅용 소스맵

보안·성능 측면에서는 부트스트랩에 data-sap-ui-async="true"를 반드시 유지해야 합니다. 동기 로딩 폴백은 성능 저하뿐 아니라 CSP(Content Security Policy) 위반 소지가 있는 동기 XHR + eval 경로를 타기 때문입니다. 캐시 무효화는 launchpad 환경이면 AppCacheBuster가 sap-ui-cachebuster-info.json 기반으로 처리하고, standalone 배포면 배포 파이프라인에서 버전 경로를 부여하는 방식이 일반적입니다. 배포 전 테스트로는 산출물 크기 회귀 검사를 CI에 넣는 것을 권장합니다.

// ci/check-bundle-size.js — 번들 크기 회귀 가드
const fs = require("fs");
const LIMIT_KB = 450;
const size = fs.statSync("dist/Component-preload.js").size / 1024;
if (size > LIMIT_KB) {
  console.error(`Component-preload.js ${size.toFixed(1)}KB > ${LIMIT_KB}KB`);
  process.exit(1);
}
console.log(`bundle size OK: ${size.toFixed(1)}KB`);

자주 겪는 문제와 진단 포인트

Q1. 빌드했는데도 개별 파일이 계속 요청됩니다. 가장 흔한 원인은 (1) dist가 아닌 소스 폴더를 서빙 중이거나, (2) 네임스페이스와 폴더 구조가 어긋나 프리로드 맵의 키와 요청 경로가 불일치하는 경우입니다. Component-preload.js를 열어 모듈 키와 실제 요청 URL 경로를 대조해 보세요. sap.ui.define 대신 전역 스크립트로 작성된 파일도 번들에서 누락됩니다.

Q2. 커스텀 라이브러리의 library-preload.js가 404입니다. 앱과 라이브러리를 같은 서버 경로에 배포했는지, resourceRoots 또는 launchpad의 컴포넌트 등록으로 라이브러리 네임스페이스 경로가 매핑됐는지 확인해야 합니다. 404 자체는 치명적이지 않지만(개별 모듈 폴백) 요청 수가 폭증하므로 성능상 반드시 잡아야 합니다.

Q3. 프리로드 때문에 초기 로딩이 오히려 느려진 것 같습니다. 모든 것을 한 번들에 넣으면 첫 화면에 불필요한 코드까지 다운로드·파싱합니다. 라우팅상 뒤쪽 화면의 무거운 모듈은 excludes로 빼고 필요 시점에 로드하거나 별도 번들로 분리하세요. "요청 수 최소화"와 "초기 페이로드 최소화"는 트레이드오프이며, 일반적으로 첫 화면 기준으로 균형점을 잡습니다.

Q4. Grunt 기반 구 프로젝트는 어떻게 하나요? openui5_preload 산출물도 런타임에서는 동일하게 동작하지만, specVersion 기반 설정·프레임워크 버전 관리·커스텀 번들 기능이 없으므로 UI5 Tooling으로의 마이그레이션을 권장합니다.

여기서 더 나아가려면

프리로드 튜닝을 마쳤다면 다음 주제들이 자연스러운 확장입니다. 첫째, 부트스트랩까지 하나로 합치는 self-contained 빌드(ui5 build self-contained)와 프리로드 방식의 손익 비교. 둘째, sap.ui.getVersionInfo 없이도 병렬성을 높이는 preload 힌트(data-sap-ui-preload="async")와 HTTP/2 환경에서의 번들 전략 재검토. 셋째, launchpad 다중 앱 환경에서 공용 라이브러리 버전 충돌을 다루는 재사용 컴포넌트(sap.app/embeds, 라이브러리 버전 정책) 설계입니다.

더 깊이 파고들 자료

댓글 0

아직 댓글이 없습니다.