@ui5/builder 3.0.0-alpha.1 → 3.0.0-alpha.4
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 +790 -0
- package/lib/builder/BuildContext.js +6 -2
- package/lib/builder/ProjectBuildContext.js +4 -0
- package/lib/builder/builder.js +8 -2
- package/lib/lbt/analyzer/JSModuleAnalyzer.js +14 -6
- package/lib/lbt/analyzer/XMLTemplateAnalyzer.js +2 -1
- package/lib/lbt/bundle/Builder.js +360 -103
- package/lib/lbt/bundle/BundleWriter.js +17 -0
- package/lib/lbt/resources/LocatorResource.js +6 -6
- package/lib/lbt/resources/LocatorResourcePool.js +8 -4
- package/lib/lbt/utils/parseUtils.js +1 -1
- package/lib/processors/bundlers/moduleBundler.js +29 -7
- package/lib/processors/minifier.js +11 -5
- package/lib/tasks/TaskUtil.js +12 -0
- package/lib/tasks/bundlers/generateBundle.js +69 -29
- package/lib/tasks/bundlers/generateComponentPreload.js +12 -5
- package/lib/tasks/bundlers/generateFlexChangesBundle.js +3 -2
- package/lib/tasks/bundlers/generateLibraryPreload.js +44 -18
- package/lib/tasks/bundlers/generateStandaloneAppBundle.js +34 -7
- package/lib/tasks/bundlers/utils/createModuleNameMapping.js +30 -0
- package/lib/tasks/generateResourcesJson.js +1 -1
- package/lib/tasks/minify.js +13 -3
- package/lib/types/library/LibraryBuilder.js +4 -3
- package/lib/types/themeLibrary/ThemeLibraryBuilder.js +2 -1
- package/package.json +6 -4
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const {pd} = require("pretty-data");
|
|
7
7
|
const {parseJS, Syntax} = require("../utils/parseUtils");
|
|
8
|
-
|
|
8
|
+
const {encode: encodeMappings, decode: decodeMappings} = require("sourcemap-codec");
|
|
9
9
|
|
|
10
10
|
const {isMethodCall} = require("../utils/ASTUtils");
|
|
11
11
|
const ModuleName = require("../utils/ModuleName");
|
|
@@ -18,6 +18,8 @@ const {SectionType} = require("./BundleDefinition");
|
|
|
18
18
|
const BundleWriter = require("./BundleWriter");
|
|
19
19
|
const log = require("@ui5/logger").getLogger("lbt:bundle:Builder");
|
|
20
20
|
|
|
21
|
+
const sourceMappingUrlPattern = /\/\/# sourceMappingURL=(.+)\s*$/;
|
|
22
|
+
const httpPattern = /^https?:\/\//i;
|
|
21
23
|
const xmlHtmlPrePattern = /<(?:\w+:)?pre\b/;
|
|
22
24
|
|
|
23
25
|
const strReplacements = {
|
|
@@ -39,30 +41,30 @@ function isEmptyBundle(resolvedBundle) {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const UI5BundleFormat = {
|
|
42
|
-
beforePreloads(
|
|
43
|
-
|
|
44
|
-
outW.writeln(`{`);
|
|
44
|
+
beforePreloads(section) {
|
|
45
|
+
let str = `jQuery.sap.registerPreloadedModules({\n`;
|
|
45
46
|
if ( section.name ) {
|
|
46
|
-
|
|
47
|
+
str += `"name":"${section.name}",\n`;
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
str += `"version":"2.0",\n`;
|
|
50
|
+
str += `"modules":{\n`;
|
|
51
|
+
return str;
|
|
50
52
|
},
|
|
51
53
|
|
|
52
|
-
afterPreloads(
|
|
53
|
-
|
|
54
|
+
afterPreloads(section) {
|
|
55
|
+
return `}});\n`;
|
|
54
56
|
},
|
|
55
57
|
|
|
56
|
-
beforeBundleInfo(
|
|
57
|
-
|
|
58
|
+
beforeBundleInfo() {
|
|
59
|
+
return `"unsupported"; /* 'bundleInfo' section mode not supported (requires ui5loader)\n`;
|
|
58
60
|
},
|
|
59
61
|
|
|
60
|
-
afterBundleInfo(
|
|
61
|
-
|
|
62
|
+
afterBundleInfo() {
|
|
63
|
+
return "*/\n";
|
|
62
64
|
},
|
|
63
65
|
|
|
64
|
-
requireSync(
|
|
65
|
-
|
|
66
|
+
requireSync(moduleName) {
|
|
67
|
+
return `sap.ui.requireSync("${ModuleName.toRequireJSName(moduleName)}");\n`;
|
|
66
68
|
},
|
|
67
69
|
|
|
68
70
|
shouldDecorate(resolvedModule) {
|
|
@@ -71,28 +73,29 @@ const UI5BundleFormat = {
|
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
const EVOBundleFormat = {
|
|
74
|
-
beforePreloads(
|
|
75
|
-
|
|
76
|
+
beforePreloads(section) {
|
|
77
|
+
return `sap.ui.require.preload({\n`;
|
|
76
78
|
},
|
|
77
79
|
|
|
78
|
-
afterPreloads(
|
|
79
|
-
|
|
80
|
+
afterPreloads(section) {
|
|
81
|
+
let str = `}`;
|
|
80
82
|
if ( section.name ) {
|
|
81
|
-
|
|
83
|
+
str += `,"${section.name}"`;
|
|
82
84
|
}
|
|
83
|
-
|
|
85
|
+
str += `);\n`;
|
|
86
|
+
return str;
|
|
84
87
|
},
|
|
85
88
|
|
|
86
|
-
beforeBundleInfo(
|
|
87
|
-
|
|
89
|
+
beforeBundleInfo() {
|
|
90
|
+
return "sap.ui.loader.config({bundlesUI5:{\n";
|
|
88
91
|
},
|
|
89
92
|
|
|
90
|
-
afterBundleInfo(
|
|
91
|
-
|
|
93
|
+
afterBundleInfo() {
|
|
94
|
+
return "}});\n";
|
|
92
95
|
},
|
|
93
96
|
|
|
94
|
-
requireSync(
|
|
95
|
-
|
|
97
|
+
requireSync(moduleName) {
|
|
98
|
+
return `sap.ui.requireSync("${ModuleName.toRequireJSName(moduleName)}");\n`;
|
|
96
99
|
},
|
|
97
100
|
|
|
98
101
|
shouldDecorate(resolvedModule) {
|
|
@@ -146,6 +149,9 @@ class BundleBuilder {
|
|
|
146
149
|
|
|
147
150
|
this.options = options || {};
|
|
148
151
|
this.optimize = !!this.options.optimize;
|
|
152
|
+
if (this.options.sourceMap === undefined) {
|
|
153
|
+
this.options.sourceMap = true;
|
|
154
|
+
}
|
|
149
155
|
|
|
150
156
|
// when decorateBootstrapModule is set to false, we don't write the optimized flag
|
|
151
157
|
// and don't write the try catch wrapper
|
|
@@ -155,6 +161,15 @@ class BundleBuilder {
|
|
|
155
161
|
// TODO is the following condition ok or should the availability of jquery.sap.global.js be configurable?
|
|
156
162
|
this.jqglobalAvailable = !resolvedModule.containsGlobal;
|
|
157
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
|
+
|
|
158
173
|
let bundleInfos = [];
|
|
159
174
|
// create all sections in sequence
|
|
160
175
|
for ( const section of resolvedModule.sections ) {
|
|
@@ -183,6 +198,7 @@ class BundleBuilder {
|
|
|
183
198
|
return {
|
|
184
199
|
name: module.name,
|
|
185
200
|
content: this.outW.toString(),
|
|
201
|
+
sourceMap: this.options.sourceMap ? JSON.stringify(this._sourceMap) : null,
|
|
186
202
|
bundleInfo: bundleInfo
|
|
187
203
|
};
|
|
188
204
|
}
|
|
@@ -200,23 +216,36 @@ class BundleBuilder {
|
|
|
200
216
|
}
|
|
201
217
|
}
|
|
202
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
|
+
|
|
203
232
|
closeModule(resolvedModule) {
|
|
204
233
|
if ( resolvedModule.containsCore ) {
|
|
205
234
|
this.outW.ensureNewLine(); // for clarity and to avoid issues with single line comments
|
|
206
|
-
this.
|
|
207
|
-
|
|
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();`);
|
|
208
238
|
}
|
|
209
239
|
if ( this.shouldDecorate && this.options.addTryCatchRestartWrapper ) {
|
|
210
240
|
this.outW.ensureNewLine(); // for clarity and to avoid issues with single line comments
|
|
211
|
-
this.
|
|
212
|
-
|
|
213
|
-
|
|
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`);
|
|
214
248
|
}
|
|
215
|
-
/* NODE-TODO
|
|
216
|
-
if ( writeSourceMap && writeSourceMapAnnotation ) {
|
|
217
|
-
outW.ensureNewLine();
|
|
218
|
-
outW.write("//# sourceMappingURL=" + moduleName.getBaseName().replaceFirst("\\.js$", ".js.map"));
|
|
219
|
-
}*/
|
|
220
249
|
}
|
|
221
250
|
|
|
222
251
|
addSection(section) {
|
|
@@ -258,31 +287,43 @@ class BundleBuilder {
|
|
|
258
287
|
// TODO check that there are only JS modules contained
|
|
259
288
|
async writeRaw(section) {
|
|
260
289
|
// write all modules in sequence
|
|
261
|
-
for ( const
|
|
262
|
-
const resource = await this.pool.findResourceWithInfo(
|
|
290
|
+
for ( const moduleName of section.modules ) {
|
|
291
|
+
const resource = await this.pool.findResourceWithInfo(moduleName);
|
|
263
292
|
if ( resource != null ) {
|
|
264
|
-
this.outW.startSegment(
|
|
293
|
+
this.outW.startSegment(moduleName);
|
|
265
294
|
this.outW.ensureNewLine();
|
|
266
|
-
this.outW.writeln("//@ui5-bundle-raw-include " +
|
|
267
|
-
await this.writeRawModule(
|
|
295
|
+
this.outW.writeln("//@ui5-bundle-raw-include " + moduleName);
|
|
296
|
+
await this.writeRawModule(moduleName, resource);
|
|
268
297
|
const compressedSize = this.outW.endSegment();
|
|
269
|
-
log.verbose(" %s (%d,%d)",
|
|
298
|
+
log.verbose(" %s (%d,%d)", moduleName,
|
|
299
|
+
resource.info != null ? resource.info.size : -1, compressedSize);
|
|
270
300
|
if ( section.declareRawModules ) {
|
|
271
|
-
this.missingRawDeclarations.push(
|
|
301
|
+
this.missingRawDeclarations.push(moduleName);
|
|
272
302
|
}
|
|
273
|
-
if (
|
|
303
|
+
if ( moduleName === UI5ClientConstants.MODULE__JQUERY_SAP_GLOBAL ) {
|
|
274
304
|
this.jqglobalAvailable = true;
|
|
275
305
|
}
|
|
276
306
|
} else {
|
|
277
|
-
log.error(" couldn't find %s",
|
|
307
|
+
log.error(" couldn't find %s", moduleName);
|
|
278
308
|
}
|
|
279
309
|
}
|
|
280
310
|
}
|
|
281
311
|
|
|
282
|
-
async writeRawModule(
|
|
283
|
-
|
|
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);
|
|
324
|
+
}
|
|
325
|
+
this.outW.write(moduleContent);
|
|
284
326
|
this.outW.ensureNewLine();
|
|
285
|
-
this.outW.write(fileContent);
|
|
286
327
|
}
|
|
287
328
|
|
|
288
329
|
async writePreloadFunction(section) {
|
|
@@ -296,7 +337,7 @@ class BundleBuilder {
|
|
|
296
337
|
|
|
297
338
|
await this.rewriteAMDModules(sequence);
|
|
298
339
|
if ( sequence.length > 0 ) {
|
|
299
|
-
this.targetBundleFormat.beforePreloads(
|
|
340
|
+
this.writeWithSourceMap(this.targetBundleFormat.beforePreloads(section));
|
|
300
341
|
let i = 0;
|
|
301
342
|
for ( const module of sequence ) {
|
|
302
343
|
const resource = await this.pool.findResourceWithInfo(module);
|
|
@@ -304,7 +345,7 @@ class BundleBuilder {
|
|
|
304
345
|
if ( i>0 ) {
|
|
305
346
|
outW.writeln(",");
|
|
306
347
|
}
|
|
307
|
-
this.beforeWritePreloadModule(module, resource.info, resource);
|
|
348
|
+
// this.beforeWritePreloadModule(module, resource.info, resource);
|
|
308
349
|
outW.write(`\t"${module.toString()}":`);
|
|
309
350
|
outW.startSegment(module);
|
|
310
351
|
await this.writePreloadModule(module, resource.info, resource);
|
|
@@ -320,7 +361,7 @@ class BundleBuilder {
|
|
|
320
361
|
if ( i > 0 ) {
|
|
321
362
|
outW.writeln();
|
|
322
363
|
}
|
|
323
|
-
this.targetBundleFormat.afterPreloads(
|
|
364
|
+
outW.write(this.targetBundleFormat.afterPreloads(section));
|
|
324
365
|
}
|
|
325
366
|
|
|
326
367
|
// this.afterWriteFunctionPreloadSection();
|
|
@@ -331,32 +372,72 @@ class BundleBuilder {
|
|
|
331
372
|
sequence.sort();
|
|
332
373
|
}
|
|
333
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
|
+
|
|
334
399
|
async rewriteAMDModules(sequence) {
|
|
335
400
|
if ( this.options.usePredefineCalls ) {
|
|
336
401
|
const outW = this.outW;
|
|
337
402
|
|
|
338
403
|
const remaining = [];
|
|
339
|
-
for ( const
|
|
340
|
-
if ( /\.js$/.test(
|
|
341
|
-
// console.log("Processing " +
|
|
342
|
-
const resource = await this.pool.findResourceWithInfo(
|
|
343
|
-
let moduleContent = await resource.
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
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);
|
|
347
425
|
outW.ensureNewLine();
|
|
426
|
+
if (moduleSourceMap) {
|
|
427
|
+
this.addSourceMap(moduleName, moduleSourceMap);
|
|
428
|
+
}
|
|
348
429
|
outW.write(moduleContent);
|
|
349
430
|
outW.ensureNewLine();
|
|
350
431
|
const compressedSize = outW.endSegment();
|
|
351
|
-
log.verbose(" %s (%d,%d)",
|
|
432
|
+
log.verbose(" %s (%d,%d)", moduleName,
|
|
352
433
|
resource.info != null ? resource.info.size : -1, compressedSize);
|
|
353
434
|
} else {
|
|
354
435
|
// keep unprocessed modules
|
|
355
|
-
remaining.push(
|
|
436
|
+
remaining.push(moduleName);
|
|
356
437
|
}
|
|
357
438
|
} else {
|
|
358
439
|
// keep unprocessed modules
|
|
359
|
-
remaining.push(
|
|
440
|
+
remaining.push(moduleName);
|
|
360
441
|
}
|
|
361
442
|
}
|
|
362
443
|
|
|
@@ -372,42 +453,54 @@ class BundleBuilder {
|
|
|
372
453
|
|
|
373
454
|
/**
|
|
374
455
|
*
|
|
375
|
-
* @param {string}
|
|
456
|
+
* @param {string} moduleName module name
|
|
376
457
|
* @param {ModuleInfo} info
|
|
377
458
|
* @param {module:@ui5/fs.Resource} resource
|
|
378
459
|
* @returns {Promise<boolean>}
|
|
379
460
|
*/
|
|
380
|
-
async writePreloadModule(
|
|
461
|
+
async writePreloadModule(moduleName, info, resource) {
|
|
381
462
|
const outW = this.outW;
|
|
382
463
|
|
|
383
|
-
if ( /\.js$/.test(
|
|
384
|
-
|
|
385
|
-
|
|
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
|
+
}
|
|
386
480
|
outW.write(moduleContent);
|
|
387
481
|
this.exportGlobalNames(info);
|
|
388
482
|
outW.ensureNewLine();
|
|
389
483
|
outW.write(`}`);
|
|
390
|
-
} else if ( /\.js$/.test(
|
|
484
|
+
} else if ( /\.js$/.test(moduleName) /* implicitly: && info != null && info.requiresTopLevelScope */ ) {
|
|
391
485
|
log.warn("**** warning: module %s requires top level scope" +
|
|
392
|
-
" and can only be embedded as a string (requires 'eval')",
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
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();
|
|
397
490
|
outW.write( makeStringLiteral( fileContent ) );
|
|
398
|
-
} else if ( /\.json$/.test(
|
|
399
|
-
let fileContent = await resource.buffer();
|
|
491
|
+
} else if ( /\.json$/.test(moduleName) ) {
|
|
492
|
+
let fileContent = (await resource.buffer()).toString();
|
|
400
493
|
if ( this.optimize ) {
|
|
401
494
|
try {
|
|
402
495
|
fileContent = JSON.stringify( JSON.parse( fileContent) );
|
|
403
496
|
} catch (e) {
|
|
404
|
-
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);
|
|
405
498
|
log.verbose(e);
|
|
406
499
|
}
|
|
407
500
|
}
|
|
408
501
|
outW.write(makeStringLiteral(fileContent));
|
|
409
|
-
} else if ( /\.xml$/.test(
|
|
410
|
-
let fileContent = await resource.
|
|
502
|
+
} else if ( /\.xml$/.test(moduleName) ) {
|
|
503
|
+
let fileContent = (await resource.buffer()).toString();
|
|
411
504
|
if ( this.optimize ) {
|
|
412
505
|
// For XML we use the pretty data
|
|
413
506
|
// Do not minify if XML(View) contains an <*:pre> tag,
|
|
@@ -417,14 +510,14 @@ class BundleBuilder {
|
|
|
417
510
|
}
|
|
418
511
|
}
|
|
419
512
|
outW.write( makeStringLiteral( fileContent ) );
|
|
420
|
-
} else if ( /\.properties$/.test(
|
|
513
|
+
} else if ( /\.properties$/.test(moduleName) ) {
|
|
421
514
|
// Since the Builder is also used when building non-project resources (e.g. dependencies)
|
|
422
515
|
// *.properties files should be escaped if encoding option is specified
|
|
423
516
|
const fileContent = await escapePropertiesFile(resource);
|
|
424
517
|
|
|
425
518
|
outW.write( makeStringLiteral( fileContent ) );
|
|
426
519
|
} else {
|
|
427
|
-
log.error("don't know how to embed module " +
|
|
520
|
+
log.error("don't know how to embed module " + moduleName); // TODO throw?
|
|
428
521
|
}
|
|
429
522
|
|
|
430
523
|
return true;
|
|
@@ -442,18 +535,19 @@ class BundleBuilder {
|
|
|
442
535
|
this.outW.ensureNewLine();
|
|
443
536
|
info.exposedGlobals.forEach( (globalName) => {
|
|
444
537
|
// Note: globalName can be assumed to be a valid identifier as it is used as variable name anyhow
|
|
445
|
-
this.
|
|
538
|
+
this.writeWithSourceMap(`this.${globalName}=${globalName};\n`);
|
|
446
539
|
});
|
|
447
540
|
}
|
|
448
541
|
|
|
449
542
|
writeBundleInfos(sections) {
|
|
450
543
|
this.outW.ensureNewLine();
|
|
451
544
|
|
|
545
|
+
let bundleInfoStr = "";
|
|
452
546
|
if ( sections.length > 0 ) {
|
|
453
|
-
this.targetBundleFormat.beforeBundleInfo(
|
|
547
|
+
bundleInfoStr = this.targetBundleFormat.beforeBundleInfo();
|
|
454
548
|
sections.forEach((section, idx) => {
|
|
455
549
|
if ( idx > 0 ) {
|
|
456
|
-
|
|
550
|
+
bundleInfoStr += ",\n";
|
|
457
551
|
}
|
|
458
552
|
|
|
459
553
|
if (!section.name) {
|
|
@@ -464,31 +558,116 @@ class BundleBuilder {
|
|
|
464
558
|
`The info might not work as expected. ` +
|
|
465
559
|
`The name must match the bundle filename (incl. extension such as '.js')`);
|
|
466
560
|
}
|
|
467
|
-
|
|
561
|
+
bundleInfoStr += `"${section.name}":[${section.modules.map(makeStringLiteral).join(",")}]`;
|
|
468
562
|
});
|
|
469
|
-
|
|
470
|
-
this.targetBundleFormat.afterBundleInfo(
|
|
563
|
+
bundleInfoStr += "\n";
|
|
564
|
+
bundleInfoStr += this.targetBundleFormat.afterBundleInfo();
|
|
565
|
+
|
|
566
|
+
this.writeWithSourceMap(bundleInfoStr);
|
|
471
567
|
}
|
|
472
568
|
}
|
|
473
569
|
|
|
474
570
|
writeRequires(section) {
|
|
475
571
|
this.outW.ensureNewLine();
|
|
476
572
|
section.modules.forEach( (module) => {
|
|
477
|
-
this.targetBundleFormat.requireSync(
|
|
573
|
+
this.writeWithSourceMap(this.targetBundleFormat.requireSync(module));
|
|
478
574
|
});
|
|
479
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
|
+
}
|
|
480
652
|
}
|
|
481
653
|
|
|
482
654
|
const CALL_SAP_UI_DEFINE = ["sap", "ui", "define"];
|
|
483
655
|
|
|
484
|
-
|
|
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}) {
|
|
485
665
|
let ast;
|
|
486
|
-
const codeStr = code.toString();
|
|
487
666
|
try {
|
|
488
|
-
ast = parseJS(
|
|
667
|
+
ast = parseJS(moduleContent, {range: true});
|
|
489
668
|
} catch (e) {
|
|
490
669
|
log.error("error while parsing %s: %s", moduleName, e.message);
|
|
491
|
-
return;
|
|
670
|
+
return {};
|
|
492
671
|
}
|
|
493
672
|
|
|
494
673
|
if ( ast.type === Syntax.Program &&
|
|
@@ -497,7 +676,6 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
|
|
|
497
676
|
const changes = [];
|
|
498
677
|
const defineCall = ast.body[0].expression;
|
|
499
678
|
|
|
500
|
-
|
|
501
679
|
// Inject module name if missing
|
|
502
680
|
if ( defineCall.arguments.length == 0 ||
|
|
503
681
|
defineCall.arguments[0].type !== Syntax.Literal ) {
|
|
@@ -515,7 +693,6 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
|
|
|
515
693
|
|
|
516
694
|
changes.push({
|
|
517
695
|
index,
|
|
518
|
-
count: 0,
|
|
519
696
|
value
|
|
520
697
|
});
|
|
521
698
|
}
|
|
@@ -527,29 +704,109 @@ function rewriteDefine(targetBundleFormat, code, moduleName) {
|
|
|
527
704
|
changes.push({
|
|
528
705
|
// asterisk marks the index: sap.ui.*define()
|
|
529
706
|
index: defineCall.callee.property.range[0],
|
|
530
|
-
count: 0,
|
|
531
707
|
value: "pre"
|
|
532
708
|
});
|
|
533
709
|
}
|
|
534
710
|
|
|
535
|
-
return
|
|
711
|
+
return transform(changes, moduleContent, moduleSourceMap);
|
|
536
712
|
}
|
|
537
713
|
|
|
538
|
-
return
|
|
714
|
+
return null;
|
|
539
715
|
}
|
|
540
716
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
|
545
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
|
|
546
752
|
array.splice(
|
|
547
753
|
change.index,
|
|
548
|
-
|
|
754
|
+
0,
|
|
549
755
|
change.value
|
|
550
756
|
);
|
|
551
757
|
});
|
|
552
|
-
|
|
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;
|
|
553
804
|
}
|
|
554
805
|
|
|
555
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
|
+
}
|