@lwrjs/loader 0.22.10 → 0.22.12

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.
Files changed (27) hide show
  1. package/README.md +33 -1
  2. package/build/assets/prod/lwr-error-shim.js +1 -1
  3. package/build/assets/prod/lwr-loader-shim-legacy.bundle.js +413 -18
  4. package/build/assets/prod/lwr-loader-shim-legacy.bundle.min.js +3 -3
  5. package/build/assets/prod/lwr-loader-shim-legacy.js +217 -10
  6. package/build/assets/prod/lwr-loader-shim.bundle.js +6 -6
  7. package/build/assets/prod/lwr-loader-shim.bundle.min.js +2 -2
  8. package/build/assets/prod/lwr-loader-shim.js +5 -5
  9. package/build/cjs/modules/lwr/loaderLegacy/importMap/importMap.cjs +1 -1
  10. package/build/cjs/modules/lwr/loaderLegacy/importMap/importMapResolver.cjs +12 -0
  11. package/build/cjs/modules/lwr/loaderLegacy/importMap/utils.cjs +13 -1
  12. package/build/cjs/modules/lwr/loaderLegacy/utils/validation.cjs +93 -0
  13. package/build/modules/lwr/esmLoader/esmLoader.js +1 -1
  14. package/build/modules/lwr/loader/loader.js +1 -1
  15. package/build/modules/lwr/loaderLegacy/importMap/importMap.js +3 -2
  16. package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.d.ts +1 -0
  17. package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.js +13 -0
  18. package/build/modules/lwr/loaderLegacy/importMap/utils.d.ts +12 -1
  19. package/build/modules/lwr/loaderLegacy/importMap/utils.js +24 -2
  20. package/build/modules/lwr/loaderLegacy/loaderLegacy.d.ts +11 -1
  21. package/build/modules/lwr/loaderLegacy/loaderLegacy.js +196 -8
  22. package/build/modules/lwr/loaderLegacy/utils/validation.d.ts +50 -0
  23. package/build/modules/lwr/loaderLegacy/utils/validation.js +114 -0
  24. package/build/shim-legacy/shimLegacy.d.ts +14 -0
  25. package/build/shim-legacy/shimLegacy.js +51 -2
  26. package/build/types.d.ts +4 -0
  27. package/package.json +6 -6
@@ -86,7 +86,7 @@ function resolveAndComposePackages(packages, outPackages, baseUrl, parentMap, pa
86
86
  if (!mapped) {
87
87
  (0, import_utils.targetWarning)(p, rhs, "bare specifier did not resolve");
88
88
  } else {
89
- outPackages[resolvedLhs] = mapped.uri;
89
+ (0, import_utils.mergeImportMapEntry)(resolvedLhs, mapped.uri, outPackages);
90
90
  }
91
91
  }
92
92
  }
@@ -27,6 +27,7 @@ __export(exports, {
27
27
  ImportMapResolver: () => ImportMapResolver
28
28
  });
29
29
  var import_importMap = __toModule(require("./importMap.cjs"));
30
+ var import_utils = __toModule(require("./utils.cjs"));
30
31
  var ImportMapResolver = class {
31
32
  constructor(importMap) {
32
33
  this.importMap = importMap;
@@ -34,4 +35,15 @@ var ImportMapResolver = class {
34
35
  resolve(resolvedOrPlain, parentUrl) {
35
36
  return (0, import_importMap.resolveImportMapEntry)(this.importMap, resolvedOrPlain, parentUrl);
36
37
  }
38
+ addImportMapEntries(importMap) {
39
+ if (importMap.imports) {
40
+ const current = this.importMap;
41
+ if (!current.imports) {
42
+ current.imports = {};
43
+ }
44
+ for (const specifier in importMap.imports) {
45
+ (0, import_utils.mergeImportMapEntry)(specifier, importMap.imports[specifier], current.imports);
46
+ }
47
+ }
48
+ }
37
49
  };
@@ -25,6 +25,7 @@ var __toModule = (module2) => {
25
25
  __markAsModule(exports);
26
26
  __export(exports, {
27
27
  getMatch: () => getMatch,
28
+ mergeImportMapEntry: () => mergeImportMapEntry,
28
29
  targetWarning: () => targetWarning
29
30
  });
30
31
  var import_dom = __toModule(require("../utils/dom.cjs"));
@@ -38,10 +39,21 @@ function getMatch(path, matchObj) {
38
39
  if (segment in matchObj) {
39
40
  return segment;
40
41
  }
41
- } while (path.length > 1 && (sepIndex = path.lastIndexOf("/", sepIndex - 1)) !== -1);
42
+ sepIndex = path.lastIndexOf("/", sepIndex - 1);
43
+ } while (sepIndex > 0);
42
44
  }
43
45
  function targetWarning(match, target, msg) {
44
46
  if (import_dom.hasConsole) {
45
47
  console.warn("Package target " + msg + ", resolving target '" + target + "' for " + match);
46
48
  }
47
49
  }
50
+ function mergeImportMapEntry(specifier, uri, target) {
51
+ const existing = target[specifier];
52
+ if (existing !== void 0) {
53
+ if (existing !== uri) {
54
+ targetWarning(specifier, uri, `already mapped to "${existing}", ignoring conflicting mapping`);
55
+ }
56
+ } else {
57
+ target[specifier] = uri;
58
+ }
59
+ }
@@ -0,0 +1,93 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {get: all[name], enumerable: true});
11
+ };
12
+ var __exportStar = (target, module2, desc) => {
13
+ if (module2 && typeof module2 === "object" || typeof module2 === "function") {
14
+ for (let key of __getOwnPropNames(module2))
15
+ if (!__hasOwnProp.call(target, key) && key !== "default")
16
+ __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
17
+ }
18
+ return target;
19
+ };
20
+ var __toModule = (module2) => {
21
+ return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
22
+ };
23
+
24
+ // packages/@lwrjs/loader/src/modules/lwr/loaderLegacy/utils/validation.ts
25
+ __markAsModule(exports);
26
+ __export(exports, {
27
+ isProtectedSpecifier: () => isProtectedSpecifier,
28
+ validateAndConvertImportMapUpdate: () => validateAndConvertImportMapUpdate,
29
+ validateMappingUri: () => validateMappingUri,
30
+ validateSpecifier: () => validateSpecifier
31
+ });
32
+ var import_dom = __toModule(require("../utils/dom.cjs"));
33
+ var PROTECTED_PATTERNS = [
34
+ /^lwc$/i,
35
+ /^lwc\/v\//i,
36
+ /^@lwc\//i,
37
+ /^lightningmobileruntime\//i
38
+ ];
39
+ function isProtectedSpecifier(specifier) {
40
+ return PROTECTED_PATTERNS.some((pattern) => pattern.test(specifier));
41
+ }
42
+ function validateMappingUri(uri, specifier) {
43
+ const lowerUri = uri.toLowerCase();
44
+ if (lowerUri.startsWith("blob:")) {
45
+ throw new Error(`Cannot map ${specifier} to blob: URL`);
46
+ }
47
+ if (lowerUri.startsWith("data:")) {
48
+ throw new Error(`Cannot map ${specifier} to data: URL`);
49
+ }
50
+ }
51
+ function validateSpecifier(specifier) {
52
+ if (typeof specifier !== "string" || specifier.length === 0) {
53
+ throw new Error("Specifier must be a non-empty string");
54
+ }
55
+ if (isProtectedSpecifier(specifier)) {
56
+ throw new Error(`Cannot remap protected module: ${specifier}`);
57
+ }
58
+ }
59
+ function validateAndConvertImportMapUpdate(update) {
60
+ if (!update || typeof update !== "object") {
61
+ throw new Error("LWR.importMap() requires an object argument");
62
+ }
63
+ const entries = Object.entries(update);
64
+ if (entries.length === 0) {
65
+ if (import_dom.hasConsole) {
66
+ console.warn("LWR.importMap() called with empty update object");
67
+ }
68
+ return null;
69
+ }
70
+ const convertedImports = {};
71
+ for (const [moduleScriptURL, moduleNames] of entries) {
72
+ if (!Array.isArray(moduleNames)) {
73
+ throw new Error("moduleNames must be an array");
74
+ }
75
+ if (!moduleScriptURL || typeof moduleScriptURL !== "string") {
76
+ throw new Error("moduleScriptURL must be a string");
77
+ }
78
+ validateMappingUri(moduleScriptURL, moduleScriptURL);
79
+ for (const moduleName of moduleNames) {
80
+ validateSpecifier(moduleName);
81
+ if (moduleName in convertedImports) {
82
+ if (import_dom.hasConsole) {
83
+ console.warn(`LWR.importMap(): duplicate module "${moduleName}" \u2014 already mapped to "${convertedImports[moduleName]}", ignoring mapping to "${moduleScriptURL}"`);
84
+ }
85
+ } else {
86
+ convertedImports[moduleName] = moduleScriptURL;
87
+ }
88
+ }
89
+ }
90
+ return {
91
+ imports: convertedImports
92
+ };
93
+ }
@@ -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 ESM Module Loader v0.22.10 */
7
+ /* LWR ESM Module Loader v0.22.12 */
8
8
  function _optionalChain$1(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; }
9
9
 
10
10
 
@@ -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 Module Loader v0.22.10 */
7
+ /* LWR Module Loader v0.22.12 */
8
8
  const templateRegex = /\{([0-9]+)\}/g;
9
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
10
  function templateString(template, args) {
@@ -4,7 +4,7 @@
4
4
  * This implementation is adapted from https://github.com/systemjs/systemjs/blob/master/src/features/import-map.js
5
5
  */
6
6
  import { resolveUrl, resolveIfNotPlainOrUrl, isUrl } from '../utils/url.js';
7
- import { getMatch, targetWarning } from './utils.js';
7
+ import { getMatch, targetWarning, mergeImportMapEntry } from './utils.js';
8
8
  // Resolves an import map package entry
9
9
  function applyPackages(id, packages, defaultUri) {
10
10
  const pkgName = getMatch(id, packages);
@@ -79,7 +79,8 @@ function resolveAndComposePackages(packages, outPackages, baseUrl, parentMap, pa
79
79
  targetWarning(p, rhs, 'bare specifier did not resolve');
80
80
  }
81
81
  else {
82
- outPackages[resolvedLhs] = mapped.uri;
82
+ // Merge into cache with write-once protection
83
+ mergeImportMapEntry(resolvedLhs, mapped.uri, outPackages);
83
84
  }
84
85
  }
85
86
  }
@@ -4,5 +4,6 @@ export declare class ImportMapResolver implements ImportResolver {
4
4
  importMap: ImportMap;
5
5
  constructor(importMap: ImportMap);
6
6
  resolve(resolvedOrPlain: string, parentUrl: string): ImportDefinition | undefined;
7
+ addImportMapEntries(importMap: ImportMap): void;
7
8
  }
8
9
  //# sourceMappingURL=importMapResolver.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { resolveImportMapEntry } from './importMap.js';
2
+ import { mergeImportMapEntry } from './utils.js';
2
3
  /* spec based import map resolver */
3
4
  export class ImportMapResolver {
4
5
  constructor(importMap) {
@@ -7,5 +8,17 @@ export class ImportMapResolver {
7
8
  resolve(resolvedOrPlain, parentUrl) {
8
9
  return resolveImportMapEntry(this.importMap, resolvedOrPlain, parentUrl);
9
10
  }
11
+ addImportMapEntries(importMap) {
12
+ if (importMap.imports) {
13
+ const current = this.importMap;
14
+ if (!current.imports) {
15
+ current.imports = {};
16
+ }
17
+ // Merge into cache with write-once protection
18
+ for (const specifier in importMap.imports) {
19
+ mergeImportMapEntry(specifier, importMap.imports[specifier], current.imports);
20
+ }
21
+ }
22
+ }
10
23
  }
11
24
  //# sourceMappingURL=importMapResolver.js.map
@@ -1,4 +1,15 @@
1
1
  declare function getMatch(path: string, matchObj: Record<string, unknown>): string | undefined;
2
2
  declare function targetWarning(match: string, target: string, msg: string): void;
3
- export { getMatch, targetWarning };
3
+ /**
4
+ * Import map entries are write-once: once a specifier is mapped, it cannot be overridden.
5
+ * This ensures deterministic module resolution and prevents runtime mutation of previously
6
+ * resolved dependencies. Conflicting mappings are ignored with a warning.
7
+ *
8
+ * Merges a single import map entry into a target imports object.
9
+ * - If the specifier doesn't exist, it is added.
10
+ * - If it exists with the same value, it is silently skipped.
11
+ * - If it exists with a different value, a warning is logged and the entry is skipped.
12
+ */
13
+ declare function mergeImportMapEntry(specifier: string, uri: string, target: Record<string, string>): void;
14
+ export { getMatch, targetWarning, mergeImportMapEntry };
4
15
  //# sourceMappingURL=utils.d.ts.map
@@ -11,7 +11,8 @@ function getMatch(path, matchObj) {
11
11
  if (segment in matchObj) {
12
12
  return segment;
13
13
  }
14
- } while (path.length > 1 && (sepIndex = path.lastIndexOf('/', sepIndex - 1)) !== -1);
14
+ sepIndex = path.lastIndexOf('/', sepIndex - 1);
15
+ } while (sepIndex > 0);
15
16
  }
16
17
  function targetWarning(match, target, msg) {
17
18
  if (hasConsole) {
@@ -19,5 +20,26 @@ function targetWarning(match, target, msg) {
19
20
  console.warn('Package target ' + msg + ", resolving target '" + target + "' for " + match);
20
21
  }
21
22
  }
22
- export { getMatch, targetWarning };
23
+ /**
24
+ * Import map entries are write-once: once a specifier is mapped, it cannot be overridden.
25
+ * This ensures deterministic module resolution and prevents runtime mutation of previously
26
+ * resolved dependencies. Conflicting mappings are ignored with a warning.
27
+ *
28
+ * Merges a single import map entry into a target imports object.
29
+ * - If the specifier doesn't exist, it is added.
30
+ * - If it exists with the same value, it is silently skipped.
31
+ * - If it exists with a different value, a warning is logged and the entry is skipped.
32
+ */
33
+ function mergeImportMapEntry(specifier, uri, target) {
34
+ const existing = target[specifier];
35
+ if (existing !== undefined) {
36
+ if (existing !== uri) {
37
+ targetWarning(specifier, uri, `already mapped to "${existing}", ignoring conflicting mapping`);
38
+ }
39
+ }
40
+ else {
41
+ target[specifier] = uri;
42
+ }
43
+ }
44
+ export { getMatch, targetWarning, mergeImportMapEntry };
23
45
  //# sourceMappingURL=utils.js.map
@@ -1,13 +1,14 @@
1
1
  import { Module, ModuleDefinitionSignatures } from './moduleRegistry/moduleRegistry.js';
2
2
  import { ImportMap } from './importMap/importMap.js';
3
3
  import type { ServiceAPI } from '@lwrjs/types';
4
- import type { LoaderConfig } from '../../../types.js';
4
+ import type { LoaderConfig, ImportMapUpdate } from '../../../types.js';
5
5
  /**
6
6
  * The LWR loader is inspired and borrows from the algorithms and native browser principles of https://github.com/systemjs/systemjs
7
7
  */
8
8
  export declare class Loader {
9
9
  private registry;
10
10
  private baseUrl;
11
+ private importMapResolver?;
11
12
  readonly services: Readonly<Pick<ServiceAPI, 'addLoaderPlugin' | 'handleStaleModule' | 'appMetadata'>>;
12
13
  constructor(config?: LoaderConfig);
13
14
  /**
@@ -54,6 +55,15 @@ export declare class Loader {
54
55
  * @param modules - list of module identifiers
55
56
  */
56
57
  registerExternalModules(modules: string[]): void;
58
+ /**
59
+ * Apply import map updates at runtime.
60
+ * Enables adding new import mappings dynamically.
61
+ *
62
+ * @param updates - Import Map Update object containing:
63
+ - [moduleScriptURL] - Script URL containing LWR define statements
64
+ - string[] - array of module names which are included in the given script
65
+ */
66
+ importMap(update: ImportMapUpdate): void;
57
67
  getModuleWarnings(isAppMounted?: boolean): Record<string, string[]>;
58
68
  }
59
69
  //# sourceMappingURL=loaderLegacy.d.ts.map
@@ -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 v0.22.10 */
7
+ /* LWR Legacy Module Loader v0.22.12 */
8
8
  const templateRegex = /\{([0-9]+)\}/g;
9
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
10
  function templateString(template, args) {
@@ -1461,7 +1461,8 @@ function getMatch(path, matchObj) {
1461
1461
  if (segment in matchObj) {
1462
1462
  return segment;
1463
1463
  }
1464
- } while (path.length > 1 && (sepIndex = path.lastIndexOf('/', sepIndex - 1)) !== -1);
1464
+ sepIndex = path.lastIndexOf('/', sepIndex - 1);
1465
+ } while (sepIndex > 0);
1465
1466
  }
1466
1467
  function targetWarning(match, target, msg) {
1467
1468
  if (hasConsole) {
@@ -1470,6 +1471,27 @@ function targetWarning(match, target, msg) {
1470
1471
  }
1471
1472
  }
1472
1473
 
1474
+ /**
1475
+ * Import map entries are write-once: once a specifier is mapped, it cannot be overridden.
1476
+ * This ensures deterministic module resolution and prevents runtime mutation of previously
1477
+ * resolved dependencies. Conflicting mappings are ignored with a warning.
1478
+ *
1479
+ * Merges a single import map entry into a target imports object.
1480
+ * - If the specifier doesn't exist, it is added.
1481
+ * - If it exists with the same value, it is silently skipped.
1482
+ * - If it exists with a different value, a warning is logged and the entry is skipped.
1483
+ */
1484
+ function mergeImportMapEntry(specifier, uri, target) {
1485
+ const existing = target[specifier];
1486
+ if (existing !== undefined) {
1487
+ if (existing !== uri) {
1488
+ targetWarning(specifier, uri, `already mapped to "${existing}", ignoring conflicting mapping`);
1489
+ }
1490
+ } else {
1491
+ target[specifier] = uri;
1492
+ }
1493
+ }
1494
+
1473
1495
  /**
1474
1496
  * Import map support for LWR based on the spec: https://github.com/WICG/import-maps
1475
1497
  *
@@ -1591,7 +1613,8 @@ function resolveAndComposePackages(
1591
1613
  if (!mapped) {
1592
1614
  targetWarning(p, rhs, 'bare specifier did not resolve');
1593
1615
  } else {
1594
- outPackages[resolvedLhs] = mapped.uri;
1616
+ // Merge into cache with write-once protection
1617
+ mergeImportMapEntry(resolvedLhs, mapped.uri, outPackages);
1595
1618
  }
1596
1619
  }
1597
1620
  }
@@ -1642,6 +1665,20 @@ class ImportMapResolver {
1642
1665
  resolve(resolvedOrPlain, parentUrl) {
1643
1666
  return resolveImportMapEntry(this.importMap, resolvedOrPlain, parentUrl);
1644
1667
  }
1668
+
1669
+ addImportMapEntries(importMap) {
1670
+ if (importMap.imports) {
1671
+ const current = this.importMap;
1672
+ if (!current.imports) {
1673
+ current.imports = {};
1674
+ }
1675
+
1676
+ // Merge into cache with write-once protection
1677
+ for (const specifier in importMap.imports) {
1678
+ mergeImportMapEntry(specifier, importMap.imports[specifier], current.imports);
1679
+ }
1680
+ }
1681
+ }
1645
1682
  }
1646
1683
 
1647
1684
  /**
@@ -1713,15 +1750,134 @@ async function evaluateImportMaps(baseUrl) {
1713
1750
  return importMapPromise;
1714
1751
  }
1715
1752
 
1716
- 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; }
1753
+ /**
1754
+ * List of protected module patterns that cannot be remapped.
1755
+ * Uses case-insensitive matching to prevent bypass attempts (e.g., HF-1027).
1756
+ */
1757
+ const PROTECTED_PATTERNS = [
1758
+ /^lwc$/i, // Core LWC
1759
+ /^lwc\/v\//i, // Versioned LWC variants
1760
+ /^@lwc\//i, // LWC scoped packages
1761
+ /^lightningmobileruntime\//i, // Lightning mobile runtime
1762
+ ];
1717
1763
 
1764
+ /**
1765
+ * Checks if a specifier matches any protected module pattern.
1766
+ *
1767
+ * @param specifier - The module specifier to check
1768
+ * @returns true if the specifier is protected, false otherwise
1769
+ */
1770
+ function isProtectedSpecifier(specifier) {
1771
+ return PROTECTED_PATTERNS.some((pattern) => pattern.test(specifier));
1772
+ }
1718
1773
 
1774
+ /**
1775
+ * Validates that a URI does not use dangerous URL schemes.
1776
+ *
1777
+ * Blocks:
1778
+ * - blob: URLs (HF-1027 bypass prevention) - case-insensitive
1779
+ * - data: URLs (inline code injection) - case-insensitive
1780
+ *
1781
+ * @param uri - The URI to validate
1782
+ * @param specifier - The specifier being mapped (for error messages)
1783
+ * @throws Error if the URI uses a dangerous scheme
1784
+ */
1785
+ function validateMappingUri(uri, specifier) {
1786
+ const lowerUri = uri.toLowerCase();
1787
+
1788
+ if (lowerUri.startsWith('blob:')) {
1789
+ throw new Error(`Cannot map ${specifier} to blob: URL`);
1790
+ }
1791
+
1792
+ if (lowerUri.startsWith('data:')) {
1793
+ throw new Error(`Cannot map ${specifier} to data: URL`);
1794
+ }
1795
+ }
1796
+
1797
+ /**
1798
+ * Validates that a specifier is well-formed and not protected.
1799
+ *
1800
+ * @param specifier - The module specifier to validate
1801
+ * @throws Error if the specifier is invalid or protected
1802
+ */
1803
+ function validateSpecifier(specifier) {
1804
+ if (typeof specifier !== 'string' || specifier.length === 0) {
1805
+ throw new Error('Specifier must be a non-empty string');
1806
+ }
1807
+
1808
+ if (isProtectedSpecifier(specifier)) {
1809
+ throw new Error(`Cannot remap protected module: ${specifier}`);
1810
+ }
1811
+ }
1812
+
1813
+ /**
1814
+ * Validates and converts an ImportMapUpdate to ImportMap format.
1815
+ *
1816
+ * Performs validation checks and then converts from:
1817
+ * { moduleScriptURL: [moduleName1, moduleName2] }
1818
+ * To:
1819
+ * { imports: { moduleName1: moduleScriptURL, moduleName2: moduleScriptURL } }
1820
+ *
1821
+ * @param update - The ImportMapUpdate object to validate and convert
1822
+ * @returns The converted ImportMap, or null if the update is empty
1823
+ * @throws Error if any validation fails
1824
+ */
1825
+ function validateAndConvertImportMapUpdate(update) {
1826
+ if (!update || typeof update !== 'object') {
1827
+ throw new Error('LWR.importMap() requires an object argument');
1828
+ }
1829
+
1830
+ // Check if update is empty
1831
+ const entries = Object.entries(update);
1832
+ if (entries.length === 0) {
1833
+ if (hasConsole) {
1834
+ // eslint-disable-next-line lwr/no-unguarded-apis
1835
+ console.warn('LWR.importMap() called with empty update object');
1836
+ }
1837
+ return null;
1838
+ }
1839
+
1840
+ const convertedImports = {};
1841
+
1842
+ for (const [moduleScriptURL, moduleNames] of entries) {
1843
+ if (!Array.isArray(moduleNames)) {
1844
+ throw new Error('moduleNames must be an array');
1845
+ }
1846
+
1847
+ if (!moduleScriptURL || typeof moduleScriptURL !== 'string') {
1848
+ throw new Error('moduleScriptURL must be a string');
1849
+ }
1850
+
1851
+ validateMappingUri(moduleScriptURL, moduleScriptURL);
1852
+
1853
+ for (const moduleName of moduleNames) {
1854
+ validateSpecifier(moduleName);
1855
+ if (moduleName in convertedImports) {
1856
+ if (hasConsole) {
1857
+ // eslint-disable-next-line lwr/no-unguarded-apis
1858
+ console.warn(
1859
+ `LWR.importMap(): duplicate module "${moduleName}" — already mapped to "${convertedImports[moduleName]}", ignoring mapping to "${moduleScriptURL}"`,
1860
+ );
1861
+ }
1862
+ } else {
1863
+ convertedImports[moduleName] = moduleScriptURL;
1864
+ }
1865
+ }
1866
+ }
1867
+
1868
+ return {
1869
+ imports: convertedImports,
1870
+ };
1871
+ }
1872
+
1873
+ 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; }
1719
1874
  /**
1720
1875
  * The LWR loader is inspired and borrows from the algorithms and native browser principles of https://github.com/systemjs/systemjs
1721
1876
  */
1722
1877
  class Loader {
1723
1878
 
1724
1879
 
1880
+
1725
1881
 
1726
1882
 
1727
1883
  constructor(config) {
@@ -1850,10 +2006,13 @@ class Loader {
1850
2006
  // import maps spec if we do this after resolving any imports
1851
2007
  importMap = resolveAndComposeImportMap(mappings, this.baseUrl, this.parentImportMap);
1852
2008
  }
1853
- this.parentImportMap = importMap;
1854
- if (this.parentImportMap) {
1855
- const importMapResolver = new ImportMapResolver(this.parentImportMap);
1856
- this.registry.setImportResolver(importMapResolver);
2009
+ if (importMap) {
2010
+ if (this.importMapResolver || this.parentImportMap) {
2011
+ throw new LoaderError(BAD_IMPORT_MAP);
2012
+ }
2013
+ this.importMapResolver = new ImportMapResolver(importMap);
2014
+ this.registry.setImportResolver(this.importMapResolver);
2015
+ this.parentImportMap = this.importMapResolver.importMap;
1857
2016
  }
1858
2017
  }
1859
2018
 
@@ -1866,6 +2025,35 @@ class Loader {
1866
2025
  this.registry.registerExternalModules(modules);
1867
2026
  }
1868
2027
 
2028
+ /**
2029
+ * Apply import map updates at runtime.
2030
+ * Enables adding new import mappings dynamically.
2031
+ *
2032
+ * @param updates - Import Map Update object containing:
2033
+ - [moduleScriptURL] - Script URL containing LWR define statements
2034
+ - string[] - array of module names which are included in the given script
2035
+ */
2036
+ importMap(update) {
2037
+ // Validate and convert the import map update to the ImportMap format (moduleName -> moduleScriptURL)
2038
+ const importMap = validateAndConvertImportMapUpdate(update);
2039
+
2040
+ // Early return if update was empty
2041
+ if (!importMap) {
2042
+ return;
2043
+ }
2044
+
2045
+ if (!this.parentImportMap || !this.importMapResolver) {
2046
+ throw new LoaderError(BAD_IMPORT_MAP);
2047
+ }
2048
+
2049
+ // Merge the new mappings with the base import map - note this goes against
2050
+ // import maps spec if we do this after resolving any imports
2051
+ const resolvedImportMap = resolveAndComposeImportMap(importMap, this.baseUrl, this.parentImportMap);
2052
+
2053
+ this.importMapResolver.addImportMapEntries(resolvedImportMap);
2054
+ this.parentImportMap = this.importMapResolver.importMap;
2055
+ }
2056
+
1869
2057
  getModuleWarnings(isAppMounted = false) {
1870
2058
  return this.registry.getModuleWarnings(isAppMounted);
1871
2059
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Validation logic for LWR.importMap() API
3
+ *
4
+ * This module provides validation functions to ensure that import map updates:
5
+ * 1. Do not remap protected system modules (lwc, lwr/*, etc.)
6
+ * 2. Do not use dangerous URL schemes (blob:, data:)
7
+ * 3. Have well-formed specifiers and URIs
8
+ */
9
+ import type { ImportMapUpdate } from '../../../../types.js';
10
+ import type { ImportMap } from '../importMap/importMap.js';
11
+ /**
12
+ * Checks if a specifier matches any protected module pattern.
13
+ *
14
+ * @param specifier - The module specifier to check
15
+ * @returns true if the specifier is protected, false otherwise
16
+ */
17
+ export declare function isProtectedSpecifier(specifier: string): boolean;
18
+ /**
19
+ * Validates that a URI does not use dangerous URL schemes.
20
+ *
21
+ * Blocks:
22
+ * - blob: URLs (HF-1027 bypass prevention) - case-insensitive
23
+ * - data: URLs (inline code injection) - case-insensitive
24
+ *
25
+ * @param uri - The URI to validate
26
+ * @param specifier - The specifier being mapped (for error messages)
27
+ * @throws Error if the URI uses a dangerous scheme
28
+ */
29
+ export declare function validateMappingUri(uri: string, specifier: string): void;
30
+ /**
31
+ * Validates that a specifier is well-formed and not protected.
32
+ *
33
+ * @param specifier - The module specifier to validate
34
+ * @throws Error if the specifier is invalid or protected
35
+ */
36
+ export declare function validateSpecifier(specifier: string): void;
37
+ /**
38
+ * Validates and converts an ImportMapUpdate to ImportMap format.
39
+ *
40
+ * Performs validation checks and then converts from:
41
+ * { moduleScriptURL: [moduleName1, moduleName2] }
42
+ * To:
43
+ * { imports: { moduleName1: moduleScriptURL, moduleName2: moduleScriptURL } }
44
+ *
45
+ * @param update - The ImportMapUpdate object to validate and convert
46
+ * @returns The converted ImportMap, or null if the update is empty
47
+ * @throws Error if any validation fails
48
+ */
49
+ export declare function validateAndConvertImportMapUpdate(update: ImportMapUpdate): ImportMap | null;
50
+ //# sourceMappingURL=validation.d.ts.map