1. 개요 및 핵심 포인트
SAPUI5 / OpenUI5 애플리케이션의 부트스트랩 단계는 사용자가 화면을 보기까지 걸리는 첫 번째 병목입니다. 동기(synchronous) manifest 로드 방식은 코드가 단순하지만 브라우저 메인 스레드를 차단하고, 점차 deprecated 처리되어 권장되지 않습니다. 비동기(asynchronous) manifest 로드와 Component.create(), 그리고 asyncHints를 통한 병렬 preload는 현대 UI5 앱의 표준 패턴으로 자리잡았어요.
이 글에서 다루는 핵심 포인트는 다음과 같아요.
- 동기
manifestFirst: true방식과 비동기manifest: true방식의 차이 sap.ui.core.Component.create()를 사용한 Promise 기반 부트스트랩manifest.json의sap.ui5.dependencies선언과 자동 preload 메커니즘asyncHints를 활용한 라이브러리/컴포넌트 병렬 로딩- index.html 부트스트랩 옵션(
data-sap-ui-async,data-sap-ui-compatVersion) 최적화 - 에러 핸들링, 실패 시 fallback, FLP/standalone 환경별 차이
2. 사전에 알아두면 좋은 배경
이 글은 UI5 Component, manifest.json(component descriptor), Promise/async-await 같은 ES6 비동기 패턴, 그리고 모듈 시스템(sap.ui.define, sap.ui.require)에 대한 기초 이해를 전제로 해요. sap.ui.getCore().getConfiguration() 같은 글로벌 API가 점차 모듈화 API(예: sap/base/i18n/Localization)로 교체되고 있다는 흐름도 함께 알아두면 코드 의도를 읽기 편해요. 라우팅, 모델 바인딩, OData V2/V4 manifest 선언 정도는 익숙해야 실습 예제가 자연스럽게 와 닿아요.
3. 실습 환경 및 버전
다음 환경 기준으로 코드를 작성했어요. SAPUI5 1.96 LTS 이후 버전이면 대부분 동일하게 동작하지만, deprecated 경고 메시지는 1.120 이후에서 더 강하게 출력돼요.
- SAPUI5: 1.120.x LTS (BTP Build Code / WebIDE 기본값)
- OpenUI5도 동일하게 적용 가능 (1.120 이상 권장)
- 런타임: Modern Chromium 기반 브라우저, Node.js 18+ (개발 도구용)
- 개발 도구:
@ui5/cli3.x (ui5 serve), Visual Studio Code - BTP: Cloud Foundry 또는 Kyma 런타임, HTML5 Application Repository
- FLP: Launchpad Service 또는 sandbox(
test-resources/sap/ushell/bootstrap/sandbox.js)
버전에 따라jQuery.sap.*계열 호환 API가 제거되었을 수 있어요. 새 프로젝트라면sap.ui.core.Component.create()와 모듈화된 API만 사용하는 것을 권장해요.
4. 핵심 개념과 동작 원리
4.1 동기 vs 비동기 manifest 로드
전통적인 UI5 부트스트랩은 Component 클래스를 먼저 로드한 뒤 metadata.manifest = "json" 설정으로 manifest를 읽어왔어요. 이 방식은 종종 XMLHttpRequest 동기 호출을 유발해 메인 스레드를 차단해요. 반면 manifest: true 또는 manifest: "<url>" 형태로 Component.create()에 전달하면, manifest를 먼저 비동기로 읽고 그 안의 dependencies를 분석해 필요한 라이브러리를 병렬로 preload한 뒤 Component 클래스를 로드해요. 결과적으로 워터폴(waterfall) 형태의 순차 로딩이 fan-out 형태의 병렬 로딩으로 바뀌어요.
4.2 Component.create()의 Promise 계약
sap.ui.component()(legacy factory)는 동기/비동기 동작이 옵션에 따라 갈리는 모호한 API였어요. Component.create()는 항상 Promise를 반환하고, 옵션 객체에 name, manifest, asyncHints, componentData, id 등을 전달하면 됩니다. 반환된 Promise는 Component 인스턴스로 resolve되거나, manifest 다운로드/dependency 실패 시 reject돼요. 따라서 .then(...).catch(...) 또는 try/await/catch로 반드시 실패 경로를 처리해야 해요.
4.3 asyncHints의 역할
asyncHints는 manifest만으로 추론하기 어려운 추가 의존성을 UI5 런타임에 미리 귀띔해주는 힌트예요. 비유하자면 manifest는 "이 컴포넌트가 무엇을 필요로 하는지에 대한 정식 계약서"이고, asyncHints는 "이왕이면 같이 준비해두면 좋은 보조 자재 리스트"에 가까워요. 주요 필드는 다음과 같아요.
libs: 미리 preload할 라이브러리 배열. 각 항목은{ name, url?, lazy? }components: 자식/형제 컴포넌트의 사전 preloadpreloadBundles: 별도 번들 파일(Component-preload.js) URLwaitFor: 다른 Promise를 기다린 뒤 컴포넌트 생성
도식으로 표현하면 다음과 같아요.
[index.html]
|
v
[sap-ui-core.js 로드] --- async ---> [manifest.json fetch]
|
+-------------+-------------+
v v v
[sap.m preload] [sap.f preload] [Component-preload.js]
\ | /
\ v /
+--> [Component 인스턴스 생성] --> [Router 초기화]
5. 실전 코드 (3단계)
5.1 1단계: 기본 비동기 부트스트랩
가장 단순한 형태입니다. index.html에서 data-sap-ui-async="true"로 모듈 로더를 비동기 모드로 켜고, Component를 Component.create()로 띄워요.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My Async App</title>
<script
id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-theme="sap_horizon"
data-sap-ui-libs="sap.m"
data-sap-ui-resourceroots='{ "my.app": "./" }'
data-sap-ui-async="true"
data-sap-ui-compatVersion="edge"
data-sap-ui-oninit="module:my/app/main"
></script>
</head>
<body class="sapUiBody" id="content"></body>
</html>
// webapp/main.js
sap.ui.define([
"sap/ui/core/Component",
"sap/ui/core/ComponentContainer"
], function (Component, ComponentContainer) {
"use strict";
Component.create({
name: "my.app",
manifest: true,
id: "myAppComponent"
}).then(function (oComponent) {
new ComponentContainer({
component: oComponent,
height: "100%",
async: true
}).placeAt("content");
});
});
manifest: true 한 줄이 핵심이에요. UI5 런타임이 my/app/manifest.json을 fetch한 뒤 그 안의 dependencies를 읽어 라이브러리를 병렬 로드해요.
5.2 2단계: 실무 시나리오 - asyncHints + 에러 핸들링 + 로깅
실제 프로젝트에서는 여러 커스텀 라이브러리, reuse 컴포넌트, OData 모델이 얽혀 있어요. asyncHints로 병렬 preload를 명시적으로 지정하고, 실패 시 사용자에게 안내 화면을 보여주는 패턴이에요.
sap.ui.define([
"sap/ui/core/Component",
"sap/ui/core/ComponentContainer",
"sap/base/Log",
"sap/m/MessageBox"
], function (Component, ComponentContainer, Log, MessageBox) {
"use strict";
Log.setLevel(Log.Level.INFO, "my.app.bootstrap");
Component.create({
name: "my.app",
manifest: true,
asyncHints: {
libs: [
{ name: "sap.m" },
{ name: "sap.f" },
{ name: "sap.ui.layout" },
{ name: "my.reuse.lib", url: "/resources/my/reuse/lib" }
],
components: [
{ name: "my.reuse.tile", url: "/components/tile" }
],
preloadBundles: [
"/resources/my/app/Component-preload.js"
],
waitFor: fetch("/api/bootstrap/config").then(function (r) { return r.json(); })
}
}).then(function (oComponent) {
Log.info("Component created: " + oComponent.getId(), null, "my.app.bootstrap");
new ComponentContainer({
component: oComponent,
height: "100%",
async: true,
propagateModel: true
}).placeAt("content");
}).catch(function (oError) {
Log.error("Component bootstrap failed", oError, "my.app.bootstrap");
MessageBox.error("애플리케이션을 시작할 수 없어요. 잠시 후 다시 시도해주세요.\n\n" + (oError && oError.message), {
title: "Startup Error"
});
});
});
대응되는 manifest.json의 dependencies 선언 예시예요.
{
"_version": "1.59.0",
"sap.app": {
"id": "my.app",
"type": "application",
"applicationVersion": { "version": "1.0.0" },
"dataSources": {
"mainService": {
"uri": "/odata/v4/Catalog/",
"type": "OData",
"settings": { "odataVersion": "4.0" }
}
}
},
"sap.ui5": {
"rootView": {
"viewName": "my.app.view.App",
"type": "XML",
"async": true,
"id": "app"
},
"dependencies": {
"minUI5Version": "1.120.0",
"libs": {
"sap.m": {},
"sap.f": {},
"sap.ui.layout": {}
},
"components": {
"my.reuse.tile": { "lazy": false }
}
},
"models": {
"": {
"dataSource": "mainService",
"settings": { "synchronizationMode": "None", "operationMode": "Server" }
}
},
"routing": { "config": { "routerClass": "sap.m.routing.Router", "async": true } }
}
}
5.3 3단계: 프로덕션 - FLP 호환, preload 번들, 성능 측정
FLP(Fiori Launchpad) 안에서 동작하는 컴포넌트는 직접 Component.create()를 호출하지 않고, FLP가 알아서 띄워줘요. 따라서 standalone 부트스트랩 코드와 FLP 경로를 모두 지원하도록 분리하는 게 좋아요. 또한 performance.mark로 부트스트랩 구간을 측정하면 회귀를 조기에 잡을 수 있어요.
sap.ui.define([
"sap/ui/core/Component",
"sap/ui/core/ComponentContainer",
"sap/base/Log"
], function (Component, ComponentContainer, Log) {
"use strict";
performance.mark("ui5-bootstrap-start");
function isInFLP() {
return typeof window !== "undefined" && !!window["sap-ushell-config"];
}
if (isInFLP()) {
Log.info("Running inside FLP, skip standalone bootstrap.");
return;
}
Component.create({
name: "my.app",
manifest: true,
asyncHints: {
libs: [
{ name: "sap.m" },
{ name: "sap.f" },
{ name: "sap.ui.layout" }
],
preloadBundles: [
"resources/my/app/Component-preload.js"
]
}
}).then(function (oComponent) {
new ComponentContainer({
component: oComponent,
height: "100%",
async: true
}).placeAt("content");
performance.mark("ui5-bootstrap-end");
performance.measure("ui5-bootstrap", "ui5-bootstrap-start", "ui5-bootstrap-end");
var aMeasures = performance.getEntriesByName("ui5-bootstrap");
if (aMeasures.length) {
Log.info("Bootstrap took " + Math.round(aMeasures[0].duration) + "ms");
}
}).catch(function (oError) {
Log.error("Bootstrap failed", oError);
document.getElementById("content").innerHTML =
"<div style='padding:2rem;font-family:sans-serif'>앱을 불러오지 못했어요. 새로고침 해주세요.</div>";
});
});
빌드 단계에서는 @ui5/cli의 ui5 build --all로 Component-preload.js 번들을 생성하고, manifest 내 sap.ui5.resources나 sap.platform.cf 설정으로 캐시 버스팅 전략을 함께 적용하는 것을 권장해요.
6. 실전 패턴
6.1 lazy 컴포넌트와 즉시 로드 분리
manifest의 components에서 lazy: true로 선언된 reuse 컴포넌트는 ComponentContainer가 처음 렌더링될 때 로드돼요. 진입 화면에서 곧바로 필요한 reuse는 asyncHints.components에 명시해 부트스트랩 시점에 병렬 로드하고, 거의 안 쓰는 reuse는 lazy로 두는 식의 이원화가 좋아요.
6.2 manifest를 인라인으로 주입
BTP HTML5 App Repository나 멀티 테넌트 환경에서는 manifest를 런타임에 패치해야 할 때가 있어요. 이때는 manifest: true 대신 객체 자체를 전달할 수 있어요.
fetch("/api/tenant/manifest").then(function (r) { return r.json(); })
.then(function (oManifest) {
return Component.create({ name: "my.app", manifest: oManifest });
});
6.3 라우터 async 옵션 일치시키기
비동기 부트스트랩을 했는데 라우터가 동기 모드라면 첫 라우트에서 동기 XHR이 발생할 수 있어요. sap.ui5.routing.config.async: true, 그리고 rootView.async: true를 함께 설정해 일관성을 유지해요.
7. 흔한 실수와 트러블슈팅
FAQ 1. Promise 체인을 빠뜨려서 race condition이 생겨요
Component.create()가 끝나기 전에 ComponentContainer를 동기로 만들어 붙이는 코드를 종종 볼 수 있어요. 이렇게 하면 일부 라이브러리가 아직 로드되지 않은 상태에서 컨트롤이 생성돼 "Control ... not found" 류 에러가 발생해요. 반드시 .then() 내부에서 ComponentContainer를 생성하거나, ComponentContainer의 async: true와 name/url을 사용해 컨테이너 스스로 비동기 로딩하도록 위임해요.
FAQ 2. asyncHints에 잘못된 URL을 넣어 404가 발생해요
asyncHints의 libs[].url은 라이브러리 루트 폴더를 가리켜야 해요. 예를 들어 my.reuse.lib의 url은 library-preload.js가 위치한 디렉터리(예: /resources/my/reuse/lib)여야지 파일 자체를 가리키면 안 돼요. 또한 BTP 환경에서는 App Router를 거치는 경로(/resources/...)인지 직접 경로인지에 따라 url이 달라져요. Network 탭에서 library-preload.js가 실제로 200으로 떨어지는지 확인하는 게 가장 빠른 디버깅이에요.
FAQ 3. manifest dependencies에 라이브러리를 적었는데도 preload가 안 돼요
manifest: true가 아닌 manifest: "manifest.json" 문자열로 넘기면 manifest는 읽지만 dependency 자동 처리는 제한적일 수 있어요. 또한 index.html의 data-sap-ui-libs에 동일 라이브러리를 또 적으면 동기 로드가 우선시되어 의도한 비동기 흐름이 깨질 수 있어요. data-sap-ui-libs는 최소한(sap.m 정도)으로 유지하고 나머지는 manifest와 asyncHints에 위임해요.
FAQ 4. "Modules must be loaded asynchronously" 경고가 떠요
이 경고는 어딘가에서 동기 sap.ui.requireSync나 jQuery.sap.require가 호출됐다는 신호예요. 컨트롤러나 fragment factory에서 동기 require를 쓰고 있지 않은지, 외부 라이브러리(예: 일부 OpenSAP 샘플)가 동기 패턴을 사용하지 않는지 검토해요. data-sap-ui-xx-nosync="warn" 옵션으로 동기 호출을 추적하면 좋아요.
8. 다음으로 확장해볼 주제 + 참고 자료
비동기 부트스트랩이 안정화되면, 다음 주제들로 자연스럽게 넓혀볼 수 있어요.
- UI5 Tooling의
ui5 build옵션과Component-preload.js최적화 - UI5 Flexibility / Adaptation Project와 manifest 확장(extends, customizing)
- BTP HTML5 App Repo / App Router에서 cache busting과 versioning
- OData V4 모델의
$batch,autoExpandSelect와 초기 로딩 튜닝 - FLP sandbox 환경에서
ushell설정과 cross-app navigation 디버깅 - Cards / Integration Cards에서의 manifest 비동기 패턴 응용
참고 자료
- SAPUI5 Documentation - Component (sap.ui.core.Component)
- SAPUI5 Documentation - Manifest (Descriptor for Applications, Components, and Libraries)
- SAPUI5 Documentation - Bootstrapping: Loading and Initializing
- SAPUI5 API Reference - Component.create()
- SAPUI5 Topic - Use Asynchronous Loading
- UI5 Tooling (@ui5/cli) Documentation
- OpenUI5 Project Home
본문 예시는 SAPUI5 1.120 LTS 기준이며, 실제 프로젝트의 minUI5Version과 BTP 런타임 정책에 맞춰 옵션 명칭/지원 여부를 다시 확인해주세요.
댓글 0
아직 댓글이 없습니다.