@lwrjs/loader 0.6.0-alpha.1 → 0.6.0-alpha.13

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.
@@ -4,228 +4,307 @@
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 Shim v0.6.0-alpha.1 */
7
+ /* LWR Module Loader Shim v0.6.0-alpha.13 */
8
8
  (function () {
9
- 'use strict';
9
+ 'use strict';
10
10
 
11
- function createLoader(name, definition, config, externalModules) {
12
- if (!definition || typeof definition[2] !== 'function') {
13
- throw new Error(`Expected loader with specifier "${name}" to be a module`);
14
- }
15
- // Create a Loader instance
16
- const exports = {};
17
- definition[2].call(null, exports);
18
- const { Loader } = exports;
19
- const loader = new Loader(config);
20
- if (externalModules && externalModules.length) {
21
- loader.registerExternalModules(externalModules);
22
- }
23
- // Define the loader module with public API: { define, load, services }
24
- const exporter = (exports) => {
25
- Object.assign(exports, {
26
- define: loader.define.bind(loader),
27
- load: loader.load.bind(loader),
28
- services: loader.services,
29
- });
30
- return;
31
- };
32
- loader.define(name, ['exports'], exporter);
33
- return loader;
34
- }
11
+ // Bootstrap / shim
12
+ const BOOTSTRAP_PREFIX = 'lwr.bootstrap.';
13
+ const BOOTSTRAP_ERROR = `${BOOTSTRAP_PREFIX}error`;
35
14
 
36
- const REQUIRED_MODULES_TIMEOUT = 300 * 1000;
15
+ var Phase;
37
16
 
38
- // Check for errors with autoBoot and customInit
39
- function validatePreInit(autoBoot, customInit) {
40
- // If autoBoot === false, there must be a customInit hook
41
- if (!autoBoot && !customInit) {
42
- throw new Error('The customInit hook is required when autoBoot is false');
43
- }
44
- // If autoBoot === true, there must NOT be a customInit hook
45
- if (autoBoot && customInit) {
46
- throw new Error('The customInit hook must not be defined when autoBoot is true');
47
- }
48
- }
49
- // Process the customInit hook
50
- function customInit(config, initializeApp, define, onBootstrapError) {
51
- // Validate config
52
- const { autoBoot, customInit } = config;
53
- validatePreInit(autoBoot, customInit);
54
- // Set up arguments and call the customInit hook, if available
55
- if (customInit) {
56
- const lwr = {
57
- initializeApp,
58
- define,
59
- onBootstrapError,
60
- };
61
- customInit(lwr, config);
62
- }
63
- }
17
+ (function (Phase) {
18
+ Phase[Phase["Start"] = 0] = "Start";
19
+ Phase[Phase["End"] = 1] = "End";
20
+ })(Phase || (Phase = {}));
64
21
 
65
- /* global document */
66
- /* eslint-disable lwr/no-unguarded-apis */
67
- const hasSetTimeout = typeof setTimeout === 'function';
68
- const hasConsole = typeof console !== 'undefined';
69
- /* eslint-enable lwr/no-unguarded-apis */
70
- class LoaderShim {
71
- constructor(global) {
72
- this.defineCache = {};
73
- this.orderedDefs = [];
74
- // Parse configuration
75
- this.global = global;
76
- this.config = global.LWR;
77
- this.loaderModule = 'lwr/loader/v/0_6_0-alpha_1';
78
- // Set up the temporary LWR.define function and customInit hook
79
- const tempDefine = this.tempDefine.bind(this);
80
- global.LWR.define = tempDefine;
81
- this.bootReady = this.config.autoBoot;
82
- // Start watchdog timer
83
- if (hasSetTimeout) {
84
- this.watchdogTimerId = this.startWatchdogTimer();
85
- }
86
- try {
87
- customInit(Object.freeze(this.config), this.postCustomInit.bind(this), tempDefine, (e) => {
88
- this.errorHandler = e;
89
- });
90
- }
91
- catch (e) {
92
- this.enterErrorState(e);
93
- }
94
- }
95
- // Return true if the app can be initialized
96
- canInit() {
97
- // Initialize the app if:
98
- // - bootReady: autoBoot is on OR customInit has finished
99
- // - all required modules are defined
100
- const allDefined = this.config.requiredModules.every((m) => this.orderedDefs.includes(m));
101
- return this.bootReady && allDefined;
102
- }
103
- /**
104
- * Create a temporary LWR.define() function which captures all
105
- * calls that occur BEFORE the full loader module is available
106
- *
107
- * Each call to LWR.define() is stored in 2 ways:
108
- * - in a map as [moduleName, arguments] pairs
109
- * - each moduleName is pushed onto an array, to preserve
110
- * the order in which the modules were defined
111
- */
112
- tempDefine(...args) {
113
- // Cache the incoming module
114
- const moduleName = args[0];
115
- this.defineCache[moduleName] = args;
116
- this.orderedDefs.push(moduleName);
117
- if (this.canInit()) {
118
- if (hasSetTimeout) {
119
- // requiredModules are defined, clear watchdog timer
120
- // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
121
- clearTimeout(this.watchdogTimerId);
122
- }
123
- this.initApp();
124
- }
125
- }
126
- // Called by the customInit hook via lwr.initializeApp()
127
- postCustomInit() {
128
- this.bootReady = true;
129
- if (this.canInit()) {
130
- this.initApp();
131
- }
132
- }
133
- // Create the loader and initialize the application
134
- initApp() {
135
- try {
136
- const loaderConfig = {
137
- endpoints: this.config.endpoints,
138
- baseUrl: this.config.baseUrl,
139
- };
140
- const loader = createLoader(this.loaderModule, this.defineCache[this.loaderModule], loaderConfig, this.config.preloadModules);
141
- this.mountApp(loader);
142
- }
143
- catch (e) {
144
- this.enterErrorState(e);
145
- }
146
- }
147
- waitForDOMContentLoaded() {
148
- // eslint-disable-next-line lwr/no-unguarded-apis
149
- if (typeof document === undefined) {
150
- return Promise.resolve();
151
- }
152
- // Resolve if document is already "ready" https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
153
- // eslint-disable-next-line lwr/no-unguarded-apis
154
- if (document.readyState === 'interactive' || document.readyState === 'complete') {
155
- return Promise.resolve();
156
- }
157
- return new Promise((resolve) => {
158
- // eslint-disable-next-line lwr/no-unguarded-apis
159
- document.addEventListener('DOMContentLoaded', () => {
160
- resolve();
161
- });
162
- });
163
- }
164
- // Set up the application globals, import map, root custom element...
165
- mountApp(loader) {
166
- const { bootstrapModule, rootComponent, rootComponents, endpoints, imports, index } = this.config;
167
- // Set global LWR.define to loader.define
168
- this.global.LWR = Object.freeze({
169
- define: loader.define.bind(loader),
170
- rootComponent,
171
- rootComponents,
172
- endpoints,
173
- imports: imports || {},
174
- index: index || {},
175
- });
176
- // Redefine all modules in the temporary cache
177
- this.orderedDefs.forEach((specifier) => {
178
- if (specifier !== this.loaderModule) {
179
- loader.define(...this.defineCache[specifier]);
180
- }
181
- });
182
- // by default, app initialization is gated on waiting for document to be parsed (via DOMContentLoaded)
183
- const { disableInitDefer } = this.config;
184
- // Load the import mappings and application bootstrap module
185
- loader
186
- .registerImportMappings({ imports, index }, [bootstrapModule, rootComponent])
187
- .then(() => {
188
- if (!disableInitDefer) {
189
- return this.waitForDOMContentLoaded();
190
- }
191
- })
192
- .then(() => loader.load(bootstrapModule))
193
- .catch((reason) => {
194
- this.enterErrorState(new Error(`Application ${rootComponent} could not be loaded: ${reason}`));
195
- });
196
- }
197
- // Trigger bootstrap error state, and call error handler if registered
198
- enterErrorState(error) {
199
- if (this.errorHandler) {
200
- this.errorHandler(error);
201
- }
202
- else {
203
- if (hasConsole) {
204
- // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
205
- console.error(`An error occurred during LWR bootstrap. ${error.message}`, error.stack);
206
- }
207
- }
208
- }
209
- // eslint-disable-next-line no-undef, lwr/no-unguarded-apis
210
- startWatchdogTimer() {
211
- // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
212
- return setTimeout(() => {
213
- this.enterErrorState(new Error('Failed to load required modules - timed out'));
214
- }, REQUIRED_MODULES_TIMEOUT);
215
- }
22
+ // Attach a custom dispatcher
23
+ let customDispatcher;
24
+ function attachDispatcher(dispatcher) {
25
+ customDispatcher = dispatcher;
26
+ } // Check if the Performance API is available
27
+ // e.g. JSDom (used in Jest) doesn't implement these
28
+
29
+ const perf = globalThis.performance;
30
+ const isPerfSupported = typeof perf !== 'undefined' && typeof perf.mark === 'function' && typeof perf.clearMarks === 'function' && typeof perf.measure === 'function' && typeof perf.clearMeasures === 'function'; // For marking request metrics
31
+ // Fallback to the Performance API if there is no custom dispatcher
32
+
33
+ function logOperationStart({
34
+ id,
35
+ specifier
36
+ }) {
37
+ if (customDispatcher) {
38
+ customDispatcher({
39
+ id,
40
+ phase: Phase.Start,
41
+ specifier
42
+ });
43
+ } else if (isPerfSupported) {
44
+ perf.mark(id + (specifier ? `.${specifier}` : ''));
216
45
  }
46
+ } // For measuring duration metrics
47
+ // Fallback to the Performance API if there is no custom dispatcher
48
+
49
+ /* istanbul ignore next */
217
50
 
218
- // The loader module is ALWAYS required
219
- const GLOBAL = globalThis;
220
- GLOBAL.LWR.requiredModules = GLOBAL.LWR.requiredModules || [];
221
- if (GLOBAL.LWR.requiredModules.indexOf('lwr/loader/v/0_6_0-alpha_1') < 0) {
222
- GLOBAL.LWR.requiredModules.push('lwr/loader/v/0_6_0-alpha_1');
51
+ function logOperationEnd({
52
+ id,
53
+ specifier
54
+ }) {
55
+ if (customDispatcher) {
56
+ customDispatcher({
57
+ id,
58
+ phase: Phase.End,
59
+ specifier
60
+ });
61
+ } else if (isPerfSupported) {
62
+ const suffix = specifier ? `.${specifier}` : '';
63
+ const markName = id + suffix;
64
+ const measureName = `${id}.duration${suffix}`;
65
+ perf.measure(measureName, markName); // Clear the created mark and measure to avoid filling the performance entry buffer
66
+ // Even if they get deleted, existing PerformanceObservers preserve copies of the entries
67
+
68
+ perf.clearMarks(markName);
69
+ perf.clearMeasures(measureName);
223
70
  }
224
- new LoaderShim(GLOBAL);
71
+ }
72
+
73
+ function createLoader(name, definition, config, externalModules) {
74
+ if (!definition || typeof definition[2] !== 'function') {
75
+ throw new Error(`Expected loader with specifier "${name}" to be a module`);
76
+ }
77
+ // Create a Loader instance
78
+ const exports = {};
79
+ definition[2].call(null, exports);
80
+ const { Loader } = exports;
81
+ const loader = new Loader(config);
82
+ if (externalModules && externalModules.length) {
83
+ loader.registerExternalModules(externalModules);
84
+ }
85
+ // Define the loader module with public API: { define, load, services }
86
+ const exporter = (exports) => {
87
+ Object.assign(exports, {
88
+ define: loader.define.bind(loader),
89
+ load: loader.load.bind(loader),
90
+ services: loader.services,
91
+ });
92
+ };
93
+ loader.define(name, ['exports'], exporter);
94
+ return loader;
95
+ }
96
+
97
+ const REQUIRED_MODULES_TIMEOUT = 300 * 1000;
98
+
99
+ // Check for errors with autoBoot and customInit
100
+ function validatePreInit(autoBoot, customInit) {
101
+ // If autoBoot === false, there must be a customInit hook
102
+ if (!autoBoot && !customInit) {
103
+ throw new Error('The customInit hook is required when autoBoot is false');
104
+ }
105
+ // If autoBoot === true, there must NOT be a customInit hook
106
+ if (autoBoot && customInit) {
107
+ throw new Error('The customInit hook must not be defined when autoBoot is true');
108
+ }
109
+ }
110
+ // Process the customInit hook
111
+ function customInit(config, initializeApp, define, onBootstrapError) {
112
+ // Validate config
113
+ const { autoBoot, customInit } = config;
114
+ validatePreInit(autoBoot, customInit);
115
+ // Set up arguments and call the customInit hook, if available
116
+ if (customInit) {
117
+ const lwr = {
118
+ initializeApp,
119
+ define,
120
+ onBootstrapError,
121
+ attachDispatcher,
122
+ };
123
+ customInit(lwr, config);
124
+ }
125
+ }
126
+
127
+ /* global document */
128
+ /* eslint-disable lwr/no-unguarded-apis */
129
+ const hasSetTimeout = typeof setTimeout === 'function';
130
+ const hasConsole = typeof console !== 'undefined';
131
+ /* eslint-enable lwr/no-unguarded-apis */
132
+ class LoaderShim {
133
+ constructor(global) {
134
+ this.defineCache = {};
135
+ this.orderedDefs = [];
136
+ // Parse configuration
137
+ this.global = global;
138
+ this.config = global.LWR;
139
+ this.loaderSpecifier = 'lwr/loader/v/0_6_0-alpha_13';
140
+ // Set up the temporary LWR.define function and customInit hook
141
+ const tempDefine = this.tempDefine.bind(this);
142
+ global.LWR.define = tempDefine;
143
+ this.bootReady = this.config.autoBoot;
144
+ // Start watchdog timer
145
+ if (hasSetTimeout) {
146
+ this.watchdogTimerId = this.startWatchdogTimer();
147
+ }
148
+ try {
149
+ customInit(Object.freeze(this.config), this.postCustomInit.bind(this), tempDefine, (e) => {
150
+ this.errorHandler = e;
151
+ });
152
+ }
153
+ catch (e) {
154
+ this.enterErrorState(e);
155
+ }
156
+ }
157
+ // Return true if the app can be initialized
158
+ canInit() {
159
+ // Initialize the app if:
160
+ // - bootReady: autoBoot is on OR customInit has finished
161
+ // - all required modules are defined
162
+ const allDefined = this.config.requiredModules.every((m) => this.orderedDefs.includes(m));
163
+ return this.bootReady && allDefined;
164
+ }
165
+ /**
166
+ * Create a temporary LWR.define() function which captures all
167
+ * calls that occur BEFORE the full loader module is available
168
+ *
169
+ * Each call to LWR.define() is stored in 2 ways:
170
+ * - in a map as [moduleName, arguments] pairs
171
+ * - each moduleName is pushed onto an array, to preserve
172
+ * the order in which the modules were defined
173
+ */
174
+ tempDefine(...args) {
175
+ // Cache the incoming module
176
+ const moduleName = args[0];
177
+ this.defineCache[moduleName] = args;
178
+ this.orderedDefs.push(moduleName);
179
+ if (this.canInit()) {
180
+ if (hasSetTimeout) {
181
+ // requiredModules are defined, clear watchdog timer
182
+ // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
183
+ clearTimeout(this.watchdogTimerId);
184
+ }
185
+ this.initApp();
186
+ }
187
+ }
188
+ // Called by the customInit hook via lwr.initializeApp()
189
+ postCustomInit() {
190
+ this.bootReady = true;
191
+ if (this.canInit()) {
192
+ this.initApp();
193
+ }
194
+ }
195
+ // Create the loader and initialize the application
196
+ initApp() {
197
+ try {
198
+ const loaderConfig = {
199
+ endpoints: this.config.endpoints,
200
+ baseUrl: this.config.baseUrl,
201
+ profiler: { logOperationStart, logOperationEnd },
202
+ // TODO: can be removed following https://github.com/salesforce/lwr/issues/1087
203
+ appMetadata: {
204
+ bootstrapModule: this.config.bootstrapModule,
205
+ rootComponent: this.config.rootComponent,
206
+ rootComponents: this.config.rootComponents,
207
+ },
208
+ };
209
+ const loader = createLoader(this.loaderSpecifier, this.defineCache[this.loaderSpecifier], loaderConfig, this.config.preloadModules);
210
+ this.createProfilerModule(loader);
211
+ this.mountApp(loader);
212
+ }
213
+ catch (e) {
214
+ this.enterErrorState(e);
215
+ }
216
+ }
217
+ waitForDOMContentLoaded() {
218
+ // eslint-disable-next-line lwr/no-unguarded-apis
219
+ if (typeof document === undefined) {
220
+ return Promise.resolve();
221
+ }
222
+ // Resolve if document is already "ready" https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
223
+ // eslint-disable-next-line lwr/no-unguarded-apis
224
+ if (document.readyState === 'interactive' || document.readyState === 'complete') {
225
+ return Promise.resolve();
226
+ }
227
+ return new Promise((resolve) => {
228
+ // eslint-disable-next-line lwr/no-unguarded-apis
229
+ document.addEventListener('DOMContentLoaded', () => {
230
+ resolve();
231
+ });
232
+ });
233
+ }
234
+ // Create a module out of the profiler
235
+ // Note: The profiler is also available as a module through lwc module resolution (see package.json)
236
+ createProfilerModule(loader) {
237
+ const exporter = (exports) => {
238
+ Object.assign(exports, { logOperationStart, logOperationEnd });
239
+ };
240
+ loader.define('lwr/profiler/v/0_6_0-alpha_13', ['exports'], exporter);
241
+ }
242
+ // Set up the application globals, import map, root custom element...
243
+ mountApp(loader) {
244
+ const { bootstrapModule, rootComponent, rootComponents, endpoints, imports, index } = this.config;
245
+ // Set global LWR.define to loader.define
246
+ this.global.LWR = Object.freeze({
247
+ define: loader.define.bind(loader),
248
+ rootComponent,
249
+ rootComponents,
250
+ endpoints,
251
+ imports: imports || {},
252
+ index: index || {},
253
+ });
254
+ // Redefine all modules in the temporary cache
255
+ this.orderedDefs.forEach((specifier) => {
256
+ if (specifier !== this.loaderSpecifier) {
257
+ loader.define(...this.defineCache[specifier]);
258
+ }
259
+ });
260
+ // by default, app initialization is gated on waiting for document to be parsed (via DOMContentLoaded)
261
+ const { disableInitDefer } = this.config;
262
+ // Load the import mappings and application bootstrap module
263
+ loader
264
+ .registerImportMappings({ imports, index }, [bootstrapModule, rootComponent])
265
+ .then(() => {
266
+ if (!disableInitDefer) {
267
+ return this.waitForDOMContentLoaded();
268
+ }
269
+ })
270
+ .then(() => loader.load(bootstrapModule))
271
+ .catch((reason) => {
272
+ this.enterErrorState(new Error(`Application ${rootComponent} could not be loaded: ${reason}`));
273
+ });
274
+ }
275
+ // Trigger bootstrap error state, and call error handler if registered
276
+ enterErrorState(error) {
277
+ logOperationStart({ id: BOOTSTRAP_ERROR });
278
+ if (this.errorHandler) {
279
+ this.errorHandler(error);
280
+ }
281
+ else {
282
+ if (hasConsole) {
283
+ // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
284
+ console.error(`An error occurred during LWR bootstrap. ${error.message}`, error.stack);
285
+ }
286
+ }
287
+ }
288
+ // eslint-disable-next-line no-undef, lwr/no-unguarded-apis
289
+ startWatchdogTimer() {
290
+ // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
291
+ return setTimeout(() => {
292
+ this.enterErrorState(new Error('Failed to load required modules - timed out'));
293
+ }, REQUIRED_MODULES_TIMEOUT);
294
+ }
295
+ }
296
+
297
+ // The loader module is ALWAYS required
298
+ const GLOBAL = globalThis;
299
+ GLOBAL.LWR.requiredModules = GLOBAL.LWR.requiredModules || [];
300
+ if (GLOBAL.LWR.requiredModules.indexOf('lwr/loader/v/0_6_0-alpha_13') < 0) {
301
+ GLOBAL.LWR.requiredModules.push('lwr/loader/v/0_6_0-alpha_13');
302
+ }
303
+ new LoaderShim(GLOBAL);
225
304
 
226
305
  }());
227
306
 
228
- LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use strict';
307
+ LWR.define('lwr/loader/v/0_6_0-alpha_13', ['exports'], function (exports) { 'use strict';
229
308
 
230
309
  const templateRegex = /\{([0-9]+)\}/g;
231
310
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -522,6 +601,16 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
522
601
  });
523
602
  }
524
603
 
604
+ // Bootstrap / shim
605
+
606
+ const LOADER_PREFIX = 'lwr.loader.';
607
+ const MODULE_DEFINE = `${LOADER_PREFIX}module.define`;
608
+ const MODULE_FETCH = `${LOADER_PREFIX}module.fetch`;
609
+ const MODULE_ERROR = `${LOADER_PREFIX}module.error`; // Loader: mappings
610
+
611
+ const MAPPINGS_FETCH = `${LOADER_PREFIX}mappings.fetch`;
612
+ const MAPPINGS_ERROR = `${LOADER_PREFIX}mappings.error`;
613
+
525
614
  /* spec based import map resolver */
526
615
  class ImportMetadataResolver {
527
616
  constructor(config, invalidationCallback) {
@@ -637,6 +726,7 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
637
726
  if (pending) {
638
727
  return pending;
639
728
  }
729
+ this.config.profiler.logOperationStart({ id: MAPPINGS_FETCH, specifier });
640
730
  const fetchMappingService = this.hasMappingHooks()
641
731
  ? this.evaluateMappingHooks
642
732
  : this.fetchNewMappings;
@@ -651,6 +741,7 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
651
741
  if (!uri) {
652
742
  throw new LoaderError(UNRESOLVED, [specifier]);
653
743
  }
744
+ this.config.profiler.logOperationEnd({ id: MAPPINGS_FETCH, specifier });
654
745
  return uri;
655
746
  })
656
747
  .finally(() => {
@@ -695,6 +786,7 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
695
786
  const uri = resolveUrl(this.buildMappingUrl(specifier), this.getBaseUrl());
696
787
  return globalThis.fetch(uri).then((res) => {
697
788
  if (!res.ok) {
789
+ this.config.profiler.logOperationStart({ id: MAPPINGS_ERROR, specifier });
698
790
  throw new LoaderError(UNRESOLVED, [specifier]);
699
791
  }
700
792
  return res
@@ -842,6 +934,7 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
842
934
  this.namedDefineRegistry = new Map();
843
935
  // The evaluted module registry where the module identifier (name or URL?) is the key
844
936
  this.moduleRegistry = new Map();
937
+ this.profiler = config.profiler;
845
938
  this.resolver = new ImportMetadataResolver(config, this.importMetadataInvalidationCallback.bind(this));
846
939
  }
847
940
  async load(id, importer) {
@@ -908,10 +1001,11 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
908
1001
  }
909
1002
  const resolvedUrl = this.resolver.resolveLocal(resolvedOrPlain);
910
1003
  if (resolvedUrl) {
911
- // return the plain id IFF its already defined && the resolvedUrl is NOT already in the module registry
912
- if (this.namedDefineRegistry.has(resolvedOrPlain)) {
1004
+ // return the plain id if it is already defined && the resolvedUrl is NOT already in the module registry
1005
+ if (this.namedDefineRegistry.has(resolvedOrPlain) &&
1006
+ this.namedDefineRegistry.get(resolvedOrPlain).defined) {
913
1007
  const record = this.moduleRegistry.get(resolvedUrl);
914
- if (!record || record.originalId !== resolvedOrPlain) {
1008
+ if (!record || !record.aliases.has(resolvedOrPlain)) {
915
1009
  return resolvedOrPlain;
916
1010
  }
917
1011
  }
@@ -962,6 +1056,7 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
962
1056
  // if module is "external", resolve the external promise to notify any dependees
963
1057
  mod.external.resolveExternal(moduleDef);
964
1058
  }
1059
+ this.profiler.logOperationStart({ id: MODULE_DEFINE, specifier: name });
965
1060
  this.namedDefineRegistry.set(name, moduleDef);
966
1061
  this.lastDefine = moduleDef;
967
1062
  }
@@ -1012,6 +1107,10 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
1012
1107
  getModuleRecord(resolvedId, id) {
1013
1108
  let moduleRecord = this.moduleRegistry.get(resolvedId);
1014
1109
  if (moduleRecord) {
1110
+ // Make sure the original id is in the alias set
1111
+ if (!moduleRecord.aliases.has(id)) {
1112
+ moduleRecord.aliases.add(id);
1113
+ }
1015
1114
  return moduleRecord;
1016
1115
  }
1017
1116
  const instantiation = this.getModuleDef(resolvedId, id);
@@ -1031,7 +1130,7 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
1031
1130
  });
1032
1131
  moduleRecord = {
1033
1132
  id: resolvedId,
1034
- originalId: id,
1133
+ aliases: new Set([id]),
1035
1134
  module: Object.create(null),
1036
1135
  dependencyRecords,
1037
1136
  instantiation,
@@ -1140,6 +1239,9 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
1140
1239
  if (moduleExports.__defaultInterop) {
1141
1240
  Object.defineProperty(moduleRecord.module, '__defaultInterop', { value: true });
1142
1241
  }
1242
+ if (moduleExports.__esModule) {
1243
+ Object.defineProperty(moduleRecord.module, '__esModule', { value: true });
1244
+ }
1143
1245
  moduleRecord.evaluated = true;
1144
1246
  Object.freeze(moduleRecord.module);
1145
1247
  return moduleRecord.module;
@@ -1187,6 +1289,8 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
1187
1289
  return moduleDef;
1188
1290
  }
1189
1291
  const parentUrl = this.resolver.getBaseUrl(); // only support baseUrl for now
1292
+ const specifier = moduleName || originalId;
1293
+ this.profiler.logOperationStart({ id: MODULE_FETCH, specifier });
1190
1294
  return Promise.resolve()
1191
1295
  .then(async () => {
1192
1296
  const loadHooks = this.loadHook;
@@ -1224,9 +1328,11 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
1224
1328
  if (!moduleDef) {
1225
1329
  throw new LoaderError(FAIL_INSTANTIATE, [resolvedId]);
1226
1330
  }
1331
+ this.profiler.logOperationEnd({ id: MODULE_FETCH, specifier });
1227
1332
  return moduleDef;
1228
1333
  })
1229
1334
  .catch((e) => {
1335
+ this.profiler.logOperationStart({ id: MODULE_ERROR, specifier });
1230
1336
  throw e;
1231
1337
  });
1232
1338
  }
@@ -1288,6 +1394,7 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
1288
1394
  constructor(config) {
1289
1395
  let baseUrl = config.baseUrl;
1290
1396
  const mappingEndpoint = config.endpoints ? config.endpoints.uris.mapping : undefined;
1397
+ let profiler = config.profiler;
1291
1398
  if (!mappingEndpoint) {
1292
1399
  throw new LoaderError(NO_MAPPING_URL);
1293
1400
  }
@@ -1303,10 +1410,23 @@ LWR.define('lwr/loader/v/0_6_0-alpha_1', ['exports'], function (exports) { 'use
1303
1410
  if (!baseUrl) {
1304
1411
  throw new LoaderError(NO_BASE_URL);
1305
1412
  }
1306
- this.registry = new ModuleRegistry(Object.freeze({ endpoints: config.endpoints, baseUrl }));
1413
+ if (!profiler) {
1414
+ // default noop profiler
1415
+ profiler = {
1416
+ logOperationStart: () => {
1417
+ /* noop */
1418
+ },
1419
+ logOperationEnd: () => {
1420
+ /* noop */
1421
+ },
1422
+ };
1423
+ }
1424
+ this.registry = new ModuleRegistry(Object.freeze({ endpoints: config.endpoints, baseUrl, profiler }));
1425
+ // TODO: https://github.com/salesforce/lwr/issues/1087
1307
1426
  this.services = Object.freeze({
1308
1427
  addLoaderPlugin: this.registry.addLoaderPlugin.bind(this.registry),
1309
1428
  handleStaleModule: this.registry.registerHandleStaleModuleHook.bind(this.registry),
1429
+ appMetadata: config.appMetadata,
1310
1430
  });
1311
1431
  }
1312
1432
  /**