@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
527 lines (495 loc) • 20.4 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/**
* Defines support rules for the app configuration.
*/
sap.ui.define([
"jquery.sap.global",
"sap/ui/support/library",
"sap/ui/core/mvc/XMLView",
"sap/ui/core/Configuration"
], function(
jQuery,
SupportLib,
XMLView,
Configuration) {
"use strict";
// shortcuts
var Categories = SupportLib.Categories; // Accessibility, Performance, Memory, ...
var Severity = SupportLib.Severity; // Hint, Warning, Error
var Audiences = SupportLib.Audiences; // Control, Internal, Application
//**********************************************************
// Rule Definitions
//**********************************************************
/**
* Checks whether the preload configuration was set correctly to async
*/
var oPreloadAsyncCheck = {
id: "preloadAsyncCheck",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.58",
title: "Preload Configuration",
description: "Checks whether the preload configuration was set correctly to async",
resolution: "Please execute this rule to get a specific solution based on the application's preload mode configuration.",
resolutionurls: [
{
text: "Performance: Speed Up Your App",
href: "https://sdk.openui5.org/topic/408b40efed3c416681e1bd8cdd8910d4"
},
{
text: "Best Practices for Loading Modules Asynchronously",
href: "https://sdk.openui5.org/topic/00737d6c1b864dc3ab72ef56611491c4"
},
{
text: "Is Your Application Ready for Asynchronous Loading?",
href: "https://sdk.openui5.org/topic/493a15aa978d4fe9a67ea9407166eb01"
}
]
};
oPreloadAsyncCheck.check = function(oIssueManager, oCoreFacade) {
// Check for debug mode
var bIsDebug = Configuration.getDebug();
if (bIsDebug) {
return;
}
// Check for FLP scenario
var oUshellLib = sap.ui.getCore().getLoadedLibraries()["sap.ushell"];
if (oUshellLib) {
return;
}
var vPreloadMode = Configuration.getPreload(),
bLoaderIsAsync = sap.ui.loader.config().async;
var sDetails = "It is recommended to use the configuration option " +
"'data-sap-ui-async=\"true\"' instead of 'data-sap-ui-preload=\"async\"'. " +
"With this option single modules and preload files will be loaded asynchronously. " +
"Note: Enabling this behaviour requires intensive testing of the application.";
// "data-sap-ui-preload" attribute is set to async and could be replaced with "data-sap-ui-async" (recommended).
if (vPreloadMode === "async" && !bLoaderIsAsync) {
oPreloadAsyncCheck.resolution = "Please replace 'data-sap-ui-preload=\"async\"' with 'data-sap-ui-async=\"true\"' " +
"in the bootstrap script, as it implicitly sets the loading behaviour of preload files to be asynchronous.";
oIssueManager.addIssue({
severity: Severity.High,
details: sDetails,
context: {
id: "WEBPAGE"
}
});
// "data-sap-ui-preload" attribute is set to any value, but not async.
} else if (vPreloadMode !== "async" && !bLoaderIsAsync) {
oPreloadAsyncCheck.resolution = "Please configure 'data-sap-ui-async=\"true\"' in the bootstrap script, " +
"as it implicitly sets the loading behaviour of preload files to be asynchronous. " +
"In case you have already configured the 'data-sap-ui-preload' option, you should remove it.";
oIssueManager.addIssue({
severity: Severity.High,
details: sDetails,
context: {
id: "WEBPAGE"
}
});
}
};
/**
* Checks whether all requests for SAPUI5 repository resources contain a cache buster token
* It checks the requests under ICF node "/sap/bc/ui5_ui5/"
*/
var oCacheBusterToken = {
id: "cacheBusterToken",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.28",
title: "Application Resource Caching",
description: "Checks whether the application uses cache buster tokens in its requests for static resources from SAPUI5 repositories",
resolution: "Change the application\n" +
"Note: Not using cache buster tokens has a negative impact on performance.\n" +
"For more information, see the SAPUI5 developer guide.",
resolutionurls: [{
text: "Documentation: Cache Buster for SAPUI5 Application Resources",
href: "https://sdk.openui5.org/topic/4cfe7eff3001447a9d4b0abeaba95166"
}],
check: function(oIssueManager, oCoreFacade, oScope) {
var sUI5ICFNode = "/sap/bc/ui5_ui5/";
var aAppNames = [];
var sAppName;
var aRequests = jQuery.sap.measure.getRequestTimings();
for (var i = 0; i < aRequests.length; i++) {
var sUrl = aRequests[i].name;
//We limit the check to requests under ICF node "/sap/bc/ui5_ui5/", only these are relevant here
if (sUrl.indexOf(sUI5ICFNode) > 0) {
if (!sUrl.match(/\/~[A-Z0-9]*~/g)) {
if (sUrl.indexOf("/sap-ui-cachebuster/sap-ui-core.js") < 0 && sUrl.indexOf("sap-ui-cachebuster-info.json") < 0) {
var aSegments = sUrl.split(sUI5ICFNode);
aSegments = aSegments[1].split("/");
sAppName = aSegments[0] === "sap" ? aSegments[1] : "/" + aSegments[0] + "/" + aSegments[1];
if (aAppNames.indexOf(sAppName) < 0) {
aAppNames.push(sAppName);
}
}
}
}
}
for (var i = 0; i < aAppNames.length; i++) {
sAppName = aAppNames[i];
var sICFPath = sUI5ICFNode + (sAppName.charAt(0) === "/" ? sAppName.substr(1) : "sap/" + sAppName);
oIssueManager.addIssue({
severity: Severity.Medium,
details: "Application '" + sAppName + "' has no cache buster tokens in some or all of its requests.\n " +
"For more information about the URLs affected under application '" + sAppName + "' please check the network trace for URLs starting with '" + sICFPath + "'",
context: {
id: "WEBPAGE"
}
});
}
}
};
var oLibraryUsage = {
id: "libraryUsage",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.34",
title: "Library Usage",
description: "Checks whether there are unused loaded libraries. This rule only works on global execution scope.",
resolution: "Adapt your application descriptor and your application coding to improve the performance",
resolutionurls: [{
text: 'Documentation: Descriptor Dependencies to Libraries and Components',
href: 'https://sdk.openui5.org/topic/8521ad1955f340f9a6207d615c88d7fd'
}],
check: function(oIssueManager, oCoreFacade, oScope) {
if (oScope.getType() === "global") {
//1. Ignore libraries with instantiated elements
var mLibraries = sap.ui.getCore().getLoadedLibraries();
oScope.getElements().forEach(function(oElement) {
var sElementLib = oElement.getMetadata().getLibraryName();
if (mLibraries[sElementLib]) {
delete mLibraries[sElementLib];
}
});
// 2. Ignore libraries with declared modules
// Alternative: More exact, but request-dependent solution would be loading and evaluating the resources.json file for each library
// support rules can get loaded within a ui5 version which does not have module "sap/base/util/LoaderExtensions" yet
// therefore load the jQuery.sap.getAllDeclaredModules fallback if not available
var LoaderExtensions = sap.ui.require("sap/base/util/LoaderExtensions");
var aDeclaredModules;
if (LoaderExtensions) {
aDeclaredModules = LoaderExtensions.getAllRequiredModules();
} else {
// TODO: migration not possible. jQuery.sap.getAllDeclaredModules is deprecated.
aDeclaredModules = jQuery.sap.getAllDeclaredModules();
}
Object.keys(mLibraries).forEach(function(sLibrary) {
var sLibraryWithDot = sLibrary + ".";
for (var i = 0; i < aDeclaredModules.length; i++) {
// Ignore library types and library enum files
var sDeclaredModule = aDeclaredModules[i];
if (sDeclaredModule.indexOf(sLibraryWithDot) === 0 &&
mLibraries[sLibrary].types.indexOf(sDeclaredModule) === -1 &&
sDeclaredModule.lastIndexOf(".library") !== sDeclaredModule.length - ".library".length &&
sDeclaredModule.lastIndexOf(".library-preload") !== sDeclaredModule.length - ".library-preload".length &&
sDeclaredModule.lastIndexOf(".flexibility") !== sDeclaredModule.length - ".flexibility".length &&
sDeclaredModule.lastIndexOf(".support") !== sDeclaredModule.length - ".support".length) {
delete mLibraries[sLibrary];
break;
}
}
});
// 3. Remove unused library dependent unused libraries
var aUnusedLibrary = Object.keys(mLibraries);
Object.keys(mLibraries).forEach(function(sLibrary) {
mLibraries[sLibrary].dependencies.forEach(function(oDependency) {
var iIndex = aUnusedLibrary.indexOf(oDependency);
if (iIndex > -1) {
aUnusedLibrary.splice(iIndex, 1);
}
});
});
aUnusedLibrary.forEach(function(sUnusedLibrary) {
// There are apps which use modules with default lib (empty string)
if (sUnusedLibrary){
oIssueManager.addIssue({
severity: Severity.Medium,
details: "The library '" + sUnusedLibrary + "' has been loaded, but not used so far in the analyzed scope of the application. There are two options to solve this issue: \n" +
"1. If the library is needed at later state in your application, you can make use of lazy library loading (see resolution section)." +
" Please be aware that if this lazy flag isn't used correctly this might lead to a performance decrease. \n" +
"2. If the library has been loaded by accident and is never used in the application, you should remove the library from the bootstrap or application descriptor.",
context: {
id: "WEBPAGE"
}
});
}
});
}
}
};
var oLazyComponents = {
id: "lazyComponents",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.48",
title: "Lazy loading of components",
description: "Checks whether lazy loading of components is used",
resolution: "Adapt your application descriptor and your application coding to improve the performance",
resolutionurls: [{
text: 'Documentation: Descriptor Dependencies to Libraries and Components',
href: 'https://sdk.openui5.org/topic/8521ad1955f340f9a6207d615c88d7fd'
}],
check: function(oIssueManager, oCoreFacade, oScope) {
var mComponents = oCoreFacade.getComponents();
var mComponentReuseUsage = {};
var bComponentLazyKnown = false;
Object.keys(mComponents).forEach(function(sComponentId) {
var oManifest = mComponents[sComponentId].getManifest();
if (oManifest && oManifest['sap.ui5'] && oManifest['sap.ui5'].dependencies) {
var mComps = oManifest['sap.ui5'].dependencies.components;
if (mComps && Object.keys(mComps).length > 0) {
mComponentReuseUsage[sComponentId] = true;
Object.keys(mComps).forEach(function(sComp) {
if (mComps[sComp].lazy !== undefined) {
bComponentLazyKnown = true;
}
});
}
}
});
if (Object.keys(mComponentReuseUsage).length > 0 && !bComponentLazyKnown) {
Object.keys(mComponentReuseUsage).forEach(function(sComponent) {
oIssueManager.addIssue({
severity: Severity.Medium,
details: "No lazy Component loading detected. Define lazy components in your application descriptor, if this feature can be used in the application.",
context: {
id: sComponent
}
});
});
}
}
};
var oReuseComponents = {
id: "reuseComponents",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.48",
title: "Components reusage via componentUsages",
description: "Components are more performant and flexible, if defined via componentUsages",
resolution: "Adapt your application descriptor and your application coding to improve the performance",
resolutionurls: [{
text: 'Documentation: Using and Nesting Components',
href: 'https://sdk.openui5.org/topic/346599f0890d4dfaaa11c6b4ffa96312'
}],
check: function(oIssueManager, oCoreFacade, oScope) {
var mComponents = oCoreFacade.getComponents();
var mComponentUsage = {};
var bComponentUsagesUsed = false;
Object.keys(mComponents).forEach(function(sComponentId) {
var oManifest = mComponents[sComponentId].getManifest();
var oManifestSapUi5 = oManifest['sap.ui5'];
// Check usage of old way of defining Components
if (oManifestSapUi5 && oManifestSapUi5.dependencies &&
oManifestSapUi5.dependencies['components'] &&
Object.keys(oManifestSapUi5.dependencies['components']).length > 0) {
mComponentUsage[sComponentId] = true;
}
// Check usage of new way of defining Components
if (oManifestSapUi5 && oManifestSapUi5.componentUsages !== undefined) {
bComponentUsagesUsed = true;
}
});
if (Object.keys(mComponentUsage).length > 0 && !bComponentUsagesUsed) {
Object.keys(mComponentUsage).forEach(function(sComponentId) {
oIssueManager.addIssue({
severity: Severity.Medium,
details: "There are defined reuse components in the application descriptor. Please check the documentation," +
" whether you can define your components via componentUsage.",
context: {
id: sComponentId
}
});
});
}
}
};
var oModelPreloading = {
id: "modelPreloading",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.38",
title: "Model preloading",
description: "Preloaded models, which load their data from external locations, can load data earlier",
resolution: "Adapt your application descriptor and your application coding to improve the performance",
resolutionurls: [{
text: 'Documentation: Manifest Model Preload',
href: 'https://sdk.openui5.org/topic/26ba6a5c1e5c417f8b21cce1411dba2c'
}],
check: function(oIssueManager, oCoreFacade, oScope) {
var mComponents = oCoreFacade.getComponents();
var mComponentsWithRelevantModels = {};
var bModelPreloadKnown = false;
Object.keys(mComponents).forEach(function(sComponentId) {
var oManifest = mComponents[sComponentId].getManifest();
var mModels = oManifest['sap.ui5'].models || {};
var mDataSources = oManifest['sap.app'].dataSources;
Object.keys(mModels).forEach(function(sModel) {
var mModel = mModels[sModel];
var mDataSource;
if (mModel.dataSource) {
mDataSource = mDataSources[mModel.dataSource];
}
if (mModel.type === "sap.ui.model.odata.v2.ODataModel"
|| mModel.type === "sap.ui.model.odata.v4.ODataModel"
|| mDataSource && mDataSource.type === "OData") {
mComponentsWithRelevantModels[sComponentId] = true;
if (mModel.preload !== undefined) {
bModelPreloadKnown = true;
}
}
});
});
if (!bModelPreloadKnown) {
Object.keys(mComponentsWithRelevantModels).forEach(function(sComponentId) {
oIssueManager.addIssue({
severity: Severity.High,
details: "The used OData models don't make use of the preloading feature.",
context: {
id: sComponentId
}
});
});
}
}
};
var oModelPreloadAndEarlyRequests = {
id: "modelPreloadAndEarlyRequests",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.53",
title: "OData V4 model preloading and no earlyRequests",
description: "Manifest model preload is useless if V4 ODataModel earlyRequests is false",
resolution: "Set manifest parameter models[<Model Name>].settings.earlyRequests to true",
resolutionurls: [{
text: 'Documentation: Manifest Model Preload',
href: 'https://sdk.openui5.org/topic/26ba6a5c1e5c417f8b21cce1411dba2c'
}, {
text: 'API: V4 ODataModel, parameter earlyRequests',
href: 'https://sdk.openui5.org/api/sap.ui.model.odata.v4.ODataModel'
}],
check: function(oIssueManager, oCoreFacade, oScope) {
var mComponents = oCoreFacade.getComponents();
Object.keys(mComponents).forEach(function(sComponentId) {
var oManifest = mComponents[sComponentId].getManifest(),
mDataSources = oManifest['sap.app'].dataSources,
mModels = oManifest['sap.ui5'].models || {};
Object.keys(mModels).forEach(function(sModel) {
var mDataSource,
mModel = mModels[sModel];
if (mModel.dataSource) {
mDataSource = mDataSources[mModel.dataSource];
}
if (mModel.type === "sap.ui.model.odata.v4.ODataModel"
|| mDataSource && mDataSource.type === "OData" && mDataSource.settings
&& mDataSource.settings.odataVersion === "4.0") {
if (mModel.preload === true
&& !(mModel.settings && mModel.settings.earlyRequests === true)) {
oIssueManager.addIssue({
severity: Severity.High,
details: "Set sap.ui5.models['" + sModel + "'].settings" +
".earlyRequests in manifest to true",
context: {
id: sComponentId
}
});
}
}
});
});
}
};
var oAsynchronousXMLViews = {
id: "asynchronousXMLViews",
audiences: [Audiences.Application],
categories: [Categories.Performance],
enabled: true,
minversion: "1.34",
title: "Asynchronous XML views",
description: "Asynchronous XML views leads to smoother view transitions, doesn't block the UI and allows for more efficient SAPUI5 flexibility services",
resolution: "Adapt your application descriptor and your application coding to improve the performance and efficiency",
resolutionurls: [{
text: 'Documentation: Routing Configuration',
href: 'https://sdk.openui5.org/topic/902313063d6f45aeaa3388cc4c13c34e'
}, {
text: "Documentation: Instantiating Views",
href: "https://sdk.openui5.org/topic/68d0e58857a647d49470d9f92dd859bd"
}, {
text: "Documentation: UI Adaptation at Runtime: Enable Your App",
href: "https://sdk.openui5.org/topic/f1430c0337534d469da3a56307ff76af"
}],
check: function(oIssueManager, oCoreFacade, oScope) {
var mComponents = oCoreFacade.getComponents();
var mComponentsRoutingSync = {};
// 1. Collect XML views in analyzed scope
var aSyncXMLViews = oScope.getElementsByClassName(XMLView).filter(function(oXMLView) {
return oXMLView.oAsyncState === undefined && !oXMLView.isSubView();
});
Object.keys(mComponents).forEach(function(sComponentId) {
// 2. Check router instances and collect connected views (also other view types than XML)
var oRouter = mComponents[sComponentId].getRouter && mComponents[sComponentId].getRouter();
if (oRouter && oRouter._oConfig) {
if (oRouter._oConfig._async !== true) {
mComponentsRoutingSync[sComponentId] = [];
if (mComponents[sComponentId].getTargets() &&
mComponents[sComponentId].getTargets()._oViews &&
mComponents[sComponentId].getTargets()._oViews._oViews) {
var oTargetViews = mComponents[sComponentId].getTargets()._oViews._oViews;
Object.keys(oTargetViews).forEach(function(sViewId) {
var sViewName = oTargetViews[sViewId].getViewName().split("\.").pop();
mComponentsRoutingSync[sComponentId].push(sViewName);
aSyncXMLViews = aSyncXMLViews.filter(function(oXMLView) {
return oTargetViews[sViewId] !== oXMLView;
});
});
}
}
}
});
Object.keys(mComponentsRoutingSync).forEach(function(sComponentId) {
oIssueManager.addIssue({
severity: Severity.High,
details: "Routing between views (" + mComponentsRoutingSync[sComponentId].join(', ') + ") is used, but configured to be synchronous." +
" Please take a look at the resolution 'Routing Configuration'.",
context: {
id: sComponentId
}
});
});
aSyncXMLViews.forEach(function(oSyncView) {
var sSyncViewId = oSyncView.getId();
var sViewName = oSyncView.getViewName().split("\.").pop();
oIssueManager.addIssue({
severity: Severity.Medium,
details: "The XML view '" + sViewName + " is loaded synchronous. Please take a look at the resolution 'Instantiating Views'.",
context: {
id: sSyncViewId
}
});
});
}
};
return [
oPreloadAsyncCheck,
oCacheBusterToken,
oLibraryUsage,
oLazyComponents,
oReuseComponents,
oModelPreloading,
oModelPreloadAndEarlyRequests,
oAsynchronousXMLViews
];
}, true);