UI5

CDN vs Local UI5 — 배포 때 뭐가 맞아? #shorts #SAP #UI5

▶ YouTube에서 보기

이 글에서 다루는 것

SAPUI5/OpenUI5 애플리케이션을 부트스트랩(bootstrap)하는 방식은 크게 두 가지로 나뉩니다. 하나는 SAP가 운영하는 CDN(sapui5.hana.ondemand.com 또는 openui5.hana.ondemand.com)에서 런타임 리소스를 직접 로드하는 방식이고, 다른 하나는 npm 패키지(@sapui5/distribution, @openui5/sap.ui.core 등)를 통해 로컬에 설치한 뒤 애플리케이션 서버에서 함께 서빙하는 방식입니다. 이 글에서는 BTP(Business Technology Platform) 환경에서 동작하는 Fiori 앱과, 폐쇄망 S/4HANA On-Premise 환경에서 동작해야 하는 사내 앱을 두 축으로 두고, 각 방식의 부트스트랩 구성, ui5.yaml 설정, 버전 고정 전략, 빌드 파이프라인, CSP(Content Security Policy) 헤더 정책까지 비교합니다.

  • CDN 부트스트랩 URL 구조와 캐싱 헤더 동작을 이해한다
  • 로컬 호스팅 시 ui5.yaml@ui5/cli의 의존성 관리 방식을 파악한다
  • BTP Cloud Foundry/Approuter와 On-Premise NetWeaver 환경의 차이를 구분한다
  • CSP script-src, connect-src 헤더가 부트스트랩에 미치는 영향을 점검한다

읽기 전 알아두면 좋은 배경

HTML <script> 태그의 src/data-* 속성, npm 기반 모던 자바스크립트 빌드 도구(특히 @ui5/cli)의 기본 명령(ui5 serve, ui5 build), SAP Fiori Launchpad의 리소스 로딩 흐름, 그리고 HTTP 캐싱(Cache-Control, ETag) 개념을 알면 도움이 됩니다. Cloud Foundry Approuter의 xs-app.json 라우팅 룰을 다뤄본 경험이 있다면 BTP 시나리오 이해가 훨씬 쉽습니다.

환경과 준비물

이 글의 예제는 다음 환경에서 검증된 기준으로 작성되었습니다. 실제 프로젝트에 적용할 때는 회사 표준 버전 정책을 우선 확인하세요.

  • SAPUI5: 1.120.x LTS (BTP 시나리오), 1.108.x (On-Premise 시나리오)
  • OpenUI5: 1.120.x (사내 격리망 대안)
  • Node.js: 20.x LTS, npm 10.x
  • @ui5/cli: 3.x
  • BTP: Cloud Foundry, @sap/approuter 16.x
  • On-Premise: NetWeaver AS ABAP 7.5x 이상 또는 사내 nginx 1.24
  • 브라우저: Chromium 계열 최신, Edge for Business

버전을 명시하는 이유는 UI5의 마이너 버전 간에도 컨트롤 시그니처가 바뀌는 경우가 있기 때문입니다. LTS 버전을 고정하면 일반적으로 유지보수 비용이 낮아집니다.

핵심 개념 한눈에

UI5 앱이 브라우저에서 실행되기까지의 과정을 비유하자면, CDN 방식은 "프랜차이즈 본사가 모든 식자재를 매장으로 직배송"하는 모델이고, 로컬 호스팅은 "매장이 직접 창고를 운영"하는 모델에 가깝습니다. 전자는 운영 부담이 적지만 본사 네트워크에 의존하며, 후자는 자율성이 높지만 재고(버전) 관리 책임이 매장으로 넘어옵니다.

UI5 부트스트랩의 핵심은 sap-ui-core.js 한 개의 스크립트가 로드되면서 시작됩니다. 이 스크립트는 data-sap-ui-* 속성을 읽어 라이브러리, 테마, 리소스 루트, 비동기 로딩 여부를 결정합니다. CDN을 사용하면 이 모든 리소스가 SAP 운영 인프라에서 내려오고, 로컬을 사용하면 resources/ 경로 아래의 정적 파일에서 내려옵니다.

CDN에는 두 가지 채널이 있습니다. ui5.sap.com(또는 구 sapui5.hana.ondemand.com)은 SAPUI5 상용 배포본을, openui5.hana.ondemand.com은 OpenUI5 오픈소스 배포본을 제공합니다. 라이선스 관점에서 사내 사용 시 SAPUI5는 SAP 제품 라이선스 범위 내에서만 허용되므로, 외부 공개 사이트에는 OpenUI5 CDN을 쓰는 것이 일반적으로 권장됩니다.

로컬 호스팅의 핵심 도구는 @ui5/cli입니다. ui5.yaml 파일이 프로젝트의 의존성 트리와 빌드 변환기(preload, babel, theme-build)를 선언합니다. 빌드 단계에서는 Component-preload.js가 생성되어 수백 개의 작은 파일 요청을 하나의 번들로 묶고, 캐시 버스팅을 위한 sap-ui-cachebuster-info.json이 함께 만들어집니다.

캐싱 동작도 두 방식에서 다르게 작동합니다. CDN은 일반적으로 Cache-Control: max-age를 길게 설정하지만, 사용자가 다른 앱에서 같은 버전을 이미 캐시한 상태라면 cross-origin 캐시 격리(HTTP cache partitioning) 때문에 재사용이 안 될 수 있습니다. 로컬 호스팅은 cachebuster 토큰을 URL에 끼워 무한 캐시(immutable)를 안전하게 적용할 수 있다는 장점이 있습니다.

1단계 — CDN 부트스트랩 기본 골격

가장 단순한 BTP 앱은 index.html에서 OpenUI5 CDN을 직접 가리킵니다. 아래 예제는 영업 대시보드용 더미 앱 com.acme.salesdash의 진입점입니다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Sales Dashboard</title>
  <script
    id="sap-ui-bootstrap"
    src="https://openui5.hana.ondemand.com/1.120.10/resources/sap-ui-core.js"
    data-sap-ui-theme="sap_horizon"
    data-sap-ui-libs="sap.m,sap.ui.layout"
    data-sap-ui-resourceroots='{"com.acme.salesdash": "./"}'
    data-sap-ui-onInit="module:com/acme/salesdash/index"
    data-sap-ui-compatVersion="edge"
    data-sap-ui-async="true">
  </script>
</head>
<body class="sapUiBody" id="content"></body>
</html>

여기서 가장 중요한 부분은 URL에 박힌 1.120.10입니다. latest 같은 별칭을 쓰면 어느 날 갑자기 마이너 업그레이드가 적용돼 회귀(regression)가 발생할 수 있습니다. 일반적으로 패치 버전까지 못박는 방식이 권장됩니다.

2단계 — 로컬 호스팅과 ui5.yaml 의존성 구성

On-Premise 격리망 또는 보안 정책상 외부 도메인을 차단해야 하는 환경에서는 npm으로 UI5 런타임을 끌어와 자체 서버에서 서빙합니다. 다음은 package.jsonui5.yaml의 발췌 예시입니다.

{
  "name": "com.acme.plantops",
  "version": "1.0.0",
  "scripts": {
    "start": "ui5 serve --port 8080 --open index.html",
    "build": "ui5 build --clean-dest --include-task=generateManifestBundle"
  },
  "devDependencies": {
    "@ui5/cli": "^3.9.2",
    "@sapui5/distribution-metadata": "1.108.30"
  }
}
specVersion: "3.0"
metadata:
  name: com.acme.plantops
type: application
framework:
  name: SAPUI5
  version: "1.108.30"
  libraries:
    - name: sap.m
    - name: sap.ui.layout
    - name: sap.ui.unified
    - name: sap.ushell
      development: true
builder:
  resources:
    excludes:
      - "/test/**"
      - "/localService/**"
  customTasks:
    - name: ui5-tooling-transpile-task
      afterTask: replaceVersion
server:
  customMiddleware:
    - name: ui5-middleware-simpleproxy
      afterMiddleware: compression
      configuration:
        baseUri: "https://s4hana-dev.internal.acme.lan"

이 설정에서 framework.version이 빌드 시점에 SAP NPM 레지스트리에서 정확히 그 버전의 라이브러리만 끌어옵니다. ui5 build를 실행하면 dist/ 폴더에 Component-preload.js가 만들어지고, 사내 nginx 또는 NetWeaver의 BSP 컨테이너에 그대로 업로드해 정적 자원으로 배포할 수 있습니다.

3단계 — 프로덕션 등급 부트스트랩과 CSP 강화

실서비스 단계에서는 단순히 동작 여부를 넘어 성능, 보안, 관측성을 함께 챙겨야 합니다. 아래 예제는 BTP Approuter에서 CDN 사용 시 CSP 헤더를 강제하고, 무결성(integrity) 해시를 부트스트랩에 적용하는 패턴입니다.

{
  "welcomeFile": "/index.html",
  "responseHeaders": [
    {
      "name": "Content-Security-Policy",
      "value": "default-src 'self'; script-src 'self' https://openui5.hana.ondemand.com 'unsafe-eval'; style-src 'self' https://openui5.hana.ondemand.com 'unsafe-inline'; img-src 'self' data: https://openui5.hana.ondemand.com; connect-src 'self' https://*.hana.ondemand.com; font-src 'self' https://openui5.hana.ondemand.com data:;"
    },
    {
      "name": "Strict-Transport-Security",
      "value": "max-age=31536000; includeSubDomains"
    }
  ],
  "routes": [
    {
      "source": "^/odata/v4/(.*)$",
      "target": "/odata/v4/$1",
      "destination": "s4-backend",
      "authenticationType": "xsuaa",
      "csrfProtection": true
    },
    {
      "source": "^(.*)$",
      "localDir": "webapp",
      "cacheControl": "public, max-age=300"
    }
  ]
}

로컬 호스팅 시나리오의 프로덕션 부트스트랩에서는 cachebuster 토큰을 활용해 캐시를 적극적으로 사용합니다.

<script
  id="sap-ui-bootstrap"
  src="./resources/sap-ui-core.js"
  data-sap-ui-theme="sap_horizon"
  data-sap-ui-libs="sap.m,sap.ui.layout,sap.ui.unified"
  data-sap-ui-resourceroots='{"com.acme.plantops": "./"}'
  data-sap-ui-preload="async"
  data-sap-ui-async="true"
  data-sap-ui-frameOptions="trusted"
  data-sap-ui-xx-componentPreload="sync"
  data-sap-ui-xx-cache-use="true"
  data-sap-ui-xx-cache-excludedKeys=""
>
</script>

여기서 frameOptions="trusted"는 클릭재킹 방지를 위한 X-Frame-Options/CSP frame-ancestors와 짝을 이뤄야 하며, preload="async"는 라이브러리 preload 번들을 백그라운드에서 끌어옵니다. 빌드 산출물의 무결성 확인을 위해 CI 파이프라인에서 sha384 해시를 산출해 SBOM(Software Bill of Materials)에 기록해두면 운영 사고 시 추적이 쉬워집니다.

또한 운영 모니터링에서는 window.performance.getEntriesByType("resource")sap-ui-core.js 응답 시간을 수집해 SAP Cloud ALM 또는 사내 APM에 전송하는 패턴이 일반적으로 추천됩니다. CDN 장애가 났을 때 가장 먼저 보이는 신호이기 때문입니다.

자주 부딪치는 문제와 해결 단서

현장에서 반복적으로 보고되는 이슈와 대응 방향을 정리합니다.

  • 증상: CDN에서 404 또는 CORS 오류. 패치 버전이 더 이상 호스팅되지 않거나, 사내 프록시가 *.hana.ondemand.com을 차단했을 가능성이 큽니다. 운영 환경에 배포하기 전 네트워크 팀과 도메인 화이트리스트를 협의하는 것이 안전합니다.
  • 증상: 로컬 빌드 후 컨트롤이 늦게 뜬다. Component-preload.js가 생성되지 않았을 가능성이 있습니다. ui5 build --all 또는 --include-task=generateComponentPreload로 강제하고, 브라우저 Network 탭에서 preload 번들 1개로 합쳐졌는지 확인하세요.
  • 증상: CSP 위반으로 부트스트랩이 멈춤. UI5 코어는 일부 동적 평가(eval)를 사용하므로 script-src'unsafe-eval'이 필요한 경우가 있습니다. 가능하면 nonce 기반 정책으로 좁히되, 라이브러리 호환성을 먼저 점검하세요.

FAQ 형태로 보면 다음과 같습니다. 첫째, "CDN과 로컬을 동시에 쓸 수 있나?" 가능하지만 권장되지 않습니다. 리소스 루트가 두 도메인으로 분기되면 동일 라이브러리가 중복 로드돼 메모리와 캐시가 낭비됩니다. 둘째, "버전을 어떻게 고정하나?" CDN은 URL의 패치 버전을, 로컬은 ui5.yamlframework.versionpackage-lock.json을 함께 못박는 방식이 일반적으로 안전합니다. 셋째, "OpenUI5 CDN과 SAPUI5 CDN을 섞어도 되나?" 라이브러리 시그니처는 호환되지만 동일 페이지에서 섞으면 모듈 경로 충돌이 발생합니다. 한 앱에는 한 채널만 쓰는 것이 권장됩니다.

이어서 살펴볼만한 주제

이 글의 비교를 더 깊이 확장하려면 다음 주제를 차례로 살펴보면 좋습니다. SAP Build Work Zone에서의 Standalone Approuter 통합, ui5-tooling-modules를 활용한 외부 npm 라이브러리 번들링, cds-plugin-ui5를 통한 CAP 프로젝트와의 통합 빌드, 그리고 SAP Cloud ALM에서 UI5 클라이언트 RUM(Real User Monitoring)을 수집하는 파이프라인입니다. 추가로 OpenUI5 1.130 이후의 ESM(ECMAScript Modules) 지원 로드맵이 부트스트랩 방식을 어떻게 단순화하는지 추적하면 향후 마이그레이션 비용을 줄일 수 있습니다.

더 읽어볼 자료

댓글 0

아직 댓글이 없습니다.