diff --git a/lib/lbt/bundle/Builder.js b/lib/lbt/bundle/Builder.js index 1e6c4f17f..18aeb8bf2 100644 --- a/lib/lbt/bundle/Builder.js +++ b/lib/lbt/bundle/Builder.js @@ -2,10 +2,10 @@ // for consistency of write calls, we generally allow template literals "use strict"; -const terser = require("terser"); +const path = require("path"); const {pd} = require("pretty-data"); const {parseJS, Syntax} = require("../utils/parseUtils"); -// const MOZ_SourceMap = require("source-map"); +const {encode: encodeMappings, decode: decodeMappings} = require("sourcemap-codec"); const {isMethodCall} = require("../utils/ASTUtils"); const ModuleName = require("../utils/ModuleName"); @@ -18,7 +18,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|^@ui5-bundle-raw-include |^@ui5-bundle /i; +const sourcemapPattern = /\/\/# sourceMappingURL=data:application\/json;charset=utf-8;base64,(.+)/i; const xmlHtmlPrePattern = /<(?:\w+:)?pre\b/; const strReplacements = { @@ -147,6 +147,7 @@ class BundleBuilder { this.options = options || {}; this.optimize = !!this.options.optimize; + this.options.sourceMap = this.options.sourceMap === undefined ? true : this.options.sourceMap; // when decorateBootstrapModule is set to false, we don't write the optimized flag // and don't write the try catch wrapper @@ -156,6 +157,14 @@ class BundleBuilder { // TODO is the following condition ok or should the availability of jquery.sap.global.js be configurable? this.jqglobalAvailable = !resolvedModule.containsGlobal; this.openModule(resolvedModule.name); + + this._sourceMap = { + version: 3, + file: path.posix.basename(resolvedModule.name), + sections: [], + }; + this._bundleName = resolvedModule.name; + let bundleInfos = []; // create all sections in sequence for ( const section of resolvedModule.sections ) { @@ -184,6 +193,7 @@ class BundleBuilder { return { name: module.name, content: this.outW.toString(), + sourceMap: this.options.sourceMap ? JSON.stringify(this._sourceMap) : null, bundleInfo: bundleInfo }; } @@ -213,11 +223,9 @@ class BundleBuilder { this.outW.writeln(`if (oError.name != "Restart") { throw oError; }`); this.outW.writeln(`}`); } - /* NODE-TODO - if ( writeSourceMap && writeSourceMapAnnotation ) { - outW.ensureNewLine(); - outW.write("//# sourceMappingURL=" + moduleName.getBaseName().replaceFirst("\\.js$", ".js.map")); - }*/ + if (this.options.sourceMap) { + this.outW.writeln(`//# sourceMappingURL=${path.basename(resolvedModule.name)}.map`); + } } addSection(section) { @@ -281,12 +289,8 @@ class BundleBuilder { } async writeRawModule(module, resource) { - let fileContent = await resource.buffer(); - if ( /\.js$/.test(module) ) { - fileContent = await this.compressJS( fileContent, resource ); - } this.outW.ensureNewLine(); - this.outW.write( fileContent ); + this.outW.write( (await resource.buffer()).toString() ); } async writePreloadFunction(section) { @@ -330,60 +334,80 @@ class BundleBuilder { // this.afterWriteFunctionPreloadSection(); } - async compressJS(fileContent, resource) { - if ( this.optimize ) { - const result = await terser.minify({ - [resource.name]: String(fileContent) - }, { - compress: false, // TODO configure? - output: { - comments: copyrightCommentsPattern, - wrap_func_args: false - } - // , outFileName: resource.name - // , outSourceMap: true - }); - // console.log(result.map); - // const map = new MOZ_SourceMap.SourceMapConsumer(result.map); - // map.eachMapping(function (m) { console.log(m); }); // console.log(map); - fileContent = result.code; - // throw new Error(); - } - return fileContent; - } - beforeWriteFunctionPreloadSection(sequence) { // simple version: just sort alphabetically sequence.sort(); } + async addSourceMap(map) { + if (!map) { + throw new Error("No source map provided"); + } + this._sourceMap.sections.push({ + offset: { + line: this.outW.lineOffset, + column: this.outW.columnOffset + }, + map + }); + } + async rewriteAMDModules(sequence) { if ( this.options.usePredefineCalls ) { const outW = this.outW; const remaining = []; - for ( const module of sequence ) { - if ( /\.js$/.test(module) ) { - // console.log("Processing " + module); - const resource = await this.pool.findResourceWithInfo(module); - let code = await resource.buffer(); - code = rewriteDefine(this.targetBundleFormat, code, module); - if ( code ) { - outW.startSegment(module); + for ( const moduleName of sequence ) { + if ( /\.js$/.test(moduleName) ) { + // console.log("Processing " + moduleName); + const resource = await this.pool.findResourceWithInfo(moduleName); + let moduleCode = (await resource.buffer()).toString(); + let moduleSourceMap = false; + if (this.options.sourceMap) { + moduleSourceMap = null; + try { + const sourceMapResource = await this.pool.findResource(`${moduleName}.map`); + moduleSourceMap = (await sourceMapResource.buffer()).toString(); + } catch (e) { + // No input sourcemap + // TODO: Differentiate real errors from file not found + } + + if (!moduleSourceMap) { + const inlineSourcemap = moduleCode.match(sourcemapPattern); + if (inlineSourcemap) { + moduleSourceMap = Buffer.from(inlineSourcemap[1], "base64").toString(); + } + } + // Strip sourceMappingURL from module code to be bundled + // It has no effect and might be cause for confusion + moduleCode = moduleCode.replace(/\/\/# sourceMappingURL=.*/, ""); + } + + const {code, sourceMap} = await rewriteDefine({ + targetBundleFormat: this.targetBundleFormat, + moduleCode, moduleName, moduleSourceMap + }); + if (code) { + outW.startSegment(moduleName); outW.ensureNewLine(); - const fileContent = await this.compressJS(code, resource); - outW.write( fileContent ); + if (sourceMap) { + sourceMap.sourceRoot = path.posix.relative( + path.dirname(this._bundleName), path.dirname(moduleName)); + await this.addSourceMap(sourceMap); + } + outW.write( code ); outW.ensureNewLine(); const compressedSize = outW.endSegment(); - log.verbose(" %s (%d,%d)", module, + log.verbose(" %s (%d,%d)", moduleName, resource.info != null ? resource.info.size : -1, compressedSize); } else { // keep unprocessed modules - remaining.push(module); + remaining.push(moduleName); } } else { // keep unprocessed modules - remaining.push(module); + remaining.push(moduleName); } } @@ -408,22 +432,20 @@ class BundleBuilder { const outW = this.outW; if ( /\.js$/.test(module) && (info == null || !info.requiresTopLevelScope) ) { - const compressedContent = await this.compressJS( await resource.buffer(), resource ); outW.write(`function(){`); - outW.write( compressedContent ); + outW.write( (await resource.buffer()).toString() ); this.exportGlobalNames(info); outW.ensureNewLine(); outW.write(`}`); } else if ( /\.js$/.test(module) /* implicitly: && info != null && info.requiresTopLevelScope */ ) { log.warn("**** warning: module %s requires top level scope" + " and can only be embedded as a string (requires 'eval')", module); - const compressedContent = await this.compressJS( await resource.buffer(), resource ); - outW.write( makeStringLiteral( compressedContent ) ); + outW.write( makeStringLiteral( (await resource.buffer()).toString() ) ); } else if ( /\.html$/.test(module) ) { - const fileContent = await resource.buffer(); + const fileContent = (await resource.buffer()).toString(); outW.write( makeStringLiteral( fileContent ) ); } else if ( /\.json$/.test(module) ) { - let fileContent = await resource.buffer(); + let fileContent = (await resource.buffer()).toString(); if ( this.optimize ) { try { fileContent = JSON.stringify( JSON.parse( fileContent) ); @@ -434,13 +456,13 @@ class BundleBuilder { } outW.write(makeStringLiteral(fileContent)); } else if ( /\.xml$/.test(module) ) { - let fileContent = await resource.buffer(); + let fileContent = (await resource.buffer()).toString(); if ( this.optimize ) { // For XML we use the pretty data // Do not minify if XML(View) contains an <*:pre> tag, // because whitespace of HTML
 should be preserved (should only happen rarely)
-				if (!xmlHtmlPrePattern.test(fileContent.toString())) {
-					fileContent = pd.xmlmin(fileContent.toString(), false);
+				if (!xmlHtmlPrePattern.test(fileContent)) {
+					fileContent = pd.xmlmin(fileContent, false);
 				}
 			}
 			outW.write( makeStringLiteral( fileContent ) );
@@ -503,11 +525,10 @@ class BundleBuilder {
 
 const CALL_SAP_UI_DEFINE = ["sap", "ui", "define"];
 
-function rewriteDefine(targetBundleFormat, code, moduleName) {
+async function rewriteDefine({targetBundleFormat, moduleCode, moduleName, moduleSourceMap}) {
 	let ast;
-	const codeStr = code.toString();
 	try {
-		ast = parseJS(codeStr, {range: true});
+		ast = parseJS(moduleCode, {range: true});
 	} catch (e) {
 		log.error("error while parsing %s: %s", moduleName, e.message);
 		return;
@@ -519,7 +540,6 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
 		const changes = [];
 		const defineCall = ast.body[0].expression;
 
-
 		// Inject module name if missing
 		if ( defineCall.arguments.length == 0 ||
 			defineCall.arguments[0].type !== Syntax.Literal ) {
@@ -537,7 +557,6 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
 
 			changes.push({
 				index,
-				count: 0,
 				value
 			});
 		}
@@ -549,29 +568,81 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
 			changes.push({
 				// asterisk marks the index: sap.ui.*define()
 				index: defineCall.callee.property.range[0],
-				count: 0,
 				value: "pre"
 			});
 		}
-
-		return applyChanges(codeStr, changes);
+		if (moduleSourceMap === null) {
+			log.info(`No input source map available for module ${moduleName}`);
+		}
+		return transform(moduleCode, changes, moduleSourceMap);
 	}
 
 	return false;
 }
 
-function applyChanges(string, changes) {
-	// No sorting needed as changes are added in correct order
+async function transform(code, changes, inputSourceMap) {
+	const mappingChanges = [];
+	const sourceMap = inputSourceMap ? JSON.parse(inputSourceMap) : null;
 
-	const array = Array.from(string);
+	const array = Array.from(code);
+	// No sorting needed as changes are added in correct (reverse) order
 	changes.forEach((change) => {
+		if (sourceMap) {
+			// Compute line and column for given index to re-align source map with inserted characters
+			const precedingCode = array.slice(0, change.index);
+
+			const line = precedingCode.reduce((lineCount, char) => {
+				if (char === "\n") {
+					lineCount++;
+				}
+				return lineCount;
+			}, 0);
+			const lineStartIndex = precedingCode.lastIndexOf("\n") + 1;
+			const column = change.index - lineStartIndex;
+
+			mappingChanges.push({
+				line,
+				column,
+				columnDiff: change.value.length
+			});
+		}
+
+		// Apply modification
 		array.splice(
 			change.index,
-			change.count,
+			0,
 			change.value
 		);
 	});
-	return array.join("");
-}
+	const transformedCode = array.join("");
+
+	if (sourceMap) {
+		// Source map re-alignment needs to be done from front to back
+		mappingChanges.reverse();
+
+		const mappings = decodeMappings(sourceMap.mappings);
+		mappingChanges.forEach((mappingChange) => {
+			const lineMapping = mappings[mappingChange.line];
+
+			// Mapping structure:
+			// [generatedCodeColumn, sourceIndex, sourceCodeLine, sourceCodeColumn, nameIndex]
+			lineMapping.forEach((mapping) => {
+				if (mapping[0] > mappingChange.column) {
+					// All column mappings for the generated code after any change
+					// need to be moved by the amount of inserted characters
+					mapping[0] = mapping[0] + mappingChange.columnDiff;
+				}
+			});
+		});
+		sourceMap.mappings = encodeMappings(mappings);
+
+		// No need for file information in source map since the bundled code does not exist in any file anyways
+		delete sourceMap.file;
+	}
 
+	return {
+		code: transformedCode,
+		sourceMap
+	};
+}
 module.exports = BundleBuilder;
diff --git a/lib/lbt/bundle/BundleWriter.js b/lib/lbt/bundle/BundleWriter.js
index 8b63ba0ad..004bc9311 100644
--- a/lib/lbt/bundle/BundleWriter.js
+++ b/lib/lbt/bundle/BundleWriter.js
@@ -11,6 +11,8 @@ const SPACES_OR_TABS_ONLY = /^[ \t]+$/;
  *
  * Most methods have been extracted from JSMergeWriter.
  *
+ * columnOffset and lineOffset are used for sourcemap merging as reference to where we are at a given point in time
+ *
  * @author Frank Weigel
  * @since 1.27.0
  * @private
@@ -18,6 +20,8 @@ const SPACES_OR_TABS_ONLY = /^[ \t]+$/;
 class BundleWriter {
 	constructor() {
 		this.buf = "";
+		this.lineOffset = 0;
+		this.columnOffset = 0;
 		this.segments = [];
 		this.currentSegment = null;
 		this.currentSourceIndex = 0;
@@ -28,6 +32,11 @@ class BundleWriter {
 		let writeBuf = "";
 		for ( let i = 0; i < str.length; i++ ) {
 			writeBuf += str[i];
+			if (str[i] != null && str[i].split) {
+				const strSplit = str[i].split(NL);
+				this.lineOffset += strSplit.length - 1;
+				this.columnOffset += strSplit[strSplit.length - 1].length;
+			}
 		}
 		if ( writeBuf.length >= 1 ) {
 			this.buf += writeBuf;
@@ -40,15 +49,23 @@ class BundleWriter {
 	writeln(...str) {
 		for ( let i = 0; i < str.length; i++ ) {
 			this.buf += str[i];
+			if (str[i] != null && str[i].split) {
+				const strSplit = str[i].split(NL);
+				this.lineOffset += strSplit.length - 1;
+			}
 		}
 		this.buf += NL;
 		this.endsWithNewLine = true;
+		this.lineOffset += 1;
+		this.columnOffset = 0;
 	}
 
 	ensureNewLine() {
 		if ( !this.endsWithNewLine ) {
 			this.buf += NL;
 			this.endsWithNewLine = true;
+			this.lineOffset += 1;
+			this.columnOffset = 0;
 		}
 	}
 
diff --git a/lib/processors/bundlers/moduleBundler.js b/lib/processors/bundlers/moduleBundler.js
index 39498f86f..3cfdd9420 100644
--- a/lib/processors/bundlers/moduleBundler.js
+++ b/lib/processors/bundlers/moduleBundler.js
@@ -142,13 +142,20 @@ module.exports = function({resources, options: {bundleDefinition, bundleOptions}
 
 			return Promise.all(bundles.map((bundleObj) => {
 				if ( bundleObj ) {
-					const {name, content} = bundleObj;
+					const {name, content, sourceMap} = bundleObj;
 					// console.log("creating bundle as '%s'", "/resources/" + name);
 					const resource = new EvoResource({
 						path: "/resources/" + name,
 						string: content
 					});
-					return resource;
+					const sourceMapResource = new EvoResource({
+						path: "/resources/" + name + ".map",
+						string: sourceMap
+					});
+					return {
+						bundle: resource,
+						sourceMap: sourceMapResource
+					};
 				}
 			}));
 		});
diff --git a/lib/tasks/bundlers/generateBundle.js b/lib/tasks/bundlers/generateBundle.js
index 781d09a02..8a9cd8393 100644
--- a/lib/tasks/bundlers/generateBundle.js
+++ b/lib/tasks/bundlers/generateBundle.js
@@ -30,7 +30,7 @@ module.exports = function({
 		}]
 	});
 
-	return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library}").then((resources) => {
+	return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}").then((resources) => {
 		return moduleBundler({
 			options: {
 				bundleDefinition,
@@ -38,11 +38,14 @@ module.exports = function({
 			},
 			resources
 		}).then((bundles) => {
-			return Promise.all(bundles.map((bundle) => {
+			return Promise.all(bundles.map(({bundle, sourceMap}) => {
 				if (taskUtil) {
 					taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle);
 				}
-				return workspace.write(bundle);
+				return Promise.all([
+					workspace.write(bundle),
+					workspace.write(sourceMap)
+				]);
 			}));
 		});
 	});
diff --git a/lib/tasks/bundlers/generateComponentPreload.js b/lib/tasks/bundlers/generateComponentPreload.js
index 454ba4cb0..4cc72c631 100644
--- a/lib/tasks/bundlers/generateComponentPreload.js
+++ b/lib/tasks/bundlers/generateComponentPreload.js
@@ -40,7 +40,7 @@ module.exports = function({
 	});
 
 	// TODO 3.0: Limit to workspace resources?
-	return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library}")
+	return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}")
 		.then(async (resources) => {
 			let allNamespaces = [];
 			if (paths) {
@@ -148,11 +148,14 @@ module.exports = function({
 			}));
 		})
 		.then((processedResources) => {
-			return Promise.all(processedResources.map((resource) => {
+			return Promise.all(processedResources.map(([{bundle, sourceMap}]) => {
 				if (taskUtil) {
-					taskUtil.setTag(resource[0], taskUtil.STANDARD_TAGS.IsBundle);
+					taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle);
 				}
-				return workspace.write(resource[0]);
+				return Promise.all([
+					workspace.write(bundle),
+					workspace.write(sourceMap)
+				]);
 			}));
 		});
 };
diff --git a/lib/tasks/bundlers/generateLibraryPreload.js b/lib/tasks/bundlers/generateLibraryPreload.js
index ef67ced79..d61bb56fc 100644
--- a/lib/tasks/bundlers/generateLibraryPreload.js
+++ b/lib/tasks/bundlers/generateLibraryPreload.js
@@ -283,7 +283,7 @@ module.exports = function({workspace, dependencies, taskUtil, options: {projectN
 		}]
 	});
 
-	return combo.byGlob("/**/*.{js,json,xml,html,properties,library}").then((resources) => {
+	return combo.byGlob("/**/*.{js,json,xml,html,properties,library,js.map}").then((resources) => {
 		// Find all libraries and create a library-preload.js bundle
 
 		let p;
@@ -330,11 +330,14 @@ module.exports = function({workspace, dependencies, taskUtil, options: {projectN
 				}),
 			]).then((results) => {
 				const bundles = Array.prototype.concat.apply([], results);
-				return Promise.all(bundles.map((bundle) => {
+				return Promise.all(bundles.map(({bundle, sourceMap}) => {
 					if (taskUtil) {
 						taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle);
 					}
-					return workspace.write(bundle);
+					return Promise.all([
+						workspace.write(bundle),
+						workspace.write(sourceMap)
+					]);
 				}));
 			});
 		} else {
@@ -373,12 +376,15 @@ module.exports = function({workspace, dependencies, taskUtil, options: {projectN
 						return createLibraryBundles(libraryNamespace, resources, excludes)
 							.then((results) => {
 								const bundles = Array.prototype.concat.apply([], results);
-								return Promise.all(bundles.map((bundle) => {
+								return Promise.all(bundles.map(({bundle, sourceMap} = {}) => {
 									if (bundle) {
 										if (taskUtil) {
 											taskUtil.setTag(bundle, taskUtil.STANDARD_TAGS.IsBundle);
 										}
-										return workspace.write(bundle);
+										return Promise.all([
+											workspace.write(bundle),
+											workspace.write(sourceMap)
+										]);
 									}
 								}));
 							});
diff --git a/package-lock.json b/package-lock.json
index f47a66bbe..8132e48a2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -617,6 +617,14 @@
 				"@vue/shared": "3.2.20",
 				"estree-walker": "^2.0.2",
 				"source-map": "^0.6.1"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.6.1",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+					"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+					"dev": true
+				}
 			}
 		},
 		"@vue/compiler-dom": {
@@ -645,6 +653,14 @@
 				"magic-string": "^0.25.7",
 				"postcss": "^8.1.10",
 				"source-map": "^0.6.1"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.6.1",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+					"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+					"dev": true
+				}
 			}
 		},
 		"@vue/compiler-ssr": {
@@ -1840,6 +1856,13 @@
 				"inherits": "^2.0.4",
 				"source-map": "^0.6.1",
 				"source-map-resolve": "^0.6.0"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.6.1",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+					"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+				}
 			}
 		},
 		"css-select": {
@@ -3385,6 +3408,14 @@
 				"debug": "^4.1.1",
 				"istanbul-lib-coverage": "^3.0.0",
 				"source-map": "^0.6.1"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.6.1",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+					"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+					"dev": true
+				}
 			}
 		},
 		"istanbul-reports": {
@@ -5151,11 +5182,6 @@
 				}
 			}
 		},
-		"source-map": {
-			"version": "0.6.1",
-			"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-			"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
-		},
 		"source-map-js": {
 			"version": "0.6.2",
 			"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz",
@@ -5178,13 +5204,19 @@
 			"requires": {
 				"buffer-from": "^1.0.0",
 				"source-map": "^0.6.0"
+			},
+			"dependencies": {
+				"source-map": {
+					"version": "0.6.1",
+					"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+					"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+				}
 			}
 		},
 		"sourcemap-codec": {
 			"version": "1.4.8",
 			"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
-			"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
-			"dev": true
+			"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
 		},
 		"spawn-wrap": {
 			"version": "2.0.0",
diff --git a/package.json b/package.json
index 4ea401446..48fb0ba30 100644
--- a/package.json
+++ b/package.json
@@ -120,6 +120,7 @@
 		"replacestream": "^4.0.3",
 		"rimraf": "^3.0.2",
 		"semver": "^7.3.5",
+		"sourcemap-codec": "^1.4.8",
 		"terser": "^5.9.0",
 		"xml2js": "^0.4.23",
 		"yazl": "^2.5.1"
diff --git a/test/lib/lbt/bundle/Builder.js b/test/lib/lbt/bundle/Builder.js
index 9eb8228e6..69b47aba5 100644
--- a/test/lib/lbt/bundle/Builder.js
+++ b/test/lib/lbt/bundle/Builder.js
@@ -619,3 +619,690 @@ sap.ui.loader.config({bundlesUI5:{
 	t.deepEqual(oResult.bundleInfo.subModules, ["a.js", "b.js"],
 		"bundle info subModules are correct");
 });
+
+test("integration: createBundle using predefine calls with source maps and a single, simple source", async (t) => {
+	const pool = new ResourcePool();
+	pool.addResource({
+		name: "jquery.sap.global-dbg.js",
+		buffer: async () => `/* Some comment */
+sap.ui.define([], function (){
+	console.log("Put me on a map!");
+	return {};
+});`
+	});
+
+	const originalSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"console",
+			"log"
+		],
+		"mappings": "AACAA,IAAIC,GAAGC,OAAO,GAAI,WACjBC,QAAQC,IAAI,oBACZ,MAAO",
+		"file": "jquery.sap.global.js"
+	};
+	pool.addResource({
+		name: "jquery.sap.global.js.map",
+		buffer: async () => JSON.stringify(originalSourceMap)
+	});
+	pool.addResource({
+		name: "jquery.sap.global.js",
+		buffer: async () => `sap.ui.define([],function(){console.log("Put me on a map!");return{}});
+//# sourceMappingURL=jquery.sap.global.js.map`
+	});
+
+	const bundleDefinition = {
+		name: `Component-preload.js`,
+		defaultFileTypes: [".js"],
+		sections: [{
+			mode: "preload",
+			name: "preload-section",
+			filters: [
+				"jquery.sap.global.js"
+			]
+		}]
+	};
+
+	const builder = new Builder(pool);
+	const oResult = await builder.createBundle(bundleDefinition, {
+		usePredefineCalls: true,
+		numberOfParts: 1,
+		decorateBootstrapModule: true,
+		optimize: false
+	});
+	t.deepEqual(oResult.name, "Component-preload.js");
+	const expectedContent = `//@ui5-bundle Component-preload.js
+sap.ui.predefine("jquery.sap.global", [],function(){console.log("Put me on a map!");return{}});
+//# sourceMappingURL=Component-preload.js.map
+`;
+	t.deepEqual(oResult.content, expectedContent, "Correct bundle content");
+	t.deepEqual(oResult.bundleInfo.name, "Component-preload.js", "bundle info name is correct");
+	t.deepEqual(oResult.bundleInfo.size, expectedContent.length, "bundle info size is correct");
+	t.deepEqual(oResult.bundleInfo.subModules,
+		[
+			"jquery.sap.global.js"
+		], "bundle info subModules are correct");
+	const indexMap = JSON.parse(oResult.sourceMap);
+	t.is(indexMap.sections.length, 1, "Bundle index source map contains one section");
+	t.deepEqual(indexMap.sections[0].offset, {
+		line: 1,
+		column: 0
+	}, "Section has correct offset");
+
+	const expectedSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"console",
+			"log"
+		],
+		"mappings": "AACAA,IAAIC,GAAGC,+BAAO,GAAI,WACjBC,QAAQC,IAAI,oBACZ,MAAO",
+		"sourceRoot": ""
+	};
+	t.deepEqual(indexMap.sections[0].map, expectedSourceMap, "Section contains correct map");
+});
+
+test("integration: createBundle using predefine calls with source maps and a single, multi-line source", async (t) => {
+	const pool = new ResourcePool();
+	pool.addResource({
+		name: "jquery.sap.global-dbg.js",
+		buffer: async () => `/* Some comment */
+sap.
+ui.
+define(
+[
+], function (){
+	console.log("Put me on a map!");
+	return {};
+});`
+	});
+
+	const originalSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"console",
+			"log"
+		],
+		"mappings": "AACAA,IACAC,GACAC,OACA,GACG,WACFC,QAAQC,IAAI,oBACZ,MAAO",
+		"file": "jquery.sap.global.js"
+	};
+	pool.addResource({
+		name: "jquery.sap.global.js.map",
+		buffer: async () => JSON.stringify(originalSourceMap)
+	});
+	pool.addResource({
+		name: "jquery.sap.global.js",
+		buffer: async () => `sap.ui.define([],function(){console.log("Put me on a map!");return{}});
+//# sourceMappingURL=jquery.sap.global.js.map`
+	});
+
+	const bundleDefinition = {
+		name: `Component-preload.js`,
+		defaultFileTypes: [".js"],
+		sections: [{
+			mode: "preload",
+			name: "preload-section",
+			filters: [
+				"jquery.sap.global.js"
+			]
+		}]
+	};
+
+	const builder = new Builder(pool);
+	const oResult = await builder.createBundle(bundleDefinition, {
+		usePredefineCalls: true,
+		numberOfParts: 1,
+		decorateBootstrapModule: true,
+		optimize: false
+	});
+	t.deepEqual(oResult.name, "Component-preload.js");
+	const expectedContent = `//@ui5-bundle Component-preload.js
+sap.ui.predefine("jquery.sap.global", [],function(){console.log("Put me on a map!");return{}});
+//# sourceMappingURL=Component-preload.js.map
+`;
+	t.deepEqual(oResult.content, expectedContent, "Correct bundle content");
+	t.deepEqual(oResult.bundleInfo.name, "Component-preload.js", "bundle info name is correct");
+	t.deepEqual(oResult.bundleInfo.size, expectedContent.length, "bundle info size is correct");
+	t.deepEqual(oResult.bundleInfo.subModules,
+		[
+			"jquery.sap.global.js"
+		], "bundle info subModules are correct");
+	const indexMap = JSON.parse(oResult.sourceMap);
+	t.is(indexMap.sections.length, 1, "Bundle index source map contains one section");
+	t.deepEqual(indexMap.sections[0].offset, {
+		line: 1,
+		column: 0
+	}, "Section has correct offset");
+
+	const expectedSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"console",
+			"log"
+		],
+		"mappings": "AACAA,IACAC,GACAC,+BACA,GACG,WACFC,QAAQC,IAAI,oBACZ,MAAO",
+		"sourceRoot": ""
+	};
+	t.deepEqual(indexMap.sections[0].map, expectedSourceMap, "Section contains correct map");
+});
+
+test("integration: createBundle using predefine calls with source maps and a single source", async (t) => {
+	const pool = new ResourcePool();
+	pool.addResource({
+		name: "jquery.sap.global-dbg.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+
+/*global XMLHttpRequest, localStorage, alert, document */
+
+/**
+ * @namespace jQuery
+ * @public
+ */
+sap.ui.define([
+	// new sap/base/* modules
+	"sap/base/util/now", "sap/base/util/Version", "sap/base/assert", "sap/base/Log"
+], function(now, Version, assert, Log) {
+	return now;
+});`
+	});
+
+	const originalSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"now",
+			"Version",
+			"assert",
+			"Log"
+		],
+		"mappings": ";;;;;AAYAA,IAAIC,GAAGC,OAAO,CAEb,oBAAqB,wBAAyB,kBAAmB,gBAC/D,SAASC,EAAKC,EAASC,EAAQC,GACjC,OAAOH",
+		"file": "jquery.sap.global.js"
+	};
+	pool.addResource({
+		name: "jquery.sap.global.js.map",
+		buffer: async () => JSON.stringify(originalSourceMap)
+	});
+	pool.addResource({
+		name: "jquery.sap.global.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.define(["sap/base/util/now","sap/base/util/Version","sap/base/assert","sap/base/Log"],function(s,a,e,i){return s});
+//# sourceMappingURL=jquery.sap.global.js.map`
+	});
+
+	const bundleDefinition = {
+		name: `Component-preload.js`,
+		defaultFileTypes: [".js"],
+		sections: [{
+			mode: "preload",
+			name: "preload-section",
+			filters: [
+				"jquery.sap.global.js"
+			]
+		}]
+	};
+
+	const builder = new Builder(pool);
+	const oResult = await builder.createBundle(bundleDefinition, {
+		usePredefineCalls: true,
+		numberOfParts: 1,
+		decorateBootstrapModule: true,
+		optimize: false
+	});
+	t.deepEqual(oResult.name, "Component-preload.js");
+	const expectedContent = `//@ui5-bundle Component-preload.js
+/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.predefine("jquery.sap.global", ["sap/base/util/now","sap/base/util/Version","sap/base/assert","sap/base/Log"],function(s,a,e,i){return s});
+//# sourceMappingURL=Component-preload.js.map
+`;
+	t.deepEqual(oResult.content, expectedContent, "Correct bundle content");
+	t.deepEqual(oResult.bundleInfo.name, "Component-preload.js", "bundle info name is correct");
+	t.deepEqual(oResult.bundleInfo.size, expectedContent.length, "bundle info size is correct");
+	t.deepEqual(oResult.bundleInfo.subModules,
+		[
+			"jquery.sap.global.js"
+		], "bundle info subModules are correct");
+	const indexMap = JSON.parse(oResult.sourceMap);
+	t.is(indexMap.sections.length, 1, "Bundle index source map contains one section");
+	t.deepEqual(indexMap.sections[0].offset, {
+		line: 1,
+		column: 0
+	}, "Section has correct offset");
+
+	const expectedSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"now",
+			"Version",
+			"assert",
+			"Log"
+		],
+		"mappings": ";;;;;AAYAA,IAAIC,GAAGC,+BAAO,CAEb,oBAAqB,wBAAyB,kBAAmB,gBAC/D,SAASC,EAAKC,EAASC,EAAQC,GACjC,OAAOH",
+		"sourceRoot": ""
+	};
+	t.deepEqual(indexMap.sections[0].map, expectedSourceMap, "Section contains correct map");
+});
+
+test("integration: createBundle using predefine calls with source maps and a multiple source", async (t) => {
+	const pool = new ResourcePool();
+	pool.addResource({
+		name: "jquery.sap.global-dbg.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+
+/*global XMLHttpRequest, localStorage, alert, document */
+
+/**
+ * @namespace jQuery
+ * @public
+ */
+sap.ui.define([
+	// new sap/base/* modules
+	"sap/base/util/now", "sap/base/util/Version", "sap/base/assert", "sap/base/Log"
+], function(now, Version, assert, Log) {
+	return now;
+});`
+	});
+
+	const originalGlobalSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"now",
+			"Version",
+			"assert",
+			"Log"
+		],
+		"mappings": ";;;;;AAYAA,IAAIC,GAAGC,OAAO,CAEb,oBAAqB,wBAAyB,kBAAmB,gBAC/D,SAASC,EAAKC,EAASC,EAAQC,GACjC,OAAOH",
+		"file": "jquery.sap.global.js"
+	};
+	pool.addResource({
+		name: "jquery.sap.global.js.map",
+		buffer: async () => JSON.stringify(originalGlobalSourceMap)
+	});
+	pool.addResource({
+		name: "jquery.sap.global.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.define(["sap/base/util/now","sap/base/util/Version","sap/base/assert","sap/base/Log"],function(s,a,e,i){return s});
+//# sourceMappingURL=jquery.sap.global.js.map`
+	});
+
+	pool.addResource({
+		name: "jquery.sap.dom-dbg.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+
+// Provides functionality related to DOM analysis and manipulation which is not provided by jQuery itself.
+sap.ui.define([
+	'jquery.sap.global', 'sap/ui/dom/containsOrEquals',
+	'sap/ui/core/syncStyleClass', 'sap/ui/dom/getOwnerWindow', 'sap/ui/dom/getScrollbarSize',
+	'sap/ui/dom/denormalizeScrollLeftRTL', 'sap/ui/dom/denormalizeScrollBeginRTL',
+	'sap/ui/dom/units/Rem', 'sap/ui/dom/jquery/Aria',
+	'sap/ui/dom/jquery/Selection', 'sap/ui/dom/jquery/zIndex', 'sap/ui/dom/jquery/parentByAttribute',
+	'sap/ui/dom/jquery/cursorPos', 'sap/ui/dom/jquery/selectText', 'sap/ui/dom/jquery/getSelectedText',
+	'sap/ui/dom/jquery/rect', 'sap/ui/dom/jquery/rectContains', 'sap/ui/dom/jquery/Focusable',
+	'sap/ui/dom/jquery/hasTabIndex', 'sap/ui/dom/jquery/scrollLeftRTL', 'sap/ui/dom/jquery/scrollRightRTL', 'sap/ui/dom/jquery/Selectors'
+], function(jQuery, domContainsOrEquals, fnSyncStyleClass, domGetOwnerWindow,
+	domGetScrollbarSize, domDenormalizeScrollLeftRTL, domDenormalizeScrollBeginRTL, domUnitsRem
+	/*
+	jqueryAria,
+	jquerySelection,
+	jqueryzIndex,
+	jqueryParentByAttribute,
+	jqueryCursorPos,
+	jquerySelectText,
+	jqueryGetSelectedText,
+	jqueryRect,
+	jqueryRectContains,
+	jqueryFocusable,
+	jqueryHasTabIndex,
+	jqueryScrollLeftRTL,
+	jqueryScrollRightRTL,
+	jquerySelectors*/
+) {
+	"use strict";
+
+	/**
+	 * Shortcut for document.getElementById().
+	 *
+	 * @param {string} sId The id of the DOM element to return
+	 * @param {Window} [oWindow=window] The window (optional)
+	 * @return {Element} The DOMNode identified by the given sId
+	 * @public
+	 * @since 0.9.0
+	 * @deprecated since 1.58 use document.getElementById instead
+	 */
+	jQuery.sap.domById = function domById(sId, oWindow) {
+		return sId ? (oWindow || window).document.getElementById(sId) : null;
+	};
+
+	return jQuery;
+
+});`
+	});
+
+	const originalDomSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.dom-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"jQuery",
+			"domContainsOrEquals",
+			"fnSyncStyleClass",
+			"domGetOwnerWindow",
+			"domGetScrollbarSize",
+			"domDenormalizeScrollLeftRTL",
+			"domDenormalizeScrollBeginRTL",
+			"domUnitsRem",
+			"domById",
+			"sId",
+			"oWindow",
+			"window",
+			"document",
+			"getElementById"
+		],
+		"mappings": ";;;;;AAOAA,IAAIC,GAAGC,OAAO,CACb,oBAAqB,8BACrB,6BAA8B,4BAA6B,8BAC3D,sCAAuC,uCACvC,uBAAwB,yBACxB,8BAA+B,2BAA4B,sCAC3D,8BAA+B,+BAAgC,oCAC/D,yBAA0B,iCAAkC,8BAC5D,gCAAiC,kCAAmC,mCAAoC,+BACtG,SAASC,OAAQC,EAAqBC,EAAkBC,EAC1DC,EAAqBC,EAA6BC,EAA8BC,GAiBhF,aAYAP,OAAOH,IAAIW,QAAU,SAASA,EAAQC,EAAKC,GAC1C,OAAOD,GAAOC,GAAWC,QAAQC,SAASC,eAAeJ,GAAO,MAGjE,OAAOT",
+		"file": "jquery.sap.dom.js"
+	};
+	pool.addResource({
+		name: "jquery.sap.dom.js.map",
+		buffer: async () => JSON.stringify(originalDomSourceMap)
+	});
+	pool.addResource({
+		name: "jquery.sap.dom.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.define(["jquery.sap.global","sap/ui/dom/containsOrEquals","sap/ui/core/syncStyleClass","sap/ui/dom/getOwnerWindow","sap/ui/dom/getScrollbarSize","sap/ui/dom/denormalizeScrollLeftRTL","sap/ui/dom/denormalizeScrollBeginRTL","sap/ui/dom/units/Rem","sap/ui/dom/jquery/Aria","sap/ui/dom/jquery/Selection","sap/ui/dom/jquery/zIndex","sap/ui/dom/jquery/parentByAttribute","sap/ui/dom/jquery/cursorPos","sap/ui/dom/jquery/selectText","sap/ui/dom/jquery/getSelectedText","sap/ui/dom/jquery/rect","sap/ui/dom/jquery/rectContains","sap/ui/dom/jquery/Focusable","sap/ui/dom/jquery/hasTabIndex","sap/ui/dom/jquery/scrollLeftRTL","sap/ui/dom/jquery/scrollRightRTL","sap/ui/dom/jquery/Selectors"],function(jQuery,e,u,o,s,i,r,a){"use strict";jQuery.sap.domById=function e(u,o){return u?(o||window).document.getElementById(u):null};return jQuery});
+//# sourceMappingURL=jquery.sap.dom.js.map`
+	});
+
+	const bundleDefinition = {
+		name: `Component-preload.js`,
+		defaultFileTypes: [".js"],
+		sections: [{
+			mode: "preload",
+			name: "preload-section",
+			filters: [
+				"jquery.sap.global.js",
+				"jquery.sap.dom.js"
+			]
+		}]
+	};
+
+	const builder = new Builder(pool);
+	const oResult = await builder.createBundle(bundleDefinition, {
+		usePredefineCalls: true,
+		numberOfParts: 1,
+		decorateBootstrapModule: true,
+		optimize: false
+	});
+	t.deepEqual(oResult.name, "Component-preload.js");
+	const expectedContent = `//@ui5-bundle Component-preload.js
+/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.predefine("jquery.sap.dom", ["jquery.sap.global","sap/ui/dom/containsOrEquals","sap/ui/core/syncStyleClass","sap/ui/dom/getOwnerWindow","sap/ui/dom/getScrollbarSize","sap/ui/dom/denormalizeScrollLeftRTL","sap/ui/dom/denormalizeScrollBeginRTL","sap/ui/dom/units/Rem","sap/ui/dom/jquery/Aria","sap/ui/dom/jquery/Selection","sap/ui/dom/jquery/zIndex","sap/ui/dom/jquery/parentByAttribute","sap/ui/dom/jquery/cursorPos","sap/ui/dom/jquery/selectText","sap/ui/dom/jquery/getSelectedText","sap/ui/dom/jquery/rect","sap/ui/dom/jquery/rectContains","sap/ui/dom/jquery/Focusable","sap/ui/dom/jquery/hasTabIndex","sap/ui/dom/jquery/scrollLeftRTL","sap/ui/dom/jquery/scrollRightRTL","sap/ui/dom/jquery/Selectors"],function(jQuery,e,u,o,s,i,r,a){"use strict";jQuery.sap.domById=function e(u,o){return u?(o||window).document.getElementById(u):null};return jQuery});
+/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.predefine("jquery.sap.global", ["sap/base/util/now","sap/base/util/Version","sap/base/assert","sap/base/Log"],function(s,a,e,i){return s});
+//# sourceMappingURL=Component-preload.js.map
+`;
+	t.deepEqual(oResult.content, expectedContent, "Correct bundle content");
+	t.deepEqual(oResult.bundleInfo.name, "Component-preload.js", "bundle info name is correct");
+	t.deepEqual(oResult.bundleInfo.size, expectedContent.length, "bundle info size is correct");
+	t.deepEqual(oResult.bundleInfo.subModules,
+		[
+			"jquery.sap.dom.js",
+			"jquery.sap.global.js"
+		], "bundle info subModules are correct");
+	const indexMap = JSON.parse(oResult.sourceMap);
+	t.is(indexMap.sections.length, 2, "Bundle index source map contains two sections");
+	t.deepEqual(indexMap.sections[0].offset, {
+		line: 1,
+		column: 0
+	}, "Section one has correct offset");
+
+	const expectedSourceMap1 = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.dom-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"jQuery",
+			"domContainsOrEquals",
+			"fnSyncStyleClass",
+			"domGetOwnerWindow",
+			"domGetScrollbarSize",
+			"domDenormalizeScrollLeftRTL",
+			"domDenormalizeScrollBeginRTL",
+			"domUnitsRem",
+			"domById",
+			"sId",
+			"oWindow",
+			"window",
+			"document",
+			"getElementById"
+		],
+		"mappings": ";;;;;AAOAA,IAAIC,GAAGC,4BAAO,CACb,oBAAqB,8BACrB,6BAA8B,4BAA6B,8BAC3D,sCAAuC,uCACvC,uBAAwB,yBACxB,8BAA+B,2BAA4B,sCAC3D,8BAA+B,+BAAgC,oCAC/D,yBAA0B,iCAAkC,8BAC5D,gCAAiC,kCAAmC,mCAAoC,+BACtG,SAASC,OAAQC,EAAqBC,EAAkBC,EAC1DC,EAAqBC,EAA6BC,EAA8BC,GAiBhF,aAYAP,OAAOH,IAAIW,QAAU,SAASA,EAAQC,EAAKC,GAC1C,OAAOD,GAAOC,GAAWC,QAAQC,SAASC,eAAeJ,GAAO,MAGjE,OAAOT",
+		"sourceRoot": ""
+	};
+	t.deepEqual(indexMap.sections[0].map, expectedSourceMap1, "Section one contains correct map");
+	t.deepEqual(indexMap.sections[1].offset, {
+		line: 7,
+		column: 0
+	}, "Section two has correct offset");
+
+	const expectedSourceMap2 = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"now",
+			"Version",
+			"assert",
+			"Log"
+		],
+		"mappings": ";;;;;AAYAA,IAAIC,GAAGC,+BAAO,CAEb,oBAAqB,wBAAyB,kBAAmB,gBAC/D,SAASC,EAAKC,EAASC,EAAQC,GACjC,OAAOH",
+		"sourceRoot": ""
+	};
+	t.deepEqual(indexMap.sections[1].map, expectedSourceMap2, "Section two contains correct map");
+});
+
+test("integration: createBundle using predefine calls with inline source maps and a single source", async (t) => {
+	const pool = new ResourcePool();
+	pool.addResource({
+		name: "jquery.sap.global-dbg.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+
+/*global XMLHttpRequest, localStorage, alert, document */
+
+/**
+ * @namespace jQuery
+ * @public
+ */
+sap.ui.define([
+	// new sap/base/* modules
+	"sap/base/util/now", "sap/base/util/Version", "sap/base/assert", "sap/base/Log"
+], function(now, Version, assert, Log) {
+	return now;
+});`
+	});
+
+	// Source map should be identical to "single source" test above
+	pool.addResource({
+		name: "jquery.sap.global.js",
+		buffer: async () => `/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.define(["sap/base/util/now","sap/base/util/Version","sap/base/assert","sap/base/Log"],function(s,a,e,i){return s});
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImpxdWVyeS5zYXAuZ2xvYmFsLWRiZy5qcyJdLCJuYW1lcyI6WyJzYXAiLCJ1aSIsImRlZmluZSIsIm5vdyIsIlZlcnNpb24iLCJhc3NlcnQiLCJMb2ciXSwibWFwcGluZ3MiOiI7Ozs7O0FBWUFBLElBQUlDLEdBQUdDLE9BQU8sQ0FFYixvQkFBcUIsd0JBQXlCLGtCQUFtQixnQkFDL0QsU0FBU0MsRUFBS0MsRUFBU0MsRUFBUUMsR0FDakMsT0FBT0giLCJmaWxlIjoianF1ZXJ5LnNhcC5nbG9iYWwuanMifQ==`
+	});
+
+	const bundleDefinition = {
+		name: `Component-preload.js`,
+		defaultFileTypes: [".js"],
+		sections: [{
+			mode: "preload",
+			name: "preload-section",
+			filters: [
+				"jquery.sap.global.js"
+			]
+		}]
+	};
+
+	const builder = new Builder(pool);
+	const oResult = await builder.createBundle(bundleDefinition, {
+		usePredefineCalls: true,
+		numberOfParts: 1,
+		decorateBootstrapModule: true,
+		optimize: false
+	});
+	t.deepEqual(oResult.name, "Component-preload.js");
+	const expectedContent = `//@ui5-bundle Component-preload.js
+/*!
+ * OpenUI5
+ * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
+ */
+sap.ui.predefine("jquery.sap.global", ["sap/base/util/now","sap/base/util/Version","sap/base/assert","sap/base/Log"],function(s,a,e,i){return s});
+//# sourceMappingURL=Component-preload.js.map
+`;
+	t.deepEqual(oResult.content, expectedContent, "Correct bundle content");
+	t.deepEqual(oResult.bundleInfo.name, "Component-preload.js", "bundle info name is correct");
+	t.deepEqual(oResult.bundleInfo.size, expectedContent.length, "bundle info size is correct");
+	t.deepEqual(oResult.bundleInfo.subModules,
+		[
+			"jquery.sap.global.js"
+		], "bundle info subModules are correct");
+	const indexMap = JSON.parse(oResult.sourceMap);
+	t.is(indexMap.sections.length, 1, "Bundle index source map contains one section");
+	t.deepEqual(indexMap.sections[0].offset, {
+		line: 1,
+		column: 0
+	}, "Section has correct offset");
+
+	const expectedSourceMap = {
+		"version": 3,
+		"sources":
+		[
+			"jquery.sap.global-dbg.js"
+		],
+		"names":
+		[
+			"sap",
+			"ui",
+			"define",
+			"now",
+			"Version",
+			"assert",
+			"Log"
+		],
+		"mappings": ";;;;;AAYAA,IAAIC,GAAGC,+BAAO,CAEb,oBAAqB,wBAAyB,kBAAmB,gBAC/D,SAASC,EAAKC,EAASC,EAAQC,GACjC,OAAOH",
+		"sourceRoot": ""
+	};
+	t.deepEqual(indexMap.sections[0].map, expectedSourceMap, "Section contains correct map");
+});