@lwrjs/loader 0.22.9 → 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.
Files changed (37) 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 +530 -25
  4. package/build/assets/prod/lwr-loader-shim-legacy.bundle.min.js +3 -3
  5. package/build/assets/prod/lwr-loader-shim-legacy.js +317 -14
  6. package/build/assets/prod/lwr-loader-shim.bundle.js +123 -12
  7. package/build/assets/prod/lwr-loader-shim.bundle.min.js +3 -3
  8. package/build/assets/prod/lwr-loader-shim.js +105 -8
  9. package/build/cjs/modules/lwr/loader/validateLoadSpecifier.cjs +4 -1
  10. package/build/cjs/modules/lwr/loaderLegacy/importMap/importMap.cjs +1 -1
  11. package/build/cjs/modules/lwr/loaderLegacy/importMap/importMapResolver.cjs +12 -0
  12. package/build/cjs/modules/lwr/loaderLegacy/importMap/utils.cjs +13 -1
  13. package/build/cjs/modules/lwr/loaderLegacy/utils/validation.cjs +93 -0
  14. package/build/modules/lwr/esmLoader/esmLoader.js +1 -1
  15. package/build/modules/lwr/loader/loader.js +18 -4
  16. package/build/modules/lwr/loader/validateLoadSpecifier.d.ts +2 -1
  17. package/build/modules/lwr/loader/validateLoadSpecifier.js +11 -2
  18. package/build/modules/lwr/loaderLegacy/importMap/importMap.js +3 -2
  19. package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.d.ts +1 -0
  20. package/build/modules/lwr/loaderLegacy/importMap/importMapResolver.js +13 -0
  21. package/build/modules/lwr/loaderLegacy/importMap/utils.d.ts +12 -1
  22. package/build/modules/lwr/loaderLegacy/importMap/utils.js +24 -2
  23. package/build/modules/lwr/loaderLegacy/loaderLegacy.d.ts +11 -1
  24. package/build/modules/lwr/loaderLegacy/loaderLegacy.js +213 -11
  25. package/build/modules/lwr/loaderLegacy/utils/validation.d.ts +50 -0
  26. package/build/modules/lwr/loaderLegacy/utils/validation.js +114 -0
  27. package/build/shim/defineCacheResolver.d.ts +10 -0
  28. package/build/shim/defineCacheResolver.js +78 -0
  29. package/build/shim/loader.d.ts +5 -1
  30. package/build/shim/loader.js +14 -3
  31. package/build/shim/shim.js +3 -2
  32. package/build/shim-legacy/loaderLegacy.d.ts +7 -2
  33. package/build/shim-legacy/loaderLegacy.js +15 -4
  34. package/build/shim-legacy/shimLegacy.d.ts +14 -0
  35. package/build/shim-legacy/shimLegacy.js +53 -4
  36. package/build/types.d.ts +22 -0
  37. package/package.json +6 -6
@@ -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.9 */
7
+ /* LWR Legacy Module Loader v0.22.11 */
8
8
  const templateRegex = /\{([0-9]+)\}/g;
9
9
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
10
  function templateString(template, args) {
@@ -143,7 +143,7 @@ const NO_IMPORT_LOADER = Object.freeze({
143
143
  level: 0,
144
144
  message: 'Cannot dynamically import the LWR loader with importer "{0}"',
145
145
  });
146
- Object.freeze({
146
+ const NO_IMPORT_TRANSPORT = Object.freeze({
147
147
  code: 3025,
148
148
  level: 0,
149
149
  message: 'Cannot dynamically import "transport" with importer "{0}"',
@@ -1399,9 +1399,10 @@ function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else {
1399
1399
 
1400
1400
 
1401
1401
 
1402
+
1402
1403
  /**
1403
1404
  * Validates that the given module id is not one of the forbidden dynamic import specifiers.
1404
- * Throws LoaderError for: lwc, the LWR loader, and blob URLs.
1405
+ * Throws LoaderError for: lwc, the LWR loader, transport/webruntime/transport, and blob URLs.
1405
1406
  *
1406
1407
  * @param id - Module identifier or URL
1407
1408
  * @param importer - Versioned specifier of the module importer (for error reporting)
@@ -1414,7 +1415,7 @@ function validateLoadSpecifier(
1414
1415
  loaderSpecifier,
1415
1416
  errors,
1416
1417
  ) {
1417
- const { LoaderError, NO_IMPORT_LWC, NO_IMPORT_LOADER, NO_BLOB_IMPORT } = errors;
1418
+ const { LoaderError, NO_IMPORT_LWC, NO_IMPORT_LOADER, NO_IMPORT_TRANSPORT, NO_BLOB_IMPORT } = errors;
1418
1419
 
1419
1420
  // Throw an error if the specifier is "lwc" or a versioned lwc specifier
1420
1421
  // Dynamic import of LWC APIs is not allowed
@@ -1430,6 +1431,18 @@ function validateLoadSpecifier(
1430
1431
  throw new LoaderError(NO_IMPORT_LOADER, [_nullishCoalesce(importer, () => ( 'unknown'))]);
1431
1432
  }
1432
1433
 
1434
+ // Throw an error if the specifier is "transport" or "webruntime/transport" (or versioned)
1435
+ // Dynamic import of transport exposes unsandboxed fetch, bypassing LWS
1436
+ // Reference: Hackforce report HF-3393
1437
+ if (
1438
+ id === 'transport' ||
1439
+ id.startsWith('transport/v/') ||
1440
+ id === 'webruntime/transport' ||
1441
+ id.startsWith('webruntime/transport/v/')
1442
+ ) {
1443
+ throw new LoaderError(NO_IMPORT_TRANSPORT, [_nullishCoalesce(importer, () => ( 'unknown'))]);
1444
+ }
1445
+
1433
1446
  // Throw an error if the specifier is a blob URL (case-insensitive check)
1434
1447
  if (id.toLowerCase().startsWith('blob:')) {
1435
1448
  throw new LoaderError(NO_BLOB_IMPORT);
@@ -1448,7 +1461,8 @@ function getMatch(path, matchObj) {
1448
1461
  if (segment in matchObj) {
1449
1462
  return segment;
1450
1463
  }
1451
- } while (path.length > 1 && (sepIndex = path.lastIndexOf('/', sepIndex - 1)) !== -1);
1464
+ sepIndex = path.lastIndexOf('/', sepIndex - 1);
1465
+ } while (sepIndex > 0);
1452
1466
  }
1453
1467
  function targetWarning(match, target, msg) {
1454
1468
  if (hasConsole) {
@@ -1457,6 +1471,27 @@ function targetWarning(match, target, msg) {
1457
1471
  }
1458
1472
  }
1459
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
+
1460
1495
  /**
1461
1496
  * Import map support for LWR based on the spec: https://github.com/WICG/import-maps
1462
1497
  *
@@ -1578,7 +1613,8 @@ function resolveAndComposePackages(
1578
1613
  if (!mapped) {
1579
1614
  targetWarning(p, rhs, 'bare specifier did not resolve');
1580
1615
  } else {
1581
- outPackages[resolvedLhs] = mapped.uri;
1616
+ // Merge into cache with write-once protection
1617
+ mergeImportMapEntry(resolvedLhs, mapped.uri, outPackages);
1582
1618
  }
1583
1619
  }
1584
1620
  }
@@ -1629,6 +1665,20 @@ class ImportMapResolver {
1629
1665
  resolve(resolvedOrPlain, parentUrl) {
1630
1666
  return resolveImportMapEntry(this.importMap, resolvedOrPlain, parentUrl);
1631
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
+ }
1632
1682
  }
1633
1683
 
1634
1684
  /**
@@ -1700,15 +1750,134 @@ async function evaluateImportMaps(baseUrl) {
1700
1750
  return importMapPromise;
1701
1751
  }
1702
1752
 
1703
- 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
+ ];
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
+ }
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
+ }
1704
1839
 
1840
+ const convertedImports = {};
1705
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; }
1706
1874
  /**
1707
1875
  * The LWR loader is inspired and borrows from the algorithms and native browser principles of https://github.com/systemjs/systemjs
1708
1876
  */
1709
1877
  class Loader {
1710
1878
 
1711
1879
 
1880
+
1712
1881
 
1713
1882
 
1714
1883
  constructor(config) {
@@ -1794,6 +1963,7 @@ class Loader {
1794
1963
  LoaderError,
1795
1964
  NO_IMPORT_LWC,
1796
1965
  NO_IMPORT_LOADER,
1966
+ NO_IMPORT_TRANSPORT,
1797
1967
  NO_BLOB_IMPORT,
1798
1968
  });
1799
1969
  return this.registry.load(id, importer);
@@ -1836,10 +2006,13 @@ class Loader {
1836
2006
  // import maps spec if we do this after resolving any imports
1837
2007
  importMap = resolveAndComposeImportMap(mappings, this.baseUrl, this.parentImportMap);
1838
2008
  }
1839
- this.parentImportMap = importMap;
1840
- if (this.parentImportMap) {
1841
- const importMapResolver = new ImportMapResolver(this.parentImportMap);
1842
- 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;
1843
2016
  }
1844
2017
  }
1845
2018
 
@@ -1852,6 +2025,35 @@ class Loader {
1852
2025
  this.registry.registerExternalModules(modules);
1853
2026
  }
1854
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
+
1855
2057
  getModuleWarnings(isAppMounted = false) {
1856
2058
  return this.registry.getModuleWarnings(isAppMounted);
1857
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
@@ -0,0 +1,114 @@
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 { hasConsole } from '../utils/dom.js';
10
+ /**
11
+ * List of protected module patterns that cannot be remapped.
12
+ * Uses case-insensitive matching to prevent bypass attempts (e.g., HF-1027).
13
+ */
14
+ const PROTECTED_PATTERNS = [
15
+ /^lwc$/i,
16
+ /^lwc\/v\//i,
17
+ /^@lwc\//i,
18
+ /^lightningmobileruntime\//i, // Lightning mobile runtime
19
+ ];
20
+ /**
21
+ * Checks if a specifier matches any protected module pattern.
22
+ *
23
+ * @param specifier - The module specifier to check
24
+ * @returns true if the specifier is protected, false otherwise
25
+ */
26
+ export function isProtectedSpecifier(specifier) {
27
+ return PROTECTED_PATTERNS.some((pattern) => pattern.test(specifier));
28
+ }
29
+ /**
30
+ * Validates that a URI does not use dangerous URL schemes.
31
+ *
32
+ * Blocks:
33
+ * - blob: URLs (HF-1027 bypass prevention) - case-insensitive
34
+ * - data: URLs (inline code injection) - case-insensitive
35
+ *
36
+ * @param uri - The URI to validate
37
+ * @param specifier - The specifier being mapped (for error messages)
38
+ * @throws Error if the URI uses a dangerous scheme
39
+ */
40
+ export function validateMappingUri(uri, specifier) {
41
+ const lowerUri = uri.toLowerCase();
42
+ if (lowerUri.startsWith('blob:')) {
43
+ throw new Error(`Cannot map ${specifier} to blob: URL`);
44
+ }
45
+ if (lowerUri.startsWith('data:')) {
46
+ throw new Error(`Cannot map ${specifier} to data: URL`);
47
+ }
48
+ }
49
+ /**
50
+ * Validates that a specifier is well-formed and not protected.
51
+ *
52
+ * @param specifier - The module specifier to validate
53
+ * @throws Error if the specifier is invalid or protected
54
+ */
55
+ export function validateSpecifier(specifier) {
56
+ if (typeof specifier !== 'string' || specifier.length === 0) {
57
+ throw new Error('Specifier must be a non-empty string');
58
+ }
59
+ if (isProtectedSpecifier(specifier)) {
60
+ throw new Error(`Cannot remap protected module: ${specifier}`);
61
+ }
62
+ }
63
+ /**
64
+ * Validates and converts an ImportMapUpdate to ImportMap format.
65
+ *
66
+ * Performs validation checks and then converts from:
67
+ * { moduleScriptURL: [moduleName1, moduleName2] }
68
+ * To:
69
+ * { imports: { moduleName1: moduleScriptURL, moduleName2: moduleScriptURL } }
70
+ *
71
+ * @param update - The ImportMapUpdate object to validate and convert
72
+ * @returns The converted ImportMap, or null if the update is empty
73
+ * @throws Error if any validation fails
74
+ */
75
+ export function validateAndConvertImportMapUpdate(update) {
76
+ if (!update || typeof update !== 'object') {
77
+ throw new Error('LWR.importMap() requires an object argument');
78
+ }
79
+ // Check if update is empty
80
+ const entries = Object.entries(update);
81
+ if (entries.length === 0) {
82
+ if (hasConsole) {
83
+ // eslint-disable-next-line lwr/no-unguarded-apis
84
+ console.warn('LWR.importMap() called with empty update object');
85
+ }
86
+ return null;
87
+ }
88
+ const convertedImports = {};
89
+ for (const [moduleScriptURL, moduleNames] of entries) {
90
+ if (!Array.isArray(moduleNames)) {
91
+ throw new Error('moduleNames must be an array');
92
+ }
93
+ if (!moduleScriptURL || typeof moduleScriptURL !== 'string') {
94
+ throw new Error('moduleScriptURL must be a string');
95
+ }
96
+ validateMappingUri(moduleScriptURL, moduleScriptURL);
97
+ for (const moduleName of moduleNames) {
98
+ validateSpecifier(moduleName);
99
+ if (moduleName in convertedImports) {
100
+ if (hasConsole) {
101
+ // eslint-disable-next-line lwr/no-unguarded-apis
102
+ console.warn(`LWR.importMap(): duplicate module "${moduleName}" — already mapped to "${convertedImports[moduleName]}", ignoring mapping to "${moduleScriptURL}"`);
103
+ }
104
+ }
105
+ else {
106
+ convertedImports[moduleName] = moduleScriptURL;
107
+ }
108
+ }
109
+ }
110
+ return {
111
+ imports: convertedImports,
112
+ };
113
+ }
114
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1,10 @@
1
+ import type { DefineArguments, DefineArgsInput, ParseDefineResult, ResolvedLoader } from '../types.js';
2
+ /**
3
+ * Parse AMD define args. Supports define(id, deps, factory).
4
+ */
5
+ export declare function parseDefine(def: DefineArgsInput): ParseDefineResult;
6
+ /**
7
+ * Resolve the loader's dependencies from defineCache. Throws if definition is invalid or deps cannot be resolved.
8
+ */
9
+ export declare function resolveLoaderDepsFromDefineCache(loaderSpecifier: string, definition: DefineArguments, defineCache: Record<string, DefineArguments>): ResolvedLoader;
10
+ //# sourceMappingURL=defineCacheResolver.d.ts.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Parse AMD define args. Supports define(id, deps, factory).
3
+ */
4
+ export function parseDefine(def) {
5
+ const [, depsOrFactory, factory] = def;
6
+ if (Array.isArray(depsOrFactory) && typeof factory === 'function') {
7
+ return { deps: depsOrFactory, factory };
8
+ }
9
+ if (typeof depsOrFactory === 'function') {
10
+ return { deps: [], factory: depsOrFactory };
11
+ }
12
+ throw new Error('Invalid module definition');
13
+ }
14
+ /**
15
+ * Resolve a loader dependency: 'exports' returns the bag; otherwise look up in cache and instantiate.
16
+ * Loader deps are assumed to be leaf modules (no deps of their own)—no recursive resolution.
17
+ */
18
+ function resolveDep(dep, exportsObj, cache) {
19
+ if (dep === 'exports')
20
+ return exportsObj;
21
+ const mod = cache[dep];
22
+ if (!mod) {
23
+ throw new Error(`Dependency "${dep}" not found in defineCache for loader`);
24
+ }
25
+ try {
26
+ return instantiateLeafModule(mod);
27
+ }
28
+ catch (e) {
29
+ const msg = e instanceof Error ? e.message : String(e);
30
+ throw new Error(`Loader dependency "${dep}" has invalid definition: ${msg}`);
31
+ }
32
+ }
33
+ /**
34
+ * Instantiate a leaf module (loader dep). Assumes the module has no dependencies of its own.
35
+ * Supports only deps: [] or ['exports']; any other dep list throws.
36
+ */
37
+ function instantiateLeafModule(def) {
38
+ const { deps, factory } = parseDefine(def);
39
+ if (deps.length > 1 || (deps.length === 1 && deps[0] !== 'exports')) {
40
+ throw new Error(`Loader dependencies must have no deps or only ['exports']; got [${deps.join(', ')}]`);
41
+ }
42
+ const exports = {};
43
+ const out = factory(exports);
44
+ return out !== undefined ? out : exports;
45
+ }
46
+ /**
47
+ * Normalize loader definition to canonical (exports, ...deps). Injects 'exports' if missing.
48
+ */
49
+ function normalizeLoaderDefinition(def) {
50
+ const { deps, factory } = parseDefine(def);
51
+ if (deps.includes('exports')) {
52
+ return { deps, factory };
53
+ }
54
+ const isEmptyDeps = deps.length === 0;
55
+ const normalizedFactory = (exports, ...rest) => {
56
+ const result = isEmptyDeps ? factory(exports) : factory(...rest);
57
+ if (result !== undefined && typeof result === 'object') {
58
+ Object.assign(exports, result);
59
+ }
60
+ };
61
+ return { deps: ['exports', ...deps], factory: normalizedFactory };
62
+ }
63
+ /**
64
+ * Resolve the loader's dependencies from defineCache. Throws if definition is invalid or deps cannot be resolved.
65
+ */
66
+ export function resolveLoaderDepsFromDefineCache(loaderSpecifier, definition, defineCache) {
67
+ try {
68
+ const { deps, factory } = normalizeLoaderDefinition(definition);
69
+ const exportsObj = {};
70
+ const args = deps.map((dep) => resolveDep(dep, exportsObj, defineCache));
71
+ return { factory: factory, args, exportsObj };
72
+ }
73
+ catch (e) {
74
+ const msg = e instanceof Error ? e.message : String(e);
75
+ throw new Error(`Expected loader with specifier "${loaderSpecifier}" to be a module. ${msg}`);
76
+ }
77
+ }
78
+ //# sourceMappingURL=defineCacheResolver.js.map
@@ -1,3 +1,7 @@
1
1
  import type { DefineArguments, FingerprintsLoaderAPI as LoaderAPI, FingerprintsLoaderConfig as LoaderConfig } from '../types.js';
2
- export declare function createLoader(name: string, definition: DefineArguments, config: LoaderConfig, externalModules?: string[]): LoaderAPI;
2
+ /**
3
+ * Create a loader from a definition. Original API preserved.
4
+ * Optional defineCache for resolving loader deps (when loader has dependencies).
5
+ */
6
+ export declare function createLoader(name: string, definition: DefineArguments, config: LoaderConfig, externalModules?: string[], defineCache?: Record<string, DefineArguments>): LoaderAPI;
3
7
  //# sourceMappingURL=loader.d.ts.map
@@ -1,10 +1,21 @@
1
- export function createLoader(name, definition, config, externalModules) {
1
+ import { resolveLoaderDepsFromDefineCache } from './defineCacheResolver.js';
2
+ /**
3
+ * Create a loader from a definition. Original API preserved.
4
+ * Optional defineCache for resolving loader deps (when loader has dependencies).
5
+ */
6
+ export function createLoader(name, definition, config, externalModules, defineCache) {
2
7
  if (!definition || typeof definition[2] !== 'function') {
3
8
  throw new Error(`Expected loader with specifier "${name}" to be a module`);
4
9
  }
5
- // Create a Loader instance
6
10
  const exports = {};
7
- definition[2].call(null, exports);
11
+ if (defineCache) {
12
+ const { factory, args, exportsObj } = resolveLoaderDepsFromDefineCache(name, definition, defineCache);
13
+ factory(...args);
14
+ Object.assign(exports, exportsObj);
15
+ }
16
+ else {
17
+ definition[2].call(null, exports);
18
+ }
8
19
  const { Loader } = exports;
9
20
  if (!Loader) {
10
21
  throw new Error('Expected Loader class to be defined');
@@ -112,7 +112,7 @@ export default class LoaderShim {
112
112
  rootComponents: this.config.rootComponents,
113
113
  },
114
114
  };
115
- const loader = createLoader(this.loaderSpecifier, this.defineCache[this.loaderSpecifier], loaderConfig, this.config.preloadModules);
115
+ const loader = createLoader(this.loaderSpecifier, this.defineCache[this.loaderSpecifier], loaderConfig, this.config.preloadModules, this.defineCache);
116
116
  this.mountApp(loader);
117
117
  if (loader &&
118
118
  typeof loader.getModuleWarnings === 'function' &&
@@ -134,6 +134,7 @@ export default class LoaderShim {
134
134
  resolve();
135
135
  }
136
136
  else {
137
+ /* istanbul ignore next */
137
138
  const observer = new MutationObserver(() => {
138
139
  // eslint-disable-next-line lwr/no-unguarded-apis
139
140
  if (document.body) {
@@ -198,7 +199,7 @@ export default class LoaderShim {
198
199
  ])
199
200
  .then(() => {
200
201
  // eslint-disable-next-line lwr/no-unguarded-apis
201
- if (typeof window === 'undefined' || typeof document === undefined) {
202
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
202
203
  return Promise.resolve();
203
204
  }
204
205
  if (initDeferDOM) {
@@ -1,3 +1,8 @@
1
- import type { LoaderConfig, DefineArguments, LoaderAPI } from '../types.js';
2
- export declare function createLoader(name: string, definition: DefineArguments, config: LoaderConfig, externalModules?: string[]): LoaderAPI;
1
+ import type { LoaderAPI, LoaderConfig, DefineArguments } from '../types.js';
2
+ /**
3
+ * Create a loader from a definition. Original API preserved.
4
+ * Optional defineCache for resolving loader deps.
5
+ * definition[3] (signatures: ownHash, hashes) is passed to loader.define for cache invalidation.
6
+ */
7
+ export declare function createLoader(name: string, definition: DefineArguments, config: LoaderConfig, externalModules?: string[], defineCache?: Record<string, DefineArguments>): LoaderAPI;
3
8
  //# sourceMappingURL=loaderLegacy.d.ts.map
@@ -1,16 +1,27 @@
1
- export function createLoader(name, definition, config, externalModules) {
1
+ import { resolveLoaderDepsFromDefineCache } from '../shim/defineCacheResolver.js';
2
+ /**
3
+ * Create a loader from a definition. Original API preserved.
4
+ * Optional defineCache for resolving loader deps.
5
+ * definition[3] (signatures: ownHash, hashes) is passed to loader.define for cache invalidation.
6
+ */
7
+ export function createLoader(name, definition, config, externalModules, defineCache) {
2
8
  if (!definition || typeof definition[2] !== 'function') {
3
9
  throw new Error(`Expected loader with specifier "${name}" to be a module`);
4
10
  }
5
- // Create a Loader instance
6
11
  const exports = {};
7
- definition[2].call(null, exports);
12
+ if (defineCache) {
13
+ const { factory, args, exportsObj } = resolveLoaderDepsFromDefineCache(name, definition, defineCache);
14
+ factory(...args);
15
+ Object.assign(exports, exportsObj);
16
+ }
17
+ else {
18
+ definition[2].call(null, exports);
19
+ }
8
20
  const { Loader } = exports;
9
21
  if (!Loader) {
10
22
  throw new Error('Expected Loader class to be defined');
11
23
  }
12
24
  const loader = new Loader(config);
13
- // register externally loaded modules
14
25
  if (externalModules && externalModules.length) {
15
26
  loader.registerExternalModules(externalModules);
16
27
  }