diff --git a/lib/builder/builder.js b/lib/builder/builder.js
index 8ea6caa99..d79e39c19 100644
--- a/lib/builder/builder.js
+++ b/lib/builder/builder.js
@@ -60,6 +60,11 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask
selectedTasks.generateCachebusterInfo = false;
selectedTasks.generateApiIndex = false;
+ // Disable generateResourcesJson due to performance.
+ // When executed it analyzes each module's AST and therefore
+ // takes up much time (~10% more)
+ selectedTasks.generateResourcesJson = false;
+
if (selfContained) {
// No preloads, bundle only
selectedTasks.generateComponentPreload = false;
diff --git a/lib/lbt/analyzer/JSModuleAnalyzer.js b/lib/lbt/analyzer/JSModuleAnalyzer.js
index a25694fe8..67821e3a2 100644
--- a/lib/lbt/analyzer/JSModuleAnalyzer.js
+++ b/lib/lbt/analyzer/JSModuleAnalyzer.js
@@ -239,6 +239,13 @@ function getDocumentation(node) {
* @private
*/
class JSModuleAnalyzer {
+ /**
+ * Analyzes the JS AST
+ *
+ * @param {object} ast js ast
+ * @param {string} defaultName default name
+ * @param {ModuleInfo} info module info
+ */
analyze(ast, defaultName, info) {
let mainModuleFound = false;
/**
@@ -260,6 +267,20 @@ class JSModuleAnalyzer {
*/
let nModuleDeclarations = 0;
+ /**
+ * Whether or not this is a UI5 module
+ *
+ * When in the non-conditional module execution there is a call to:
+ *
+ * - sap.ui.define call
+ * - jQuery.sap.declare call
+ *
+ * this value is true
+ *
+ * @type {boolean}
+ */
+ let bIsUi5Module = false;
+
// first analyze the whole AST...
visit(ast, false);
@@ -307,7 +328,7 @@ class JSModuleAnalyzer {
info.addImplicitDependency(UI5ClientConstants.MODULE__UI5LOADER_AUTOCONFIG);
}
- if ( nModuleDeclarations === 0 && info.dependencies.length === 0 && info.subModules.length === 0 ) {
+ if ( !bIsUi5Module ) {
// when there are no indicators for module APIs, mark the module as 'raw' module
info.rawModule = true;
}
@@ -320,7 +341,6 @@ class JSModuleAnalyzer {
// console.log(info.name, "exposed globals", info.exposedGlobals, "ignoredGlobals", info.ignoredGlobals);
}
- return;
// hoisted functions
function setMainModuleInfo(name, description) {
@@ -353,6 +373,7 @@ class JSModuleAnalyzer {
// recognized a call to jQuery.sap.declare()
nModuleDeclarations++;
info.setFormat(ModuleFormat.UI5_LEGACY);
+ bIsUi5Module = true;
onDeclare(node);
} else if ( !conditional &&
(isMethodCall(node, CALL_SAP_UI_DEFINE) || isMethodCall(node, CALL_AMD_DEFINE)) ) {
@@ -364,6 +385,7 @@ class JSModuleAnalyzer {
} else {
info.setFormat(ModuleFormat.AMD);
}
+ bIsUi5Module = true;
onDefine(node);
const args = node.arguments;
@@ -380,8 +402,24 @@ class JSModuleAnalyzer {
}
} else if ( isMethodCall(node, CALL_REQUIRE_PREDEFINE) || isMethodCall(node, CALL_SAP_UI_PREDEFINE) ) {
// recognized a call to require.predefine() or sap.ui.predefine()
+ if (!conditional) {
+ bIsUi5Module = true;
+ }
info.setFormat(ModuleFormat.UI5_DEFINE);
- onSapUiPredefine(node);
+ onSapUiPredefine(node, conditional);
+
+ const args = node.arguments;
+ let iArg = 0;
+ if ( iArg < args.length && isString(args[iArg]) ) {
+ iArg++;
+ }
+ if ( iArg < args.length && args[iArg].type == Syntax.ArrayExpression ) {
+ iArg++;
+ }
+ if ( iArg < args.length && isCallableExpression(args[iArg]) ) {
+ // unconditionally execute the factory function
+ visit(args[iArg].body, conditional);
+ }
} else if ( isMethodCall(node, CALL_SAP_UI_REQUIRE) || isMethodCall(node, CALL_AMD_REQUIRE) ) {
// recognized a call to require() or sap.ui.require()
if ( isMethodCall(node, CALL_SAP_UI_REQUIRE) ) {
@@ -404,6 +442,7 @@ class JSModuleAnalyzer {
} else if ( isMethodCall(node, CALL_REQUIRE_SYNC) || isMethodCall(node, CALL_SAP_UI_REQUIRE_SYNC) ) {
// recognizes a call to sap.ui.requireSync
info.setFormat(ModuleFormat.UI5_DEFINE);
+
onSapUiRequireSync(node, conditional);
} else if ( isMethodCall(node, CALL_JQUERY_SAP_REQUIRE) ) {
// recognizes a call to jQuery.sap.require
@@ -411,10 +450,16 @@ class JSModuleAnalyzer {
onJQuerySapRequire(node, conditional);
} else if ( isMethodCall(node, CALL_JQUERY_SAP_REGISTER_PRELOADED_MODULES) ) {
// recognizes a call to jQuery.sap.registerPreloadedModules
+ if (!conditional) {
+ bIsUi5Module = true;
+ }
info.setFormat(ModuleFormat.UI5_LEGACY);
onRegisterPreloadedModules(node, /* evoSyntax= */ false);
} else if ( isMethodCall(node, CALL_SAP_UI_REQUIRE_PRELOAD) ) {
// recognizes a call to sap.ui.require.preload
+ if (!conditional) {
+ bIsUi5Module = true;
+ }
info.setFormat(ModuleFormat.UI5_DEFINE);
onRegisterPreloadedModules(node, /* evoSyntax= */ true);
} else if ( isCallableExpression(node.callee) ) {
@@ -560,6 +605,7 @@ class JSModuleAnalyzer {
if ( i < nArgs ) {
if ( isString(args[i]) ) {
+ // sap.ui.requireSync does not support relative dependencies
const moduleName = ModuleName.fromRequireJSName( args[i].value );
info.addDependency(moduleName, conditional);
} else {
@@ -569,7 +615,7 @@ class JSModuleAnalyzer {
}
}
- function onSapUiPredefine(predefineCall) {
+ function onSapUiPredefine(predefineCall, conditional) {
const args = predefineCall.arguments;
const nArgs = args.length;
let i = 0;
@@ -578,6 +624,16 @@ class JSModuleAnalyzer {
if ( i < nArgs && isString(args[i]) ) {
const moduleName = ModuleName.fromRequireJSName( args[i++].value );
info.addSubModule(moduleName);
+
+ // add dependencies
+ // to correctly identify dependencies e.g. of a library-preload
+ const elementArg = args[i++];
+ if (elementArg && elementArg.type === Syntax.ArrayExpression) {
+ elementArg.elements.forEach((element) => {
+ const dependencyName = ModuleName.resolveRelativeRequireJSName(moduleName, element.value);
+ info.addDependency(dependencyName, conditional);
+ });
+ }
} else {
log.warn("sap.ui.predefine call is missing a module name (ignored)");
}
diff --git a/lib/lbt/bundle/Builder.js b/lib/lbt/bundle/Builder.js
index 7b6686d55..6892d3eb2 100644
--- a/lib/lbt/bundle/Builder.js
+++ b/lib/lbt/bundle/Builder.js
@@ -20,7 +20,7 @@ const {SectionType} = require("./BundleDefinition");
const BundleWriter = require("./BundleWriter");
const log = require("@ui5/logger").getLogger("lbt:bundle:Builder");
-const copyrightCommentsPattern = /copyright|\(c\)(?:[0-9]+|\s+[0-9A-za-z])|released under|license|\u00a9/i;
+const copyrightCommentsPattern = /copyright|\(c\)(?:[0-9]+|\s+[0-9A-za-z])|released under|license|\u00a9|^@ui5-bundle-raw-include |^@ui5-bundle /i;
const xmlHtmlPrePattern = /<(?:\w+:)?pre>/;
const strReplacements = {
diff --git a/lib/lbt/resources/LocatorResource.js b/lib/lbt/resources/LocatorResource.js
new file mode 100644
index 000000000..0724e6698
--- /dev/null
+++ b/lib/lbt/resources/LocatorResource.js
@@ -0,0 +1,24 @@
+const Resource = require("./Resource");
+
+
+function extractName(path) {
+ return path.slice( "/resources/".length);
+}
+
+
+class LocatorResource extends Resource {
+ constructor(pool, resource) {
+ super(pool, extractName(resource.getPath()), null, resource.getStatInfo());
+ this.resource = resource;
+ }
+
+ buffer() {
+ return this.resource.getBuffer();
+ }
+
+ getProject() {
+ return this.resource._project;
+ }
+}
+
+module.exports = LocatorResource;
diff --git a/lib/lbt/resources/LocatorResourcePool.js b/lib/lbt/resources/LocatorResourcePool.js
new file mode 100644
index 000000000..c617bac4b
--- /dev/null
+++ b/lib/lbt/resources/LocatorResourcePool.js
@@ -0,0 +1,20 @@
+const ResourcePool = require("./ResourcePool");
+const LocatorResource = require("./LocatorResource");
+
+
+class LocatorResourcePool extends ResourcePool {
+ constructor() {
+ super();
+ }
+
+ prepare(resources) {
+ resources = resources.filter( (res) => !res.getStatInfo().isDirectory() );
+ return Promise.all(
+ resources.map(
+ (resource) => this.addResource( new LocatorResource(this, resource) )
+ ).filter(Boolean)
+ );
+ }
+}
+
+module.exports = LocatorResourcePool;
diff --git a/lib/lbt/resources/ModuleInfo.js b/lib/lbt/resources/ModuleInfo.js
index bcdb3011e..1e0a2b8b2 100644
--- a/lib/lbt/resources/ModuleInfo.js
+++ b/lib/lbt/resources/ModuleInfo.js
@@ -58,6 +58,12 @@ class ModuleInfo {
/**
* 'raw' modules are modules that don't use UI5's module system (require/declare)
* TODO align with module format (ui5, amd, es6, raw)
+ *
+ * A raw module is a module which does not have in its non-conditional execution:
+ *
+ * - sap.ui.define call
+ * - jQuery.sap.declare call
+ *
*/
this.rawModule = false;
diff --git a/lib/lbt/resources/ResourceCollector.js b/lib/lbt/resources/ResourceCollector.js
new file mode 100644
index 000000000..f9cf97f5b
--- /dev/null
+++ b/lib/lbt/resources/ResourceCollector.js
@@ -0,0 +1,312 @@
+const ResourceInfoList = require("./ResourceInfoList");
+const ResourceFilterList = require("./ResourceFilterList");
+const ResourceInfo = require("./ResourceInfo");
+
+
+const LOCALE = /^((?:[^/]+\/)*[^/]+?)_([A-Z]{2}(?:_[A-Z]{2}(?:_[A-Z0-9_]+)?)?)(\.properties|\.hdbtextbundle)$/i;
+const THEME = /^((?:[^/]+\/)*)themes\/([^/]+)\//;
+
+const log = require("@ui5/logger").getLogger("lbt:resources:ResourceCollector");
+
+/**
+ * Collects all resources in a set of resource folders or from an archive
+ * and writes a 'resources.json' file for each component or library
+ * that can be found in the resources.
+ *
+ * @since 1.29.0
+ */
+class ResourceCollector {
+ /**
+ * Collects a set of ResourceInfo objects and groups them by components, libraries and themes.
+ *
+ * @param {ResourcePool} pool
+ * @param {ResourceFilterList} [filter] used to filter the resources based on their name
+ */
+ constructor(pool, filter) {
+ this._pool = pool;
+ /**
+ * Global filters that should be taken into account when collecting resources.
+ *
+ * @type {ResourceFilterList}
+ * @private
+ */
+ this._filter = filter != null ? filter : new ResourceFilterList();
+ /**
+ * name to resource info
+ *
+ * @type {Map}
+ */
+ this._resources = new Map();
+
+ /**
+ * prefix to ResourceInfoList
+ *
+ * @type {Map}
+ * @private
+ */
+ this._components = new Map();
+
+ /**
+ * prefix to ResourceInfoList
+ *
+ * @type {Map}
+ * @private
+ */
+ this._themePackages = new Map();
+ }
+
+ /**
+ * Comma separated list of components to which orphaned resources should be added.
+ *
+ * A component and a separated list of resource patterns of orphans that should be added
+ * to the preceding component.
+ * If no such list is given, any orphaned resource will be added to the component.
+ * The evaluation logic for the filter list is the same as for the filters
+ * parameters: excludes can be denoted with a leading '-' or '!' and order is significant.
+ * Later filters can override the result of earlier ones.
+ *
+ * If no component is given, orphans will only be reported but not added to any component (default).
+ *
+ * @param {object} list component to list of components
+ */
+ setExternalResources(list) {
+ this._externalResources = list;
+ }
+
+ /**
+ * Processes a resource
+ *
+ * @param {module:@ui5/fs.Resource} resource
+ */
+ async visitResource(resource) {
+ const virPath = resource.getPath();
+ if ( !virPath.startsWith("/resources/") ) {
+ log.warn(`non-runtime resource ${virPath} ignored`);
+ return;
+ }
+ const name = virPath.slice("/resources/".length);
+ if ( this._filter.matches(name) ) {
+ const resourceInfo = new ResourceInfo(name);
+ resourceInfo.size = await resource.getSize();
+ this._resources.set(name, resourceInfo);
+
+ const p = name.lastIndexOf("/");
+ const prefix = name.substring(0, p + 1);
+ const basename = name.substring(p + 1);
+ if ( basename.match("[^/]*\\.library|Component\\.js|manifest\\.json") && !this._components.has(prefix) ) {
+ this._components.set(prefix, new ResourceInfoList(prefix));
+ }
+ // a .theme file within a theme folder indicates a library/theme package
+ // Note: ignores .theme files in library folders
+
+ // .theming files are not always present therefore this check is relevant for the library.source.less
+ if ( name.match("(?:[^/]+/)*themes/[^/]+/(?:\\.theming|library\\.source\\.less)") && !this._themePackages.has(prefix) ) {
+ // log.info("found new theme package %s", prefix);
+ this._themePackages.set(prefix, new ResourceInfoList(prefix));
+ }
+ }
+ }
+
+ async enrichWithDependencyInfo(resourceInfo) {
+ return this._pool.getModuleInfo(resourceInfo.name).then((moduleInfo) => {
+ if ( moduleInfo.name ) {
+ resourceInfo.module = moduleInfo.name;
+ }
+ if ( moduleInfo.dynamicDependencies ) {
+ resourceInfo.dynRequired = true;
+ }
+ if ( moduleInfo.dependencies.length > 0 ) {
+ resourceInfo.required = resourceInfo.required || new Set();
+ resourceInfo.condRequired = resourceInfo.condRequired || new Set();
+ moduleInfo.dependencies.forEach((dep) => {
+ if ( moduleInfo.isConditionalDependency(dep) ) {
+ resourceInfo.condRequired.add(dep);
+ } else if ( !moduleInfo.isImplicitDependency(dep) ) {
+ resourceInfo.required.add(dep);
+ }
+ });
+ }
+ if ( moduleInfo.subModules.length > 0 ) {
+ resourceInfo.included = resourceInfo.included || new Set();
+ moduleInfo.subModules.forEach((mod) => {
+ resourceInfo.included.add(mod);
+ });
+ }
+
+ if (moduleInfo.requiresTopLevelScope) {
+ resourceInfo.requiresTopLevelScope = true;
+ }
+ if (moduleInfo.exposedGlobals != null && moduleInfo.exposedGlobals.length) {
+ resourceInfo.exposedGlobalNames = resourceInfo.exposedGlobalNames || new Set();
+ moduleInfo.exposedGlobals.forEach((exposedGlobalName) => {
+ resourceInfo.exposedGlobalNames.add(exposedGlobalName);
+ });
+ }
+
+ if (moduleInfo.rawModule) {
+ resourceInfo.format = "raw";
+ }
+ });
+ }
+
+ async determineResourceDetails({pool, debugResources, mergedResources, designtimeResources, supportResources}) {
+ const baseNames = new Set();
+ const debugFilter = new ResourceFilterList(debugResources);
+ const mergeFilter = new ResourceFilterList(mergedResources);
+ const designtimeFilter = new ResourceFilterList(designtimeResources);
+ const supportFilter = new ResourceFilterList(supportResources);
+
+ const promises = [];
+
+ for (const [name, info] of this._resources.entries()) {
+ // log.verbose(` checking ${name}`);
+ let m;
+ if ( m = LOCALE.exec(name) ) {
+ const baseName = m[1] + m[3];
+ log.verbose(` found potential i18n resource '${name}', base name is '${baseName}', locale is ${m[2]}`);
+ info.i18nName = baseName; // e.g. "i18n.properties"
+ info.i18nLocale = m[2]; // e.g. "de"
+ baseNames.add(baseName);
+ }
+
+ if ( m = THEME.exec(name) ) {
+ // log.verbose("found theme candidate %s with prefix %s", name, m[1]);
+ if ( this._themePackages.has(m[0]) ) {
+ const theme = m[2];
+ info.theme = theme;
+ log.verbose(` found potential theme resource '${name}', theme ${theme}`);
+ }
+ }
+
+ if ( /(?:\.js|\.view\.xml|\.control\.xml|\.fragment\.xml)$/.test(name) ) {
+ promises.push(
+ this.enrichWithDependencyInfo(info)
+ );
+ }
+
+ // set the module name for .properties and .json
+ if ( /(?:\.properties|\.json)$/.test(name) ) {
+ promises.push(new Promise((resolve) => {
+ return this._pool.getModuleInfo(info.name).then((moduleInfo) => {
+ if (moduleInfo.name) {
+ info.module = moduleInfo.name;
+ }
+ resolve();
+ });
+ }));
+ }
+
+ if ( debugFilter.matches(name) ) {
+ info.isDebug = true;
+ log.verbose(` found potential debug resource '${name}'`);
+ }
+
+ if ( mergeFilter.matches(name) ) {
+ info.merged = true;
+ log.verbose(` found potential merged resource '${name}'`);
+ }
+
+ if ( designtimeFilter.matches(name) ) {
+ info.designtime = true;
+ log.verbose(` found potential designtime resource '${name}'`);
+ }
+
+ if ( supportFilter.matches(name) ) {
+ info.support = true;
+ log.verbose(` found potential support resource '${name}'`);
+ }
+ }
+
+ for (const baseName of baseNames) {
+ if ( this._resources.has(baseName) ) {
+ const info = this._resources.get(baseName);
+ info.i18nName = baseName;
+ info.i18nLocale = "";
+ }
+ }
+
+ return Promise.all(promises);
+ }
+
+ createOrphanFilters() {
+ log.verbose(
+ ` configured external resources filters (resources outside the namespace): ${this._externalResources == null ? "(none)" : this._externalResources}`);
+
+ const filtersByComponent = new Map();
+
+ if ( this._externalResources != null ) {
+ for ( let [component, filters] of Object.entries(this._externalResources) ) {
+ const packageFilters = new ResourceFilterList(filters);
+ if ( component === "/" || component === "" ) {
+ component = "";
+ } else if ( !component.endsWith("/") ) {
+ component += "/";
+ }
+ log.verbose(` resulting filter list for '${component}': '${packageFilters}'`);
+ filtersByComponent.set(component, packageFilters);
+ }
+ }
+ return filtersByComponent;
+ }
+
+ groupResourcesByComponents(options) {
+ const orphanFilters = this.createOrphanFilters();
+ const debugBundlesFilter = new ResourceFilterList(options.debugBundles);
+ for (const resource of this._resources.values()) {
+ let contained = false;
+ for (const [prefix, list] of this._components.entries()) {
+ const isDebugBundle = debugBundlesFilter.matches(resource.name);
+ if ( resource.name.startsWith(prefix) ) {
+ list.add(resource, !isDebugBundle);
+ contained = true;
+ } else if ( orphanFilters.has(prefix) ) {
+ // log.verbose(` checking '${resource.name}' against orphan filter '${orphanFilters.get(prefix)}' (${prefix})`);
+ if ( orphanFilters.get(prefix).matches(resource.name) ) {
+ list.add(resource, !isDebugBundle);
+ contained = true;
+ }
+ }
+ }
+
+ if ( resource.theme != null ) {
+ // assign theme resources additionally to theme packages
+ for (const [prefix, list] of this._themePackages.entries()) {
+ if ( resource.name.startsWith(prefix) ) {
+ list.add(resource);
+ contained = true;
+ }
+ }
+ }
+
+ if ( contained ) {
+ this._resources.delete(resource.name);
+ }
+ }
+ }
+
+ /**
+ *
+ * @returns {Set} resource names
+ */
+ get resources() {
+ return new Set(this._resources.keys());
+ }
+
+ /**
+ * Components
+ *
+ * @returns {Map}
+ */
+ get components() {
+ return this._components;
+ }
+
+ /**
+ * @returns {Map}
+ */
+ get themePackages() {
+ return this._themePackages;
+ }
+}
+
+module.exports = ResourceCollector;
diff --git a/lib/lbt/resources/ResourceFilterList.js b/lib/lbt/resources/ResourceFilterList.js
index 67e23f53e..e29301177 100644
--- a/lib/lbt/resources/ResourceFilterList.js
+++ b/lib/lbt/resources/ResourceFilterList.js
@@ -127,6 +127,32 @@ class ResourceFilterList {
}
}
+/**
+ * Each filter entry can be a comma separated list of simple filters. Each simple filter
+ * can be a pattern in resource name pattern syntax: A double asterisk '&0x2a;&0x2a;/' denotes an arbitrary
+ * number of resource name segments (folders) incl. a trailing slash, whereas a simple asterisk '*'
+ * denotes an arbitrary number of resource name characters, but not the segment separator '/'.
+ * A dot is interpreted as a dot, all other special regular expression characters keep their
+ * special meaning. This is a mixture of ANT-style path patterns and regular expressions.
+ *
+ * Excludes can be denoted by a leading '-' or '!', includes optionally by a leading '+'.
+ * Order of filters is significant, a later exclusion overrides an earlier inclusion
+ * and vice versa.
+ *
+ * Example:
+ *
+ * !sap/ui/core/
+ * +sap/ui/core/utils/
+ *
+ * excludes everything from sap/ui/core, but includes everything from the subpackage sap/ui/core/utils/.
+ *
+ * Note that the filter operates on the full name of each resource. If a resource name
+ * prefix
is configured for a resource set, the filter will be applied
+ * to the combination of prefix and local file path and not only to the local file path.
+ *
+ * @param {string} filterStr comma separated list of simple filters
+ * @returns {ResourceFilterList}
+ */
ResourceFilterList.fromString = function(filterStr) {
const result = new ResourceFilterList();
if ( filterStr != null ) {
diff --git a/lib/lbt/resources/ResourceInfo.js b/lib/lbt/resources/ResourceInfo.js
new file mode 100644
index 000000000..a6a378473
--- /dev/null
+++ b/lib/lbt/resources/ResourceInfo.js
@@ -0,0 +1,180 @@
+/**
+ * Information about a single resource as stored in the resources.json file.
+ *
+ * @author Frank Weigel
+ * @since 1.33.0
+ */
+class ResourceInfo {
+ /**
+ * @param {string} name name of the resource
+ */
+ constructor(name) {
+ this.name = name;
+ this.i18nName = null;
+ this.i18nLocale = null;
+ this.isDebug = false;
+ this.theme = null;
+ this.merged = false;
+ this.designtime = false;
+ this.support = false;
+ this._module = null;
+ this.required = null;
+ this.condRequired = null;
+ this.included = null;
+ this.dynRequired = false;
+ this.requiresTopLevelScope = false;
+ this.exposedGlobalNames = null;
+ this._format = null;
+ this._size = -1;
+ }
+
+
+ get module() {
+ return this._module;
+ }
+
+ set module(value) {
+ this._module = value;
+ }
+
+ get format() {
+ return this._format;
+ }
+
+ set format(value) {
+ this._format = value;
+ }
+
+ get size() {
+ return this._size;
+ }
+
+ set size(value) {
+ this._size = value;
+ }
+
+ /**
+ * Copies the properties of the given ResourceInfo into this
+ *
+ * @param {string} prefix
+ * @param {ResourceInfo} orig
+ */
+ copyFrom(prefix, orig) {
+ this.i18nName = orig.i18nName;
+ this.i18nLocale = orig.i18nLocale;
+ this.isDebug = orig.isDebug;
+ this.theme = orig.theme;
+ this.merged = orig.merged;
+ this.designtime = orig.designtime;
+ this.support = orig.support;
+ if ( this.module == null ) {
+ this.module = orig.module;
+ }
+ if ( orig.required != null ) {
+ if ( this.required == null ) {
+ this.required = new Set();
+ }
+ orig.required.forEach(this.required.add, this.required);
+ }
+ if ( orig.condRequired != null ) {
+ if ( this.condRequired == null ) {
+ this.condRequired = new Set();
+ }
+ orig.condRequired.forEach(this.condRequired.add, this.condRequired);
+ }
+ if ( orig.dynRequired ) {
+ this.dynRequired = orig.dynRequired;
+ }
+ if ( orig.included != null ) {
+ if ( this.included == null ) {
+ this.included = new Set();
+ }
+ orig.included.forEach(this.included.add, this.included);
+ }
+ if ( this.included != null && this.included.size > 0 ) {
+ this.merged = true;
+ }
+ if (orig.size >= 0) {
+ this.size = orig.size;
+ }
+ if ( orig.requiresTopLevelScope ) {
+ this.requiresTopLevelScope = orig.requiresTopLevelScope;
+ }
+ if ( orig.exposedGlobalNames != null ) {
+ if ( this.exposedGlobalNames == null ) {
+ this.exposedGlobalNames = new Set();
+ }
+ orig.exposedGlobalNames.forEach(this.exposedGlobalNames.add, this.exposedGlobalNames);
+ }
+ if ( orig.format != null ) {
+ this.format = orig.format;
+ }
+ }
+
+ /**
+ * called from JSON.stringify()
+ *
+ * @returns {{name: *}}
+ */
+ toJSON() {
+ const result = {
+ name: this.name
+ };
+ if ( this._module != null ) {
+ result.module = this.module;
+ }
+ if ( this.size >= 0 ) {
+ result.size = this.size;
+ }
+ if ( this.requiresTopLevelScope ) {
+ result.requiresTopLevelScope = this.requiresTopLevelScope;
+ }
+ if ( this.exposedGlobalNames != null && this.exposedGlobalNames.size > 0 ) {
+ result.exposedGlobalNames = [...this.exposedGlobalNames];
+ }
+ if ( this.format ) {
+ result.format = this.format;
+ }
+
+ //
+
+ if ( this.isDebug ) {
+ result.isDebug = this.isDebug;
+ }
+ if ( this.merged ) {
+ result.merged = this.merged;
+ }
+ if ( this.designtime ) {
+ result.designtime = this.designtime;
+ }
+ if ( this.support ) {
+ result.support = this.support;
+ }
+ if ( this.i18nLocale != null ) {
+ result.locale = this.i18nLocale;
+ result.raw = this.i18nName;
+ }
+ if ( this.theme != null ) {
+ result.theme = this.theme;
+ }
+
+ //
+
+ if ( this.required != null && this.required.size > 0 ) {
+ result.required = [...this.required].sort();
+ }
+ if ( this.condRequired != null && this.condRequired.size > 0 ) {
+ result.condRequired = [...this.condRequired].sort();
+ }
+ if ( this.dynRequired ) {
+ result.dynRequired = this.dynRequired;
+ }
+ if ( this.included != null && this.included.size > 0 ) {
+ result.included = [...this.included];
+ }
+
+ return result;
+ }
+}
+
+module.exports = ResourceInfo;
diff --git a/lib/lbt/resources/ResourceInfoList.js b/lib/lbt/resources/ResourceInfoList.js
new file mode 100644
index 000000000..af5b655ea
--- /dev/null
+++ b/lib/lbt/resources/ResourceInfoList.js
@@ -0,0 +1,153 @@
+const ResourceInfo = require("./ResourceInfo");
+
+const DEBUG_RESOURCES_PATTERN = /-dbg((?:\.view|\.fragment|\.controller|\.designtime|\.support)?\.js|\.css)$/;
+const RESOURCES_PATTERN = /((?:\.view|\.fragment|\.controller|\.designtime|\.support)?\.js|\.css)$/;
+
+/**
+ * A list of ResourceInfo objects, suitable for (but not dependent on) JSON serialization.
+ *
+ * @author Frank Weigel
+ * @since 1.33.0
+ */
+class ResourceInfoList {
+ /**
+ * Holds ResourceInfos
+ *
+ * @param {string} prefix
+ */
+ constructor(prefix) {
+ /**
+ * List of resources information objects
+ *
+ * @type {ResourceInfo[]}
+ */
+ this.resources = [];
+
+ // --- transient state ---
+ /**
+ * @type {string} name of the resource
+ */
+ this.name = prefix;
+ /**
+ *
+ * @type {Map}
+ */
+ this.resourcesByName = new Map();
+ }
+
+ /**
+ * Add ResourceInfo to list
+ *
+ * @param {ResourceInfo} info
+ * @param {boolean} shareDebugInformation
+ */
+ add(info, shareDebugInformation=true) {
+ const relativeName = ResourceInfoList.makePathRelativeTo(this.name, info.name);
+
+ // search for a resource with the same name
+ let myInfo = this.resourcesByName.get(relativeName);
+
+ if ( myInfo == null && shareDebugInformation) {
+ // when not found, check if the given resource is a debug resource and
+ // share the information with the non-dbg version
+ const nonDbgName = ResourceInfoList.getNonDebugName(relativeName);
+ const dbgName = ResourceInfoList.getDebugName(relativeName);
+ if ( nonDbgName != null && this.resourcesByName.has(nonDbgName) ) {
+ // copy from source
+ myInfo = new ResourceInfo(relativeName);
+ const source = this.resourcesByName.get(nonDbgName);
+ myInfo.copyFrom(this.name, source);
+ this.resources.push(myInfo);
+ this.resourcesByName.set(relativeName, myInfo);
+ } else if (dbgName != null && this.resourcesByName.has(dbgName)) {
+ // copy from debug
+ myInfo = new ResourceInfo(relativeName);
+ const source = this.resourcesByName.get(dbgName);
+ myInfo.copyFrom(this.name, source);
+ myInfo.module = ResourceInfoList.getNonDebugName(source.module);
+ this.resources.push(myInfo);
+ this.resourcesByName.set(relativeName, myInfo);
+ }
+ }
+
+ // this is the assumption, that the debug one is the same as the non-dbg one
+ if ( myInfo == null ) {
+ myInfo = new ResourceInfo(relativeName);
+ myInfo.size = info.size;
+ myInfo.module = ResourceInfoList.getNonDebugName(info.name);
+ this.resources.push(myInfo);
+ this.resourcesByName.set(relativeName, myInfo);
+ }
+ myInfo.copyFrom(this.name, info);
+ if (info.i18nName) {
+ myInfo.i18nName = ResourceInfoList.makePathRelativeTo(this.name, info.i18nName);
+ }
+ }
+
+ /**
+ * Serializes its content to JSON format
+ *
+ * @returns {{resources: ResourceInfo[], _version: string}}
+ */
+ toJSON() {
+ this.resources.sort((a, b) => {
+ if ( a.name === b.name ) {
+ return 0;
+ }
+ return a.name < b.name ? -1 : 1;
+ });
+ return {
+ /**
+ * Version of the resources.json file format, must be 1.1.0 or higher to store dependencies
+ */
+ _version: "1.1.0",
+ resources: this.resources
+ };
+ }
+
+ /**
+ * Retrieves the relative path
+ *
+ * @param {string} prefix
+ * @param {string} name
+ * @returns {string}
+ */
+ static makePathRelativeTo(prefix, name) {
+ let back = "";
+ while ( !name.startsWith(prefix) ) {
+ const p = prefix.lastIndexOf("/", prefix.length - 2);
+ back = back + "../";
+ if ( p >= 0 ) {
+ prefix = prefix.slice(0, p + 1);
+ } else {
+ prefix = "";
+ break;
+ }
+ }
+ return back + name.slice(prefix.length);
+ }
+
+ /**
+ * If the given module is a -dbg file, calculate and return the non-dbg name.
+ *
+ * @param {string} path
+ * @returns {string|null} Non-debug name of the module
+ */
+ static getNonDebugName(path) {
+ if ( DEBUG_RESOURCES_PATTERN.test(path) ) {
+ return path.replace( DEBUG_RESOURCES_PATTERN, "$1");
+ }
+ return null;
+ }
+
+ static getDebugName(path) {
+ if ( RESOURCES_PATTERN.test(path) ) {
+ if (!path.replace(RESOURCES_PATTERN, "").endsWith("-dbg")) {
+ return path.replace( RESOURCES_PATTERN, "-dbg$1");
+ }
+ }
+ return null;
+ }
+}
+
+module.exports = ResourceInfoList;
diff --git a/lib/lbt/resources/ResourcePool.js b/lib/lbt/resources/ResourcePool.js
index 0e0e478b2..ed7e45239 100644
--- a/lib/lbt/resources/ResourcePool.js
+++ b/lib/lbt/resources/ResourcePool.js
@@ -161,6 +161,12 @@ class ResourcePool {
}
}
+ /**
+ * Retrieves the module info
+ *
+ * @param {string} name module name
+ * @returns {Promise}
+ */
async getModuleInfo(name) {
let info = this._dependencyInfos.get(name);
if ( info == null ) {
diff --git a/lib/processors/bundlers/moduleBundler.js b/lib/processors/bundlers/moduleBundler.js
index 673e2e1f8..62e7f24c0 100644
--- a/lib/processors/bundlers/moduleBundler.js
+++ b/lib/processors/bundlers/moduleBundler.js
@@ -1,45 +1,7 @@
const BundleBuilder = require("../../lbt/bundle/Builder");
-const Resource = require("../../lbt/resources/Resource");
-const ResourcePool = require("../../lbt/resources/ResourcePool");
+const LocatorResourcePool = require("../../lbt/resources/LocatorResourcePool");
const EvoResource = require("@ui5/fs").Resource;
-function extractName(path) {
- return path.slice( "/resources/".length);
-}
-
-class LocatorResource extends Resource {
- constructor(pool, resource) {
- super(pool, extractName(resource.getPath()), null, resource.getStatInfo());
- this.resource = resource;
- }
-
- buffer() {
- return this.resource.getBuffer();
- }
-
- getProject() {
- return this.resource._project;
- }
-}
-
-class LocatorResourcePool extends ResourcePool {
- constructor({ignoreMissingModules}) {
- super({ignoreMissingModules});
- }
-
- prepare(resources) {
- resources = resources.filter( (res) => !res.getStatInfo().isDirectory() );
- // console.log(resources.map($ => $.getPath()));
- return Promise.all(
- resources.map(
- (resource) => this.addResource( new LocatorResource(this, resource) )
- ).filter( (followUp) => followUp )
- );
- // .then( () => {
- // console.log(" found %d resources", this.size);
- // });
- }
-}
/**
* A ModuleBundleDefinitionSection specifies the embedding mode ('provided', 'raw', 'preload' or 'require')
diff --git a/lib/processors/resourceListCreator.js b/lib/processors/resourceListCreator.js
new file mode 100644
index 000000000..20b5034a5
--- /dev/null
+++ b/lib/processors/resourceListCreator.js
@@ -0,0 +1,200 @@
+"use strict";
+
+const log = require("@ui5/logger").getLogger("builder:processors:resourceListCreator");
+const ResourceCollector = require("../lbt/resources/ResourceCollector");
+const LocatorResourcePool = require("../lbt/resources/LocatorResourcePool");
+const ResourceInfo = require("../lbt/resources/ResourceInfo");
+
+const EvoResource = require("@ui5/fs").Resource;
+
+/**
+ * List of resource patterns that describe all debug resources.
+ *
+ * @since 1.29.1
+ */
+const DEFAULT_DEBUG_RESOURCES_FILTER = [
+ "**/*-dbg.js",
+ "**/*-dbg.controller.js",
+ "**/*-dbg.designtime.js",
+ "**/*-dbg.support.js",
+ "**/*-dbg.view.js",
+ "**/*-dbg.fragment.js",
+ "**/*-dbg.css",
+ "**/*.js.map"
+];
+
+/**
+ * List of resource patterns that describe bundled resources.
+ *
+ * @since 1.29.1
+ */
+const DEFAULT_BUNDLE_RESOURCES_FILTER = [
+ "**/Component-preload.js",
+ "**/library-preload.js",
+ "**/library-preload-dbg.js",
+ "**/library-preload.json",
+ "**/library-h2-preload.js",
+ "**/designtime/library-preload.designtime.js",
+ "**/library-preload.support.js",
+ "**/library-all.js",
+ "**/library-all-dbg.js"
+];
+
+/**
+ * List of resource patterns that describe all designtime resources.
+ *
+ * @since 1.31.0
+ */
+const DEFAULT_DESIGNTIME_RESOURCES_FILTER = [
+ "**/designtime/*",
+ "**/*.designtime.js",
+ "**/*.control",
+ "**/*.interface",
+ "**/*.type",
+ "**/themes/*/*.less",
+ "**/library.templates.xml",
+ "**/library.dependencies.xml",
+ "**/library.dependencies.json"
+];
+
+/**
+ * List of resource patterns that describe all support (assistant) resources.
+ *
+ * @since 1.53.0
+ */
+const DEFAULT_SUPPORT_RESOURCES_FILTER = [
+ "**/*.support.js"
+];
+
+/**
+ * Hard coded debug bundle, to trigger separate analysis for this filename
+ * because sap-ui-core.js and sap-ui-core-dbg.js have different includes
+ *
+ * @type {string[]}
+ */
+const DEBUG_BUNDLES = [
+ "sap-ui-core-dbg.js"
+];
+
+/**
+ * Creates and adds resources.json entry (itself) to the list.
+ *
+ * Retrieves the string content of the overall result and returns it.
+ *
+ * @param {ResourceInfoList} list resources list
+ * @param {string} prefix
+ * @returns {string} new content with resources.json entry
+ */
+function makeResourcesJSON(list, prefix) {
+ // having the file size entry part of the file is a bit like the chicken egg scenario
+ // you can't change the value of the file size without changing the file size
+ // so this part here tries to cope with that.
+
+ // try to add resources.json entry with previous size of the list string.
+ // get the content to be added (resources.json entry)
+ // modify the size of the entry from the calculated one
+
+ let contentString = JSON.stringify(list, null, "\t");
+ const resourcesJson = new ResourceInfo(prefix + "resources.json");
+ // initial size
+ resourcesJson.size = Buffer.from(contentString).byteLength;
+ list.add(resourcesJson);
+
+ contentString = JSON.stringify(list, null, "\t");
+
+ let newLength = Buffer.from(contentString).byteLength;
+
+ // Adjust size until it is correct
+ // This entry's size depends on the file size which depends on this entry's size,...
+ // Updating the number of the size in the content might influence the size of the file itself
+ // This is deterministic because e.g. in the content -> "size": 1000
has the same amount of bytes as "size": 9999
+ // the difference might only come for:
+ // * adding the initial entry of resources.json
+ // * changes when the number of digits of the number changes, e.g. 100 -> 1000
+ while (resourcesJson.size !== newLength) {
+ resourcesJson.size = newLength;
+ list.add(resourcesJson);
+ contentString = JSON.stringify(list, null, "\t");
+ newLength = Buffer.from(contentString).byteLength;
+ }
+ return contentString;
+}
+
+/**
+ * Whether the detection of orphans should result in a build failure.
+ *
+ * @since 1.29.1
+ * @param {object} configuration
+ * @param {module:@ui5/fs.Resource[]} configuration.resources
+ * @param {object} [options] configuration options
+ * @returns {module:@ui5/fs.Resource[]} list of resources.json files
+ */
+module.exports = async function({resources}, options) {
+ options = Object.assign({
+ failOnOrphans: true,
+ externalResources: undefined,
+ debugResources: DEFAULT_DEBUG_RESOURCES_FILTER,
+ mergedResources: DEFAULT_BUNDLE_RESOURCES_FILTER,
+ designtimeResources: DEFAULT_DESIGNTIME_RESOURCES_FILTER,
+ supportResources: DEFAULT_SUPPORT_RESOURCES_FILTER,
+ debugBundles: DEBUG_BUNDLES
+ }, options);
+
+ const pool = new LocatorResourcePool();
+ await pool.prepare( resources );
+
+ const collector = new ResourceCollector(pool);
+ const visitPromises = resources.map((resource) => collector.visitResource(resource));
+
+ await Promise.all(visitPromises);
+ log.verbose(` found ${collector.resources.size} resources`);
+
+ // determine additional information for the found resources
+ if ( options && options.externalResources ) {
+ collector.setExternalResources(options.externalResources);
+ }
+
+ await collector.determineResourceDetails({
+ pool,
+ debugResources: options.debugResources,
+ mergedResources: options.mergedResources,
+ designtimeResources: options.designtimeResources,
+ supportResources: options.supportResources
+ });
+
+ // group resources by components and create ResourceInfoLists
+ collector.groupResourcesByComponents({
+ debugBundles: options.debugBundles
+ });
+
+ const resourceLists = [];
+
+ // write out resources.json files
+ for (const [prefix, list] of collector.components.entries()) {
+ log.verbose(` writing '${prefix}resources.json'`);
+
+ const contentString = makeResourcesJSON(list, prefix);
+
+ resourceLists.push(new EvoResource({
+ path: `/resources/${prefix}resources.json`,
+ string: contentString
+ }));
+ }
+ for (const [prefix, list] of collector.themePackages.entries()) {
+ log.verbose(` writing '${prefix}resources.json'`);
+
+ const contentString = makeResourcesJSON(list, prefix);
+
+ resourceLists.push(new EvoResource({
+ path: `/resources/${prefix}resources.json`,
+ string: contentString
+ }));
+ }
+ const unassigned = collector.resources;
+ if ( unassigned.size > 0 && options.failOnOrphans ) {
+ log.error(`resources.json generation failed because of unassigned resources: ${[...unassigned].join(", ")}`);
+ throw new Error(`resources.json generation failed with error: There are ${unassigned.size} resources which could not be assigned to components.`);
+ }
+
+ return resourceLists;
+};
diff --git a/lib/processors/uglifier.js b/lib/processors/uglifier.js
index a20707397..f5c4c9591 100644
--- a/lib/processors/uglifier.js
+++ b/lib/processors/uglifier.js
@@ -1,5 +1,16 @@
const terser = require("terser");
-const copyrightCommentsPattern = /copyright|\(c\)(?:[0-9]+|\s+[0-9A-za-z])|released under|license|\u00a9/i;
+/**
+ * Preserve comments which contain:
+ *
+ * - copyright notice
+ * - license terms
+ * - "@ui5-bundle"
+ * - "@ui5-bundle-raw-include"
+ *
+ *
+ * @type {RegExp}
+ */
+const copyrightCommentsAndBundleCommentPattern = /copyright|\(c\)(?:[0-9]+|\s+[0-9A-za-z])|released under|license|\u00a9|^@ui5-bundle-raw-include |^@ui5-bundle /i;
/**
* Minifies the supplied resources.
@@ -18,7 +29,7 @@ module.exports = function({resources}) {
}, {
warnings: false,
output: {
- comments: copyrightCommentsPattern,
+ comments: copyrightCommentsAndBundleCommentPattern,
wrap_func_args: false
},
compress: false
diff --git a/lib/tasks/generateResourcesJson.js b/lib/tasks/generateResourcesJson.js
new file mode 100644
index 000000000..399aa524d
--- /dev/null
+++ b/lib/tasks/generateResourcesJson.js
@@ -0,0 +1,115 @@
+"use strict";
+
+const resourceListCreator = require("../processors/resourceListCreator");
+
+const DEFAULT_EXCLUDES = [
+ /*
+ * exclude mac metadata files
+ */
+ "!**/.DS_Store",
+ /*
+ * sap-ui-version.json is not part of the resources
+ */
+ "!/resources/sap-ui-version.json"
+];
+
+function getCreatorOptions(projectName) {
+ const creatorOptions = {};
+ if ( projectName === "sap.ui.core" ) {
+ Object.assign(creatorOptions, {
+ externalResources: {
+ "sap/ui/core": [
+ "*",
+ "sap/base/",
+ "sap/ui/"
+ ]
+ },
+ mergedResourcesFilters: [
+ "jquery-sap*.js",
+ "sap-ui-core*.js",
+ "**/Component-preload.js",
+ "**/library-preload.js",
+ "**/library-preload-dbg.js",
+ "**/library-preload.json",
+ "**/library-all.js",
+ "**/library-all-dbg.js",
+ "**/designtime/library-preload.designtime.js",
+ "**/library-preload.support.js"
+ ]
+ });
+ }
+ return creatorOptions;
+}
+
+/**
+ * Task for creating a resources.json file, describing all productive build resources.
+ *
+ *
+ * The detailed structure can be found in the documentation:
+ * {@link https://openui5.hana.ondemand.com/#topic/adcbcf8b50924556ab3f321fcd9353ea}
+ *
+ *
+ *
+ * Not supported in combination with task {@link module:@ui5/builder.tasks.generateStandaloneAppBundle}.
+ * Therefore it is also not supported in combination with self-contained build.
+ *
+ *
+ * @example sample resources.json
+ * {
+ * "_version": "1.1.0",
+ * "resources": [
+ * {
+ * "name": "Component-preload.js",
+ * "module": "application/mine/Component-preload.js",
+ * "size": 3746,
+ * "merged": true,
+ * "included": [
+ * "application/mine/Component.js",
+ * "application/mine/changes/coding/MyExtension.js",
+ * "application/mine/changes/flexibility-bundle.json",
+ * "application/mine/changes/fragments/MyFragment.fragment.xml",
+ * "application/mine/manifest.json"
+ * ]
+ * },
+ * {
+ * "name": "resources.json",
+ * "size": 1870
+ * },
+ * {
+ * "name": "rules/Button-dbg.support.js",
+ * "module": "application/mine/rules/Button.support.js",
+ * "size": 211,
+ * "format": "raw",
+ * "isDebug": true,
+ * "required": [
+ * "application/mine/library.js",
+ * "sap/ui/core/Control.js"
+ * ],
+ * "condRequired": [
+ * "application/mine/changeHandler/SplitButton.js",
+ * "sap/ui/core/format/DateFormat.js"
+ * ],
+ * "dynRequired": true,
+ * "support": true
+ * }
+ * ]
+ * }
+ *
+ * @public
+ * @alias module:@ui5/builder.tasks.generateResourcesJson
+ * @param {object} parameters Parameters
+ * @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
+ * @param {object} parameters.options Options
+ * @param {string} parameters.options.projectName Project name
+ * @returns {Promise} Promise resolving with undefined
once data has been written
+ */
+module.exports = async function({workspace, options: {projectName}}) {
+ const resources = await workspace.byGlob(["/resources/**/*.*"].concat(DEFAULT_EXCLUDES));
+
+ const resourceLists = await resourceListCreator({
+ resources
+ }, getCreatorOptions(projectName));
+ await Promise.all(
+ resourceLists.map((resourceList) => workspace.write(resourceList))
+ );
+};
diff --git a/lib/tasks/taskRepository.js b/lib/tasks/taskRepository.js
index 278c4dd40..aa45af249 100644
--- a/lib/tasks/taskRepository.js
+++ b/lib/tasks/taskRepository.js
@@ -14,6 +14,7 @@ const taskInfos = {
generateManifestBundle: {path: "./bundlers/generateManifestBundle"},
generateFlexChangesBundle: {path: "./bundlers/generateFlexChangesBundle"},
generateComponentPreload: {path: "./bundlers/generateComponentPreload"},
+ generateResourcesJson: {path: "./generateResourcesJson"},
generateStandaloneAppBundle: {path: "./bundlers/generateStandaloneAppBundle"},
generateBundle: {path: "./bundlers/generateBundle"},
generateLibraryPreload: {path: "./bundlers/generateLibraryPreload"},
diff --git a/lib/types/application/ApplicationBuilder.js b/lib/types/application/ApplicationBuilder.js
index 7ff80869b..ec5d11358 100644
--- a/lib/types/application/ApplicationBuilder.js
+++ b/lib/types/application/ApplicationBuilder.js
@@ -187,6 +187,16 @@ class ApplicationBuilder extends AbstractBuilder {
}
});
});
+
+ this.addTask("generateResourcesJson", () => {
+ return getTask("generateResourcesJson").task({
+ workspace: resourceCollections.workspace,
+ dependencies: resourceCollections.dependencies,
+ options: {
+ projectName: project.metadata.name
+ }
+ });
+ });
}
}
diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js
index a5b4935a4..9e1833037 100644
--- a/lib/types/library/LibraryBuilder.js
+++ b/lib/types/library/LibraryBuilder.js
@@ -177,6 +177,16 @@ class LibraryBuilder extends AbstractBuilder {
}
});
});
+
+ this.addTask("generateResourcesJson", () => {
+ return getTask("generateResourcesJson").task({
+ workspace: resourceCollections.workspace,
+ dependencies: resourceCollections.dependencies,
+ options: {
+ projectName: project.metadata.name
+ }
+ });
+ });
}
}
diff --git a/lib/types/themeLibrary/ThemeLibraryBuilder.js b/lib/types/themeLibrary/ThemeLibraryBuilder.js
index 29baabb31..b1b8799a5 100644
--- a/lib/types/themeLibrary/ThemeLibraryBuilder.js
+++ b/lib/types/themeLibrary/ThemeLibraryBuilder.js
@@ -35,6 +35,16 @@ class ThemeLibraryBuilder extends AbstractBuilder {
}
});
});
+
+ this.addTask("generateResourcesJson", () => {
+ return getTask("generateResourcesJson").task({
+ workspace: resourceCollections.workspace,
+ dependencies: resourceCollections.dependencies,
+ options: {
+ projectName: project.metadata.name
+ }
+ });
+ });
}
}
diff --git a/test/expected/build/application.j/dest-resources-json/Component-preload.js b/test/expected/build/application.j/dest-resources-json/Component-preload.js
new file mode 100644
index 000000000..4e58fdf09
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/Component-preload.js
@@ -0,0 +1,12 @@
+//@ui5-bundle application/j/Component-preload.js
+jQuery.sap.registerPreloadedModules({
+"version":"2.0",
+"modules":{
+ "application/j/Component.js":function(){sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.j.Component",{metadata:{manifest:"json"}})});
+},
+ "application/j/changes/coding/MyExtension.js":function(){sap.ui.define([],function(){return{}});
+},
+ "application/j/changes/flexibility-bundle.json":'{"changes":[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"compVariants":[{"fileName":"id_111_compVariants","fileType":"variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"},"appDescriptorChange":false}],"variants":[{"fileName":"id_111_test","fileType":"ctrl_variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"variantChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"variantDependentControlChanges":[{"fileName":"id_111_variantDependentControlChange","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"},"variantReference":"someting here"}],"variantManagementChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_management_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}]}',
+ "application/j/changes/fragments/MyFragment.fragment.xml":'',
+ "application/j/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"application.j","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"},"sap.ui5":{"dependencies":{"minUI5Version":"1.73.2","libs":{"sap.ui.layout":{},"sap.ui.core":{},"sap.m":{},"sap.ui.fl":{"lazy":false}}}}}'
+}});
diff --git a/test/expected/build/application.j/dest-resources-json/Component.js b/test/expected/build/application.j/dest-resources-json/Component.js
new file mode 100644
index 000000000..984f96eaf
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/Component.js
@@ -0,0 +1 @@
+sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.j.Component",{metadata:{manifest:"json"}})});
\ No newline at end of file
diff --git a/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js b/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js
new file mode 100644
index 000000000..b9e475d8e
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/changes/coding/MyExtension.js
@@ -0,0 +1 @@
+sap.ui.define([],function(){return{}});
\ No newline at end of file
diff --git a/test/expected/build/application.j/dest-resources-json/changes/flexibility-bundle.json b/test/expected/build/application.j/dest-resources-json/changes/flexibility-bundle.json
new file mode 100644
index 000000000..b3a5c5b1e
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/changes/flexibility-bundle.json
@@ -0,0 +1 @@
+{"changes":[{"fileName":"id_456_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2023-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}},{"fileName":"id_123_addField","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"compVariants":[{"fileName":"id_111_compVariants","fileType":"variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"},"appDescriptorChange":false}],"variants":[{"fileName":"id_111_test","fileType":"ctrl_variant","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"variantChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}],"variantDependentControlChanges":[{"fileName":"id_111_variantDependentControlChange","fileType":"change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"},"variantReference":"someting here"}],"variantManagementChanges":[{"fileName":"id_111_test","fileType":"ctrl_variant_management_change","changeType":"hideControl","component":"application.j.Component","content":{},"selector":{"id":"control1"},"layer":"VENDOR","texts":{},"namespace":"apps/application.j.Component/changes","creation":"2025-10-30T13:52:40.4754350Z","originalLanguage":"","conditions":{},"support":{"generator":"did it","user":"Max Mustermann"}}]}
\ No newline at end of file
diff --git a/test/expected/build/application.j/dest-resources-json/changes/fragments/MyFragment.fragment.xml b/test/expected/build/application.j/dest-resources-json/changes/fragments/MyFragment.fragment.xml
new file mode 100644
index 000000000..39ce6859b
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/changes/fragments/MyFragment.fragment.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/test/expected/build/application.j/dest-resources-json/manifest.json b/test/expected/build/application.j/dest-resources-json/manifest.json
new file mode 100644
index 000000000..f82703903
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/manifest.json
@@ -0,0 +1,28 @@
+{
+ "_version": "1.1.0",
+ "sap.app": {
+ "_version": "1.1.0",
+ "id": "application.j",
+ "type": "application",
+ "applicationVersion": {
+ "version": "1.2.2"
+ },
+ "embeds": [
+ "embedded"
+ ],
+ "title": "{{title}}"
+ },
+ "sap.ui5": {
+ "dependencies": {
+ "minUI5Version": "1.73.2",
+ "libs": {
+ "sap.ui.layout": {},
+ "sap.ui.core": {},
+ "sap.m": {},
+ "sap.ui.fl": {
+ "lazy": false
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/expected/build/application.j/dest-resources-json/resources.json b/test/expected/build/application.j/dest-resources-json/resources.json
new file mode 100644
index 000000000..6cc8eb8a7
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/resources.json
@@ -0,0 +1,86 @@
+{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": "Component-preload.js",
+ "module": "application/j/Component-preload.js",
+ "size": 3746,
+ "merged": true,
+ "included": [
+ "application/j/Component.js",
+ "application/j/changes/coding/MyExtension.js",
+ "application/j/changes/flexibility-bundle.json",
+ "application/j/changes/fragments/MyFragment.fragment.xml",
+ "application/j/manifest.json"
+ ]
+ },
+ {
+ "name": "Component.js",
+ "module": "application/j/Component.js",
+ "size": 141,
+ "required": [
+ "sap/m/library.js",
+ "sap/ui/core/UIComponent.js",
+ "sap/ui/core/library.js",
+ "sap/ui/fl/library.js",
+ "sap/ui/layout/library.js"
+ ]
+ },
+ {
+ "name": "changes/coding/MyExtension.js",
+ "module": "application/j/changes/coding/MyExtension.js",
+ "size": 39
+ },
+ {
+ "name": "changes/flexibility-bundle.json",
+ "module": "application/j/changes/flexibility-bundle.json",
+ "size": 2864
+ },
+ {
+ "name": "changes/fragments/MyFragment.fragment.xml",
+ "module": "application/j/changes/fragments/MyFragment.fragment.xml",
+ "size": 13
+ },
+ {
+ "name": "changes/id_111_appDescriptor.change",
+ "size": 464
+ },
+ {
+ "name": "changes/id_111_compVariants.variant",
+ "size": 466
+ },
+ {
+ "name": "changes/id_111_test.ctrl_variant",
+ "size": 432
+ },
+ {
+ "name": "changes/id_111_test.ctrl_variant_change",
+ "size": 439
+ },
+ {
+ "name": "changes/id_111_test.ctrl_variant_management_change",
+ "size": 450
+ },
+ {
+ "name": "changes/id_111_variantDependentControlChange.change",
+ "size": 489
+ },
+ {
+ "name": "changes/id_123_addField.change",
+ "size": 430
+ },
+ {
+ "name": "changes/id_456_addField.change",
+ "size": 430
+ },
+ {
+ "name": "manifest.json",
+ "module": "application/j/manifest.json",
+ "size": 423
+ },
+ {
+ "name": "resources.json",
+ "size": 1870
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json b/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json
new file mode 100644
index 000000000..551fc7d92
--- /dev/null
+++ b/test/expected/build/application.j/dest-resources-json/resources/sap-ui-version.json
@@ -0,0 +1,7 @@
+{
+ "name": "application.j",
+ "version": "1.0.0",
+ "buildTimestamp": "202008120917",
+ "scmRevision": "",
+ "libraries": []
+}
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/.library b/test/expected/build/library.h/dest-resources-json/resources/library/h/.library
new file mode 100644
index 000000000..5bdab13ab
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/.library
@@ -0,0 +1,19 @@
+
+
+
+ library.h
+ SAP SE
+ Some fancy copyright
+ 1.0.0
+
+ Library D
+
+
+
+
+
+
+
+
+
+
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js
new file mode 100644
index 000000000..f66daec7d
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component-preload.js
@@ -0,0 +1,10 @@
+//@ui5-bundle library/h/components/Component-preload.js
+sap.ui.require.preload({
+ "library/h/components/Component.js":function(){sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
+},
+ "library/h/components/TodoComponent.js":function(){/*!
+ * Some fancy copyright
+ */
+console.log(" File ");
+}
+});
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js
new file mode 100644
index 000000000..422a97071
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/Component.js
@@ -0,0 +1 @@
+sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js
new file mode 100644
index 000000000..bcf866e67
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/TodoComponent.js
@@ -0,0 +1,4 @@
+/*!
+ * Some fancy copyright
+ */
+console.log(" File ");
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/resources.json b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/resources.json
new file mode 100644
index 000000000..a1dda2e2d
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/resources.json
@@ -0,0 +1,84 @@
+{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": "Component-preload.js",
+ "module": "library/h/components/Component-preload.js",
+ "size": 361,
+ "merged": true,
+ "included": [
+ "library/h/components/Component.js",
+ "library/h/components/TodoComponent.js"
+ ]
+ },
+ {
+ "name": "Component.js",
+ "module": "library/h/components/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "TodoComponent.js",
+ "module": "library/h/components/TodoComponent.js",
+ "size": 54,
+ "format": "raw"
+ },
+ {
+ "name": "resources.json",
+ "size": 1904
+ },
+ {
+ "name": "subcomponent1/Component-preload.js",
+ "module": "library/h/components/subcomponent1/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent1/Component.js"
+ ]
+ },
+ {
+ "name": "subcomponent1/Component.js",
+ "module": "library/h/components/subcomponent1/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "subcomponent2/Component-preload.js",
+ "module": "library/h/components/subcomponent2/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent2/Component.js"
+ ]
+ },
+ {
+ "name": "subcomponent2/Component.js",
+ "module": "library/h/components/subcomponent2/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "subcomponent3/Component-preload.js",
+ "module": "library/h/components/subcomponent3/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent3/Component.js"
+ ]
+ },
+ {
+ "name": "subcomponent3/Component.js",
+ "module": "library/h/components/subcomponent3/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js
new file mode 100644
index 000000000..d2309dcdf
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component-preload.js
@@ -0,0 +1,5 @@
+//@ui5-bundle library/h/components/subcomponent1/Component-preload.js
+sap.ui.require.preload({
+ "library/h/components/subcomponent1/Component.js":function(){sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
+}
+});
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js
new file mode 100644
index 000000000..422a97071
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/Component.js
@@ -0,0 +1 @@
+sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/resources.json b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/resources.json
new file mode 100644
index 000000000..5de7b8334
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent1/resources.json
@@ -0,0 +1,26 @@
+{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": "Component-preload.js",
+ "module": "library/h/components/subcomponent1/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent1/Component.js"
+ ]
+ },
+ {
+ "name": "Component.js",
+ "module": "library/h/components/subcomponent1/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "resources.json",
+ "size": 494
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js
new file mode 100644
index 000000000..ce88226b9
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component-preload.js
@@ -0,0 +1,5 @@
+//@ui5-bundle library/h/components/subcomponent2/Component-preload.js
+sap.ui.require.preload({
+ "library/h/components/subcomponent2/Component.js":function(){sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
+}
+});
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js
new file mode 100644
index 000000000..422a97071
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/Component.js
@@ -0,0 +1 @@
+sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/resources.json b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/resources.json
new file mode 100644
index 000000000..ba1172a4c
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent2/resources.json
@@ -0,0 +1,26 @@
+{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": "Component-preload.js",
+ "module": "library/h/components/subcomponent2/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent2/Component.js"
+ ]
+ },
+ {
+ "name": "Component.js",
+ "module": "library/h/components/subcomponent2/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "resources.json",
+ "size": 494
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js
new file mode 100644
index 000000000..4b88fc476
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component-preload.js
@@ -0,0 +1,5 @@
+//@ui5-bundle library/h/components/subcomponent3/Component-preload.js
+sap.ui.require.preload({
+ "library/h/components/subcomponent3/Component.js":function(){sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
+}
+});
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js
new file mode 100644
index 000000000..422a97071
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/Component.js
@@ -0,0 +1 @@
+sap.ui.define(["sap/ui/core/UIComponent"],function(n){"use strict";return n.extend("application.g.Component",{})});
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/resources.json b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/resources.json
new file mode 100644
index 000000000..b24c48464
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/components/subcomponent3/resources.json
@@ -0,0 +1,26 @@
+{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": "Component-preload.js",
+ "module": "library/h/components/subcomponent3/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent3/Component.js"
+ ]
+ },
+ {
+ "name": "Component.js",
+ "module": "library/h/components/subcomponent3/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "resources.json",
+ "size": 494
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js
new file mode 100644
index 000000000..e9e0e00ed
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/customBundle.js
@@ -0,0 +1,24 @@
+//@ui5-bundle library/h/customBundle.js
+sap.ui.require.preload({
+ "library/h/file.js":function(){/*!
+ * Some fancy copyright
+ */
+console.log(" File ");
+},
+ "library/h/library.js":function(){/*!
+ * Some fancy copyright
+ */
+console.log(" Library ");
+},
+ "library/h/some.js":function(){/*!
+ * Some fancy copyright
+ */
+//@ui5-bundle-raw-include library/h/other.js
+console.log(" Some ");
+}
+});
+//@ui5-bundle-raw-include library/h/not.js
+/*!
+ * Some fancy copyright
+ */
+console.log(" Not including ");
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js
new file mode 100644
index 000000000..9b135171d
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/designtime/library.designtime.js
@@ -0,0 +1,4 @@
+/*!
+ * Some fancy copyright
+ */
+var myexport=function(){"use strict";String("asd")}();
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js
new file mode 100644
index 000000000..bcf866e67
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/file.js
@@ -0,0 +1,4 @@
+/*!
+ * Some fancy copyright
+ */
+console.log(" File ");
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle.properties b/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle.properties
new file mode 100644
index 000000000..1c6a1a9e8
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle.properties
@@ -0,0 +1 @@
+a=b
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle_en.properties b/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle_en.properties
new file mode 100644
index 000000000..1c6a1a9e8
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/i18n/messagebundle_en.properties
@@ -0,0 +1 @@
+a=b
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js
new file mode 100644
index 000000000..6900e2218
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/library.js
@@ -0,0 +1,4 @@
+/*!
+ * Some fancy copyright
+ */
+console.log(" Library ");
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/manifest.json b/test/expected/build/library.h/dest-resources-json/resources/library/h/manifest.json
new file mode 100644
index 000000000..2e67ab58d
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/manifest.json
@@ -0,0 +1,39 @@
+{
+ "_version": "1.9.0",
+ "sap.app": {
+ "id": "library.h",
+ "type": "library",
+ "embeds": [
+ "components",
+ "components/subcomponent1",
+ "components/subcomponent2",
+ "components/subcomponent3"
+ ],
+ "applicationVersion": {
+ "version": "1.0.0"
+ },
+ "title": "Library D",
+ "description": "Library D",
+ "resources": "resources.json",
+ "offline": true
+ },
+ "sap.ui": {
+ "technology": "UI5",
+ "supportedThemes": []
+ },
+ "sap.ui5": {
+ "dependencies": {
+ "minUI5Version": "1.0",
+ "libs": {}
+ },
+ "library": {
+ "i18n": false,
+ "content": {
+ "controls": [],
+ "elements": [],
+ "types": [],
+ "interfaces": []
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js
new file mode 100644
index 000000000..c249a10c8
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/not.js
@@ -0,0 +1,4 @@
+/*!
+ * Some fancy copyright
+ */
+console.log(" Not including ");
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/resources.json b/test/expected/build/library.h/dest-resources-json/resources/library/h/resources.json
new file mode 100644
index 000000000..d0820dcf4
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/resources.json
@@ -0,0 +1,159 @@
+{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": ".library",
+ "size": 473
+ },
+ {
+ "name": "components/Component-preload.js",
+ "module": "library/h/components/Component-preload.js",
+ "size": 361,
+ "merged": true,
+ "included": [
+ "library/h/components/Component.js",
+ "library/h/components/TodoComponent.js"
+ ]
+ },
+ {
+ "name": "components/Component.js",
+ "module": "library/h/components/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "components/TodoComponent.js",
+ "module": "library/h/components/TodoComponent.js",
+ "size": 54,
+ "format": "raw"
+ },
+ {
+ "name": "components/subcomponent1/Component-preload.js",
+ "module": "library/h/components/subcomponent1/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent1/Component.js"
+ ]
+ },
+ {
+ "name": "components/subcomponent1/Component.js",
+ "module": "library/h/components/subcomponent1/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "components/subcomponent2/Component-preload.js",
+ "module": "library/h/components/subcomponent2/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent2/Component.js"
+ ]
+ },
+ {
+ "name": "components/subcomponent2/Component.js",
+ "module": "library/h/components/subcomponent2/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "components/subcomponent3/Component-preload.js",
+ "module": "library/h/components/subcomponent3/Component-preload.js",
+ "size": 279,
+ "merged": true,
+ "included": [
+ "library/h/components/subcomponent3/Component.js"
+ ]
+ },
+ {
+ "name": "components/subcomponent3/Component.js",
+ "module": "library/h/components/subcomponent3/Component.js",
+ "size": 115,
+ "required": [
+ "sap/ui/core/UIComponent.js"
+ ]
+ },
+ {
+ "name": "customBundle.js",
+ "module": "library/h/customBundle.js",
+ "size": 495,
+ "merged": true,
+ "included": [
+ "library/h/file.js",
+ "library/h/library.js",
+ "library/h/some.js",
+ "library/h/other.js",
+ "library/h/not.js"
+ ]
+ },
+ {
+ "name": "designtime/library.designtime.js",
+ "module": "library/h/designtime/library.designtime.js",
+ "size": 86,
+ "requiresTopLevelScope": true,
+ "exposedGlobalNames": [
+ "myexport"
+ ],
+ "format": "raw",
+ "designtime": true
+ },
+ {
+ "name": "file.js",
+ "module": "library/h/file.js",
+ "size": 54,
+ "format": "raw"
+ },
+ {
+ "name": "i18n/messagebundle.properties",
+ "module": "library/h/i18n/messagebundle.properties",
+ "size": 3,
+ "locale": "",
+ "raw": "i18n/messagebundle.properties"
+ },
+ {
+ "name": "i18n/messagebundle_en.properties",
+ "module": "library/h/i18n/messagebundle_en.properties",
+ "size": 3,
+ "locale": "en",
+ "raw": "i18n/messagebundle.properties"
+ },
+ {
+ "name": "library.js",
+ "module": "library/h/library.js",
+ "size": 57,
+ "format": "raw"
+ },
+ {
+ "name": "manifest.json",
+ "module": "library/h/manifest.json",
+ "size": 739
+ },
+ {
+ "name": "not.js",
+ "module": "library/h/not.js",
+ "size": 63,
+ "format": "raw"
+ },
+ {
+ "name": "resources.json",
+ "size": 3500
+ },
+ {
+ "name": "some.js",
+ "module": "library/h/some.js",
+ "size": 99,
+ "format": "raw",
+ "merged": true,
+ "included": [
+ "library/h/other.js"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js b/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js
new file mode 100644
index 000000000..1e86f9afc
--- /dev/null
+++ b/test/expected/build/library.h/dest-resources-json/resources/library/h/some.js
@@ -0,0 +1,5 @@
+/*!
+ * Some fancy copyright
+ */
+//@ui5-bundle-raw-include library/h/other.js
+console.log(" Some ");
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest-resources-json/test-resources/library/d/Test.html b/test/expected/build/library.h/dest-resources-json/test-resources/library/d/Test.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/expected/build/library.h/dest/resources/library/h/.library b/test/expected/build/library.h/dest/resources/library/h/.library
index 2b110f205..5bdab13ab 100644
--- a/test/expected/build/library.h/dest/resources/library/h/.library
+++ b/test/expected/build/library.h/dest/resources/library/h/.library
@@ -7,5 +7,13 @@
1.0.0
Library D
+
+
+
+
+
+
+
+
diff --git a/test/expected/build/library.h/dest/resources/library/h/customBundle.js b/test/expected/build/library.h/dest/resources/library/h/customBundle.js
index 21db087bb..e9e0e00ed 100644
--- a/test/expected/build/library.h/dest/resources/library/h/customBundle.js
+++ b/test/expected/build/library.h/dest/resources/library/h/customBundle.js
@@ -13,6 +13,12 @@ console.log(" Library ");
"library/h/some.js":function(){/*!
* Some fancy copyright
*/
+//@ui5-bundle-raw-include library/h/other.js
console.log(" Some ");
}
});
+//@ui5-bundle-raw-include library/h/not.js
+/*!
+ * Some fancy copyright
+ */
+console.log(" Not including ");
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js b/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js
new file mode 100644
index 000000000..9b135171d
--- /dev/null
+++ b/test/expected/build/library.h/dest/resources/library/h/designtime/library.designtime.js
@@ -0,0 +1,4 @@
+/*!
+ * Some fancy copyright
+ */
+var myexport=function(){"use strict";String("asd")}();
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle.properties b/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle.properties
new file mode 100644
index 000000000..1c6a1a9e8
--- /dev/null
+++ b/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle.properties
@@ -0,0 +1 @@
+a=b
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle_en.properties b/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle_en.properties
new file mode 100644
index 000000000..1c6a1a9e8
--- /dev/null
+++ b/test/expected/build/library.h/dest/resources/library/h/i18n/messagebundle_en.properties
@@ -0,0 +1 @@
+a=b
\ No newline at end of file
diff --git a/test/expected/build/library.h/dest/resources/library/h/some.js b/test/expected/build/library.h/dest/resources/library/h/some.js
index 98e1e6665..1e86f9afc 100644
--- a/test/expected/build/library.h/dest/resources/library/h/some.js
+++ b/test/expected/build/library.h/dest/resources/library/h/some.js
@@ -1,4 +1,5 @@
/*!
* Some fancy copyright
*/
+//@ui5-bundle-raw-include library/h/other.js
console.log(" Some ");
\ No newline at end of file
diff --git a/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/Button.less b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/Button.less
new file mode 100644
index 000000000..ca968183f
--- /dev/null
+++ b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/Button.less
@@ -0,0 +1,3 @@
+.someClass {
+ color: @someColor
+}
diff --git a/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-RTL.css b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-RTL.css
new file mode 100644
index 000000000..5009ca50e
--- /dev/null
+++ b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-RTL.css
@@ -0,0 +1,3 @@
+.someClass{color:#000}
+/* Inline theming parameters */
+#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')}
diff --git a/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-parameters.json b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-parameters.json
new file mode 100644
index 000000000..a190cda03
--- /dev/null
+++ b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library-parameters.json
@@ -0,0 +1 @@
+{"someColor":"#000"}
\ No newline at end of file
diff --git a/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.css b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.css
new file mode 100644
index 000000000..5009ca50e
--- /dev/null
+++ b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.css
@@ -0,0 +1,3 @@
+.someClass{color:#000}
+/* Inline theming parameters */
+#sap-ui-theme-theme\.j{background-image:url('data:text/plain;utf-8,%7B%22someColor%22%3A%22%23000%22%7D')}
diff --git a/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.source.less b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.source.less
new file mode 100644
index 000000000..834de919e
--- /dev/null
+++ b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/library.source.less
@@ -0,0 +1,2 @@
+@someColor: black;
+@import "Button.less";
diff --git a/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/resources.json b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/resources.json
new file mode 100644
index 000000000..d4c81a925
--- /dev/null
+++ b/test/expected/build/theme.j/dest-resources-json/resources/theme/j/themes/somefancytheme/resources.json
@@ -0,0 +1,37 @@
+{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": "Button.less",
+ "size": 34,
+ "designtime": true,
+ "theme": "somefancytheme"
+ },
+ {
+ "name": "library-RTL.css",
+ "size": 162,
+ "theme": "somefancytheme"
+ },
+ {
+ "name": "library-parameters.json",
+ "module": "theme/j/themes/somefancytheme/library-parameters.json",
+ "size": 20,
+ "theme": "somefancytheme"
+ },
+ {
+ "name": "library.css",
+ "size": 162,
+ "theme": "somefancytheme"
+ },
+ {
+ "name": "library.source.less",
+ "size": 42,
+ "designtime": true,
+ "theme": "somefancytheme"
+ },
+ {
+ "name": "resources.json",
+ "size": 633
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/fixtures/library.h/main/src/library/h/.library b/test/fixtures/library.h/main/src/library/h/.library
index 8d1e46775..797c61cc8 100644
--- a/test/fixtures/library.h/main/src/library/h/.library
+++ b/test/fixtures/library.h/main/src/library/h/.library
@@ -7,5 +7,13 @@
${version}
Library D
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/library.h/main/src/library/h/designtime/library.designtime.js b/test/fixtures/library.h/main/src/library/h/designtime/library.designtime.js
new file mode 100644
index 000000000..e961048c1
--- /dev/null
+++ b/test/fixtures/library.h/main/src/library/h/designtime/library.designtime.js
@@ -0,0 +1,14 @@
+/*!
+ * ${copyright}
+ */
+
+/**
+ * designtime and global export
+ */
+var myexport = (function() {
+
+ "use strict";
+
+ String("asd");
+
+}());
diff --git a/test/fixtures/library.h/main/src/library/h/i18n/messagebundle.properties b/test/fixtures/library.h/main/src/library/h/i18n/messagebundle.properties
new file mode 100644
index 000000000..1c6a1a9e8
--- /dev/null
+++ b/test/fixtures/library.h/main/src/library/h/i18n/messagebundle.properties
@@ -0,0 +1 @@
+a=b
\ No newline at end of file
diff --git a/test/fixtures/library.h/main/src/library/h/i18n/messagebundle_en.properties b/test/fixtures/library.h/main/src/library/h/i18n/messagebundle_en.properties
new file mode 100644
index 000000000..1c6a1a9e8
--- /dev/null
+++ b/test/fixtures/library.h/main/src/library/h/i18n/messagebundle_en.properties
@@ -0,0 +1 @@
+a=b
\ No newline at end of file
diff --git a/test/fixtures/library.h/main/src/library/h/some.js b/test/fixtures/library.h/main/src/library/h/some.js
index f14efacd4..c1a44398b 100644
--- a/test/fixtures/library.h/main/src/library/h/some.js
+++ b/test/fixtures/library.h/main/src/library/h/some.js
@@ -1,4 +1,5 @@
/*!
* ${copyright}
*/
+//@ui5-bundle-raw-include library/h/other.js
console.log(' Some ');
diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js
index 4c3d19821..06c27325a 100644
--- a/test/lib/builder/builder.js
+++ b/test/lib/builder/builder.js
@@ -8,6 +8,7 @@ const readFile = promisify(fs.readFile);
const assert = chai.assert;
const sinon = require("sinon");
const mock = require("mock-require");
+const resourceFactory = require("@ui5/fs").resourceFactory;
const ui5Builder = require("../../../");
const builder = ui5Builder.builder;
@@ -55,7 +56,7 @@ function cloneProjectTree(tree) {
return clone;
}
-async function checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath) {
+async function checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath) {
for (let i = 0; i < expectedFiles.length; i++) {
const expectedFile = expectedFiles[i];
const relativeFile = path.relative(expectedPath, expectedFile);
@@ -66,9 +67,16 @@ async function checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, des
if (expectedFile.endsWith("sap-ui-cachebuster-info.json")) {
currentContent = JSON.parse(currentContent.replace(/(:\s+)(\d+)/g, ": 0"));
expectedContent = JSON.parse(expectedContent.replace(/(:\s+)(\d+)/g, ": 0"));
- assert.deepEqual(currentContent, expectedContent);
+ t.deepEqual(currentContent, expectedContent);
} else {
- assert.equal(currentContent.replace(newLineRegexp, "\n"), expectedContent.replace(newLineRegexp, "\n"));
+ if (expectedFile.endsWith(".json")) {
+ try {
+ t.deepEqual(JSON.parse(currentContent), JSON.parse(expectedContent), expectedFile);
+ } catch (e) {
+ t.falsy(e, expectedFile);
+ }
+ }
+ t.is(currentContent.replace(newLineRegexp, "\n"), expectedContent.replace(newLineRegexp, "\n"), relativeFile);
}
};
await Promise.all([currentFileContentPromise, expectedFileContentPromise]).then(assertContents);
@@ -177,7 +185,7 @@ test("Build application.a", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -208,7 +216,7 @@ test("Build application.a with dependencies", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -229,7 +237,7 @@ test("Build application.a with dependencies include", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -250,7 +258,7 @@ test("Build application.a with dependencies exclude", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -271,7 +279,7 @@ test("Build application.a self-contained", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -293,7 +301,7 @@ test("Build application.a with dependencies self-contained", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -314,7 +322,7 @@ test("Build application.a [dev mode]", (t) => {
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -342,7 +350,7 @@ test("Build application.a and clean target path [dev mode]", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -362,7 +370,7 @@ test("Build application.g", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -382,7 +390,7 @@ test("Build application.g with component preload paths", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -403,7 +411,7 @@ test("Build application.g with excludes", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -424,7 +432,7 @@ test("Build application.h", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -444,7 +452,7 @@ test("Build application.i", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -464,7 +472,52 @@ test("Build application.j", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
+ }).then(() => {
+ t.pass();
+ });
+});
+
+test("Build application.j with resources.json and version info", (t) => {
+ const destPath = "./test/tmp/build/application.j/dest-resources-json";
+ const expectedPath = path.join("test", "expected", "build", "application.j", "dest-resources-json");
+
+
+ const dummyVersionInfoGenerator = () => {
+ const versionJson = {
+ "name": "application.j",
+ "version": "1.0.0",
+ "buildTimestamp": "202008120917",
+ "scmRevision": "",
+ "libraries": []
+ };
+
+ return [resourceFactory.createResource({
+ path: "/resources/sap-ui-version.json",
+ string: JSON.stringify(versionJson, null, "\t")
+ })];
+ };
+
+ mock("../../../lib/processors/versionInfoGenerator", dummyVersionInfoGenerator);
+ mock.reRequire("../../../lib/tasks/generateVersionInfo");
+
+ const builder = mock.reRequire("../../../lib/builder/builder");
+
+
+ return builder.build({
+ includedTasks: [
+ "generateResourcesJson"
+ ],
+ tree: applicationJTree,
+ destPath,
+ excludedTasks: ["createDebugFiles", "generateStandaloneAppBundle"]
+ }).then(() => {
+ return findFiles(expectedPath);
+ }).then((expectedFiles) => {
+ // Check for all directories and files
+ assert.directoryDeepEqual(destPath, expectedPath);
+ // Check for all file contents
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -485,7 +538,7 @@ test("Build library.d with copyright from .library file", (t) => {
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -506,7 +559,7 @@ test("Build library.e with copyright from settings of ui5.yaml", (t) => {
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -527,7 +580,31 @@ test("Build library.h with custom bundles and component-preloads", (t) => {
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
+ }).then(() => {
+ t.pass();
+ });
+});
+
+test("Build library.h with custom bundles and component-preloads with resources.json", (t) => {
+ const destPath = path.join("test", "tmp", "build", "library.h", "dest-resources-json");
+ const expectedPath = path.join("test", "expected", "build", "library.h", "dest-resources-json");
+
+ return builder.build({
+ includedTasks: [
+ "generateResourcesJson"
+ ],
+ tree: libraryHTree,
+ destPath,
+ excludedTasks: ["createDebugFiles", "generateLibraryPreload"]
+ }).then(() => {
+ return findFiles(expectedPath);
+ }).then((expectedFiles) => {
+ // Check for all directories and files
+ assert.directoryDeepEqual(destPath, expectedPath);
+
+ // Check for all file contents
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -548,7 +625,7 @@ test("Build library.i with manifest info taken from .library and library.js", (t
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -570,7 +647,7 @@ test("Build library.j with JSDoc build only", (t) => {
assert.directoryDeepEqual(destPath, expectedPath);
// Check for all file contents
- return checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, destPath);
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
}).then(() => {
t.pass();
});
@@ -588,13 +665,30 @@ test("Build theme.j even without an library", (t) => {
// Check for all directories and files
assert.directoryDeepEqual(destPath, expectedPath);
- // Check for all file contents
- expectedFiles.forEach((expectedFile) => {
- const relativeFile = path.relative(expectedPath, expectedFile);
- const destFile = path.join(destPath, relativeFile);
- assert.fileEqual(destFile, expectedFile);
- t.pass();
- });
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
+ }).then(() => {
+ t.pass();
+ });
+});
+
+test("Build theme.j even without an library with resources.json", (t) => {
+ const destPath = "./test/tmp/build/theme.j/dest-resources-json";
+ const expectedPath = "./test/expected/build/theme.j/dest-resources-json";
+ return builder.build({
+ includedTasks: [
+ "generateResourcesJson"
+ ],
+ tree: themeJTree,
+ destPath
+ }).then(() => {
+ return findFiles(expectedPath);
+ }).then((expectedFiles) => {
+ // Check for all directories and files
+ assert.directoryDeepEqual(destPath, expectedPath);
+
+ return checkFileContentsIgnoreLineFeeds(t, expectedFiles, expectedPath, destPath);
+ }).then(() => {
+ t.pass();
});
});
@@ -1181,11 +1275,19 @@ const libraryHTree = {
"library/h/some.js",
"library/h/library.js",
"library/h/file.js",
- "!library/h/not.js",
"!library/h/components/"
],
"resolve": false,
"renderer": false
+ }, {
+ "mode": "raw",
+ "filters": [
+ "library/h/not.js"
+ ],
+ "resolve": true,
+ "declareModules": false,
+ "sort": true,
+ "renderer": false
}]
},
"bundleOptions": {
diff --git a/test/lib/lbt/analyzer/JSModuleAnalyzer.js b/test/lib/lbt/analyzer/JSModuleAnalyzer.js
index 23a1b4be1..e08b57a6a 100644
--- a/test/lib/lbt/analyzer/JSModuleAnalyzer.js
+++ b/test/lib/lbt/analyzer/JSModuleAnalyzer.js
@@ -39,9 +39,7 @@ function analyze(file, name) {
reject(err);
}
try {
- const ast = esprima.parseScript(buffer.toString(), {comment: true});
- const info = new ModuleInfo(name);
- new JSModuleAnalyzer().analyze(ast, name, info);
+ const info = analyzeString(buffer.toString(), name);
resolve(info);
} catch (execErr) {
reject(execErr);
@@ -50,6 +48,13 @@ function analyze(file, name) {
});
}
+function analyzeString(content, name) {
+ const ast = esprima.parseScript(content, {comment: true});
+ const info = new ModuleInfo(name);
+ new JSModuleAnalyzer().analyze(ast, name, info);
+ return info;
+}
+
function assertModuleNamesEqual(t, actual, expected, msg) {
actual.sort();
expected.sort();
@@ -67,7 +72,8 @@ function analyzeModule(
expectedDependencies,
expectedConditionalDependencies,
expectedSubmodules,
- ignoreImplicitDependencies
+ ignoreImplicitDependencies,
+ rawModule
) {
//
analyze(file, name).then( (info) => {
@@ -96,6 +102,13 @@ function analyzeModule(
}
t.false(info.dynamicDependencies,
"no use of dynamic dependencies should have been detected");
+ if (rawModule) {
+ t.true(info.rawModule,
+ "raw module");
+ } else {
+ t.false(info.rawModule,
+ "ui5 module");
+ }
}).then(() => t.end(), (e) => t.fail(`failed to analyze module with error: ${e.message}`));
}
@@ -114,6 +127,7 @@ test.cb("DefineWithLegacyCalls", analyzeModule, "modules/define_with_legacy_call
test.cb("OldStyleModuleWithoutDeclare", function(t) {
analyze("modules/no_declare_but_requires.js", null).then((info) => {
t.is(info.name, null, "module name should be null");
+ t.true(info.rawModule, "raw module");
assertModuleNamesEqual(t,
info.dependencies,
["dependency1.js", "dependency2.js", "jquery.sap.global.js"],
@@ -122,7 +136,7 @@ test.cb("OldStyleModuleWithoutDeclare", function(t) {
});
});
-test.cb("NotAnUI5Module", analyzeModule, "modules/not_a_module.js", "modules/not_a_module.js", NO_DEPENDENCIES);
+test.cb("NotAnUI5Module", analyzeModule, "modules/not_a_module.js", "modules/not_a_module.js", NO_DEPENDENCIES, NO_DEPENDENCIES, NO_DEPENDENCIES, NO_DEPENDENCIES, true);
test.cb("AMDSpecialDependenciesShouldBeIgnored", (t) => {
analyzeModule(t,
@@ -239,7 +253,42 @@ test.cb("OldStyleBundle", (t) => {
analyzeModule(t,
"modules/bundle-oldstyle.js",
"sap-ui-core.js",
- [],
+ [
+ "jquery.sap.dom.js",
+ "jquery.sap.script.js",
+ "sap/ui/base/Object.js",
+ "sap/ui/base/BindingParser.js",
+ "sap/ui/base/EventProvider.js",
+ "sap/ui/base/ManagedObjectMetadata.js",
+ "sap/ui/model/BindingMode.js",
+ "sap/ui/model/CompositeBinding.js",
+ "sap/ui/model/Context.js",
+ "sap/ui/model/FormatException.js",
+ "sap/ui/model/ListBinding.js",
+ "sap/ui/model/Model.js",
+ "sap/ui/model/ParseException.js",
+ "sap/ui/model/TreeBinding.js",
+ "sap/ui/model/Type.js",
+ "sap/ui/model/ValidateException.js",
+ "jquery.sap.strings.js",
+ "sap/ui/Global.js",
+ "sap/ui/base/Interface.js",
+ "sap/ui/core/Component.js",
+ "sap/ui/core/Configuration.js",
+ "sap/ui/core/Control.js",
+ "sap/ui/core/Element.js",
+ "sap/ui/core/ElementMetadata.js",
+ "sap/ui/core/FocusHandler.js",
+ "sap/ui/core/RenderManager.js",
+ "sap/ui/core/ResizeHandler.js",
+ "sap/ui/core/ThemeCheck.js",
+ "sap/ui/core/UIArea.js",
+ "sap/ui/core/message/MessageManager.js",
+ "jquery.sap.mobile.js",
+ "jquery.sap.properties.js",
+ "jquery.sap.resources.js",
+ "jquery.sap.sjax.js"
+ ],
[],
[
"sap/ui/Device.js",
@@ -266,7 +315,42 @@ test.cb("OldStyleBundleV2", (t) => {
analyzeModule(t,
"modules/bundle-oldstyle-v2.js",
"sap-ui-core.js",
- [],
+ [
+ "jquery.sap.dom.js",
+ "jquery.sap.script.js",
+ "sap/ui/base/Object.js",
+ "sap/ui/base/BindingParser.js",
+ "sap/ui/base/EventProvider.js",
+ "sap/ui/base/ManagedObjectMetadata.js",
+ "sap/ui/model/BindingMode.js",
+ "sap/ui/model/CompositeBinding.js",
+ "sap/ui/model/Context.js",
+ "sap/ui/model/FormatException.js",
+ "sap/ui/model/ListBinding.js",
+ "sap/ui/model/Model.js",
+ "sap/ui/model/ParseException.js",
+ "sap/ui/model/TreeBinding.js",
+ "sap/ui/model/Type.js",
+ "sap/ui/model/ValidateException.js",
+ "jquery.sap.strings.js",
+ "sap/ui/Global.js",
+ "sap/ui/base/Interface.js",
+ "sap/ui/core/Component.js",
+ "sap/ui/core/Configuration.js",
+ "sap/ui/core/Control.js",
+ "sap/ui/core/Element.js",
+ "sap/ui/core/ElementMetadata.js",
+ "sap/ui/core/FocusHandler.js",
+ "sap/ui/core/RenderManager.js",
+ "sap/ui/core/ResizeHandler.js",
+ "sap/ui/core/ThemeCheck.js",
+ "sap/ui/core/UIArea.js",
+ "sap/ui/core/message/MessageManager.js",
+ "jquery.sap.mobile.js",
+ "jquery.sap.properties.js",
+ "jquery.sap.resources.js",
+ "jquery.sap.sjax.js"
+ ],
[],
[
"sap/ui/Device.js",
@@ -293,7 +377,60 @@ test.cb("EvoBundle", (t) => {
analyzeModule(t,
"modules/bundle-evo.js",
"sap-ui-core.js",
- [],
+ [
+ "sap/base/util/now.js",
+ "sap/base/util/Version.js",
+ "sap/ui/dom/getComputedStyleFix.js",
+ "sap/ui/dom/activeElementFix.js",
+ "sap/ui/dom/includeScript.js",
+ "sap/ui/dom/includeStylesheet.js",
+ "sap/ui/core/support/Hotkeys.js",
+ "sap/ui/security/FrameOptions.js",
+ "sap/ui/performance/Measurement.js",
+ "sap/ui/performance/trace/Interaction.js",
+ "sap/ui/base/syncXHRFix.js",
+ "sap/base/util/LoaderExtensions.js",
+ "sap/base/util/defineLazyProperty.js",
+ "sap/base/util/ObjectPath.js",
+ "sap/base/util/isPlainObject.js",
+ "sap/ui/base/Object.js",
+ "sap/ui/base/BindingParser.js",
+ "sap/ui/base/EventProvider.js",
+ "sap/ui/base/ManagedObjectMetadata.js",
+ "sap/ui/model/BindingMode.js",
+ "sap/ui/model/StaticBinding.js",
+ "sap/ui/model/CompositeBinding.js",
+ "sap/ui/model/Context.js",
+ "sap/ui/model/FormatException.js",
+ "sap/ui/model/ParseException.js",
+ "sap/ui/model/Type.js",
+ "sap/ui/model/ValidateException.js",
+ "sap/ui/base/SyncPromise.js",
+ "sap/ui/util/ActivityDetection.js",
+ "sap/base/util/deepClone.js",
+ "sap/base/util/deepEqual.js",
+ "sap/base/util/uid.js",
+ "sap/ui/Global.js",
+ "sap/ui/base/Interface.js",
+ "sap/ui/core/Component.js",
+ "sap/ui/core/Configuration.js",
+ "sap/ui/core/Control.js",
+ "sap/ui/core/Element.js",
+ "sap/ui/core/ElementMetadata.js",
+ "sap/ui/core/FocusHandler.js",
+ "sap/ui/core/RenderManager.js",
+ "sap/ui/core/ResizeHandler.js",
+ "sap/ui/core/ThemeCheck.js",
+ "sap/ui/core/UIArea.js",
+ "sap/ui/core/message/MessageManager.js",
+ "sap/ui/dom/getScrollbarSize.js",
+ "sap/base/i18n/ResourceBundle.js",
+ "sap/base/util/array/uniqueSort.js",
+ "sap/ui/performance/trace/initTraces.js",
+ "sap/base/util/isEmptyObject.js",
+ "sap/base/util/each.js",
+ "sap/ui/events/jquery/EventSimulation.js"
+ ],
[],
[
"sap/ui/thirdparty/baseuri.js",
@@ -333,6 +470,8 @@ test("Bundle", (t) => {
];
t.deepEqual(info.subModules, expected, "module dependencies should match");
t.truthy(info.dependencies.every((dep) => !info.isConditionalDependency(dep)), "none of the dependencies must be 'conditional'");
+ t.false(info.rawModule,
+ "ui5 module");
});
});
@@ -360,6 +499,8 @@ test("ES6 Syntax", (t) => {
"all dependencies other than 'conditional/*' and 'static/*' should be implicit");
t.false(info.dynamicDependencies,
"no use of dynamic dependencies should have been detected");
+ t.false(info.rawModule,
+ "ui5 module");
});
});
});
@@ -368,6 +509,8 @@ test("Dynamic import (declare/require)", (t) => {
return analyze("modules/declare_dynamic_require.js").then((info) => {
t.true(info.dynamicDependencies,
"the use of dynamic dependencies should have been detected");
+ t.false(info.rawModule,
+ "ui5 module");
});
});
@@ -375,6 +518,8 @@ test("Dynamic import (define/require)", (t) => {
return analyze("modules/amd_dynamic_require.js").then((info) => {
t.true(info.dynamicDependencies,
"the use of dynamic dependencies should have been detected");
+ t.false(info.rawModule,
+ "ui5 module");
});
});
@@ -382,7 +527,96 @@ test("Dynamic import (define/requireSync)", (t) => {
return analyze("modules/amd_dynamic_require_sync.js").then((info) => {
t.true(info.dynamicDependencies,
"the use of dynamic dependencies should have been detected");
+ t.false(info.rawModule,
+ "ui5 module");
});
});
+test("Nested require", (t) => {
+ const content = `
+(function(deps, callback) {
+ function doIt(array, callback) {
+ callback();
+ }
+ var aArray = [];
+ doIt(aArray, function() {
+ doIt(["foo"], function() {
+ doIt(["bar"], function() {
+ // nested sap.ui.require
+ sap.ui.require(deps, callback);
+ });
+ });
+ });
+}([
+ "my/dependency"
+], function(myDep) {
+ console.log("done")
+}));`;
+ const info = analyzeString(content, "modules/nestedRequire.js");
+ t.true(info.rawModule, "raw module");
+});
+
+test("Toplevel define", (t) => {
+ const content = `
+(function() {
+ function defineMyFile() {
+ sap.ui.define('def/MyFile', ['dep/myDep'],
+ function(myDep) {
+ return 47;
+ });
+ }
+
+ // conditional
+ if (!(window.sap && window.sap.ui && window.sap.ui.define)) {
+ var fnHandler = function() {
+ defineMyFile();
+ };
+ my.addEventListener("myevent", fnHandler);
+ } else {
+ defineMyFile();
+ }
+}()); `;
+ const info = analyzeString(content, "modules/functionDefine.js");
+ t.true(info.rawModule, "raw module");
+});
+
+test("Invalid ui5 bundle comment", (t) => {
+ const content = `/@ui5-bundles sap/ui/thirdparty/xxx.js
+if(!('xxx'in Node.prototype)){}
+//@ui5-bundle-raw-includes sap/ui/thirdparty/aaa.js
+(function(g,f){g.AAA=f();}(this,(function(){})));
+sap.ui.define("my/module", ["sap/ui/core/UIComponent"],function(n){"use strict";return 47+n});`;
+ const info = analyzeString(content, "modules/bundle-evo_invalid_comment.js");
+ t.is(info.name, "my/module.js",
+ "module name matches");
+ t.deepEqual(info.subModules, [],
+ "no submodules");
+});
+
+test("Declare two times", (t) => {
+ const content = `jQuery.sap.declare("sap.ui.testmodule");
+sap.ui.testmodule.load = function(modName) {
+ jQuery.sap.require(modName);
+};
+jQuery.sap.declare("sap.ui.testmodule");`;
+ const info = analyzeString(content, "modules/declare_times_two.js");
+ t.is(info.name, "sap/ui/testmodule.js",
+ "module name matches");
+ t.deepEqual(info.subModules, [],
+ "no submodules");
+});
+
+test("Declare dynamic name", (t) => {
+ const content = `var sCommonName = "sap.ui"
+jQuery.sap.declare(sCommonName + ".testmodule");
+
+sap.ui.testmodule.load = function(modName) {
+ jQuery.sap.require(modName);
+};`;
+ const info = analyzeString(content, "modules/dynamic_name.js");
+ t.is(info.name, "modules/dynamic_name.js",
+ "module name matches");
+ t.deepEqual(info.subModules, [],
+ "no submodules");
+});
diff --git a/test/lib/lbt/resources/Resource.js b/test/lib/lbt/resources/Resource.js
new file mode 100644
index 000000000..b6e0670ce
--- /dev/null
+++ b/test/lib/lbt/resources/Resource.js
@@ -0,0 +1,33 @@
+const test = require("ava");
+const sinon = require("sinon");
+const mock = require("mock-require");
+
+let Resource = require("../../../../lib/lbt/resources/Resource");
+
+test.serial("Resource: buffer", async (t) => {
+ const readFileStub = sinon.stub().callsArg(1);
+ mock("graceful-fs", {
+ readFile: readFileStub
+ });
+ mock.reRequire("graceful-fs");
+
+ // Re-require tested module
+ Resource = mock.reRequire("../../../../lib/lbt/resources/Resource");
+ const resource = new Resource({}, "name", "file");
+ await resource.buffer();
+
+ mock.stop("graceful-fs");
+
+ t.is(readFileStub.callCount, 1, "called once");
+ t.is(readFileStub.getCall(0).args[0], "file", "called with file parameter");
+});
+
+test.serial("Resource: constructor", async (t) => {
+ const resource = new Resource({}, "name", "file");
+ t.is(resource.fileSize, -1, "called once");
+});
+
+test.serial("Resource: constructor with stat", async (t) => {
+ const resource = new Resource({}, "name", "file", {size: 47});
+ t.is(resource.fileSize, 47, "called once");
+});
diff --git a/test/lib/lbt/resources/ResourceCollector.js b/test/lib/lbt/resources/ResourceCollector.js
new file mode 100644
index 000000000..6bd5763a6
--- /dev/null
+++ b/test/lib/lbt/resources/ResourceCollector.js
@@ -0,0 +1,161 @@
+const test = require("ava");
+const sinon = require("sinon");
+const mock = require("mock-require");
+
+let ResourceCollector = require("../../../../lib/lbt/resources/ResourceCollector");
+
+test.beforeEach((t) => {
+ // Spying logger of processors/bootstrapHtmlTransformer
+ const log = require("@ui5/logger");
+ const loggerInstance = log.getLogger("lbt:resources:ResourceCollector");
+ mock("@ui5/logger", {
+ getLogger: () => loggerInstance
+ });
+ mock.reRequire("@ui5/logger");
+ t.context.logWarnSpy = sinon.spy(loggerInstance, "warn");
+
+ // Re-require tested module
+ ResourceCollector = mock.reRequire("../../../../lib/lbt/resources/ResourceCollector");
+});
+
+test.afterEach.always((t) => {
+ mock.stop("@ui5/logger");
+ t.context.logWarnSpy.restore();
+});
+
+
+test.serial("add: empty constructor dummy params", (t) => {
+ const resourceCollector = new ResourceCollector({}, {});
+ t.is(resourceCollector.resources.size, 0, "empty");
+});
+
+test.serial("add: empty constructor", (t) => {
+ const resourceCollector = new ResourceCollector();
+ t.is(resourceCollector.resources.size, 0, "empty");
+});
+
+test.serial("setExternalResources: empty filters", (t) => {
+ const resourceCollector = new ResourceCollector();
+ resourceCollector.setExternalResources({
+ "testcomp": []
+ });
+ const orphanFilters = resourceCollector.createOrphanFilters();
+ t.is(orphanFilters.size, 1, "1 filter");
+});
+
+test.serial("createOrphanFilters: filters", (t) => {
+ const resourceCollector = new ResourceCollector();
+ resourceCollector.setExternalResources({
+ "testcomp": ["test"],
+ "/": ["test"],
+ "": ["test"],
+ "a/": ["test"],
+ "b": ["test"],
+ });
+ const orphanFilters = resourceCollector.createOrphanFilters();
+ t.is(orphanFilters.size, 4, "4 filters");
+});
+
+test.serial("visitResource: path", async (t) => {
+ const resourceCollector = new ResourceCollector();
+ await resourceCollector.visitResource({getPath: () => "mypath", getSize: async () => 13});
+ t.is(t.context.logWarnSpy.callCount, 1);
+ t.is(t.context.logWarnSpy.getCall(0).args[0], "non-runtime resource mypath ignored");
+});
+
+test.serial("visitResource: library.source.less", async (t) => {
+ const resourceCollector = new ResourceCollector();
+ t.is(resourceCollector.themePackages.size, 0, "initially there is no theme package");
+ await resourceCollector.visitResource({getPath: () => "/resources/themes/a/library.source.less", getSize: async () => 13});
+ t.is(resourceCollector.themePackages.size, 1, "theme package was added");
+});
+
+test.serial("groupResourcesByComponents: debugBundles", async (t) => {
+ const resourceCollector = new ResourceCollector();
+ resourceCollector.setExternalResources({
+ "testcomp": ["my/file.js"]
+ });
+ await resourceCollector.visitResource({getPath: () => "/resources/testcomp/Component.js", getSize: async () => 13});
+ await resourceCollector.visitResource({getPath: () => "/resources/my/file.js", getSize: async () => 13});
+ resourceCollector.groupResourcesByComponents({debugBundles: [".*-dbg.js"]});
+ t.is(resourceCollector.resources.size, 0, "all resources were deleted");
+});
+
+test.serial("groupResourcesByComponents: theme", async (t) => {
+ const resourceCollector = new ResourceCollector();
+ await resourceCollector.visitResource({getPath: () => "/resources/themes/a/.theming", getSize: async () => 13});
+ t.is(resourceCollector.themePackages.size, 1, "1 theme was added");
+ await resourceCollector.determineResourceDetails({});
+ resourceCollector.groupResourcesByComponents({});
+ t.is(resourceCollector.themePackages.get("themes/a/").resources.length, 1, "1 theme was grouped");
+});
+
+test.serial("determineResourceDetails: properties", async (t) => {
+ const resourceCollector = new ResourceCollector({
+ getModuleInfo: async (moduleInfo) => {
+ return {
+ name: "myName"
+ };
+ }
+ });
+ await resourceCollector.visitResource({getPath: () => "/resources/mylib/manifest.json", getSize: async () => 13});
+ await resourceCollector.visitResource({getPath: () => "/resources/mylib/i18n/i18n_de.properties", getSize: async () => 13});
+ await resourceCollector.visitResource({getPath: () => "/resources/mylib/i18n/i18n.properties", getSize: async () => 13});
+ await resourceCollector.determineResourceDetails({});
+ resourceCollector.groupResourcesByComponents({});
+ const resources = resourceCollector.components.get("mylib/").resources;
+ t.deepEqual(resources.map((res) => res.i18nName), [null, "i18n/i18n.properties", "i18n/i18n.properties"], "i18nName was set");
+});
+
+test.serial("determineResourceDetails: view.xml", async (t) => {
+ const resourceCollector = new ResourceCollector({
+ getModuleInfo: async (moduleInfo) => {
+ return {
+ name: "myName"
+ };
+ }
+ });
+ const enrichWithDependencyInfoStub = sinon.stub(resourceCollector, "enrichWithDependencyInfo").returns(Promise.resolve());
+ await resourceCollector.visitResource({getPath: () => "/resources/mylib/my.view.xml", getSize: async () => 13});
+ await resourceCollector.determineResourceDetails({});
+ t.is(enrichWithDependencyInfoStub.callCount, 1, "is called once");
+ t.is(enrichWithDependencyInfoStub.getCall(0).args[0].name, "mylib/my.view.xml", "is called with view");
+});
+
+test.serial("enrichWithDependencyInfo: add infos to resourceinfo", async (t) => {
+ const resourceCollector = new ResourceCollector({
+ getModuleInfo: async () => {
+ return {
+ name: "myname",
+ dynamicDependencies: true,
+ isConditionalDependency: (dep) => {
+ return dep.includes("conditional");
+ },
+ isImplicitDependency: (dep) => {
+ return dep.includes("implicit");
+ },
+ dependencies: [
+ "mydependency.conditional", "mydependency.implicit", "mydependency"
+ ],
+ subModules: [
+ "mySubmodule"
+ ],
+ requiresTopLevelScope: true,
+ exposedGlobals: ["myGlobal"],
+ rawModule: true
+ };
+ }
+ });
+ const resourceInfo = {};
+ await resourceCollector.enrichWithDependencyInfo(resourceInfo);
+ t.deepEqual(resourceInfo, {
+ condRequired: new Set(["mydependency.conditional"]),
+ dynRequired: true,
+ exposedGlobalNames: new Set(["myGlobal"]),
+ format: "raw",
+ included: new Set(["mySubmodule"]),
+ module: "myname",
+ required: new Set(["mydependency"]),
+ requiresTopLevelScope: true
+ }, "all information gets used for the resourceInfo");
+});
diff --git a/test/lib/lbt/resources/ResourceInfo.js b/test/lib/lbt/resources/ResourceInfo.js
new file mode 100644
index 000000000..67d264520
--- /dev/null
+++ b/test/lib/lbt/resources/ResourceInfo.js
@@ -0,0 +1,101 @@
+const test = require("ava");
+
+const ResourceInfo = require("../../../../lib/lbt/resources/ResourceInfo");
+
+test("ResourceInfo: constructor", async (t) => {
+ const resourceInfo = new ResourceInfo("myName");
+ t.falsy(resourceInfo.module, "module not set");
+ t.falsy(resourceInfo.format, "format not set");
+ t.is(resourceInfo.size, -1, "size not set");
+});
+
+test("ResourceInfo: copyFrom", async (t) => {
+ // setup
+ const resourceInfo = new ResourceInfo("myName");
+ const origResourceInfo = new ResourceInfo("origMyName");
+ origResourceInfo.i18nName = "i18nName";
+ origResourceInfo.i18nLocale = "i18nLocale";
+ origResourceInfo.isDebug = false;
+ origResourceInfo.theme = "theme";
+ origResourceInfo.merged = false;
+ origResourceInfo.designtime = false;
+ origResourceInfo.support = false;
+ origResourceInfo.module = "module";
+ origResourceInfo.required = new Set(["required"]);
+ origResourceInfo.condRequired = new Set(["condRequired"]);
+ origResourceInfo.included = new Set(["included"]);
+ origResourceInfo.dynRequired = false;
+ origResourceInfo.requiresTopLevelScope = false;
+ origResourceInfo.exposedGlobalNames = new Set(["myGlobal"]);
+ origResourceInfo.format = "raw";
+ origResourceInfo.size = 13;
+
+ // action
+ resourceInfo.copyFrom("prefix", origResourceInfo);
+
+ // expectation
+ t.is(resourceInfo.i18nName, "i18nName", "value is copied over");
+ t.is(resourceInfo.i18nLocale, "i18nLocale", "value is copied over");
+ t.is(resourceInfo.isDebug, false, "value is copied over");
+ t.is(resourceInfo.theme, "theme", "value is copied over");
+ t.is(resourceInfo.merged, true, "value is copied over");
+ t.is(resourceInfo.designtime, false, "value is copied over");
+ t.is(resourceInfo.support, false, "value is copied over");
+ t.is(resourceInfo.module, "module", "value is copied over");
+ t.deepEqual(resourceInfo.required, new Set(["required"]), "value is copied over");
+ t.deepEqual(resourceInfo.condRequired, new Set(["condRequired"]), "value is copied over");
+ t.deepEqual(resourceInfo.included, new Set(["included"]), "value is copied over");
+ t.is(resourceInfo.dynRequired, false, "value is copied over");
+ t.is(resourceInfo.requiresTopLevelScope, false, "value is copied over");
+ t.deepEqual(resourceInfo.exposedGlobalNames, new Set(["myGlobal"]), "value is copied over");
+ t.is(resourceInfo.format, "raw", "value is copied over");
+ t.is(resourceInfo.size, 13, "value is copied over");
+});
+
+test("ResourceInfo: toJSON", async (t) => {
+ const resourceInfo = new ResourceInfo("myName");
+ resourceInfo.i18nName = "i18nName";
+ resourceInfo.i18nLocale = "i18nLocale";
+ resourceInfo.isDebug = true;
+ resourceInfo.theme = "theme";
+ resourceInfo.merged = true;
+ resourceInfo.designtime = true;
+ resourceInfo.support = true;
+ resourceInfo.module = "module";
+ resourceInfo.required = new Set(["required"]);
+ resourceInfo.condRequired = new Set(["condRequired"]);
+ resourceInfo.included = new Set(["included"]);
+ resourceInfo.dynRequired = true;
+ resourceInfo.requiresTopLevelScope = true;
+ resourceInfo.exposedGlobalNames = new Set(["myGlobal"]);
+ resourceInfo.format = "raw";
+ resourceInfo.size = 13;
+
+ t.deepEqual(resourceInfo.toJSON(), {
+ condRequired: [
+ "condRequired",
+ ],
+ designtime: true,
+ dynRequired: true,
+ exposedGlobalNames: [
+ "myGlobal",
+ ],
+ format: "raw",
+ included: [
+ "included",
+ ],
+ isDebug: true,
+ locale: "i18nLocale",
+ merged: true,
+ module: "module",
+ name: "myName",
+ raw: "i18nName",
+ required: [
+ "required",
+ ],
+ requiresTopLevelScope: true,
+ size: 13,
+ support: true,
+ theme: "theme",
+ }, "json content is correct");
+});
diff --git a/test/lib/lbt/resources/ResourceInfoList.js b/test/lib/lbt/resources/ResourceInfoList.js
new file mode 100644
index 000000000..8a0be81af
--- /dev/null
+++ b/test/lib/lbt/resources/ResourceInfoList.js
@@ -0,0 +1,101 @@
+const test = require("ava");
+
+const ResourceInfoList = require("../../../../lib/lbt/resources/ResourceInfoList");
+
+test("add: add new resources", (t) => {
+ const resourceInfoList = new ResourceInfoList("prefix");
+
+ const myInfo = {name: "myfile.js", size: 13};
+ t.deepEqual(resourceInfoList.resources, [], "empty list");
+
+ resourceInfoList.add(myInfo);
+
+ t.is(resourceInfoList.resources.length, 1, "one entry");
+
+ const result = resourceInfoList.resources[0];
+ t.falsy(result.module, "module is not set");
+});
+
+test("add: add source then debug resources", (t) => {
+ const resourceInfoList = new ResourceInfoList("prefix");
+
+ resourceInfoList.add({name: "myfile.js", module: "myfile.js", size: 13});
+
+ const myInfo = {name: "myfile-dbg.js", size: 13};
+ resourceInfoList.add(myInfo);
+
+ t.is(resourceInfoList.resources.length, 2, "two entries");
+
+ const result = resourceInfoList.resourcesByName.get("../myfile.js");
+ t.is(result.module, "myfile.js", "module is set");
+
+ const resultDbg = resourceInfoList.resourcesByName.get("../myfile-dbg.js");
+ t.is(resultDbg.module, "myfile.js", "module is set");
+});
+
+test("add: add debug then source resources", (t) => {
+ const resourceInfoList = new ResourceInfoList("prefix");
+
+ resourceInfoList.add({name: "myfile-dbg.js", size: 13});
+
+ const myInfo = {name: "myfile.js", module: "myfile.js", size: 13};
+ resourceInfoList.add(myInfo);
+
+ t.is(resourceInfoList.resources.length, 2, "two entries");
+
+ const result = resourceInfoList.resourcesByName.get("../myfile.js");
+ t.is(result.module, "myfile.js", "module is set");
+
+ const resultDbg = resourceInfoList.resourcesByName.get("../myfile-dbg.js");
+ t.is(resultDbg.module, "myfile.js", "module is set");
+});
+
+test("add: add i18n resource", (t) => {
+ const resourceInfoList = new ResourceInfoList("prefix");
+
+ resourceInfoList.add({name: "i18n_en.properties", i18nName: "i18n.properties", size: 13});
+
+ t.is(resourceInfoList.resources.length, 1, "one entry");
+
+ const result = resourceInfoList.resourcesByName.get("../i18n_en.properties");
+ t.is(result.i18nName, "../i18n.properties", "i18n name is set relative");
+});
+
+test("add: resource with the same name", (t) => {
+ const resourceInfoList = new ResourceInfoList("prefix");
+
+ resourceInfoList.add({name: "myfile.js", size: 13});
+ resourceInfoList.add({name: "myfile.js", size: 13});
+
+ t.is(resourceInfoList.resources.length, 1, "one entry");
+
+ const result = resourceInfoList.resourcesByName.get("../myfile.js");
+ t.is(result.name, "../myfile.js", "name is set relative");
+});
+
+test("toJSON: resource with the same name", (t) => {
+ const resourceInfoList = new ResourceInfoList("prefix");
+
+ resourceInfoList.resources.push({name: "myfile.js", size: 13});
+ resourceInfoList.resources.push({name: "myfile.js", size: 13});
+
+ t.deepEqual(resourceInfoList.toJSON(), {
+ _version: "1.1.0",
+ resources: [
+ {
+ name: "myfile.js",
+ size: 13,
+ },
+ {
+ name: "myfile.js",
+ size: 13,
+ },
+ ],
+ }, "one entry");
+});
+
+test("makePathRelativeTo: same prefix", (t) => {
+ const relativePath = ResourceInfoList.makePathRelativeTo("am/bn/cf", "args/myfile.js");
+
+ t.is(relativePath, "../../../args/myfile.js", "relative path");
+});
diff --git a/test/lib/processors/resourceListCreator.js b/test/lib/processors/resourceListCreator.js
new file mode 100644
index 000000000..27547de6c
--- /dev/null
+++ b/test/lib/processors/resourceListCreator.js
@@ -0,0 +1,111 @@
+const test = require("ava");
+const sinon = require("sinon");
+const mock = require("mock-require");
+
+let resourceListCreator = require("../../../lib/processors/resourceListCreator");
+const resourceFactory = require("@ui5/fs").resourceFactory;
+
+test.beforeEach((t) => {
+ // Spying logger of processors/bootstrapHtmlTransformer
+ const log = require("@ui5/logger");
+ const loggerInstance = log.getLogger("builder:processors:resourceListCreator");
+ mock("@ui5/logger", {
+ getLogger: () => loggerInstance
+ });
+ mock.reRequire("@ui5/logger");
+ t.context.logErrorSpy = sinon.spy(loggerInstance, "error");
+
+ // Re-require tested module
+ resourceListCreator = mock.reRequire("../../../lib/processors/resourceListCreator");
+});
+
+test.afterEach.always((t) => {
+ mock.stop("@ui5/logger");
+ t.context.logErrorSpy.restore();
+});
+
+test.serial("Empty resources", async (t) => {
+ const result = await resourceListCreator({
+ resources: []
+ });
+ t.deepEqual(result, []);
+});
+
+test.serial("Empty resources but options", async (t) => {
+ const result = await resourceListCreator({
+ resources: []
+ }, {
+ externalResources: {
+ "mycomp": [".*dbg.js"]
+ }
+ });
+ t.deepEqual(result, []);
+});
+
+test.serial("Orphaned resources", async (t) => {
+ const resource = resourceFactory.createResource({
+ path: "/resources/nomodule.foo",
+ string: "bar content"
+ });
+ const errorObject = await t.throwsAsync(() => {
+ return resourceListCreator({
+ resources: [resource]
+ });
+ });
+ t.is(errorObject.message, "resources.json generation failed with error: There are 1 resources which could not be assigned to components.");
+ t.is(t.context.logErrorSpy.callCount, 1);
+ t.is(t.context.logErrorSpy.getCall(0).args[0], "resources.json generation failed because of unassigned resources: nomodule.foo");
+});
+
+// 114,134-168,174-175
+
+test.serial("components and themes", async (t) => {
+ const componentResource = resourceFactory.createResource({
+ path: "/resources/mylib/manifest.json",
+ string: "bar content"
+ });
+ const themeResource = resourceFactory.createResource({
+ path: "/resources/themes/a/.theming",
+ string: "base less content"
+ });
+ const resources = await resourceListCreator({
+ resources: [componentResource, themeResource]
+ });
+
+ t.is(resources.length, 2);
+ const libResourceJson = resources[0];
+ const themeResourceJson = resources[1];
+ t.is(libResourceJson.getPath(), "/resources/mylib/resources.json");
+ t.is(themeResourceJson.getPath(), "/resources/themes/a/resources.json");
+
+ const libResourcesJsonContent = await libResourceJson.getString();
+ const themeResourcesJsonContent = await themeResourceJson.getString();
+ t.is(libResourcesJsonContent, `{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": "manifest.json",
+ "module": "mylib/manifest.json",
+ "size": 11
+ },
+ {
+ "name": "resources.json",
+ "size": 183
+ }
+ ]
+}`);
+ t.is(themeResourcesJsonContent, `{
+ "_version": "1.1.0",
+ "resources": [
+ {
+ "name": ".theming",
+ "size": 17,
+ "theme": "a"
+ },
+ {
+ "name": "resources.json",
+ "size": 159
+ }
+ ]
+}`);
+});
diff --git a/test/lib/tasks/generateResourcesJson.js b/test/lib/tasks/generateResourcesJson.js
new file mode 100644
index 000000000..992f20bc3
--- /dev/null
+++ b/test/lib/tasks/generateResourcesJson.js
@@ -0,0 +1,68 @@
+const test = require("ava");
+const sinon = require("sinon");
+const mock = require("mock-require");
+
+
+const ui5Fs = require("@ui5/fs");
+const resourceFactory = ui5Fs.resourceFactory;
+
+function createWorkspace() {
+ return resourceFactory.createAdapter({
+ virBasePath: "/",
+ project: {
+ metadata: {
+ name: "test.lib"
+ },
+ version: "2.0.0",
+ dependencies: [
+ {
+ metadata: {
+ name: "sap.ui.core"
+ },
+ version: "1.0.0"
+ }
+ ]
+ }
+ });
+}
+
+test.beforeEach((t) => {
+ t.context.resourceListCreatorStub = sinon.stub();
+ t.context.resourceListCreatorStub.returns(Promise.resolve([]));
+ mock("../../../lib/processors/resourceListCreator", t.context.resourceListCreatorStub);
+ mock.reRequire("../../../lib/processors/resourceListCreator");
+});
+
+test.afterEach.always((t) => {
+ mock.stop("../../../lib/processors/resourceListCreator");
+});
+
+test("empty resources", async (t) => {
+ const generateResourcesJson = require("../../../lib/tasks/generateResourcesJson");
+
+ const result = await generateResourcesJson({workspace: createWorkspace(), dependencies: undefined, options: {projectName: "sap.ui.core"}});
+ t.deepEqual(result, undefined, "no resources returned");
+ t.is(t.context.resourceListCreatorStub.callCount, 1);
+ const expectedOptions = {
+ externalResources: {
+ "sap/ui/core": [
+ "*",
+ "sap/base/",
+ "sap/ui/"
+ ]
+ },
+ mergedResourcesFilters: [
+ "jquery-sap*.js",
+ "sap-ui-core*.js",
+ "**/Component-preload.js",
+ "**/library-preload.js",
+ "**/library-preload-dbg.js",
+ "**/library-preload.json",
+ "**/library-all.js",
+ "**/library-all-dbg.js",
+ "**/designtime/library-preload.designtime.js",
+ "**/library-preload.support.js"
+ ]
+ };
+ t.deepEqual(t.context.resourceListCreatorStub.getCall(0).args[1], expectedOptions, "options match");
+});
diff --git a/test/lib/tasks/uglify.js b/test/lib/tasks/uglify.js
index fe397df7d..135743494 100644
--- a/test/lib/tasks/uglify.js
+++ b/test/lib/tasks/uglify.js
@@ -47,3 +47,142 @@ test();`;
return t.deepEqual(buffer.toString(), expected, "Correct content");
});
});
+
+test("integration: uglify copyright", (t) => {
+ const reader = resourceFactory.createAdapter({
+ virBasePath: "/"
+ });
+ const writer = resourceFactory.createAdapter({
+ virBasePath: "/"
+ });
+ const duplexCollection = new DuplexCollection({reader: reader, writer: writer});
+ const content = `
+/*
+ * Copyright jQuery Foundation and other contributors
+ */
+function test(paramA) {
+ var variableA = paramA;
+ console.log(variableA);
+}
+test();`;
+ const testResource = resourceFactory.createResource({
+ path: "/test.js",
+ string: content
+ });
+ const expected = `/*
+ * Copyright jQuery Foundation and other contributors
+ */
+function test(t){var o=t;console.log(o)}test();`;
+
+ return reader.write(testResource)
+ .then(() => {
+ return reader.byPath("/test.js");
+ }).then(() => {
+ return uglify({
+ workspace: duplexCollection,
+ options: {
+ pattern: "/test.js"
+ }
+ });
+ }).then(() => {
+ return writer.byPath("/test.js").then((resource) => {
+ if (!resource) {
+ t.fail("Could not find /test.js in target locator");
+ } else {
+ return resource.getBuffer();
+ }
+ });
+ }).then((buffer) => {
+ return t.deepEqual(buffer.toString(), expected, "Correct content");
+ });
+});
+
+test("integration: uglify raw module (@ui5-bundle-raw-include)", (t) => {
+ const reader = resourceFactory.createAdapter({
+ virBasePath: "/"
+ });
+ const writer = resourceFactory.createAdapter({
+ virBasePath: "/"
+ });
+ const duplexCollection = new DuplexCollection({reader: reader, writer: writer});
+ const content = `
+//@ui5-bundle-raw-include sap/ui/my/module.js
+function test(paramA) {
+ var variableA = paramA;
+ console.log(variableA);
+}
+test();`;
+ const testResource = resourceFactory.createResource({
+ path: "/test.js",
+ string: content
+ });
+ const expected = `//@ui5-bundle-raw-include sap/ui/my/module.js
+function test(t){var o=t;console.log(o)}test();`;
+
+ return reader.write(testResource)
+ .then(() => {
+ return reader.byPath("/test.js");
+ }).then(() => {
+ return uglify({
+ workspace: duplexCollection,
+ options: {
+ pattern: "/test.js"
+ }
+ });
+ }).then(() => {
+ return writer.byPath("/test.js").then((resource) => {
+ if (!resource) {
+ t.fail("Could not find /test.js in target locator");
+ } else {
+ return resource.getBuffer();
+ }
+ });
+ }).then((buffer) => {
+ return t.deepEqual(buffer.toString(), expected, "Correct content");
+ });
+});
+
+test("integration: uglify raw module (@ui5-bundle)", (t) => {
+ const reader = resourceFactory.createAdapter({
+ virBasePath: "/"
+ });
+ const writer = resourceFactory.createAdapter({
+ virBasePath: "/"
+ });
+ const duplexCollection = new DuplexCollection({reader: reader, writer: writer});
+ const content = `
+//@ui5-bundle sap/ui/my/module.js
+function test(paramA) {
+ var variableA = paramA;
+ console.log(variableA);
+}
+test();`;
+ const testResource = resourceFactory.createResource({
+ path: "/test.js",
+ string: content
+ });
+ const expected = `//@ui5-bundle sap/ui/my/module.js
+function test(t){var o=t;console.log(o)}test();`;
+
+ return reader.write(testResource)
+ .then(() => {
+ return reader.byPath("/test.js");
+ }).then(() => {
+ return uglify({
+ workspace: duplexCollection,
+ options: {
+ pattern: "/test.js"
+ }
+ });
+ }).then(() => {
+ return writer.byPath("/test.js").then((resource) => {
+ if (!resource) {
+ t.fail("Could not find /test.js in target locator");
+ } else {
+ return resource.getBuffer();
+ }
+ });
+ }).then((buffer) => {
+ return t.deepEqual(buffer.toString(), expected, "Correct content");
+ });
+});
diff --git a/test/lib/types/application/ApplicationBuilder.js b/test/lib/types/application/ApplicationBuilder.js
index f05d56552..b37cb222f 100644
--- a/test/lib/types/application/ApplicationBuilder.js
+++ b/test/lib/types/application/ApplicationBuilder.js
@@ -70,7 +70,8 @@ test("Instantiation", (t) => {
"uglify",
"generateVersionInfo",
"generateCachebusterInfo",
- "generateApiIndex"
+ "generateApiIndex",
+ "generateResourcesJson"
], "ApplicationBuilder is instantiated with standard tasks");
});
@@ -93,7 +94,8 @@ test("Instantiation without component preload project configuration", (t) => {
"uglify",
"generateVersionInfo",
"generateCachebusterInfo",
- "generateApiIndex"
+ "generateApiIndex",
+ "generateResourcesJson"
], "ApplicationBuilder is still instantiated with standard tasks");
});
@@ -114,7 +116,8 @@ test("Instantiation without project namespace", (t) => {
"createDebugFiles",
"uglify",
"generateVersionInfo",
- "generateApiIndex"
+ "generateApiIndex",
+ "generateResourcesJson"
], "All standard tasks but generateComponentPreload will be executed");
});
@@ -142,6 +145,7 @@ test("Instantiation with custom tasks", (t) => {
"replaceVersion--1",
"generateVersionInfo",
"generateCachebusterInfo",
- "generateApiIndex"
+ "generateApiIndex",
+ "generateResourcesJson"
], "ApplicationBuilder is still instantiated with standard tasks");
});