@lwrjs/loader 0.6.0-alpha.10 → 0.6.0-alpha.14

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,232 +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 Legacy Module Loader Shim v0.6.0-alpha.10 */
7
+ /* LWR Legacy Module Loader Shim v0.6.0-alpha.14 */
8
8
  (function () {
9
- 'use strict';
9
+ 'use strict';
10
10
 
11
- function createLoader(name, definition, baseUrl, 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(baseUrl);
20
- // register externally loaded modules
21
- if (externalModules && externalModules.length) {
22
- loader.registerExternalModules(externalModules);
23
- }
24
- // Define the loader module with public API: { define, load, services }
25
- const exporter = (exports) => {
26
- Object.assign(exports, {
27
- define: loader.define.bind(loader),
28
- load: loader.load.bind(loader),
29
- services: loader.services,
30
- });
31
- return;
32
- };
33
- loader.define(name, ['exports'], exporter, definition[3]);
34
- return loader;
35
- }
11
+ // Bootstrap / shim
12
+ const BOOTSTRAP_PREFIX = 'lwr.bootstrap.';
13
+ const BOOTSTRAP_ERROR = `${BOOTSTRAP_PREFIX}error`;
36
14
 
37
- const REQUIRED_MODULES_TIMEOUT = 300 * 1000;
15
+ var Phase;
38
16
 
39
- var Phase;
40
- (function (Phase) {
41
- Phase[Phase["Start"] = 0] = "Start";
42
- })(Phase || (Phase = {}));
43
- function attachDispatcher(dispatcher) {
44
- }
17
+ (function (Phase) {
18
+ Phase[Phase["Start"] = 0] = "Start";
19
+ Phase[Phase["End"] = 1] = "End";
20
+ })(Phase || (Phase = {}));
45
21
 
46
- // Check for errors with autoBoot and customInit
47
- function validatePreInit(autoBoot, customInit) {
48
- // If autoBoot === false, there must be a customInit hook
49
- if (!autoBoot && !customInit) {
50
- throw new Error('The customInit hook is required when autoBoot is false');
51
- }
52
- // If autoBoot === true, there must NOT be a customInit hook
53
- if (autoBoot && customInit) {
54
- throw new Error('The customInit hook must not be defined when autoBoot is true');
55
- }
56
- }
57
- // Process the customInit hook
58
- function customInit(config, initializeApp, define, onBootstrapError) {
59
- // Validate config
60
- const { autoBoot, customInit } = config;
61
- validatePreInit(autoBoot, customInit);
62
- // Set up arguments and call the customInit hook, if available
63
- if (customInit) {
64
- const lwr = {
65
- initializeApp,
66
- define,
67
- onBootstrapError,
68
- attachDispatcher,
69
- };
70
- customInit(lwr, config);
71
- }
72
- }
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
73
28
 
74
- /* global document */
75
- /* eslint-disable lwr/no-unguarded-apis */
76
- const hasSetTimeout = typeof setTimeout === 'function';
77
- const hasConsole = typeof console !== 'undefined';
78
- /* eslint-enable lwr/no-unguarded-apis */
79
- class LoaderShim {
80
- constructor(global) {
81
- this.defineCache = {};
82
- this.orderedDefs = [];
83
- // Parse configuration
84
- this.global = global;
85
- this.config = global.LWR;
86
- this.loaderModule = 'lwr/loaderLegacy/v/0_6_0-alpha_10';
87
- // Set up the temporary LWR.define function and customInit hook
88
- const tempDefine = this.tempDefine.bind(this);
89
- global.LWR.define = tempDefine;
90
- this.bootReady = this.config.autoBoot;
91
- // Start watchdog timer
92
- if (hasSetTimeout) {
93
- this.watchdogTimerId = this.startWatchdogTimer();
94
- }
95
- try {
96
- customInit(Object.freeze(this.config), this.postCustomInit.bind(this), tempDefine, (e) => {
97
- this.errorHandler = e;
98
- });
99
- }
100
- catch (e) {
101
- this.enterErrorState(e);
102
- }
103
- }
104
- // Return true if the app can be initialized
105
- canInit() {
106
- // Initialize the app if:
107
- // - bootReady: autoBoot is on OR customInit has finished
108
- // - all required modules are defined
109
- const allDefined = this.config.requiredModules.every((m) => this.orderedDefs.includes(m));
110
- return this.bootReady && allDefined;
111
- }
112
- /**
113
- * Create a temporary LWR.define() function which captures all
114
- * calls that occur BEFORE the full loader module is available
115
- *
116
- * Each call to LWR.define() is stored in 2 ways:
117
- * - in a map as [moduleName, arguments] pairs
118
- * - each moduleName is pushed onto an array, to preserve
119
- * the order in which the modules were defined
120
- */
121
- tempDefine(...args) {
122
- // Cache the incoming module
123
- const moduleName = args[0];
124
- this.defineCache[moduleName] = args;
125
- this.orderedDefs.push(moduleName);
126
- if (this.canInit()) {
127
- if (hasSetTimeout) {
128
- // requiredModules are defined, clear watchdog timer
129
- // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
130
- clearTimeout(this.watchdogTimerId);
131
- }
132
- this.initApp();
133
- }
134
- }
135
- // Called by the customInit hook via lwr.initializeApp()
136
- postCustomInit() {
137
- this.bootReady = true;
138
- if (this.canInit()) {
139
- this.initApp();
140
- }
141
- }
142
- // Create the loader and initialize the application
143
- initApp() {
144
- try {
145
- const loader = createLoader(this.loaderModule, this.defineCache[this.loaderModule], this.config.baseUrl, this.config.preloadModules);
146
- this.mountApp(loader);
147
- }
148
- catch (e) {
149
- this.enterErrorState(e);
150
- }
151
- }
152
- waitForDOMContentLoaded() {
153
- // eslint-disable-next-line lwr/no-unguarded-apis
154
- if (typeof document === undefined) {
155
- return Promise.resolve();
156
- }
157
- // Resolve if document is already "ready" https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
158
- // eslint-disable-next-line lwr/no-unguarded-apis
159
- if (document.readyState === 'interactive' || document.readyState === 'complete') {
160
- return Promise.resolve();
161
- }
162
- return new Promise((resolve) => {
163
- // eslint-disable-next-line lwr/no-unguarded-apis
164
- document.addEventListener('DOMContentLoaded', () => {
165
- resolve();
166
- });
167
- });
168
- }
169
- // Set up the application globals, import map, root custom element...
170
- mountApp(loader) {
171
- const { bootstrapModule, rootComponent, importMappings, rootComponents, endpoints } = this.config;
172
- // Set global LWR.define to loader.define
173
- this.global.LWR = Object.freeze({
174
- define: loader.define.bind(loader),
175
- rootComponent,
176
- rootComponents,
177
- importMappings,
178
- endpoints,
179
- });
180
- // Redefine all modules in the temporary cache
181
- this.orderedDefs.forEach((specifier) => {
182
- if (specifier !== this.loaderModule) {
183
- loader.define(...this.defineCache[specifier]);
184
- }
185
- });
186
- // by default, app initialization is gated on waiting for document to be parsed (via DOMContentLoaded)
187
- const { disableInitDefer } = this.config;
188
- // Load the import mappings and application bootstrap module
189
- loader
190
- .registerImportMappings(importMappings)
191
- .then(() => {
192
- if (!disableInitDefer) {
193
- return this.waitForDOMContentLoaded();
194
- }
195
- })
196
- .then(() => loader.load(bootstrapModule))
197
- .catch((reason) => {
198
- this.enterErrorState(new Error(`Application ${rootComponent} could not be loaded: ${reason}`));
199
- });
200
- }
201
- // Trigger bootstrap error state, and call error handler if registered
202
- enterErrorState(error) {
203
- if (this.errorHandler) {
204
- this.errorHandler(error);
205
- }
206
- else {
207
- if (hasConsole) {
208
- // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
209
- console.error(`An error occurred during LWR bootstrap. ${error.message}`, error.stack);
210
- }
211
- }
212
- }
213
- // eslint-disable-next-line no-undef, lwr/no-unguarded-apis
214
- startWatchdogTimer() {
215
- // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
216
- return setTimeout(() => {
217
- this.enterErrorState(new Error('Failed to load required modules - timed out'));
218
- }, REQUIRED_MODULES_TIMEOUT);
219
- }
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}` : ''));
220
45
  }
46
+ } // For measuring duration metrics
47
+ // Fallback to the Performance API if there is no custom dispatcher
48
+
49
+ /* istanbul ignore next */
221
50
 
222
- // The loader module is ALWAYS required
223
- const GLOBAL = globalThis;
224
- GLOBAL.LWR.requiredModules = GLOBAL.LWR.requiredModules || [];
225
- if (GLOBAL.LWR.requiredModules.indexOf('lwr/loaderLegacy/v/0_6_0-alpha_10') < 0) {
226
- GLOBAL.LWR.requiredModules.push('lwr/loaderLegacy/v/0_6_0-alpha_10');
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);
227
70
  }
228
- 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
+ // register externally loaded modules
83
+ if (externalModules && externalModules.length) {
84
+ loader.registerExternalModules(externalModules);
85
+ }
86
+ // Define the loader module with public API: { define, load, services }
87
+ const exporter = (exports) => {
88
+ Object.assign(exports, {
89
+ define: loader.define.bind(loader),
90
+ load: loader.load.bind(loader),
91
+ services: loader.services,
92
+ });
93
+ return;
94
+ };
95
+ loader.define(name, ['exports'], exporter, definition[3]);
96
+ return loader;
97
+ }
98
+
99
+ const REQUIRED_MODULES_TIMEOUT = 300 * 1000;
100
+
101
+ // Check for errors with autoBoot and customInit
102
+ function validatePreInit(autoBoot, customInit) {
103
+ // If autoBoot === false, there must be a customInit hook
104
+ if (!autoBoot && !customInit) {
105
+ throw new Error('The customInit hook is required when autoBoot is false');
106
+ }
107
+ // If autoBoot === true, there must NOT be a customInit hook
108
+ if (autoBoot && customInit) {
109
+ throw new Error('The customInit hook must not be defined when autoBoot is true');
110
+ }
111
+ }
112
+ // Process the customInit hook
113
+ function customInit(config, initializeApp, define, onBootstrapError) {
114
+ // Validate config
115
+ const { autoBoot, customInit } = config;
116
+ validatePreInit(autoBoot, customInit);
117
+ // Set up arguments and call the customInit hook, if available
118
+ if (customInit) {
119
+ const lwr = {
120
+ initializeApp,
121
+ define,
122
+ onBootstrapError,
123
+ attachDispatcher,
124
+ };
125
+ customInit(lwr, config);
126
+ }
127
+ }
128
+
129
+ /* global document */
130
+ /* eslint-disable lwr/no-unguarded-apis */
131
+ const hasSetTimeout = typeof setTimeout === 'function';
132
+ const hasConsole = typeof console !== 'undefined';
133
+ /* eslint-enable lwr/no-unguarded-apis */
134
+ class LoaderShim {
135
+ constructor(global) {
136
+ this.defineCache = {};
137
+ this.orderedDefs = [];
138
+ // Parse configuration
139
+ this.global = global;
140
+ this.config = global.LWR;
141
+ this.loaderModule = 'lwr/loaderLegacy/v/0_6_0-alpha_14';
142
+ // Set up the temporary LWR.define function and customInit hook
143
+ const tempDefine = this.tempDefine.bind(this);
144
+ global.LWR.define = tempDefine;
145
+ this.bootReady = this.config.autoBoot;
146
+ // Start watchdog timer
147
+ if (hasSetTimeout) {
148
+ this.watchdogTimerId = this.startWatchdogTimer();
149
+ }
150
+ try {
151
+ customInit(Object.freeze(this.config), this.postCustomInit.bind(this), tempDefine, (e) => {
152
+ this.errorHandler = e;
153
+ });
154
+ }
155
+ catch (e) {
156
+ this.enterErrorState(e);
157
+ }
158
+ }
159
+ // Return true if the app can be initialized
160
+ canInit() {
161
+ // Initialize the app if:
162
+ // - bootReady: autoBoot is on OR customInit has finished
163
+ // - all required modules are defined
164
+ const allDefined = this.config.requiredModules.every((m) => this.orderedDefs.includes(m));
165
+ return this.bootReady && allDefined;
166
+ }
167
+ /**
168
+ * Create a temporary LWR.define() function which captures all
169
+ * calls that occur BEFORE the full loader module is available
170
+ *
171
+ * Each call to LWR.define() is stored in 2 ways:
172
+ * - in a map as [moduleName, arguments] pairs
173
+ * - each moduleName is pushed onto an array, to preserve
174
+ * the order in which the modules were defined
175
+ */
176
+ tempDefine(...args) {
177
+ // Cache the incoming module
178
+ const moduleName = args[0];
179
+ this.defineCache[moduleName] = args;
180
+ this.orderedDefs.push(moduleName);
181
+ if (this.canInit()) {
182
+ if (hasSetTimeout) {
183
+ // requiredModules are defined, clear watchdog timer
184
+ // eslint-disable-next-line lwr/no-unguarded-apis, no-undef
185
+ clearTimeout(this.watchdogTimerId);
186
+ }
187
+ this.initApp();
188
+ }
189
+ }
190
+ // Called by the customInit hook via lwr.initializeApp()
191
+ postCustomInit() {
192
+ this.bootReady = true;
193
+ if (this.canInit()) {
194
+ this.initApp();
195
+ }
196
+ }
197
+ // Create the loader and initialize the application
198
+ initApp() {
199
+ try {
200
+ const loaderConfig = {
201
+ baseUrl: this.config.baseUrl,
202
+ profiler: { logOperationStart, logOperationEnd },
203
+ // TODO: can be removed following https://github.com/salesforce/lwr/issues/1087
204
+ appMetadata: {
205
+ bootstrapModule: this.config.bootstrapModule,
206
+ rootComponent: this.config.rootComponent,
207
+ rootComponents: this.config.rootComponents,
208
+ },
209
+ };
210
+ const loader = createLoader(this.loaderModule, this.defineCache[this.loaderModule], loaderConfig, this.config.preloadModules);
211
+ this.createProfilerModule(loader);
212
+ this.mountApp(loader);
213
+ }
214
+ catch (e) {
215
+ this.enterErrorState(e);
216
+ }
217
+ }
218
+ waitForDOMContentLoaded() {
219
+ // eslint-disable-next-line lwr/no-unguarded-apis
220
+ if (typeof document === undefined) {
221
+ return Promise.resolve();
222
+ }
223
+ // Resolve if document is already "ready" https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
224
+ // eslint-disable-next-line lwr/no-unguarded-apis
225
+ if (document.readyState === 'interactive' || document.readyState === 'complete') {
226
+ return Promise.resolve();
227
+ }
228
+ return new Promise((resolve) => {
229
+ // eslint-disable-next-line lwr/no-unguarded-apis
230
+ document.addEventListener('DOMContentLoaded', () => {
231
+ resolve();
232
+ });
233
+ });
234
+ }
235
+ // Create a module out of the profiler
236
+ // Note: The profiler is also available as a module through lwc module resolution (see package.json)
237
+ createProfilerModule(loader) {
238
+ const exporter = (exports) => {
239
+ Object.assign(exports, { logOperationStart, logOperationEnd });
240
+ };
241
+ loader.define('lwr/profiler/v/0_6_0-alpha_14', ['exports'], exporter, {});
242
+ }
243
+ // Set up the application globals, import map, root custom element...
244
+ mountApp(loader) {
245
+ const { bootstrapModule, rootComponent, importMappings, rootComponents, endpoints } = this.config;
246
+ // Set global LWR.define to loader.define
247
+ this.global.LWR = Object.freeze({
248
+ define: loader.define.bind(loader),
249
+ rootComponent,
250
+ rootComponents,
251
+ importMappings,
252
+ endpoints,
253
+ });
254
+ // Redefine all modules in the temporary cache
255
+ this.orderedDefs.forEach((specifier) => {
256
+ if (specifier !== this.loaderModule) {
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(importMappings)
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/loaderLegacy/v/0_6_0-alpha_14') < 0) {
301
+ GLOBAL.LWR.requiredModules.push('lwr/loaderLegacy/v/0_6_0-alpha_14');
302
+ }
303
+ new LoaderShim(GLOBAL);
229
304
 
230
305
  }());
231
306
 
232
- LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports) { 'use strict';
307
+ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_14', ['exports'], function (exports) { 'use strict';
233
308
 
234
309
  const templateRegex = /\{([0-9]+)\}/g;
235
310
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -627,14 +702,22 @@ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports)
627
702
  }
628
703
  }
629
704
 
705
+ // Bootstrap / shim
706
+
707
+ const LOADER_PREFIX = 'lwr.loader.';
708
+ const MODULE_DEFINE = `${LOADER_PREFIX}module.define`;
709
+ const MODULE_FETCH = `${LOADER_PREFIX}module.fetch`;
710
+ const MODULE_ERROR = `${LOADER_PREFIX}module.error`; // Loader: mappings
711
+
630
712
  /* global console,process */
631
713
  class ModuleRegistry {
632
- constructor(baseUrl) {
714
+ constructor(config) {
633
715
  // A registry for named AMD defines containing the *metadata* of AMD module
634
716
  this.namedDefineRegistry = new Map();
635
717
  // The evaluted module registry where the module identifier (name or URL?) is the key
636
718
  this.moduleRegistry = new Map();
637
- this.baseUrl = baseUrl;
719
+ this.baseUrl = config.baseUrl;
720
+ this.profiler = config.profiler;
638
721
  }
639
722
  async load(id, importer) {
640
723
  const resolvedId = await this.resolve(id, importer);
@@ -749,6 +832,7 @@ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports)
749
832
  // if module is "external", resolve the external promise to notify any dependees
750
833
  mod.external.resolveExternal(moduleDef);
751
834
  }
835
+ this.profiler.logOperationStart({ id: MODULE_DEFINE, specifier: name });
752
836
  this.namedDefineRegistry.set(name, moduleDef);
753
837
  this.lastDefine = moduleDef;
754
838
  // Check signatures of dependencies against those in the namedDefineRegistry
@@ -969,6 +1053,9 @@ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports)
969
1053
  if (moduleExports.__defaultInterop) {
970
1054
  Object.defineProperty(moduleRecord.module, '__defaultInterop', { value: true });
971
1055
  }
1056
+ if (moduleExports.__esModule) {
1057
+ Object.defineProperty(moduleRecord.module, '__esModule', { value: true });
1058
+ }
972
1059
  moduleRecord.evaluated = true;
973
1060
  Object.freeze(moduleRecord.module);
974
1061
  return moduleRecord.module;
@@ -1016,6 +1103,8 @@ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports)
1016
1103
  return moduleDef;
1017
1104
  }
1018
1105
  const parentUrl = this.baseUrl; // only support baseUrl for now
1106
+ const specifier = moduleName || originalId;
1107
+ this.profiler.logOperationStart({ id: MODULE_FETCH, specifier });
1019
1108
  return Promise.resolve()
1020
1109
  .then(async () => {
1021
1110
  const loadHooks = this.loadHook;
@@ -1053,9 +1142,11 @@ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports)
1053
1142
  if (!moduleDef) {
1054
1143
  throw new LoaderError(FAIL_INSTANTIATE, [resolvedId]);
1055
1144
  }
1145
+ this.profiler.logOperationEnd({ id: MODULE_FETCH, specifier });
1056
1146
  return moduleDef;
1057
1147
  })
1058
1148
  .catch((e) => {
1149
+ this.profiler.logOperationStart({ id: MODULE_ERROR, specifier });
1059
1150
  throw e;
1060
1151
  });
1061
1152
  }
@@ -1283,7 +1374,10 @@ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports)
1283
1374
  * The LWR loader is inspired and borrows from the algorithms and native browser principles of https://github.com/systemjs/systemjs
1284
1375
  */
1285
1376
  class Loader {
1286
- constructor(baseUrl) {
1377
+ constructor(config) {
1378
+ config = config || {};
1379
+ let baseUrl = config.baseUrl;
1380
+ let profiler = config.profiler;
1287
1381
  if (baseUrl) {
1288
1382
  // add a trailing slash, if it does not exist
1289
1383
  baseUrl = baseUrl.replace(/\/?$/, '/');
@@ -1295,10 +1389,23 @@ LWR.define('lwr/loaderLegacy/v/0_6_0-alpha_10', ['exports'], function (exports)
1295
1389
  throw new LoaderError(NO_BASE_URL);
1296
1390
  }
1297
1391
  this.baseUrl = baseUrl;
1298
- this.registry = new ModuleRegistry(baseUrl);
1392
+ if (!profiler) {
1393
+ // default noop profiler
1394
+ profiler = {
1395
+ logOperationStart: () => {
1396
+ /* noop */
1397
+ },
1398
+ logOperationEnd: () => {
1399
+ /* noop */
1400
+ },
1401
+ };
1402
+ }
1403
+ this.registry = new ModuleRegistry({ baseUrl, profiler });
1404
+ // TODO: https://github.com/salesforce/lwr/issues/1087
1299
1405
  this.services = Object.freeze({
1300
1406
  addLoaderPlugin: this.registry.addLoaderPlugin.bind(this.registry),
1301
1407
  handleStaleModule: this.registry.registerHandleStaleModuleHook.bind(this.registry),
1408
+ appMetadata: config.appMetadata,
1302
1409
  });
1303
1410
  }
1304
1411
  /**