@ui5/builder 2.11.4 → 3.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -9
- package/index.js +4 -16
- package/jsdoc.json +0 -1
- package/lib/builder/BuildContext.js +17 -0
- package/lib/builder/ProjectBuildContext.js +9 -7
- package/lib/builder/builder.js +1 -2
- package/lib/lbt/analyzer/JSModuleAnalyzer.js +18 -3
- package/lib/lbt/analyzer/XMLTemplateAnalyzer.js +2 -1
- package/lib/lbt/bundle/AutoSplitter.js +10 -24
- package/lib/lbt/bundle/Builder.js +363 -134
- package/lib/lbt/bundle/BundleWriter.js +17 -0
- package/lib/lbt/bundle/Resolver.js +1 -1
- package/lib/lbt/resources/LocatorResource.js +9 -7
- package/lib/lbt/resources/LocatorResourcePool.js +8 -4
- package/lib/lbt/resources/Resource.js +7 -0
- package/lib/lbt/resources/ResourceCollector.js +33 -15
- package/lib/lbt/utils/parseUtils.js +1 -1
- package/lib/processors/bundlers/moduleBundler.js +39 -14
- package/lib/processors/minifier.js +89 -0
- package/lib/processors/resourceListCreator.js +2 -16
- package/lib/tasks/TaskUtil.js +9 -9
- package/lib/tasks/bundlers/generateBundle.js +92 -6
- package/lib/tasks/bundlers/generateComponentPreload.js +20 -6
- package/lib/tasks/bundlers/generateLibraryPreload.js +77 -10
- package/lib/tasks/bundlers/generateStandaloneAppBundle.js +58 -12
- package/lib/tasks/generateResourcesJson.js +14 -8
- package/lib/tasks/minify.js +39 -0
- package/lib/tasks/replaceVersion.js +1 -1
- package/lib/tasks/taskRepository.js +6 -2
- package/lib/types/application/ApplicationBuilder.js +22 -31
- package/lib/types/library/LibraryBuilder.js +23 -29
- package/lib/types/themeLibrary/ThemeLibraryBuilder.js +1 -0
- package/package.json +15 -13
- package/lib/processors/debugFileCreator.js +0 -52
- package/lib/processors/resourceCopier.js +0 -24
- package/lib/processors/uglifier.js +0 -45
- package/lib/tasks/createDebugFiles.js +0 -30
- package/lib/tasks/uglify.js +0 -33
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const terser = require("terser");
|
|
7
6
|
const {pd} = require("pretty-data");
|
|
8
7
|
const {parseJS, Syntax} = require("../utils/parseUtils");
|
|
9
|
-
|
|
8
|
+
const {encode: encodeMappings, decode: decodeMappings} = require("sourcemap-codec");
|
|
10
9
|
|
|
11
10
|
const {isMethodCall} = require("../utils/ASTUtils");
|
|
12
11
|
const ModuleName = require("../utils/ModuleName");
|
|
@@ -19,7 +18,8 @@ const {SectionType} = require("./BundleDefinition");
|
|
|
19
18
|
const BundleWriter = require("./BundleWriter");
|
|
20
19
|
const log = require("@ui5/logger").getLogger("lbt:bundle:Builder");
|
|
21
20
|
|
|
22
|
-
const
|
|
21
|
+
const sourceMappingUrlPattern = /\/\/# sourceMappingURL=(.+)\s*$/;
|
|
22
|
+
const httpPattern = /^https?:\/\//i;
|
|
23
23
|
const xmlHtmlPrePattern = /<(?:\w+:)?pre\b/;
|
|
24
24
|
|
|
25
25
|
const strReplacements = {
|
|
@@ -41,30 +41,30 @@ function isEmptyBundle(resolvedBundle) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const UI5BundleFormat = {
|
|
44
|
-
beforePreloads(
|
|
45
|
-
|
|
46
|
-
outW.writeln(`{`);
|
|
44
|
+
beforePreloads(section) {
|
|
45
|
+
let str = `jQuery.sap.registerPreloadedModules({\n`;
|
|
47
46
|
if ( section.name ) {
|
|
48
|
-
|
|
47
|
+
str += `"name":"${section.name}",\n`;
|
|
49
48
|
}
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
str += `"version":"2.0",\n`;
|
|
50
|
+
str += `"modules":{\n`;
|
|
51
|
+
return str;
|
|
52
52
|
},
|
|
53
53
|
|
|
54
|
-
afterPreloads(
|
|
55
|
-
|
|
54
|
+
afterPreloads(section) {
|
|
55
|
+
return `}});\n`;
|
|
56
56
|
},
|
|
57
57
|
|
|
58
|
-
beforeBundleInfo(
|
|
59
|
-
|
|
58
|
+
beforeBundleInfo() {
|
|
59
|
+
return `"unsupported"; /* 'bundleInfo' section mode not supported (requires ui5loader)\n`;
|
|
60
60
|
},
|
|
61
61
|
|
|
62
|
-
afterBundleInfo(
|
|
63
|
-
|
|
62
|
+
afterBundleInfo() {
|
|
63
|
+
return "*/\n";
|
|
64
64
|
},
|
|
65
65
|
|
|
66
|
-
requireSync(
|
|
67
|
-
|
|
66
|
+
requireSync(moduleName) {
|
|
67
|
+
return `sap.ui.requireSync("${ModuleName.toRequireJSName(moduleName)}");\n`;
|
|
68
68
|
},
|
|
69
69
|
|
|
70
70
|
shouldDecorate(resolvedModule) {
|
|
@@ -73,28 +73,29 @@ const UI5BundleFormat = {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const EVOBundleFormat = {
|
|
76
|
-
beforePreloads(
|
|
77
|
-
|
|
76
|
+
beforePreloads(section) {
|
|
77
|
+
return `sap.ui.require.preload({\n`;
|
|
78
78
|
},
|
|
79
79
|
|
|
80
|
-
afterPreloads(
|
|
81
|
-
|
|
80
|
+
afterPreloads(section) {
|
|
81
|
+
let str = `}`;
|
|
82
82
|
if ( section.name ) {
|
|
83
|
-
|
|
83
|
+
str += `,"${section.name}"`;
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
str += `);\n`;
|
|
86
|
+
return str;
|
|
86
87
|
},
|
|
87
88
|
|
|
88
|
-
beforeBundleInfo(
|
|
89
|
-
|
|
89
|
+
beforeBundleInfo() {
|
|
90
|
+
return "sap.ui.loader.config({bundlesUI5:{\n";
|
|
90
91
|
},
|
|
91
92
|
|
|
92
|
-
afterBundleInfo(
|
|
93
|
-
|
|
93
|
+
afterBundleInfo() {
|
|
94
|
+
return "}});\n";
|
|
94
95
|
},
|
|
95
96
|
|
|
96
|
-
requireSync(
|
|
97
|
-
|
|
97
|
+
requireSync(moduleName) {
|
|
98
|
+
return `sap.ui.requireSync("${ModuleName.toRequireJSName(moduleName)}");\n`;
|
|
98
99
|
},
|
|
99
100
|
|
|
100
101
|
shouldDecorate(resolvedModule) {
|
|
@@ -148,6 +149,9 @@ class BundleBuilder {
|
|
|
148
149
|
|
|
149
150
|
this.options = options || {};
|
|
150
151
|
this.optimize = !!this.options.optimize;
|
|
152
|
+
if (this.options.sourceMap === undefined) {
|
|
153
|
+
this.options.sourceMap = true;
|
|
154
|
+
}
|
|
151
155
|
|
|
152
156
|
// when decorateBootstrapModule is set to false, we don't write the optimized flag
|
|
153
157
|
// and don't write the try catch wrapper
|
|
@@ -157,6 +161,15 @@ class BundleBuilder {
|
|
|
157
161
|
// TODO is the following condition ok or should the availability of jquery.sap.global.js be configurable?
|
|
158
162
|
this.jqglobalAvailable = !resolvedModule.containsGlobal;
|
|
159
163
|
this.openModule(resolvedModule.name);
|
|
164
|
+
|
|
165
|
+
this._sourceMap = {
|
|
166
|
+
version: 3,
|
|
167
|
+
file: path.posix.basename(resolvedModule.name),
|
|
168
|
+
sections: [],
|
|
169
|
+
};
|
|
170
|
+
this._bundleName = resolvedModule.name;
|
|
171
|
+
this._inlineContentCounter = 0;
|
|
172
|
+
|
|
160
173
|
let bundleInfos = [];
|
|
161
174
|
// create all sections in sequence
|
|
162
175
|
for ( const section of resolvedModule.sections ) {
|
|
@@ -185,6 +198,7 @@ class BundleBuilder {
|
|
|
185
198
|
return {
|
|
186
199
|
name: module.name,
|
|
187
200
|
content: this.outW.toString(),
|
|
201
|
+
sourceMap: this.options.sourceMap ? JSON.stringify(this._sourceMap) : null,
|
|
188
202
|
bundleInfo: bundleInfo
|
|
189
203
|
};
|
|
190
204
|
}
|
|
@@ -202,23 +216,36 @@ class BundleBuilder {
|
|
|
202
216
|
}
|
|
203
217
|
}
|
|
204
218
|
|
|
219
|
+
writeWithSourceMap(content) {
|
|
220
|
+
const transientSourceName =
|
|
221
|
+
`${path.posix.basename(this._bundleName)}?bundle-code-${this._inlineContentCounter++}`;
|
|
222
|
+
const sourceMap = createTransientSourceMap({
|
|
223
|
+
moduleName: transientSourceName,
|
|
224
|
+
moduleContent: content,
|
|
225
|
+
includeContent: true
|
|
226
|
+
});
|
|
227
|
+
this.addSourceMap(this._bundleName, sourceMap);
|
|
228
|
+
this.outW.write(content);
|
|
229
|
+
this.outW.ensureNewLine();
|
|
230
|
+
}
|
|
231
|
+
|
|
205
232
|
closeModule(resolvedModule) {
|
|
206
233
|
if ( resolvedModule.containsCore ) {
|
|
207
234
|
this.outW.ensureNewLine(); // for clarity and to avoid issues with single line comments
|
|
208
|
-
this.
|
|
209
|
-
|
|
235
|
+
this.writeWithSourceMap(
|
|
236
|
+
`// as this module contains the Core, we ensure that the Core has been booted\n` +
|
|
237
|
+
`sap.ui.getCore().boot && sap.ui.getCore().boot();`);
|
|
210
238
|
}
|
|
211
239
|
if ( this.shouldDecorate && this.options.addTryCatchRestartWrapper ) {
|
|
212
240
|
this.outW.ensureNewLine(); // for clarity and to avoid issues with single line comments
|
|
213
|
-
this.
|
|
214
|
-
|
|
215
|
-
|
|
241
|
+
this.writeWithSourceMap(
|
|
242
|
+
`} catch(oError) {\n` +
|
|
243
|
+
`if (oError.name != "Restart") { throw oError; }\n` +
|
|
244
|
+
`}`);
|
|
245
|
+
}
|
|
246
|
+
if (this.options.sourceMap) {
|
|
247
|
+
this.outW.writeln(`//# sourceMappingURL=${path.posix.basename(resolvedModule.name)}.map`);
|
|
216
248
|
}
|
|
217
|
-
/* NODE-TODO
|
|
218
|
-
if ( writeSourceMap && writeSourceMapAnnotation ) {
|
|
219
|
-
outW.ensureNewLine();
|
|
220
|
-
outW.write("//# sourceMappingURL=" + moduleName.getBaseName().replaceFirst("\\.js$", ".js.map"));
|
|
221
|
-
}*/
|
|
222
249
|
}
|
|
223
250
|
|
|
224
251
|
addSection(section) {
|
|
@@ -260,34 +287,43 @@ class BundleBuilder {
|
|
|
260
287
|
// TODO check that there are only JS modules contained
|
|
261
288
|
async writeRaw(section) {
|
|
262
289
|
// write all modules in sequence
|
|
263
|
-
for ( const
|
|
264
|
-
const resource = await this.pool.findResourceWithInfo(
|
|
290
|
+
for ( const moduleName of section.modules ) {
|
|
291
|
+
const resource = await this.pool.findResourceWithInfo(moduleName);
|
|
265
292
|
if ( resource != null ) {
|
|
266
|
-
this.outW.startSegment(
|
|
293
|
+
this.outW.startSegment(moduleName);
|
|
267
294
|
this.outW.ensureNewLine();
|
|
268
|
-
this.outW.writeln("//@ui5-bundle-raw-include " +
|
|
269
|
-
await this.writeRawModule(
|
|
295
|
+
this.outW.writeln("//@ui5-bundle-raw-include " + moduleName);
|
|
296
|
+
await this.writeRawModule(moduleName, resource);
|
|
270
297
|
const compressedSize = this.outW.endSegment();
|
|
271
|
-
log.verbose(" %s (%d,%d)",
|
|
298
|
+
log.verbose(" %s (%d,%d)", moduleName,
|
|
299
|
+
resource.info != null ? resource.info.size : -1, compressedSize);
|
|
272
300
|
if ( section.declareRawModules ) {
|
|
273
|
-
this.missingRawDeclarations.push(
|
|
301
|
+
this.missingRawDeclarations.push(moduleName);
|
|
274
302
|
}
|
|
275
|
-
if (
|
|
303
|
+
if ( moduleName === UI5ClientConstants.MODULE__JQUERY_SAP_GLOBAL ) {
|
|
276
304
|
this.jqglobalAvailable = true;
|
|
277
305
|
}
|
|
278
306
|
} else {
|
|
279
|
-
log.error(" couldn't find %s",
|
|
307
|
+
log.error(" couldn't find %s", moduleName);
|
|
280
308
|
}
|
|
281
309
|
}
|
|
282
310
|
}
|
|
283
311
|
|
|
284
|
-
async writeRawModule(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
312
|
+
async writeRawModule(moduleName, resource) {
|
|
313
|
+
this.outW.ensureNewLine();
|
|
314
|
+
let moduleContent = (await resource.buffer()).toString();
|
|
315
|
+
if (this.options.sourceMap) {
|
|
316
|
+
let moduleSourceMap;
|
|
317
|
+
({moduleContent, moduleSourceMap} =
|
|
318
|
+
await this.getSourceMapForModule({
|
|
319
|
+
moduleName,
|
|
320
|
+
moduleContent,
|
|
321
|
+
resourcePath: resource.getPath()
|
|
322
|
+
}));
|
|
323
|
+
this.addSourceMap(moduleName, moduleSourceMap);
|
|
288
324
|
}
|
|
325
|
+
this.outW.write(moduleContent);
|
|
289
326
|
this.outW.ensureNewLine();
|
|
290
|
-
this.outW.write( fileContent );
|
|
291
327
|
}
|
|
292
328
|
|
|
293
329
|
async writePreloadFunction(section) {
|
|
@@ -301,7 +337,7 @@ class BundleBuilder {
|
|
|
301
337
|
|
|
302
338
|
await this.rewriteAMDModules(sequence);
|
|
303
339
|
if ( sequence.length > 0 ) {
|
|
304
|
-
this.targetBundleFormat.beforePreloads(
|
|
340
|
+
this.writeWithSourceMap(this.targetBundleFormat.beforePreloads(section));
|
|
305
341
|
let i = 0;
|
|
306
342
|
for ( const module of sequence ) {
|
|
307
343
|
const resource = await this.pool.findResourceWithInfo(module);
|
|
@@ -309,7 +345,7 @@ class BundleBuilder {
|
|
|
309
345
|
if ( i>0 ) {
|
|
310
346
|
outW.writeln(",");
|
|
311
347
|
}
|
|
312
|
-
this.beforeWritePreloadModule(module, resource.info, resource);
|
|
348
|
+
// this.beforeWritePreloadModule(module, resource.info, resource);
|
|
313
349
|
outW.write(`\t"${module.toString()}":`);
|
|
314
350
|
outW.startSegment(module);
|
|
315
351
|
await this.writePreloadModule(module, resource.info, resource);
|
|
@@ -325,66 +361,83 @@ class BundleBuilder {
|
|
|
325
361
|
if ( i > 0 ) {
|
|
326
362
|
outW.writeln();
|
|
327
363
|
}
|
|
328
|
-
this.targetBundleFormat.afterPreloads(
|
|
364
|
+
outW.write(this.targetBundleFormat.afterPreloads(section));
|
|
329
365
|
}
|
|
330
366
|
|
|
331
367
|
// this.afterWriteFunctionPreloadSection();
|
|
332
368
|
}
|
|
333
369
|
|
|
334
|
-
async compressJS(fileContent, resource) {
|
|
335
|
-
if ( this.optimize ) {
|
|
336
|
-
const result = await terser.minify({
|
|
337
|
-
[resource.name]: String(fileContent)
|
|
338
|
-
}, {
|
|
339
|
-
compress: false, // TODO configure?
|
|
340
|
-
output: {
|
|
341
|
-
comments: copyrightCommentsPattern,
|
|
342
|
-
wrap_func_args: false
|
|
343
|
-
}
|
|
344
|
-
// , outFileName: resource.name
|
|
345
|
-
// , outSourceMap: true
|
|
346
|
-
});
|
|
347
|
-
// console.log(result.map);
|
|
348
|
-
// const map = new MOZ_SourceMap.SourceMapConsumer(result.map);
|
|
349
|
-
// map.eachMapping(function (m) { console.log(m); }); // console.log(map);
|
|
350
|
-
fileContent = result.code;
|
|
351
|
-
// throw new Error();
|
|
352
|
-
}
|
|
353
|
-
return fileContent;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
370
|
beforeWriteFunctionPreloadSection(sequence) {
|
|
357
371
|
// simple version: just sort alphabetically
|
|
358
372
|
sequence.sort();
|
|
359
373
|
}
|
|
360
374
|
|
|
375
|
+
addSourceMap(moduleName, map) {
|
|
376
|
+
if (!map) {
|
|
377
|
+
throw new Error("No source map provided");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (map.mappings.startsWith(";")) {
|
|
381
|
+
// If first line is not already mapped (typical for comments or parentheses), add a mapping to
|
|
382
|
+
// make sure that dev-tools (especially Chrome's) don't choose the end of the preceding module
|
|
383
|
+
// when the user tries to set a breakpoint from the bundle file
|
|
384
|
+
map.mappings = "AAAA" + map.mappings;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
map.sourceRoot = path.posix.relative(
|
|
388
|
+
path.posix.dirname(this._bundleName), path.posix.dirname(moduleName));
|
|
389
|
+
|
|
390
|
+
this._sourceMap.sections.push({
|
|
391
|
+
offset: {
|
|
392
|
+
line: this.outW.lineOffset,
|
|
393
|
+
column: this.outW.columnOffset
|
|
394
|
+
},
|
|
395
|
+
map
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
361
399
|
async rewriteAMDModules(sequence) {
|
|
362
400
|
if ( this.options.usePredefineCalls ) {
|
|
363
401
|
const outW = this.outW;
|
|
364
402
|
|
|
365
403
|
const remaining = [];
|
|
366
|
-
for ( const
|
|
367
|
-
if ( /\.js$/.test(
|
|
368
|
-
// console.log("Processing " +
|
|
369
|
-
const resource = await this.pool.findResourceWithInfo(
|
|
370
|
-
let
|
|
371
|
-
|
|
372
|
-
if (
|
|
373
|
-
|
|
404
|
+
for ( const moduleName of sequence ) {
|
|
405
|
+
if ( /\.js$/.test(moduleName) ) {
|
|
406
|
+
// console.log("Processing " + moduleName);
|
|
407
|
+
const resource = await this.pool.findResourceWithInfo(moduleName);
|
|
408
|
+
let moduleContent = (await resource.buffer()).toString();
|
|
409
|
+
let moduleSourceMap;
|
|
410
|
+
if (this.options.sourceMap) {
|
|
411
|
+
({moduleContent, moduleSourceMap} =
|
|
412
|
+
await this.getSourceMapForModule({
|
|
413
|
+
moduleName,
|
|
414
|
+
moduleContent,
|
|
415
|
+
resourcePath: resource.getPath()
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const rewriteRes = await rewriteDefine({
|
|
420
|
+
moduleName, moduleContent, moduleSourceMap
|
|
421
|
+
});
|
|
422
|
+
if (rewriteRes) {
|
|
423
|
+
const {moduleContent, moduleSourceMap} = rewriteRes;
|
|
424
|
+
outW.startSegment(moduleName);
|
|
374
425
|
outW.ensureNewLine();
|
|
375
|
-
|
|
376
|
-
|
|
426
|
+
if (moduleSourceMap) {
|
|
427
|
+
this.addSourceMap(moduleName, moduleSourceMap);
|
|
428
|
+
}
|
|
429
|
+
outW.write(moduleContent);
|
|
377
430
|
outW.ensureNewLine();
|
|
378
431
|
const compressedSize = outW.endSegment();
|
|
379
|
-
log.verbose(" %s (%d,%d)",
|
|
432
|
+
log.verbose(" %s (%d,%d)", moduleName,
|
|
380
433
|
resource.info != null ? resource.info.size : -1, compressedSize);
|
|
381
434
|
} else {
|
|
382
435
|
// keep unprocessed modules
|
|
383
|
-
remaining.push(
|
|
436
|
+
remaining.push(moduleName);
|
|
384
437
|
}
|
|
385
438
|
} else {
|
|
386
439
|
// keep unprocessed modules
|
|
387
|
-
remaining.push(
|
|
440
|
+
remaining.push(moduleName);
|
|
388
441
|
}
|
|
389
442
|
}
|
|
390
443
|
|
|
@@ -400,59 +453,71 @@ class BundleBuilder {
|
|
|
400
453
|
|
|
401
454
|
/**
|
|
402
455
|
*
|
|
403
|
-
* @param {string}
|
|
456
|
+
* @param {string} moduleName module name
|
|
404
457
|
* @param {ModuleInfo} info
|
|
405
458
|
* @param {module:@ui5/fs.Resource} resource
|
|
406
459
|
* @returns {Promise<boolean>}
|
|
407
460
|
*/
|
|
408
|
-
async writePreloadModule(
|
|
461
|
+
async writePreloadModule(moduleName, info, resource) {
|
|
409
462
|
const outW = this.outW;
|
|
410
463
|
|
|
411
|
-
if ( /\.js$/.test(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
outW.
|
|
464
|
+
if ( /\.js$/.test(moduleName) && (info == null || !info.requiresTopLevelScope) ) {
|
|
465
|
+
outW.writeln(`function(){`);
|
|
466
|
+
// The module should be written to a new line in order for dev-tools to map breakpoints to it
|
|
467
|
+
outW.ensureNewLine();
|
|
468
|
+
let moduleContent = (await resource.buffer()).toString();
|
|
469
|
+
if (this.options.sourceMap) {
|
|
470
|
+
let moduleSourceMap;
|
|
471
|
+
({moduleContent, moduleSourceMap} =
|
|
472
|
+
await this.getSourceMapForModule({
|
|
473
|
+
moduleName,
|
|
474
|
+
moduleContent,
|
|
475
|
+
resourcePath: resource.getPath()
|
|
476
|
+
}));
|
|
477
|
+
|
|
478
|
+
this.addSourceMap(moduleName, moduleSourceMap);
|
|
479
|
+
}
|
|
480
|
+
outW.write(moduleContent);
|
|
415
481
|
this.exportGlobalNames(info);
|
|
416
482
|
outW.ensureNewLine();
|
|
417
483
|
outW.write(`}`);
|
|
418
|
-
} else if ( /\.js$/.test(
|
|
484
|
+
} else if ( /\.js$/.test(moduleName) /* implicitly: && info != null && info.requiresTopLevelScope */ ) {
|
|
419
485
|
log.warn("**** warning: module %s requires top level scope" +
|
|
420
|
-
" and can only be embedded as a string (requires 'eval')",
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const fileContent = await resource.buffer();
|
|
486
|
+
" and can only be embedded as a string (requires 'eval')", moduleName);
|
|
487
|
+
outW.write( makeStringLiteral( (await resource.buffer()).toString() ) );
|
|
488
|
+
} else if ( /\.html$/.test(moduleName) ) {
|
|
489
|
+
const fileContent = (await resource.buffer()).toString();
|
|
425
490
|
outW.write( makeStringLiteral( fileContent ) );
|
|
426
|
-
} else if ( /\.json$/.test(
|
|
427
|
-
let fileContent = await resource.buffer();
|
|
491
|
+
} else if ( /\.json$/.test(moduleName) ) {
|
|
492
|
+
let fileContent = (await resource.buffer()).toString();
|
|
428
493
|
if ( this.optimize ) {
|
|
429
494
|
try {
|
|
430
495
|
fileContent = JSON.stringify( JSON.parse( fileContent) );
|
|
431
496
|
} catch (e) {
|
|
432
|
-
log.verbose("Failed to parse JSON file %s. Ignoring error, skipping compression.",
|
|
497
|
+
log.verbose("Failed to parse JSON file %s. Ignoring error, skipping compression.", moduleName);
|
|
433
498
|
log.verbose(e);
|
|
434
499
|
}
|
|
435
500
|
}
|
|
436
501
|
outW.write(makeStringLiteral(fileContent));
|
|
437
|
-
} else if ( /\.xml$/.test(
|
|
438
|
-
let fileContent = await resource.buffer();
|
|
502
|
+
} else if ( /\.xml$/.test(moduleName) ) {
|
|
503
|
+
let fileContent = (await resource.buffer()).toString();
|
|
439
504
|
if ( this.optimize ) {
|
|
440
505
|
// For XML we use the pretty data
|
|
441
506
|
// Do not minify if XML(View) contains an <*:pre> tag,
|
|
442
507
|
// because whitespace of HTML <pre> should be preserved (should only happen rarely)
|
|
443
|
-
if (!xmlHtmlPrePattern.test(fileContent
|
|
444
|
-
fileContent = pd.xmlmin(fileContent
|
|
508
|
+
if (!xmlHtmlPrePattern.test(fileContent)) {
|
|
509
|
+
fileContent = pd.xmlmin(fileContent, false);
|
|
445
510
|
}
|
|
446
511
|
}
|
|
447
512
|
outW.write( makeStringLiteral( fileContent ) );
|
|
448
|
-
} else if ( /\.properties$/.test(
|
|
513
|
+
} else if ( /\.properties$/.test(moduleName) ) {
|
|
449
514
|
// Since the Builder is also used when building non-project resources (e.g. dependencies)
|
|
450
515
|
// *.properties files should be escaped if encoding option is specified
|
|
451
516
|
const fileContent = await escapePropertiesFile(resource);
|
|
452
517
|
|
|
453
518
|
outW.write( makeStringLiteral( fileContent ) );
|
|
454
519
|
} else {
|
|
455
|
-
log.error("don't know how to embed module " +
|
|
520
|
+
log.error("don't know how to embed module " + moduleName); // TODO throw?
|
|
456
521
|
}
|
|
457
522
|
|
|
458
523
|
return true;
|
|
@@ -470,18 +535,19 @@ class BundleBuilder {
|
|
|
470
535
|
this.outW.ensureNewLine();
|
|
471
536
|
info.exposedGlobals.forEach( (globalName) => {
|
|
472
537
|
// Note: globalName can be assumed to be a valid identifier as it is used as variable name anyhow
|
|
473
|
-
this.
|
|
538
|
+
this.writeWithSourceMap(`this.${globalName}=${globalName};\n`);
|
|
474
539
|
});
|
|
475
540
|
}
|
|
476
541
|
|
|
477
542
|
writeBundleInfos(sections) {
|
|
478
543
|
this.outW.ensureNewLine();
|
|
479
544
|
|
|
545
|
+
let bundleInfoStr = "";
|
|
480
546
|
if ( sections.length > 0 ) {
|
|
481
|
-
this.targetBundleFormat.beforeBundleInfo(
|
|
547
|
+
bundleInfoStr = this.targetBundleFormat.beforeBundleInfo();
|
|
482
548
|
sections.forEach((section, idx) => {
|
|
483
549
|
if ( idx > 0 ) {
|
|
484
|
-
|
|
550
|
+
bundleInfoStr += ",\n";
|
|
485
551
|
}
|
|
486
552
|
|
|
487
553
|
if (!section.name) {
|
|
@@ -492,31 +558,116 @@ class BundleBuilder {
|
|
|
492
558
|
`The info might not work as expected. ` +
|
|
493
559
|
`The name must match the bundle filename (incl. extension such as '.js')`);
|
|
494
560
|
}
|
|
495
|
-
|
|
561
|
+
bundleInfoStr += `"${section.name}":[${section.modules.map(makeStringLiteral).join(",")}]`;
|
|
496
562
|
});
|
|
497
|
-
|
|
498
|
-
this.targetBundleFormat.afterBundleInfo(
|
|
563
|
+
bundleInfoStr += "\n";
|
|
564
|
+
bundleInfoStr += this.targetBundleFormat.afterBundleInfo();
|
|
565
|
+
|
|
566
|
+
this.writeWithSourceMap(bundleInfoStr);
|
|
499
567
|
}
|
|
500
568
|
}
|
|
501
569
|
|
|
502
570
|
writeRequires(section) {
|
|
503
571
|
this.outW.ensureNewLine();
|
|
504
572
|
section.modules.forEach( (module) => {
|
|
505
|
-
this.targetBundleFormat.requireSync(
|
|
573
|
+
this.writeWithSourceMap(this.targetBundleFormat.requireSync(module));
|
|
506
574
|
});
|
|
507
575
|
}
|
|
576
|
+
|
|
577
|
+
async getSourceMapForModule({moduleName, moduleContent, resourcePath}) {
|
|
578
|
+
let moduleSourceMap = null;
|
|
579
|
+
let newModuleContent = moduleContent;
|
|
580
|
+
|
|
581
|
+
const sourceMapUrlMatch = moduleContent.match(sourceMappingUrlPattern);
|
|
582
|
+
if (sourceMapUrlMatch) {
|
|
583
|
+
const sourceMapUrl = sourceMapUrlMatch[1];
|
|
584
|
+
log.verbose(`Found source map reference in content of module ${moduleName}: ${sourceMapUrl}`);
|
|
585
|
+
|
|
586
|
+
// Strip sourceMappingURL from module code to be bundled
|
|
587
|
+
// It has no effect and might be cause for confusion
|
|
588
|
+
newModuleContent = moduleContent.replace(sourceMappingUrlPattern, "");
|
|
589
|
+
|
|
590
|
+
if (sourceMapUrl) {
|
|
591
|
+
if (sourceMapUrl.startsWith("data:")) {
|
|
592
|
+
// Data-URI indicates an inline source map
|
|
593
|
+
const expectedTypeAndEncoding = "data:application/json;charset=utf-8;base64,";
|
|
594
|
+
if (sourceMapUrl.startsWith(expectedTypeAndEncoding)) {
|
|
595
|
+
const base64Content = sourceMapUrl.slice(expectedTypeAndEncoding.length);
|
|
596
|
+
moduleSourceMap = Buffer.from(base64Content, "base64").toString();
|
|
597
|
+
} else {
|
|
598
|
+
log.warn(
|
|
599
|
+
`Source map reference in module ${moduleName} is a data URI but has an unexpected` +
|
|
600
|
+
`encoding: ${sourceMapUrl}. Expected it to start with ` +
|
|
601
|
+
`"data:application/json;charset=utf-8;base64,"`);
|
|
602
|
+
}
|
|
603
|
+
} else if (httpPattern.test(sourceMapUrl)) {
|
|
604
|
+
log.warn(`Source map reference in module ${moduleName} is an absolute URL. ` +
|
|
605
|
+
`Currently, only relative URLs are supported.`);
|
|
606
|
+
} else if (path.posix.isAbsolute(sourceMapUrl)) {
|
|
607
|
+
log.warn(`Source map reference in module ${moduleName} is an absolute path. ` +
|
|
608
|
+
`Currently, only relative paths are supported.`);
|
|
609
|
+
} else {
|
|
610
|
+
const sourceMapPath = path.posix.join(path.posix.dirname(moduleName), sourceMapUrl);
|
|
611
|
+
|
|
612
|
+
try {
|
|
613
|
+
const sourceMapResource = await this.pool.findResource(sourceMapPath);
|
|
614
|
+
moduleSourceMap = (await sourceMapResource.buffer()).toString();
|
|
615
|
+
} catch (e) {
|
|
616
|
+
// No input source map
|
|
617
|
+
log.warn(`Unable to read source map for module ${moduleName}: ${e.message}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
const sourceMapFileCandidate = resourcePath.slice("/resources/".length) + ".map";
|
|
623
|
+
log.verbose(`Could not find a sourceMappingURL reference in content of module ${moduleName}. ` +
|
|
624
|
+
`Attempting to find a source map resource based on the module's path: ${sourceMapFileCandidate}`);
|
|
625
|
+
try {
|
|
626
|
+
const sourceMapResource = await this.pool.findResource(sourceMapFileCandidate);
|
|
627
|
+
if (sourceMapResource) {
|
|
628
|
+
moduleSourceMap = (await sourceMapResource.buffer()).toString();
|
|
629
|
+
}
|
|
630
|
+
} catch (e) {
|
|
631
|
+
// No input source map
|
|
632
|
+
log.verbose(`Could not find a source map for module ${moduleName}: ${e.message}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
if (moduleSourceMap) {
|
|
638
|
+
moduleSourceMap = JSON.parse(moduleSourceMap);
|
|
639
|
+
} else {
|
|
640
|
+
log.verbose(`No source map available for module ${moduleName}. Creating transient source map...`);
|
|
641
|
+
moduleSourceMap = createTransientSourceMap({
|
|
642
|
+
moduleName: path.posix.basename(resourcePath),
|
|
643
|
+
moduleContent
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
moduleSourceMap,
|
|
649
|
+
moduleContent: newModuleContent
|
|
650
|
+
};
|
|
651
|
+
}
|
|
508
652
|
}
|
|
509
653
|
|
|
510
654
|
const CALL_SAP_UI_DEFINE = ["sap", "ui", "define"];
|
|
511
655
|
|
|
512
|
-
|
|
656
|
+
/*
|
|
657
|
+
* @param {object} parameters
|
|
658
|
+
* @param {string} parameters.moduleName
|
|
659
|
+
* @param {string} parameters.moduleContent
|
|
660
|
+
* @param {object} [parameters.moduleSourceMap]
|
|
661
|
+
* @returns {Promise<object|null>} Object containing <code>moduleContent</code> and
|
|
662
|
+
* <code>moduleSourceMap</code> (if one was provided) or <code>null</code> if no rewrite was applicable
|
|
663
|
+
*/
|
|
664
|
+
async function rewriteDefine({moduleName, moduleContent, moduleSourceMap}) {
|
|
513
665
|
let ast;
|
|
514
|
-
const codeStr = code.toString();
|
|
515
666
|
try {
|
|
516
|
-
ast = parseJS(
|
|
667
|
+
ast = parseJS(moduleContent, {range: true});
|
|
517
668
|
} catch (e) {
|
|
518
669
|
log.error("error while parsing %s: %s", moduleName, e.message);
|
|
519
|
-
return;
|
|
670
|
+
return {};
|
|
520
671
|
}
|
|
521
672
|
|
|
522
673
|
if ( ast.type === Syntax.Program &&
|
|
@@ -525,7 +676,6 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
|
|
|
525
676
|
const changes = [];
|
|
526
677
|
const defineCall = ast.body[0].expression;
|
|
527
678
|
|
|
528
|
-
|
|
529
679
|
// Inject module name if missing
|
|
530
680
|
if ( defineCall.arguments.length == 0 ||
|
|
531
681
|
defineCall.arguments[0].type !== Syntax.Literal ) {
|
|
@@ -543,7 +693,6 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
|
|
|
543
693
|
|
|
544
694
|
changes.push({
|
|
545
695
|
index,
|
|
546
|
-
count: 0,
|
|
547
696
|
value
|
|
548
697
|
});
|
|
549
698
|
}
|
|
@@ -555,29 +704,109 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
|
|
|
555
704
|
changes.push({
|
|
556
705
|
// asterisk marks the index: sap.ui.*define()
|
|
557
706
|
index: defineCall.callee.property.range[0],
|
|
558
|
-
count: 0,
|
|
559
707
|
value: "pre"
|
|
560
708
|
});
|
|
561
709
|
}
|
|
562
710
|
|
|
563
|
-
return
|
|
711
|
+
return transform(changes, moduleContent, moduleSourceMap);
|
|
564
712
|
}
|
|
565
713
|
|
|
566
|
-
return
|
|
714
|
+
return null;
|
|
567
715
|
}
|
|
568
716
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
717
|
+
/*
|
|
718
|
+
* @param {object[]} changes Changes that should be applied to the code
|
|
719
|
+
* @param {string} moduleContent Code to transform
|
|
720
|
+
* @param {object} [moduleSourceMap] Optional source map that should be aligned with the content change
|
|
721
|
+
* @returns {Promise<object>} Object containing <code>moduleContent</code> and
|
|
722
|
+
* <code>moduleSourceMap</code> (if one was provided)
|
|
723
|
+
*/
|
|
724
|
+
async function transform(changes, moduleContent, moduleSourceMap) {
|
|
725
|
+
const mappingChanges = [];
|
|
726
|
+
|
|
727
|
+
const array = Array.from(moduleContent);
|
|
728
|
+
// No sorting needed as changes are added in correct (reverse) order
|
|
573
729
|
changes.forEach((change) => {
|
|
730
|
+
if (moduleSourceMap) {
|
|
731
|
+
// Compute line and column for given index to re-align source map with inserted characters
|
|
732
|
+
const precedingCode = array.slice(0, change.index);
|
|
733
|
+
|
|
734
|
+
const line = precedingCode.reduce((lineCount, char) => {
|
|
735
|
+
if (char === "\n") {
|
|
736
|
+
lineCount++;
|
|
737
|
+
}
|
|
738
|
+
return lineCount;
|
|
739
|
+
}, 0);
|
|
740
|
+
const lineStartIndex = precedingCode.lastIndexOf("\n") + 1;
|
|
741
|
+
const column = change.index - lineStartIndex;
|
|
742
|
+
|
|
743
|
+
// Source map re-alignment needs to be done from front to back
|
|
744
|
+
mappingChanges.unshift({
|
|
745
|
+
line,
|
|
746
|
+
column,
|
|
747
|
+
columnDiff: change.value.length
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Apply modification
|
|
574
752
|
array.splice(
|
|
575
753
|
change.index,
|
|
576
|
-
|
|
754
|
+
0,
|
|
577
755
|
change.value
|
|
578
756
|
);
|
|
579
757
|
});
|
|
580
|
-
|
|
758
|
+
const transformedCode = array.join("");
|
|
759
|
+
|
|
760
|
+
if (moduleSourceMap) {
|
|
761
|
+
const mappings = decodeMappings(moduleSourceMap.mappings);
|
|
762
|
+
mappingChanges.forEach((mappingChange) => {
|
|
763
|
+
const lineMapping = mappings[mappingChange.line];
|
|
764
|
+
if (!lineMapping) {
|
|
765
|
+
// No mapping available that could be transformed
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
// Mapping structure:
|
|
769
|
+
// [generatedCodeColumn, sourceIndex, sourceCodeLine, sourceCodeColumn, nameIndex]
|
|
770
|
+
lineMapping.forEach((mapping) => {
|
|
771
|
+
if (mapping[0] > mappingChange.column) {
|
|
772
|
+
// All column mappings for the generated code after any change
|
|
773
|
+
// need to be moved by the amount of inserted characters
|
|
774
|
+
mapping[0] = mapping[0] + mappingChange.columnDiff;
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
moduleSourceMap.mappings = encodeMappings(mappings);
|
|
780
|
+
|
|
781
|
+
// No need for file information in source map since the bundled code does not exist in any file anyways
|
|
782
|
+
delete moduleSourceMap.file;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return {
|
|
786
|
+
moduleContent: transformedCode,
|
|
787
|
+
moduleSourceMap
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function createTransientSourceMap({moduleName, moduleContent, includeContent = false}) {
|
|
792
|
+
const sourceMap = {
|
|
793
|
+
version: 3,
|
|
794
|
+
sources: [moduleName],
|
|
795
|
+
// TODO: check whether moduleContent.match() with \n is better w.r.t performance/memory usage
|
|
796
|
+
mappings: encodeMappings(moduleContent.split("\n").map((line, i) => {
|
|
797
|
+
return [[0, 0, i, 0]];
|
|
798
|
+
}))
|
|
799
|
+
};
|
|
800
|
+
if (includeContent) {
|
|
801
|
+
sourceMap.sourcesContent = [moduleContent];
|
|
802
|
+
}
|
|
803
|
+
return sourceMap;
|
|
581
804
|
}
|
|
582
805
|
|
|
583
806
|
module.exports = BundleBuilder;
|
|
807
|
+
|
|
808
|
+
// Export local functions for testing only
|
|
809
|
+
/* istanbul ignore else */
|
|
810
|
+
if (process.env.NODE_ENV === "test") {
|
|
811
|
+
module.exports.__localFunctions__ = {rewriteDefine, createTransientSourceMap};
|
|
812
|
+
}
|