@lwrjs/loader 0.22.10 → 0.22.11
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/README.md +33 -1
- package/build/assets/prod/lwr-error-shim.js +1 -1
- package/build/assets/prod/lwr-loader-shim-legacy.bundle.js +413 -18
- package/build/assets/prod/lwr-loader-shim-legacy.bundle.min.js +3 -3
- package/build/assets/prod/lwr-loader-shim-legacy.js +217 -10
- package/build/assets/prod/lwr-loader-shim.bundle.js +6 -6
- package/build/assets/prod/lwr-loader-shim.bundle.min.js +2 -2
- package/build/assets/prod/lwr-loader-shim.js +5 -5
- package/build/cjs/modules/lwr/loaderLegacy/importMap/importMap.cjs +1 -1
- package/build/cjs/modules/lwr/loaderLegacy/importMap/importMapResolver.cjs +12 -0
- package/build/cjs/modules/lwr/loaderLegacy/importMap/utils.cjs +13 -1
- package/build/cjs/modules/lwr/loaderLegacy/utils/validation.cjs +93 -0
- package/build/modules/lwr/esmLoader/esmLoader.js +1 -1
- package/build/modules/lwr/loader/loader.js +1 -1
- package/build/modules/lwr/loaderLegacy/importMap/importMap.js +3 -2
- package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.d.ts +1 -0
- package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.js +13 -0
- package/build/modules/lwr/loaderLegacy/importMap/utils.d.ts +12 -1
- package/build/modules/lwr/loaderLegacy/importMap/utils.js +24 -2
- package/build/modules/lwr/loaderLegacy/loaderLegacy.d.ts +11 -1
- package/build/modules/lwr/loaderLegacy/loaderLegacy.js +196 -8
- package/build/modules/lwr/loaderLegacy/utils/validation.d.ts +50 -0
- package/build/modules/lwr/loaderLegacy/utils/validation.js +114 -0
- package/build/shim-legacy/shimLegacy.d.ts +14 -0
- package/build/shim-legacy/shimLegacy.js +51 -2
- package/build/types.d.ts +4 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -83,6 +83,38 @@ LWR.define('foo/bar', ['exports'], (exports) => {
|
|
|
83
83
|
});
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
### `loader.importMap()`
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
interface LwrImportMapUpdateMethod {
|
|
90
|
+
(importMapUpdate: ImportMapUpdate): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type ImportMapUpdate = Record<string, string[]>;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Apply update of import mappings at runtime. This allows dynamically registering new module mappings after the loader has been initialized. This is a synchronous operation.
|
|
97
|
+
|
|
98
|
+
**parameters**
|
|
99
|
+
|
|
100
|
+
- `importMapUpdate` - Import Map Update object containing:
|
|
101
|
+
- [moduleScriptURL] - Script URL containing LWR define statements
|
|
102
|
+
- string[] - array of module names which are included in the given script
|
|
103
|
+
|
|
104
|
+
**Important**: Import map entries are **write-once** — once a specifier is mapped, it cannot be overridden. This ensures deterministic module resolution and prevents runtime mutation of previously resolved dependencies. If a conflicting mapping is provided for an existing specifier, the new mapping is ignored and a warning is logged.
|
|
105
|
+
|
|
106
|
+
**Important**: Protected modules (`lwc`, `lwr/*`) cannot be remapped for security reasons. Attempting to remap these modules will throw an error.
|
|
107
|
+
|
|
108
|
+
**Example**
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
// Register a runtime import mapping
|
|
112
|
+
// ONLY AVAILABLE WHEN USING THE GLOBAL API (AMD / Legacy Mode):
|
|
113
|
+
LWR.importMap({
|
|
114
|
+
'/bundles/my-module.js': ['my/module'],
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
86
118
|
### `loader.load()`
|
|
87
119
|
|
|
88
120
|
```ts
|
|
@@ -280,7 +312,7 @@ console.log(result); // 'abc'
|
|
|
280
312
|
The Client Runtime Loader Shim bootstraps the [Loader instance](#api) and other required modules for LWR applications in AMD format. The AMD Loader Shim is responsible for:
|
|
281
313
|
|
|
282
314
|
- defining and executing the [`@lwr/loader`](#api)
|
|
283
|
-
- exposure of the loader's `define`
|
|
315
|
+
- exposure of the loader's `define` and `importMap` APIs at `globalThis.LWR.define` and `globalThis.LWR.importMap`
|
|
284
316
|
- client-side orchestration for the generated application bootstrap module
|
|
285
317
|
|
|
286
318
|
### LWR Application Document
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
6
6
|
*/
|
|
7
|
-
/* LWR Error Shim v0.22.
|
|
7
|
+
/* LWR Error Shim v0.22.11 */
|
|
8
8
|
!function(){"use strict";const o=globalThis;if(!(o.LWR&&o.LWR.define)){const r=new Error("The LWR application failed to bootstrap");if(!o.LWR||!o.LWR.onError)throw r;o.LWR.onError(r)}}();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
|
|
6
6
|
*/
|
|
7
|
-
/* LWR Legacy Module Loader Shim v0.22.
|
|
7
|
+
/* LWR Legacy Module Loader Shim v0.22.11 */
|
|
8
8
|
(function () {
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
@@ -276,9 +276,164 @@
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
/*
|
|
279
|
+
/* eslint-disable lwr/no-unguarded-apis */
|
|
280
|
+
|
|
281
|
+
const hasConsole$1 = typeof console !== 'undefined';
|
|
282
|
+
|
|
283
|
+
const hasProcess$1 = typeof process !== 'undefined';
|
|
284
|
+
|
|
285
|
+
// eslint-disable-next-line no-undef
|
|
286
|
+
hasProcess$1 && process.env;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* List of protected module patterns that cannot be remapped.
|
|
290
|
+
* Uses case-insensitive matching to prevent bypass attempts (e.g., HF-1027).
|
|
291
|
+
*/
|
|
292
|
+
const PROTECTED_PATTERNS = [
|
|
293
|
+
/^lwc$/i, // Core LWC
|
|
294
|
+
/^lwc\/v\//i, // Versioned LWC variants
|
|
295
|
+
/^@lwc\//i, // LWC scoped packages
|
|
296
|
+
/^lightningmobileruntime\//i, // Lightning mobile runtime
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Checks if a specifier matches any protected module pattern.
|
|
301
|
+
*
|
|
302
|
+
* @param specifier - The module specifier to check
|
|
303
|
+
* @returns true if the specifier is protected, false otherwise
|
|
304
|
+
*/
|
|
305
|
+
function isProtectedSpecifier(specifier) {
|
|
306
|
+
return PROTECTED_PATTERNS.some((pattern) => pattern.test(specifier));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Validates that a URI does not use dangerous URL schemes.
|
|
311
|
+
*
|
|
312
|
+
* Blocks:
|
|
313
|
+
* - blob: URLs (HF-1027 bypass prevention) - case-insensitive
|
|
314
|
+
* - data: URLs (inline code injection) - case-insensitive
|
|
315
|
+
*
|
|
316
|
+
* @param uri - The URI to validate
|
|
317
|
+
* @param specifier - The specifier being mapped (for error messages)
|
|
318
|
+
* @throws Error if the URI uses a dangerous scheme
|
|
319
|
+
*/
|
|
320
|
+
function validateMappingUri(uri, specifier) {
|
|
321
|
+
const lowerUri = uri.toLowerCase();
|
|
322
|
+
|
|
323
|
+
if (lowerUri.startsWith('blob:')) {
|
|
324
|
+
throw new Error(`Cannot map ${specifier} to blob: URL`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (lowerUri.startsWith('data:')) {
|
|
328
|
+
throw new Error(`Cannot map ${specifier} to data: URL`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Validates that a specifier is well-formed and not protected.
|
|
334
|
+
*
|
|
335
|
+
* @param specifier - The module specifier to validate
|
|
336
|
+
* @throws Error if the specifier is invalid or protected
|
|
337
|
+
*/
|
|
338
|
+
function validateSpecifier(specifier) {
|
|
339
|
+
if (typeof specifier !== 'string' || specifier.length === 0) {
|
|
340
|
+
throw new Error('Specifier must be a non-empty string');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (isProtectedSpecifier(specifier)) {
|
|
344
|
+
throw new Error(`Cannot remap protected module: ${specifier}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Validates and converts an ImportMapUpdate to ImportMap format.
|
|
350
|
+
*
|
|
351
|
+
* Performs validation checks and then converts from:
|
|
352
|
+
* { moduleScriptURL: [moduleName1, moduleName2] }
|
|
353
|
+
* To:
|
|
354
|
+
* { imports: { moduleName1: moduleScriptURL, moduleName2: moduleScriptURL } }
|
|
355
|
+
*
|
|
356
|
+
* @param update - The ImportMapUpdate object to validate and convert
|
|
357
|
+
* @returns The converted ImportMap, or null if the update is empty
|
|
358
|
+
* @throws Error if any validation fails
|
|
359
|
+
*/
|
|
360
|
+
function validateAndConvertImportMapUpdate(update) {
|
|
361
|
+
if (!update || typeof update !== 'object') {
|
|
362
|
+
throw new Error('LWR.importMap() requires an object argument');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check if update is empty
|
|
366
|
+
const entries = Object.entries(update);
|
|
367
|
+
if (entries.length === 0) {
|
|
368
|
+
if (hasConsole$1) {
|
|
369
|
+
// eslint-disable-next-line lwr/no-unguarded-apis
|
|
370
|
+
console.warn('LWR.importMap() called with empty update object');
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const convertedImports = {};
|
|
376
|
+
|
|
377
|
+
for (const [moduleScriptURL, moduleNames] of entries) {
|
|
378
|
+
if (!Array.isArray(moduleNames)) {
|
|
379
|
+
throw new Error('moduleNames must be an array');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!moduleScriptURL || typeof moduleScriptURL !== 'string') {
|
|
383
|
+
throw new Error('moduleScriptURL must be a string');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
validateMappingUri(moduleScriptURL, moduleScriptURL);
|
|
387
|
+
|
|
388
|
+
for (const moduleName of moduleNames) {
|
|
389
|
+
validateSpecifier(moduleName);
|
|
390
|
+
if (moduleName in convertedImports) {
|
|
391
|
+
if (hasConsole$1) {
|
|
392
|
+
// eslint-disable-next-line lwr/no-unguarded-apis
|
|
393
|
+
console.warn(
|
|
394
|
+
`LWR.importMap(): duplicate module "${moduleName}" — already mapped to "${convertedImports[moduleName]}", ignoring mapping to "${moduleScriptURL}"`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
convertedImports[moduleName] = moduleScriptURL;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
280
402
|
|
|
403
|
+
return {
|
|
404
|
+
imports: convertedImports,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
281
407
|
|
|
408
|
+
function targetWarning(match, target, msg) {
|
|
409
|
+
if (hasConsole$1) {
|
|
410
|
+
// eslint-disable-next-line lwr/no-unguarded-apis, no-undef
|
|
411
|
+
console.warn('Package target ' + msg + ", resolving target '" + target + "' for " + match);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Import map entries are write-once: once a specifier is mapped, it cannot be overridden.
|
|
417
|
+
* This ensures deterministic module resolution and prevents runtime mutation of previously
|
|
418
|
+
* resolved dependencies. Conflicting mappings are ignored with a warning.
|
|
419
|
+
*
|
|
420
|
+
* Merges a single import map entry into a target imports object.
|
|
421
|
+
* - If the specifier doesn't exist, it is added.
|
|
422
|
+
* - If it exists with the same value, it is silently skipped.
|
|
423
|
+
* - If it exists with a different value, a warning is logged and the entry is skipped.
|
|
424
|
+
*/
|
|
425
|
+
function mergeImportMapEntry(specifier, uri, target) {
|
|
426
|
+
const existing = target[specifier];
|
|
427
|
+
if (existing !== undefined) {
|
|
428
|
+
if (existing !== uri) {
|
|
429
|
+
targetWarning(specifier, uri, `already mapped to "${existing}", ignoring conflicting mapping`);
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
target[specifier] = uri;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }/* global document, process, console */
|
|
282
437
|
|
|
283
438
|
/* eslint-disable lwr/no-unguarded-apis */
|
|
284
439
|
const hasSetTimeout = typeof setTimeout === 'function';
|
|
@@ -293,10 +448,11 @@
|
|
|
293
448
|
|
|
294
449
|
__init() {this.defineCache = {};}
|
|
295
450
|
__init2() {this.orderedDefs = [];}
|
|
451
|
+
__init3() {this.importMapUpdatesCache = {};}
|
|
296
452
|
|
|
297
453
|
// eslint-disable-line no-undef, lwr/no-unguarded-apis
|
|
298
454
|
|
|
299
|
-
constructor(global) {LoaderShim.prototype.__init.call(this);LoaderShim.prototype.__init2.call(this);
|
|
455
|
+
constructor(global) {LoaderShim.prototype.__init.call(this);LoaderShim.prototype.__init2.call(this);LoaderShim.prototype.__init3.call(this);
|
|
300
456
|
// Start watchdog timer
|
|
301
457
|
if (hasSetTimeout) {
|
|
302
458
|
this.watchdogTimerId = this.startWatchdogTimer();
|
|
@@ -305,14 +461,16 @@
|
|
|
305
461
|
// Parse configuration
|
|
306
462
|
this.global = global;
|
|
307
463
|
this.config = global.LWR ;
|
|
308
|
-
this.loaderModule = 'lwr/loaderLegacy/v/
|
|
464
|
+
this.loaderModule = 'lwr/loaderLegacy/v/0_22_11';
|
|
309
465
|
|
|
310
466
|
// Set up error handler
|
|
311
467
|
this.errorHandler = this.config.onError ;
|
|
312
468
|
|
|
313
|
-
// Set up the temporary LWR.define
|
|
469
|
+
// Set up the temporary LWR.define and LWR.importMap functions
|
|
314
470
|
const tempDefine = this.tempDefine.bind(this);
|
|
315
471
|
global.LWR.define = tempDefine;
|
|
472
|
+
const tempImportMapMethod = this.tempImportMap.bind(this);
|
|
473
|
+
global.LWR.importMap = tempImportMapMethod;
|
|
316
474
|
this.bootReady = this.config.autoBoot;
|
|
317
475
|
|
|
318
476
|
try {
|
|
@@ -368,6 +526,53 @@
|
|
|
368
526
|
}
|
|
369
527
|
}
|
|
370
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Create a temporary LWR.importMap() function which captures all
|
|
531
|
+
* import map updates that occur BEFORE the full loader module is available
|
|
532
|
+
*
|
|
533
|
+
* Each import map update is validated, converted to moduleName -> URL mapping,
|
|
534
|
+
* and merged into the importMapUpdatesCache with write-once protection
|
|
535
|
+
*/
|
|
536
|
+
tempImportMap(importMapUpdate) {
|
|
537
|
+
try {
|
|
538
|
+
// Validate and convert the import map update to { imports: { moduleName: moduleScriptURL } }
|
|
539
|
+
const importMap = validateAndConvertImportMapUpdate(importMapUpdate);
|
|
540
|
+
|
|
541
|
+
// Early return if update was empty
|
|
542
|
+
if (!importMap) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Merge into cache with write-once protection
|
|
547
|
+
for (const [moduleName, moduleScriptURL] of Object.entries(importMap.imports)) {
|
|
548
|
+
mergeImportMapEntry(moduleName, moduleScriptURL, this.importMapUpdatesCache);
|
|
549
|
+
}
|
|
550
|
+
} catch (e) {
|
|
551
|
+
this.enterErrorState(e);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Apply all cached import map updates and merge with bootstrap import map
|
|
557
|
+
* Returns merged import map
|
|
558
|
+
*/
|
|
559
|
+
getImportMappingsWithUpdates() {
|
|
560
|
+
// Start with bootstrap import map
|
|
561
|
+
// Cast importMappings from object to ImportMap to access properties
|
|
562
|
+
const bootstrapMappings = this.config.importMappings ;
|
|
563
|
+
|
|
564
|
+
// Merge with write-once protection: bootstrap mappings take precedence
|
|
565
|
+
const mergedImports = { ...(_optionalChain([bootstrapMappings, 'optionalAccess', _ => _.imports]) || {}) };
|
|
566
|
+
for (const [specifier, uri] of Object.entries(this.importMapUpdatesCache)) {
|
|
567
|
+
mergeImportMapEntry(specifier, uri, mergedImports);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
...(bootstrapMappings || {}),
|
|
572
|
+
imports: mergedImports,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
371
576
|
// Called by the customInit hook via lwr.initializeApp()
|
|
372
577
|
postCustomInit() {
|
|
373
578
|
this.bootReady = true;
|
|
@@ -456,17 +661,19 @@
|
|
|
456
661
|
const exporter = (exports) => {
|
|
457
662
|
Object.assign(exports, { logOperationStart, logOperationEnd });
|
|
458
663
|
};
|
|
459
|
-
define('lwr/profiler/v/
|
|
664
|
+
define('lwr/profiler/v/0_22_11', ['exports'], exporter, {});
|
|
460
665
|
}
|
|
461
666
|
|
|
462
667
|
// Set up the application globals, import map, root custom element...
|
|
463
668
|
mountApp(loader) {
|
|
464
|
-
const { bootstrapModule, rootComponent,
|
|
465
|
-
|
|
669
|
+
const { bootstrapModule, rootComponent, rootComponents, serverData, endpoints } = this.config;
|
|
670
|
+
|
|
671
|
+
const importMappings = this.getImportMappingsWithUpdates();
|
|
466
672
|
|
|
467
673
|
// Set global LWR.define to loader.define
|
|
468
674
|
this.global.LWR = Object.freeze({
|
|
469
675
|
define: loader.define.bind(loader),
|
|
676
|
+
importMap: loader.importMap.bind(loader),
|
|
470
677
|
rootComponent,
|
|
471
678
|
rootComponents,
|
|
472
679
|
serverData: serverData || {},
|
|
@@ -551,14 +758,14 @@
|
|
|
551
758
|
// The loader module is ALWAYS required
|
|
552
759
|
const GLOBAL = globalThis ;
|
|
553
760
|
GLOBAL.LWR.requiredModules = GLOBAL.LWR.requiredModules || [];
|
|
554
|
-
if (GLOBAL.LWR.requiredModules.indexOf('lwr/loaderLegacy/v/
|
|
555
|
-
GLOBAL.LWR.requiredModules.push('lwr/loaderLegacy/v/
|
|
761
|
+
if (GLOBAL.LWR.requiredModules.indexOf('lwr/loaderLegacy/v/0_22_11') < 0) {
|
|
762
|
+
GLOBAL.LWR.requiredModules.push('lwr/loaderLegacy/v/0_22_11');
|
|
556
763
|
}
|
|
557
764
|
new LoaderShim(GLOBAL);
|
|
558
765
|
|
|
559
766
|
})();
|
|
560
767
|
|
|
561
|
-
LWR.define('lwr/loaderLegacy/v/
|
|
768
|
+
LWR.define('lwr/loaderLegacy/v/0_22_11', ['exports'], (function (exports) { 'use strict';
|
|
562
769
|
|
|
563
770
|
const templateRegex = /\{([0-9]+)\}/g;
|
|
564
771
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -2015,7 +2222,8 @@ LWR.define('lwr/loaderLegacy/v/0_22_10', ['exports'], (function (exports) { 'use
|
|
|
2015
2222
|
if (segment in matchObj) {
|
|
2016
2223
|
return segment;
|
|
2017
2224
|
}
|
|
2018
|
-
|
|
2225
|
+
sepIndex = path.lastIndexOf('/', sepIndex - 1);
|
|
2226
|
+
} while (sepIndex > 0);
|
|
2019
2227
|
}
|
|
2020
2228
|
function targetWarning(match, target, msg) {
|
|
2021
2229
|
if (hasConsole) {
|
|
@@ -2024,6 +2232,27 @@ LWR.define('lwr/loaderLegacy/v/0_22_10', ['exports'], (function (exports) { 'use
|
|
|
2024
2232
|
}
|
|
2025
2233
|
}
|
|
2026
2234
|
|
|
2235
|
+
/**
|
|
2236
|
+
* Import map entries are write-once: once a specifier is mapped, it cannot be overridden.
|
|
2237
|
+
* This ensures deterministic module resolution and prevents runtime mutation of previously
|
|
2238
|
+
* resolved dependencies. Conflicting mappings are ignored with a warning.
|
|
2239
|
+
*
|
|
2240
|
+
* Merges a single import map entry into a target imports object.
|
|
2241
|
+
* - If the specifier doesn't exist, it is added.
|
|
2242
|
+
* - If it exists with the same value, it is silently skipped.
|
|
2243
|
+
* - If it exists with a different value, a warning is logged and the entry is skipped.
|
|
2244
|
+
*/
|
|
2245
|
+
function mergeImportMapEntry(specifier, uri, target) {
|
|
2246
|
+
const existing = target[specifier];
|
|
2247
|
+
if (existing !== undefined) {
|
|
2248
|
+
if (existing !== uri) {
|
|
2249
|
+
targetWarning(specifier, uri, `already mapped to "${existing}", ignoring conflicting mapping`);
|
|
2250
|
+
}
|
|
2251
|
+
} else {
|
|
2252
|
+
target[specifier] = uri;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2027
2256
|
/**
|
|
2028
2257
|
* Import map support for LWR based on the spec: https://github.com/WICG/import-maps
|
|
2029
2258
|
*
|
|
@@ -2145,7 +2374,8 @@ LWR.define('lwr/loaderLegacy/v/0_22_10', ['exports'], (function (exports) { 'use
|
|
|
2145
2374
|
if (!mapped) {
|
|
2146
2375
|
targetWarning(p, rhs, 'bare specifier did not resolve');
|
|
2147
2376
|
} else {
|
|
2148
|
-
|
|
2377
|
+
// Merge into cache with write-once protection
|
|
2378
|
+
mergeImportMapEntry(resolvedLhs, mapped.uri, outPackages);
|
|
2149
2379
|
}
|
|
2150
2380
|
}
|
|
2151
2381
|
}
|
|
@@ -2196,6 +2426,20 @@ LWR.define('lwr/loaderLegacy/v/0_22_10', ['exports'], (function (exports) { 'use
|
|
|
2196
2426
|
resolve(resolvedOrPlain, parentUrl) {
|
|
2197
2427
|
return resolveImportMapEntry(this.importMap, resolvedOrPlain, parentUrl);
|
|
2198
2428
|
}
|
|
2429
|
+
|
|
2430
|
+
addImportMapEntries(importMap) {
|
|
2431
|
+
if (importMap.imports) {
|
|
2432
|
+
const current = this.importMap;
|
|
2433
|
+
if (!current.imports) {
|
|
2434
|
+
current.imports = {};
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
// Merge into cache with write-once protection
|
|
2438
|
+
for (const specifier in importMap.imports) {
|
|
2439
|
+
mergeImportMapEntry(specifier, importMap.imports[specifier], current.imports);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2199
2443
|
}
|
|
2200
2444
|
|
|
2201
2445
|
/**
|
|
@@ -2267,15 +2511,134 @@ LWR.define('lwr/loaderLegacy/v/0_22_10', ['exports'], (function (exports) { 'use
|
|
|
2267
2511
|
return importMapPromise;
|
|
2268
2512
|
}
|
|
2269
2513
|
|
|
2270
|
-
|
|
2514
|
+
/**
|
|
2515
|
+
* List of protected module patterns that cannot be remapped.
|
|
2516
|
+
* Uses case-insensitive matching to prevent bypass attempts (e.g., HF-1027).
|
|
2517
|
+
*/
|
|
2518
|
+
const PROTECTED_PATTERNS = [
|
|
2519
|
+
/^lwc$/i, // Core LWC
|
|
2520
|
+
/^lwc\/v\//i, // Versioned LWC variants
|
|
2521
|
+
/^@lwc\//i, // LWC scoped packages
|
|
2522
|
+
/^lightningmobileruntime\//i, // Lightning mobile runtime
|
|
2523
|
+
];
|
|
2524
|
+
|
|
2525
|
+
/**
|
|
2526
|
+
* Checks if a specifier matches any protected module pattern.
|
|
2527
|
+
*
|
|
2528
|
+
* @param specifier - The module specifier to check
|
|
2529
|
+
* @returns true if the specifier is protected, false otherwise
|
|
2530
|
+
*/
|
|
2531
|
+
function isProtectedSpecifier(specifier) {
|
|
2532
|
+
return PROTECTED_PATTERNS.some((pattern) => pattern.test(specifier));
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
/**
|
|
2536
|
+
* Validates that a URI does not use dangerous URL schemes.
|
|
2537
|
+
*
|
|
2538
|
+
* Blocks:
|
|
2539
|
+
* - blob: URLs (HF-1027 bypass prevention) - case-insensitive
|
|
2540
|
+
* - data: URLs (inline code injection) - case-insensitive
|
|
2541
|
+
*
|
|
2542
|
+
* @param uri - The URI to validate
|
|
2543
|
+
* @param specifier - The specifier being mapped (for error messages)
|
|
2544
|
+
* @throws Error if the URI uses a dangerous scheme
|
|
2545
|
+
*/
|
|
2546
|
+
function validateMappingUri(uri, specifier) {
|
|
2547
|
+
const lowerUri = uri.toLowerCase();
|
|
2548
|
+
|
|
2549
|
+
if (lowerUri.startsWith('blob:')) {
|
|
2550
|
+
throw new Error(`Cannot map ${specifier} to blob: URL`);
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
if (lowerUri.startsWith('data:')) {
|
|
2554
|
+
throw new Error(`Cannot map ${specifier} to data: URL`);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
/**
|
|
2559
|
+
* Validates that a specifier is well-formed and not protected.
|
|
2560
|
+
*
|
|
2561
|
+
* @param specifier - The module specifier to validate
|
|
2562
|
+
* @throws Error if the specifier is invalid or protected
|
|
2563
|
+
*/
|
|
2564
|
+
function validateSpecifier(specifier) {
|
|
2565
|
+
if (typeof specifier !== 'string' || specifier.length === 0) {
|
|
2566
|
+
throw new Error('Specifier must be a non-empty string');
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
if (isProtectedSpecifier(specifier)) {
|
|
2570
|
+
throw new Error(`Cannot remap protected module: ${specifier}`);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
/**
|
|
2575
|
+
* Validates and converts an ImportMapUpdate to ImportMap format.
|
|
2576
|
+
*
|
|
2577
|
+
* Performs validation checks and then converts from:
|
|
2578
|
+
* { moduleScriptURL: [moduleName1, moduleName2] }
|
|
2579
|
+
* To:
|
|
2580
|
+
* { imports: { moduleName1: moduleScriptURL, moduleName2: moduleScriptURL } }
|
|
2581
|
+
*
|
|
2582
|
+
* @param update - The ImportMapUpdate object to validate and convert
|
|
2583
|
+
* @returns The converted ImportMap, or null if the update is empty
|
|
2584
|
+
* @throws Error if any validation fails
|
|
2585
|
+
*/
|
|
2586
|
+
function validateAndConvertImportMapUpdate(update) {
|
|
2587
|
+
if (!update || typeof update !== 'object') {
|
|
2588
|
+
throw new Error('LWR.importMap() requires an object argument');
|
|
2589
|
+
}
|
|
2271
2590
|
|
|
2591
|
+
// Check if update is empty
|
|
2592
|
+
const entries = Object.entries(update);
|
|
2593
|
+
if (entries.length === 0) {
|
|
2594
|
+
if (hasConsole) {
|
|
2595
|
+
// eslint-disable-next-line lwr/no-unguarded-apis
|
|
2596
|
+
console.warn('LWR.importMap() called with empty update object');
|
|
2597
|
+
}
|
|
2598
|
+
return null;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
const convertedImports = {};
|
|
2272
2602
|
|
|
2603
|
+
for (const [moduleScriptURL, moduleNames] of entries) {
|
|
2604
|
+
if (!Array.isArray(moduleNames)) {
|
|
2605
|
+
throw new Error('moduleNames must be an array');
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
if (!moduleScriptURL || typeof moduleScriptURL !== 'string') {
|
|
2609
|
+
throw new Error('moduleScriptURL must be a string');
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
validateMappingUri(moduleScriptURL, moduleScriptURL);
|
|
2613
|
+
|
|
2614
|
+
for (const moduleName of moduleNames) {
|
|
2615
|
+
validateSpecifier(moduleName);
|
|
2616
|
+
if (moduleName in convertedImports) {
|
|
2617
|
+
if (hasConsole) {
|
|
2618
|
+
// eslint-disable-next-line lwr/no-unguarded-apis
|
|
2619
|
+
console.warn(
|
|
2620
|
+
`LWR.importMap(): duplicate module "${moduleName}" — already mapped to "${convertedImports[moduleName]}", ignoring mapping to "${moduleScriptURL}"`,
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
} else {
|
|
2624
|
+
convertedImports[moduleName] = moduleScriptURL;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
return {
|
|
2630
|
+
imports: convertedImports,
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2273
2635
|
/**
|
|
2274
2636
|
* The LWR loader is inspired and borrows from the algorithms and native browser principles of https://github.com/systemjs/systemjs
|
|
2275
2637
|
*/
|
|
2276
2638
|
class Loader {
|
|
2277
2639
|
|
|
2278
2640
|
|
|
2641
|
+
|
|
2279
2642
|
|
|
2280
2643
|
|
|
2281
2644
|
constructor(config) {
|
|
@@ -2404,10 +2767,13 @@ LWR.define('lwr/loaderLegacy/v/0_22_10', ['exports'], (function (exports) { 'use
|
|
|
2404
2767
|
// import maps spec if we do this after resolving any imports
|
|
2405
2768
|
importMap = resolveAndComposeImportMap(mappings, this.baseUrl, this.parentImportMap);
|
|
2406
2769
|
}
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2770
|
+
if (importMap) {
|
|
2771
|
+
if (this.importMapResolver || this.parentImportMap) {
|
|
2772
|
+
throw new LoaderError(BAD_IMPORT_MAP);
|
|
2773
|
+
}
|
|
2774
|
+
this.importMapResolver = new ImportMapResolver(importMap);
|
|
2775
|
+
this.registry.setImportResolver(this.importMapResolver);
|
|
2776
|
+
this.parentImportMap = this.importMapResolver.importMap;
|
|
2411
2777
|
}
|
|
2412
2778
|
}
|
|
2413
2779
|
|
|
@@ -2420,6 +2786,35 @@ LWR.define('lwr/loaderLegacy/v/0_22_10', ['exports'], (function (exports) { 'use
|
|
|
2420
2786
|
this.registry.registerExternalModules(modules);
|
|
2421
2787
|
}
|
|
2422
2788
|
|
|
2789
|
+
/**
|
|
2790
|
+
* Apply import map updates at runtime.
|
|
2791
|
+
* Enables adding new import mappings dynamically.
|
|
2792
|
+
*
|
|
2793
|
+
* @param updates - Import Map Update object containing:
|
|
2794
|
+
- [moduleScriptURL] - Script URL containing LWR define statements
|
|
2795
|
+
- string[] - array of module names which are included in the given script
|
|
2796
|
+
*/
|
|
2797
|
+
importMap(update) {
|
|
2798
|
+
// Validate and convert the import map update to the ImportMap format (moduleName -> moduleScriptURL)
|
|
2799
|
+
const importMap = validateAndConvertImportMapUpdate(update);
|
|
2800
|
+
|
|
2801
|
+
// Early return if update was empty
|
|
2802
|
+
if (!importMap) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
if (!this.parentImportMap || !this.importMapResolver) {
|
|
2807
|
+
throw new LoaderError(BAD_IMPORT_MAP);
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
// Merge the new mappings with the base import map - note this goes against
|
|
2811
|
+
// import maps spec if we do this after resolving any imports
|
|
2812
|
+
const resolvedImportMap = resolveAndComposeImportMap(importMap, this.baseUrl, this.parentImportMap);
|
|
2813
|
+
|
|
2814
|
+
this.importMapResolver.addImportMapEntries(resolvedImportMap);
|
|
2815
|
+
this.parentImportMap = this.importMapResolver.importMap;
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2423
2818
|
getModuleWarnings(isAppMounted = false) {
|
|
2424
2819
|
return this.registry.getModuleWarnings(isAppMounted);
|
|
2425
2820
|
}
|