UNPKG

49.4 kBJavaScriptView Raw
1/* single-spa@5.3.0 - ESM - dev */
2var singleSpa = /*#__PURE__*/Object.freeze({
3 __proto__: null,
4 get start () { return start; },
5 get ensureJQuerySupport () { return ensureJQuerySupport; },
6 get setBootstrapMaxTime () { return setBootstrapMaxTime; },
7 get setMountMaxTime () { return setMountMaxTime; },
8 get setUnmountMaxTime () { return setUnmountMaxTime; },
9 get setUnloadMaxTime () { return setUnloadMaxTime; },
10 get registerApplication () { return registerApplication; },
11 get getMountedApps () { return getMountedApps; },
12 get getAppStatus () { return getAppStatus; },
13 get unloadApplication () { return unloadApplication; },
14 get checkActivityFunctions () { return checkActivityFunctions; },
15 get getAppNames () { return getAppNames; },
16 get navigateToUrl () { return navigateToUrl; },
17 get triggerAppChange () { return triggerAppChange; },
18 get addErrorHandler () { return addErrorHandler; },
19 get removeErrorHandler () { return removeErrorHandler; },
20 get mountRootParcel () { return mountRootParcel; },
21 get NOT_LOADED () { return NOT_LOADED; },
22 get LOADING_SOURCE_CODE () { return LOADING_SOURCE_CODE; },
23 get NOT_BOOTSTRAPPED () { return NOT_BOOTSTRAPPED; },
24 get BOOTSTRAPPING () { return BOOTSTRAPPING; },
25 get NOT_MOUNTED () { return NOT_MOUNTED; },
26 get MOUNTING () { return MOUNTING; },
27 get UPDATING () { return UPDATING; },
28 get LOAD_ERROR () { return LOAD_ERROR; },
29 get MOUNTED () { return MOUNTED; },
30 get UNMOUNTING () { return UNMOUNTING; },
31 get SKIP_BECAUSE_BROKEN () { return SKIP_BECAUSE_BROKEN; }
32});
33
34var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
35
36var NativeCustomEvent = commonjsGlobal.CustomEvent;
37
38function useNative () {
39 try {
40 var p = new NativeCustomEvent('cat', { detail: { foo: 'bar' } });
41 return 'cat' === p.type && 'bar' === p.detail.foo;
42 } catch (e) {
43 }
44 return false;
45}
46
47/**
48 * Cross-browser `CustomEvent` constructor.
49 *
50 * https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent.CustomEvent
51 *
52 * @public
53 */
54
55var customEvent = useNative() ? NativeCustomEvent :
56
57// IE >= 9
58'undefined' !== typeof document && 'function' === typeof document.createEvent ? function CustomEvent (type, params) {
59 var e = document.createEvent('CustomEvent');
60 if (params) {
61 e.initCustomEvent(type, params.bubbles, params.cancelable, params.detail);
62 } else {
63 e.initCustomEvent(type, false, false, void 0);
64 }
65 return e;
66} :
67
68// IE <= 8
69function CustomEvent (type, params) {
70 var e = document.createEventObject();
71 e.type = type;
72 if (params) {
73 e.bubbles = Boolean(params.bubbles);
74 e.cancelable = Boolean(params.cancelable);
75 e.detail = params.detail;
76 } else {
77 e.bubbles = false;
78 e.cancelable = false;
79 e.detail = void 0;
80 }
81 return e;
82};
83
84let errorHandlers = [];
85function handleAppError(err, app, newStatus) {
86 const transformedErr = transformErr(err, app, newStatus);
87
88 if (errorHandlers.length) {
89 errorHandlers.forEach(handler => handler(transformedErr));
90 } else {
91 setTimeout(() => {
92 throw transformedErr;
93 });
94 }
95}
96function addErrorHandler(handler) {
97 if (typeof handler !== "function") {
98 throw Error(formatErrorMessage(28, "a single-spa error handler must be a function"));
99 }
100
101 errorHandlers.push(handler);
102}
103function removeErrorHandler(handler) {
104 if (typeof handler !== "function") {
105 throw Error(formatErrorMessage(29, "a single-spa error handler must be a function"));
106 }
107
108 let removedSomething = false;
109 errorHandlers = errorHandlers.filter(h => {
110 const isHandler = h === handler;
111 removedSomething = removedSomething || isHandler;
112 return !isHandler;
113 });
114 return removedSomething;
115}
116function formatErrorMessage(code, msg, ...args) {
117 return `single-spa minified message #${code}: ${msg ? msg + " " : ""}See https://single-spa.js.org/error/?code=${code}${args.length ? `&arg=${args.join("&arg=")}` : ""}`;
118}
119function transformErr(ogErr, appOrParcel, newStatus) {
120 const errPrefix = `${objectType(appOrParcel)} '${toName(appOrParcel)}' died in status ${appOrParcel.status}: `;
121 let result;
122
123 if (ogErr instanceof Error) {
124 try {
125 ogErr.message = errPrefix + ogErr.message;
126 } catch (err) {
127 /* Some errors have read-only message properties, in which case there is nothing
128 * that we can do.
129 */
130 }
131
132 result = ogErr;
133 } else {
134 console.warn(formatErrorMessage(30, `While ${appOrParcel.status}, '${toName(appOrParcel)}' rejected its lifecycle function promise with a non-Error. This will cause stack traces to not be accurate.`, appOrParcel.status, toName(appOrParcel)));
135
136 try {
137 result = Error(errPrefix + JSON.stringify(ogErr));
138 } catch (err) {
139 // If it's not an Error and you can't stringify it, then what else can you even do to it?
140 result = ogErr;
141 }
142 }
143
144 result.appOrParcelName = toName(appOrParcel); // We set the status after transforming the error so that the error message
145 // references the state the application was in before the status change.
146
147 appOrParcel.status = newStatus;
148 return result;
149}
150
151const NOT_LOADED = "NOT_LOADED";
152const LOADING_SOURCE_CODE = "LOADING_SOURCE_CODE";
153const NOT_BOOTSTRAPPED = "NOT_BOOTSTRAPPED";
154const BOOTSTRAPPING = "BOOTSTRAPPING";
155const NOT_MOUNTED = "NOT_MOUNTED";
156const MOUNTING = "MOUNTING";
157const MOUNTED = "MOUNTED";
158const UPDATING = "UPDATING";
159const UNMOUNTING = "UNMOUNTING";
160const UNLOADING = "UNLOADING";
161const LOAD_ERROR = "LOAD_ERROR";
162const SKIP_BECAUSE_BROKEN = "SKIP_BECAUSE_BROKEN";
163function isActive(app) {
164 return app.status === MOUNTED;
165}
166function isntActive(app) {
167 return !isActive(app);
168}
169function isLoaded(app) {
170 return app.status !== NOT_LOADED && app.status !== LOADING_SOURCE_CODE && app.status !== LOAD_ERROR;
171}
172function isntLoaded(app) {
173 return !isLoaded(app);
174}
175function shouldBeActive(app) {
176 try {
177 return app.activeWhen(window.location);
178 } catch (err) {
179 handleAppError(err, app, SKIP_BECAUSE_BROKEN);
180 }
181}
182function shouldntBeActive(app) {
183 try {
184 return !shouldBeActive(app);
185 } catch (err) {
186 handleAppError(err, app, SKIP_BECAUSE_BROKEN);
187 }
188}
189function notSkipped(item) {
190 return item !== SKIP_BECAUSE_BROKEN && (!item || item.status !== SKIP_BECAUSE_BROKEN);
191}
192function withoutLoadErrors(app) {
193 return app.status === LOAD_ERROR ? new Date().getTime() - app.loadErrorTime >= 200 : true;
194}
195function toName(app) {
196 return app.name;
197}
198function isParcel(appOrParcel) {
199 return Boolean(appOrParcel.unmountThisParcel);
200}
201function objectType(appOrParcel) {
202 return isParcel(appOrParcel) ? "parcel" : "application";
203}
204
205// Object.assign() is not available in IE11. And the babel compiled output for object spread
206// syntax checks a bunch of Symbol stuff and is almost a kb. So this function is the smaller replacement.
207function assign() {
208 for (let i = arguments.length - 1; i > 0; i--) {
209 for (let key in arguments[i]) {
210 if (key === "__proto__") {
211 continue;
212 }
213
214 arguments[i - 1][key] = arguments[i][key];
215 }
216 }
217
218 return arguments[0];
219}
220
221/* the array.prototype.find polyfill on npmjs.com is ~20kb (not worth it)
222 * and lodash is ~200kb (not worth it)
223 */
224function find(arr, func) {
225 for (let i = 0; i < arr.length; i++) {
226 if (func(arr[i])) {
227 return arr[i];
228 }
229 }
230
231 return null;
232}
233
234function validLifecycleFn(fn) {
235 return fn && (typeof fn === "function" || isArrayOfFns(fn));
236
237 function isArrayOfFns(arr) {
238 return Array.isArray(arr) && !find(arr, item => typeof item !== "function");
239 }
240}
241function flattenFnArray(appOrParcel, lifecycle) {
242 let fns = appOrParcel[lifecycle] || [];
243 fns = Array.isArray(fns) ? fns : [fns];
244
245 if (fns.length === 0) {
246 fns = [() => Promise.resolve()];
247 }
248
249 const type = objectType(appOrParcel);
250 const name = toName(appOrParcel);
251 return function (props) {
252 return fns.reduce((resultPromise, fn, index) => {
253 return resultPromise.then(() => {
254 const thisPromise = fn(props);
255 return smellsLikeAPromise(thisPromise) ? thisPromise : Promise.reject(formatErrorMessage(15, `Within ${type} ${name}, the lifecycle function ${lifecycle} at array index ${index} did not return a promise`, type, name, lifecycle, index));
256 });
257 }, Promise.resolve());
258 };
259}
260function smellsLikeAPromise(promise) {
261 return promise && typeof promise.then === "function" && typeof promise.catch === "function";
262}
263
264function toBootstrapPromise(appOrParcel, hardFail) {
265 return Promise.resolve().then(() => {
266 if (appOrParcel.status !== NOT_BOOTSTRAPPED) {
267 return appOrParcel;
268 }
269
270 appOrParcel.status = BOOTSTRAPPING;
271 return reasonableTime(appOrParcel, "bootstrap").then(() => {
272 appOrParcel.status = NOT_MOUNTED;
273 return appOrParcel;
274 }).catch(err => {
275 if (hardFail) {
276 throw transformErr(err, appOrParcel, SKIP_BECAUSE_BROKEN);
277 } else {
278 handleAppError(err, appOrParcel, SKIP_BECAUSE_BROKEN);
279 return appOrParcel;
280 }
281 });
282 });
283}
284
285function toUnmountPromise(appOrParcel, hardFail) {
286 return Promise.resolve().then(() => {
287 if (appOrParcel.status !== MOUNTED) {
288 return appOrParcel;
289 }
290
291 appOrParcel.status = UNMOUNTING;
292 const unmountChildrenParcels = Object.keys(appOrParcel.parcels).map(parcelId => appOrParcel.parcels[parcelId].unmountThisParcel());
293 return Promise.all(unmountChildrenParcels).then(unmountAppOrParcel, parcelError => {
294 // There is a parcel unmount error
295 return unmountAppOrParcel().then(() => {
296 // Unmounting the app/parcel succeeded, but unmounting its children parcels did not
297 const parentError = Error(parcelError.message);
298
299 if (hardFail) {
300 throw transformErr(parentError, appOrParcel, SKIP_BECAUSE_BROKEN);
301 } else {
302 handleAppError(parentError, appOrParcel, SKIP_BECAUSE_BROKEN);
303 }
304 });
305 }).then(() => appOrParcel);
306
307 function unmountAppOrParcel() {
308 // We always try to unmount the appOrParcel, even if the children parcels failed to unmount.
309 return reasonableTime(appOrParcel, "unmount").then(() => {
310 // The appOrParcel needs to stay in a broken status if its children parcels fail to unmount
311 {
312 appOrParcel.status = NOT_MOUNTED;
313 }
314 }).catch(err => {
315 if (hardFail) {
316 throw transformErr(err, appOrParcel, SKIP_BECAUSE_BROKEN);
317 } else {
318 handleAppError(err, appOrParcel, SKIP_BECAUSE_BROKEN);
319 }
320 });
321 }
322 });
323}
324
325let beforeFirstMountFired = false;
326let firstMountFired = false;
327function toMountPromise(appOrParcel, hardFail) {
328 return Promise.resolve().then(() => {
329 if (appOrParcel.status !== NOT_MOUNTED) {
330 return appOrParcel;
331 }
332
333 if (!beforeFirstMountFired) {
334 window.dispatchEvent(new customEvent("single-spa:before-first-mount"));
335 beforeFirstMountFired = true;
336 }
337
338 return reasonableTime(appOrParcel, "mount").then(() => {
339 appOrParcel.status = MOUNTED;
340
341 if (!firstMountFired) {
342 window.dispatchEvent(new customEvent("single-spa:first-mount"));
343 firstMountFired = true;
344 }
345
346 return appOrParcel;
347 }).catch(err => {
348 // If we fail to mount the appOrParcel, we should attempt to unmount it before putting in SKIP_BECAUSE_BROKEN
349 // We temporarily put the appOrParcel into MOUNTED status so that toUnmountPromise actually attempts to unmount it
350 // instead of just doing a no-op.
351 appOrParcel.status = MOUNTED;
352 return toUnmountPromise(appOrParcel, true).then(setSkipBecauseBroken, setSkipBecauseBroken);
353
354 function setSkipBecauseBroken() {
355 if (!hardFail) {
356 handleAppError(err, appOrParcel, SKIP_BECAUSE_BROKEN);
357 return appOrParcel;
358 } else {
359 throw transformErr(err, appOrParcel, SKIP_BECAUSE_BROKEN);
360 }
361 }
362 });
363 });
364}
365
366function toUpdatePromise(parcel) {
367 return Promise.resolve().then(() => {
368 if (parcel.status !== MOUNTED) {
369 throw Error(formatErrorMessage(32, `Cannot update parcel '${toName(parcel)}' because it is not mounted`, toName(parcel)));
370 }
371
372 parcel.status = UPDATING;
373 return reasonableTime(parcel, "update").then(() => {
374 parcel.status = MOUNTED;
375 return parcel;
376 }).catch(err => {
377 throw transformErr(err, parcel, SKIP_BECAUSE_BROKEN);
378 });
379 });
380}
381
382let parcelCount = 0;
383const rootParcels = {
384 parcels: {}
385}; // This is a public api, exported to users of single-spa
386
387function mountRootParcel() {
388 return mountParcel.apply(rootParcels, arguments);
389}
390function mountParcel(config, customProps) {
391 const owningAppOrParcel = this; // Validate inputs
392
393 if (!config || typeof config !== "object" && typeof config !== "function") {
394 throw Error(formatErrorMessage(2, "Cannot mount parcel without a config object or config loading function"));
395 }
396
397 if (config.name && typeof config.name !== "string") {
398 throw Error(formatErrorMessage(3, `Parcel name must be a string, if provided. Was given ${typeof config.name}`, typeof config.name));
399 }
400
401 if (typeof customProps !== "object") {
402 throw Error(formatErrorMessage(4, `Parcel ${name} has invalid customProps -- must be an object but was given ${typeof customProps}`, name, typeof customProps));
403 }
404
405 if (!customProps.domElement) {
406 throw Error(formatErrorMessage(5, `Parcel ${name} cannot be mounted without a domElement provided as a prop`, name));
407 }
408
409 const id = parcelCount++;
410 const passedConfigLoadingFunction = typeof config === "function";
411 const configLoadingFunction = passedConfigLoadingFunction ? config : () => Promise.resolve(config); // Internal representation
412
413 const parcel = {
414 id,
415 parcels: {},
416 status: passedConfigLoadingFunction ? LOADING_SOURCE_CODE : NOT_BOOTSTRAPPED,
417 customProps,
418 parentName: toName(owningAppOrParcel),
419
420 unmountThisParcel() {
421 if (parcel.status !== MOUNTED) {
422 throw Error(formatErrorMessage(6, `Cannot unmount parcel '${name}' -- it is in a ${parcel.status} status`, name, parcel.status));
423 }
424
425 return toUnmountPromise(parcel, true).then(value => {
426 if (parcel.parentName) {
427 delete owningAppOrParcel.parcels[parcel.id];
428 }
429
430 return value;
431 }).then(value => {
432 resolveUnmount(value);
433 return value;
434 }).catch(err => {
435 parcel.status = SKIP_BECAUSE_BROKEN;
436 rejectUnmount(err);
437 throw err;
438 });
439 }
440
441 }; // We return an external representation
442
443 let externalRepresentation; // Add to owning app or parcel
444
445 owningAppOrParcel.parcels[id] = parcel;
446 let loadPromise = configLoadingFunction();
447
448 if (!loadPromise || typeof loadPromise.then !== "function") {
449 throw Error(formatErrorMessage(7, `When mounting a parcel, the config loading function must return a promise that resolves with the parcel config`));
450 }
451
452 loadPromise = loadPromise.then(config => {
453 if (!config) {
454 throw Error(formatErrorMessage(8, `When mounting a parcel, the config loading function returned a promise that did not resolve with a parcel config`));
455 }
456
457 const name = config.name || `parcel-${id}`;
458
459 if (!validLifecycleFn(config.bootstrap)) {
460 throw Error(formatErrorMessage(9, `Parcel ${name} must have a valid bootstrap function`, name));
461 }
462
463 if (!validLifecycleFn(config.mount)) {
464 throw Error(formatErrorMessage(10, `Parcel ${name} must have a valid mount function`, name));
465 }
466
467 if (!validLifecycleFn(config.unmount)) {
468 throw Error(formatErrorMessage(11, `Parcel ${name} must have a valid unmount function`, name));
469 }
470
471 if (config.update && !validLifecycleFn(config.update)) {
472 throw Error(formatErrorMessage(12, `Parcel ${name} provided an invalid update function`, name));
473 }
474
475 const bootstrap = flattenFnArray(config, "bootstrap");
476 const mount = flattenFnArray(config, "mount");
477 const unmount = flattenFnArray(config, "unmount");
478 parcel.status = NOT_BOOTSTRAPPED;
479 parcel.name = name;
480 parcel.bootstrap = bootstrap;
481 parcel.mount = mount;
482 parcel.unmount = unmount;
483 parcel.timeouts = ensureValidAppTimeouts(config.timeouts);
484
485 if (config.update) {
486 parcel.update = flattenFnArray(config, "update");
487
488 externalRepresentation.update = function (customProps) {
489 parcel.customProps = customProps;
490 return promiseWithoutReturnValue(toUpdatePromise(parcel));
491 };
492 }
493 }); // Start bootstrapping and mounting
494 // The .then() causes the work to be put on the event loop instead of happening immediately
495
496 const bootstrapPromise = loadPromise.then(() => toBootstrapPromise(parcel, true));
497 const mountPromise = bootstrapPromise.then(() => toMountPromise(parcel, true));
498 let resolveUnmount, rejectUnmount;
499 const unmountPromise = new Promise((resolve, reject) => {
500 resolveUnmount = resolve;
501 rejectUnmount = reject;
502 });
503 externalRepresentation = {
504 mount() {
505 return promiseWithoutReturnValue(Promise.resolve().then(() => {
506 if (parcel.status !== NOT_MOUNTED) {
507 throw Error(formatErrorMessage(13, `Cannot mount parcel '${name}' -- it is in a ${parcel.status} status`, name, parcel.status));
508 } // Add to owning app or parcel
509
510
511 owningAppOrParcel.parcels[id] = parcel;
512 return toMountPromise(parcel);
513 }));
514 },
515
516 unmount() {
517 return promiseWithoutReturnValue(parcel.unmountThisParcel());
518 },
519
520 getStatus() {
521 return parcel.status;
522 },
523
524 loadPromise: promiseWithoutReturnValue(loadPromise),
525 bootstrapPromise: promiseWithoutReturnValue(bootstrapPromise),
526 mountPromise: promiseWithoutReturnValue(mountPromise),
527 unmountPromise: promiseWithoutReturnValue(unmountPromise)
528 };
529 return externalRepresentation;
530}
531
532function promiseWithoutReturnValue(promise) {
533 return promise.then(() => null);
534}
535
536function getProps(appOrParcel) {
537 const result = assign({}, appOrParcel.customProps, {
538 name: toName(appOrParcel),
539 mountParcel: mountParcel.bind(appOrParcel),
540 singleSpa
541 });
542
543 if (isParcel(appOrParcel)) {
544 result.unmountSelf = appOrParcel.unmountThisParcel;
545 }
546
547 return result;
548}
549
550const defaultWarningMillis = 1000;
551const globalTimeoutConfig = {
552 bootstrap: {
553 millis: 4000,
554 dieOnTimeout: false,
555 warningMillis: defaultWarningMillis
556 },
557 mount: {
558 millis: 3000,
559 dieOnTimeout: false,
560 warningMillis: defaultWarningMillis
561 },
562 unmount: {
563 millis: 3000,
564 dieOnTimeout: false,
565 warningMillis: defaultWarningMillis
566 },
567 unload: {
568 millis: 3000,
569 dieOnTimeout: false,
570 warningMillis: defaultWarningMillis
571 },
572 update: {
573 millis: 3000,
574 dieOnTimeout: false,
575 warningMillis: defaultWarningMillis
576 }
577};
578function setBootstrapMaxTime(time, dieOnTimeout, warningMillis) {
579 if (typeof time !== "number" || time <= 0) {
580 throw Error(formatErrorMessage(16, `bootstrap max time must be a positive integer number of milliseconds`));
581 }
582
583 globalTimeoutConfig.bootstrap = {
584 millis: time,
585 dieOnTimeout,
586 warningMillis: warningMillis || defaultWarningMillis
587 };
588}
589function setMountMaxTime(time, dieOnTimeout, warningMillis) {
590 if (typeof time !== "number" || time <= 0) {
591 throw Error(formatErrorMessage(17, `mount max time must be a positive integer number of milliseconds`));
592 }
593
594 globalTimeoutConfig.mount = {
595 millis: time,
596 dieOnTimeout,
597 warningMillis: warningMillis || defaultWarningMillis
598 };
599}
600function setUnmountMaxTime(time, dieOnTimeout, warningMillis) {
601 if (typeof time !== "number" || time <= 0) {
602 throw Error(formatErrorMessage(18, `unmount max time must be a positive integer number of milliseconds`));
603 }
604
605 globalTimeoutConfig.unmount = {
606 millis: time,
607 dieOnTimeout,
608 warningMillis: warningMillis || defaultWarningMillis
609 };
610}
611function setUnloadMaxTime(time, dieOnTimeout, warningMillis) {
612 if (typeof time !== "number" || time <= 0) {
613 throw Error(formatErrorMessage(19, `unload max time must be a positive integer number of milliseconds`));
614 }
615
616 globalTimeoutConfig.unload = {
617 millis: time,
618 dieOnTimeout,
619 warningMillis: warningMillis || defaultWarningMillis
620 };
621}
622function reasonableTime(appOrParcel, lifecycle) {
623 const timeoutConfig = appOrParcel.timeouts[lifecycle];
624 const warningPeriod = timeoutConfig.warningMillis;
625 const type = objectType(appOrParcel);
626 return new Promise((resolve, reject) => {
627 let finished = false;
628 let errored = false;
629 appOrParcel[lifecycle](getProps(appOrParcel)).then(val => {
630 finished = true;
631 resolve(val);
632 }).catch(val => {
633 finished = true;
634 reject(val);
635 });
636 setTimeout(() => maybeTimingOut(1), warningPeriod);
637 setTimeout(() => maybeTimingOut(true), timeoutConfig.millis);
638 const errMsg = formatErrorMessage(31, `Lifecycle function ${lifecycle} for ${type} ${toName(appOrParcel)} lifecycle did not resolve or reject for ${timeoutConfig.millis} ms.`, lifecycle, type, toName(appOrParcel), timeoutConfig.millis);
639
640 function maybeTimingOut(shouldError) {
641 if (!finished) {
642 if (shouldError === true) {
643 errored = true;
644
645 if (timeoutConfig.dieOnTimeout) {
646 reject(Error(errMsg));
647 } else {
648 console.error(errMsg); //don't resolve or reject, we're waiting this one out
649 }
650 } else if (!errored) {
651 const numWarnings = shouldError;
652 const numMillis = numWarnings * warningPeriod;
653 console.warn(errMsg);
654
655 if (numMillis + warningPeriod < timeoutConfig.millis) {
656 setTimeout(() => maybeTimingOut(numWarnings + 1), warningPeriod);
657 }
658 }
659 }
660 }
661 });
662}
663function ensureValidAppTimeouts(timeouts) {
664 const result = {};
665
666 for (let key in globalTimeoutConfig) {
667 result[key] = assign({}, globalTimeoutConfig[key], timeouts && timeouts[key] || {});
668 }
669
670 return result;
671}
672
673function toLoadPromise(app) {
674 return Promise.resolve().then(() => {
675 if (app.loadPromise) {
676 return app.loadPromise;
677 }
678
679 if (app.status !== NOT_LOADED && app.status !== LOAD_ERROR) {
680 return app;
681 }
682
683 app.status = LOADING_SOURCE_CODE;
684 let appOpts, isUserErr;
685 return app.loadPromise = Promise.resolve().then(() => {
686 const loadPromise = app.loadApp(getProps(app));
687
688 if (!smellsLikeAPromise(loadPromise)) {
689 // The name of the app will be prepended to this error message inside of the handleAppError function
690 isUserErr = true;
691 throw Error(formatErrorMessage(33, `single-spa loading function did not return a promise. Check the second argument to registerApplication('${toName(app)}', loadingFunction, activityFunction)`, toName(app)));
692 }
693
694 return loadPromise.then(val => {
695 app.loadErrorTime = null;
696 appOpts = val;
697 let validationErrMessage, validationErrCode;
698
699 if (typeof appOpts !== "object") {
700 validationErrCode = 34;
701
702 {
703 validationErrMessage = `does not export anything`;
704 }
705 }
706
707 if (!validLifecycleFn(appOpts.bootstrap)) {
708 validationErrCode = 35;
709
710 {
711 validationErrMessage = `does not export a bootstrap function or array of functions`;
712 }
713 }
714
715 if (!validLifecycleFn(appOpts.mount)) {
716 validationErrCode = 36;
717
718 {
719 validationErrMessage = `does not export a bootstrap function or array of functions`;
720 }
721 }
722
723 if (!validLifecycleFn(appOpts.unmount)) {
724 validationErrCode = 37;
725
726 {
727 validationErrMessage = `does not export a bootstrap function or array of functions`;
728 }
729 }
730
731 const type = objectType(appOpts);
732
733 if (validationErrCode) {
734 let appOptsStr;
735
736 try {
737 appOptsStr = JSON.stringify(appOpts);
738 } catch (_unused) {}
739
740 console.error(formatErrorMessage(validationErrCode, `The loading function for single-spa ${type} '${toName(app)}' resolved with the following, which does not have bootstrap, mount, and unmount functions`, type, toName(app), appOptsStr), appOpts);
741 handleAppError(validationErrMessage, app, SKIP_BECAUSE_BROKEN);
742 return app;
743 }
744
745 if (appOpts.devtools && appOpts.devtools.overlays) {
746 app.devtools.overlays = assign({}, app.devtools.overlays, appOpts.devtools.overlays);
747 }
748
749 app.status = NOT_BOOTSTRAPPED;
750 app.bootstrap = flattenFnArray(appOpts, "bootstrap");
751 app.mount = flattenFnArray(appOpts, "mount");
752 app.unmount = flattenFnArray(appOpts, "unmount");
753 app.unload = flattenFnArray(appOpts, "unload");
754 app.timeouts = ensureValidAppTimeouts(appOpts.timeouts);
755 delete app.loadPromise;
756 return app;
757 });
758 }).catch(err => {
759 delete app.loadPromise;
760 let newStatus;
761
762 if (isUserErr) {
763 newStatus = SKIP_BECAUSE_BROKEN;
764 } else {
765 newStatus = LOAD_ERROR;
766 app.loadErrorTime = new Date().getTime();
767 }
768
769 handleAppError(err, app, newStatus);
770 return app;
771 });
772 });
773}
774
775const isInBrowser = typeof window !== "undefined";
776
777/* We capture navigation event listeners so that we can make sure
778 * that application navigation listeners are not called until
779 * single-spa has ensured that the correct applications are
780 * unmounted and mounted.
781 */
782
783const capturedEventListeners = {
784 hashchange: [],
785 popstate: []
786};
787const routingEventsListeningTo = ["hashchange", "popstate"];
788function navigateToUrl(obj) {
789 let url;
790
791 if (typeof obj === "string") {
792 url = obj;
793 } else if (this && this.href) {
794 url = this.href;
795 } else if (obj && obj.currentTarget && obj.currentTarget.href && obj.preventDefault) {
796 url = obj.currentTarget.href;
797 obj.preventDefault();
798 } else {
799 throw Error(formatErrorMessage(14, `singleSpaNavigate/navigateToUrl must be either called with a string url, with an <a> tag as its context, or with an event whose currentTarget is an <a> tag`));
800 }
801
802 const current = parseUri(window.location.href);
803 const destination = parseUri(url);
804
805 if (url.indexOf("#") === 0) {
806 window.location.hash = destination.hash;
807 } else if (current.host !== destination.host && destination.host) {
808 {
809 window.location.href = url;
810 }
811 } else if (destination.pathname === current.pathname && destination.search === current.pathname) {
812 window.location.hash = destination.hash;
813 } else {
814 // different path, host, or query params
815 window.history.pushState(null, null, url);
816 }
817}
818function callCapturedEventListeners(eventArguments) {
819 if (eventArguments) {
820 const eventType = eventArguments[0].type;
821
822 if (routingEventsListeningTo.indexOf(eventType) >= 0) {
823 capturedEventListeners[eventType].forEach(listener => {
824 try {
825 // The error thrown by application event listener should not break single-spa down.
826 // Just like https://github.com/single-spa/single-spa/blob/85f5042dff960e40936f3a5069d56fc9477fac04/src/navigation/reroute.js#L140-L146 did
827 listener.apply(this, eventArguments);
828 } catch (e) {
829 setTimeout(() => {
830 throw e;
831 });
832 }
833 });
834 }
835 }
836}
837let urlRerouteOnly;
838function setUrlRerouteOnly(val) {
839 urlRerouteOnly = val;
840}
841
842function urlReroute() {
843 reroute([], arguments);
844}
845
846if (isInBrowser) {
847 // We will trigger an app change for any routing events.
848 window.addEventListener("hashchange", urlReroute);
849 window.addEventListener("popstate", urlReroute); // Monkeypatch addEventListener so that we can ensure correct timing
850
851 const originalAddEventListener = window.addEventListener;
852 const originalRemoveEventListener = window.removeEventListener;
853
854 window.addEventListener = function (eventName, fn) {
855 if (typeof fn === "function") {
856 if (routingEventsListeningTo.indexOf(eventName) >= 0 && !find(capturedEventListeners[eventName], listener => listener === fn)) {
857 capturedEventListeners[eventName].push(fn);
858 return;
859 }
860 }
861
862 return originalAddEventListener.apply(this, arguments);
863 };
864
865 window.removeEventListener = function (eventName, listenerFn) {
866 if (typeof listenerFn === "function") {
867 if (routingEventsListeningTo.indexOf(eventName) >= 0) {
868 capturedEventListeners[eventName] = capturedEventListeners[eventName].filter(fn => fn !== listenerFn);
869 return;
870 }
871 }
872
873 return originalRemoveEventListener.apply(this, arguments);
874 };
875
876 window.history.pushState = patchedUpdateState(window.history.pushState);
877 window.history.replaceState = patchedUpdateState(window.history.replaceState);
878
879 function patchedUpdateState(updateState) {
880 return function () {
881 const urlBefore = window.location.href;
882 const result = updateState.apply(this, arguments);
883 const urlAfter = window.location.href;
884
885 if (!urlRerouteOnly || urlBefore !== urlAfter) {
886 urlReroute(createPopStateEvent(window.history.state));
887 }
888
889 return result;
890 };
891 }
892
893 function createPopStateEvent(state) {
894 // https://github.com/single-spa/single-spa/issues/224 and https://github.com/single-spa/single-spa-angular/issues/49
895 // We need a popstate event even though the browser doesn't do one by default when you call replaceState, so that
896 // all the applications can reroute.
897 try {
898 return new PopStateEvent("popstate", {
899 state
900 });
901 } catch (err) {
902 // IE 11 compatibility https://github.com/single-spa/single-spa/issues/299
903 // https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-html5e/bd560f47-b349-4d2c-baa8-f1560fb489dd
904 const evt = document.createEvent("PopStateEvent");
905 evt.initPopStateEvent("popstate", false, false, state);
906 return evt;
907 }
908 }
909 /* For convenience in `onclick` attributes, we expose a global function for navigating to
910 * whatever an <a> tag's href is.
911 */
912
913
914 window.singleSpaNavigate = navigateToUrl;
915}
916
917function parseUri(str) {
918 const anchor = document.createElement("a");
919 anchor.href = str;
920 return anchor;
921}
922
923let hasInitialized = false;
924function ensureJQuerySupport(jQuery = window.jQuery) {
925 if (!jQuery) {
926 if (window.$ && window.$.fn && window.$.fn.jquery) {
927 jQuery = window.$;
928 }
929 }
930
931 if (jQuery && !hasInitialized) {
932 const originalJQueryOn = jQuery.fn.on;
933 const originalJQueryOff = jQuery.fn.off;
934
935 jQuery.fn.on = function (eventString, fn) {
936 return captureRoutingEvents.call(this, originalJQueryOn, window.addEventListener, eventString, fn, arguments);
937 };
938
939 jQuery.fn.off = function (eventString, fn) {
940 return captureRoutingEvents.call(this, originalJQueryOff, window.removeEventListener, eventString, fn, arguments);
941 };
942
943 hasInitialized = true;
944 }
945}
946
947function captureRoutingEvents(originalJQueryFunction, nativeFunctionToCall, eventString, fn, originalArgs) {
948 if (typeof eventString !== "string") {
949 return originalJQueryFunction.apply(this, originalArgs);
950 }
951
952 const eventNames = eventString.split(/\s+/);
953 eventNames.forEach(eventName => {
954 if (routingEventsListeningTo.indexOf(eventName) >= 0) {
955 nativeFunctionToCall(eventName, fn);
956 eventString = eventString.replace(eventName, "");
957 }
958 });
959
960 if (eventString.trim() === "") {
961 return this;
962 } else {
963 return originalJQueryFunction.apply(this, originalArgs);
964 }
965}
966
967const appsToUnload = {};
968function toUnloadPromise(app) {
969 return Promise.resolve().then(() => {
970 const unloadInfo = appsToUnload[toName(app)];
971
972 if (!unloadInfo) {
973 /* No one has called unloadApplication for this app,
974 */
975 return app;
976 }
977
978 if (app.status === NOT_LOADED) {
979 /* This app is already unloaded. We just need to clean up
980 * anything that still thinks we need to unload the app.
981 */
982 finishUnloadingApp(app, unloadInfo);
983 return app;
984 }
985
986 if (app.status === UNLOADING) {
987 /* Both unloadApplication and reroute want to unload this app.
988 * It only needs to be done once, though.
989 */
990 return unloadInfo.promise.then(() => app);
991 }
992
993 if (app.status !== NOT_MOUNTED) {
994 /* The app cannot be unloaded until it is unmounted.
995 */
996 return app;
997 }
998
999 app.status = UNLOADING;
1000 return reasonableTime(app, "unload").then(() => {
1001 finishUnloadingApp(app, unloadInfo);
1002 return app;
1003 }).catch(err => {
1004 errorUnloadingApp(app, unloadInfo, err);
1005 return app;
1006 });
1007 });
1008}
1009
1010function finishUnloadingApp(app, unloadInfo) {
1011 delete appsToUnload[toName(app)]; // Unloaded apps don't have lifecycles
1012
1013 delete app.bootstrap;
1014 delete app.mount;
1015 delete app.unmount;
1016 delete app.unload;
1017 app.status = NOT_LOADED;
1018 /* resolve the promise of whoever called unloadApplication.
1019 * This should be done after all other cleanup/bookkeeping
1020 */
1021
1022 unloadInfo.resolve();
1023}
1024
1025function errorUnloadingApp(app, unloadInfo, err) {
1026 delete appsToUnload[toName(app)]; // Unloaded apps don't have lifecycles
1027
1028 delete app.bootstrap;
1029 delete app.mount;
1030 delete app.unmount;
1031 delete app.unload;
1032 handleAppError(err, app, SKIP_BECAUSE_BROKEN);
1033 unloadInfo.reject(err);
1034}
1035
1036function addAppToUnload(app, promiseGetter, resolve, reject) {
1037 appsToUnload[toName(app)] = {
1038 app,
1039 resolve,
1040 reject
1041 };
1042 Object.defineProperty(appsToUnload[toName(app)], "promise", {
1043 get: promiseGetter
1044 });
1045}
1046function getAppUnloadInfo(appName) {
1047 return appsToUnload[appName];
1048}
1049function getAppsToUnload() {
1050 return Object.keys(appsToUnload).map(appName => appsToUnload[appName].app).filter(isntActive);
1051}
1052
1053const apps = [];
1054function getMountedApps() {
1055 return apps.filter(isActive).map(toName);
1056}
1057function getAppNames() {
1058 return apps.map(toName);
1059} // used in devtools, not (currently) exposed as a single-spa API
1060
1061function getRawAppData() {
1062 return [...apps];
1063}
1064function getAppStatus(appName) {
1065 const app = find(apps, app => toName(app) === appName);
1066 return app ? app.status : null;
1067}
1068function registerApplication(appNameOrConfig, appOrLoadApp, activeWhen, customProps) {
1069 const registration = sanitizeArguments(appNameOrConfig, appOrLoadApp, activeWhen, customProps);
1070 if (getAppNames().indexOf(registration.name) !== -1) throw Error(formatErrorMessage(21, `There is already an app registered with name ${registration.name}`, registration.name));
1071 apps.push(assign({
1072 loadErrorTime: null,
1073 status: NOT_LOADED,
1074 parcels: {},
1075 devtools: {
1076 overlays: {
1077 options: {},
1078 selectors: []
1079 }
1080 }
1081 }, registration));
1082
1083 if (isInBrowser) {
1084 ensureJQuerySupport();
1085 reroute();
1086 }
1087}
1088function checkActivityFunctions(location) {
1089 return apps.filter(app => app.activeWhen(location)).map(toName);
1090}
1091function getAppsToLoad() {
1092 return apps.filter(notSkipped).filter(withoutLoadErrors).filter(isntLoaded).filter(shouldBeActive);
1093}
1094function getAppsToUnmount() {
1095 return apps.filter(notSkipped).filter(isActive).filter(shouldntBeActive);
1096}
1097function getAppsToMount() {
1098 return apps.filter(notSkipped).filter(isntActive).filter(isLoaded).filter(shouldBeActive);
1099}
1100function unregisterApplication(appName) {
1101 if (!apps.find(app => toName(app) === appName)) {
1102 throw Error(formatErrorMessage(25, `Cannot unregister application '${appName}' because no such application has been registered`, appName));
1103 }
1104
1105 return unloadApplication(appName).then(() => {
1106 const appIndex = apps.findIndex(app => toName(app) === appName);
1107 apps.splice(appIndex, 1);
1108 });
1109}
1110function unloadApplication(appName, opts = {
1111 waitForUnmount: false
1112}) {
1113 if (typeof appName !== "string") {
1114 throw Error(formatErrorMessage(26, `unloadApplication requires a string 'appName'`));
1115 }
1116
1117 const app = find(apps, App => toName(App) === appName);
1118
1119 if (!app) {
1120 throw Error(formatErrorMessage(27, `Could not unload application '${appName}' because no such application has been registered`, appName));
1121 }
1122
1123 const appUnloadInfo = getAppUnloadInfo(toName(app));
1124
1125 if (opts && opts.waitForUnmount) {
1126 // We need to wait for unmount before unloading the app
1127 if (appUnloadInfo) {
1128 // Someone else is already waiting for this, too
1129 return appUnloadInfo.promise;
1130 } else {
1131 // We're the first ones wanting the app to be resolved.
1132 const promise = new Promise((resolve, reject) => {
1133 addAppToUnload(app, () => promise, resolve, reject);
1134 });
1135 return promise;
1136 }
1137 } else {
1138 /* We should unmount the app, unload it, and remount it immediately.
1139 */
1140 let resultPromise;
1141
1142 if (appUnloadInfo) {
1143 // Someone else is already waiting for this app to unload
1144 resultPromise = appUnloadInfo.promise;
1145 immediatelyUnloadApp(app, appUnloadInfo.resolve, appUnloadInfo.reject);
1146 } else {
1147 // We're the first ones wanting the app to be resolved.
1148 resultPromise = new Promise((resolve, reject) => {
1149 addAppToUnload(app, () => resultPromise, resolve, reject);
1150 immediatelyUnloadApp(app, resolve, reject);
1151 });
1152 }
1153
1154 return resultPromise;
1155 }
1156}
1157
1158function immediatelyUnloadApp(app, resolve, reject) {
1159 toUnmountPromise(app).then(toUnloadPromise).then(() => {
1160 resolve();
1161 setTimeout(() => {
1162 // reroute, but the unload promise is done
1163 reroute();
1164 });
1165 }).catch(reject);
1166}
1167
1168function validateRegisterWithArguments(name, appOrLoadApp, activeWhen, customProps) {
1169 if (typeof name !== "string" || name.length === 0) throw Error(formatErrorMessage(20, `The 1st argument to registerApplication must be a non-empty string 'appName'`));
1170 if (!appOrLoadApp) throw Error(formatErrorMessage(23, "The 2nd argument to registerApplication must be an application or loading application function"));
1171 if (typeof activeWhen !== "function") throw Error(formatErrorMessage(24, "The 3rd argument to registerApplication must be an activeWhen function"));
1172 if (!!customProps && (typeof customProps !== "object" || Array.isArray(customProps))) throw Error(formatErrorMessage(22, "The optional 4th argument is a customProps and must be an object"));
1173}
1174
1175function validateRegisterWithConfig(config) {
1176 if (Array.isArray(config) || config === null) throw Error(formatErrorMessage(31, "Configuration object can't be an Array or null!"));
1177 const validKeys = ["name", "app", "activeWhen", "customProps"];
1178 const invalidKeys = Object.keys(config).reduce((invalidKeys, prop) => validKeys.includes(prop) ? invalidKeys : invalidKeys.concat(prop), []);
1179 if (invalidKeys.length !== 0) throw Error(formatErrorMessage(30, `The configuration object accepts only: ${validKeys.join(", ")}. Invalid keys: ${invalidKeys.join(", ")}.`));
1180 if (typeof config.name !== "string" || config.name.length === 0) throw Error(formatErrorMessage(20, "The config.name on registerApplication must be a non-empty string"));
1181 if (typeof config.app !== "object" && typeof config.app !== "function") throw Error(formatErrorMessage(20, "The config.app on registerApplication must be an application or a loading function"));
1182
1183 const allowsStringAndFunction = activeWhen => typeof activeWhen === "string" || typeof activeWhen === "function";
1184
1185 if (!allowsStringAndFunction(config.activeWhen) && !(Array.isArray(config.activeWhen) && config.activeWhen.every(allowsStringAndFunction))) throw Error(formatErrorMessage(24, "The config.activeWhen on registerApplication must be a string, function or an array with both"));
1186 if (!(!config.customProps || typeof config.customProps === "object" && !Array.isArray(config.customProps))) throw Error(formatErrorMessage(22, "The optional config.customProps must be an object"));
1187}
1188
1189function sanitizeArguments(appNameOrConfig, appOrLoadApp, activeWhen, customProps) {
1190 const usingObjectAPI = typeof appNameOrConfig === "object";
1191 const registration = {
1192 name: null,
1193 loadApp: null,
1194 activeWhen: null,
1195 customProps: null
1196 };
1197
1198 if (usingObjectAPI) {
1199 validateRegisterWithConfig(appNameOrConfig);
1200 registration.name = appNameOrConfig.name;
1201 registration.loadApp = appNameOrConfig.app;
1202 registration.activeWhen = appNameOrConfig.activeWhen;
1203 registration.customProps = appNameOrConfig.customProps;
1204 } else {
1205 validateRegisterWithArguments(appNameOrConfig, appOrLoadApp, activeWhen, customProps);
1206 registration.name = appNameOrConfig;
1207 registration.loadApp = appOrLoadApp;
1208 registration.activeWhen = activeWhen;
1209 registration.customProps = customProps;
1210 }
1211
1212 registration.loadApp = sanitizeLoadApp(registration.loadApp);
1213 registration.customProps = sanitizeCustomProps(registration.customProps);
1214 registration.activeWhen = sanitizeActiveWhen(registration.activeWhen);
1215 return registration;
1216}
1217
1218function sanitizeLoadApp(loadApp) {
1219 if (typeof loadApp !== "function") {
1220 return () => Promise.resolve(loadApp);
1221 }
1222
1223 return loadApp;
1224}
1225
1226function sanitizeCustomProps(customProps) {
1227 return customProps ? customProps : {};
1228}
1229
1230function sanitizeActiveWhen(activeWhen) {
1231 let activeWhenArray = Array.isArray(activeWhen) ? activeWhen : [activeWhen];
1232 activeWhenArray = activeWhenArray.map(activeWhenOrPath => typeof activeWhenOrPath === "function" ? activeWhenOrPath : pathToActiveWhen(activeWhenOrPath));
1233 return location => activeWhenArray.some(activeWhen => activeWhen(location));
1234}
1235
1236function pathToActiveWhen(path) {
1237 const regex = toDynamicPathValidatorRegex(path);
1238 return location => {
1239 const route = location.href.replace(location.origin, "");
1240 return regex.test(route);
1241 };
1242}
1243
1244function toDynamicPathValidatorRegex(path) {
1245 let lastIndex = 0,
1246 inDynamic = false,
1247 regexStr = "^";
1248
1249 for (let charIndex = 0; charIndex < path.length; charIndex++) {
1250 const char = path[charIndex];
1251 const startOfDynamic = !inDynamic && char === ":";
1252 const endOfDynamic = inDynamic && char === "/";
1253
1254 if (startOfDynamic || endOfDynamic) {
1255 appendToRegex(charIndex);
1256 }
1257 }
1258
1259 appendToRegex(path.length);
1260 return new RegExp(regexStr, "i");
1261
1262 function appendToRegex(index) {
1263 const anyCharMaybeTrailingSlashRegex = "[^/]+/?";
1264 const commonStringSubPath = escapeStrRegex(path.slice(lastIndex, index));
1265 regexStr += inDynamic ? anyCharMaybeTrailingSlashRegex : commonStringSubPath;
1266 inDynamic = !inDynamic;
1267 lastIndex = index;
1268 }
1269
1270 function escapeStrRegex(str) {
1271 // borrowed from https://github.com/sindresorhus/escape-string-regexp/blob/master/index.js
1272 return str.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
1273 }
1274}
1275
1276let appChangeUnderway = false,
1277 peopleWaitingOnAppChange = [];
1278function triggerAppChange() {
1279 // Call reroute with no arguments, intentionally
1280 return reroute();
1281}
1282function reroute(pendingPromises = [], eventArguments) {
1283 if (appChangeUnderway) {
1284 return new Promise((resolve, reject) => {
1285 peopleWaitingOnAppChange.push({
1286 resolve,
1287 reject,
1288 eventArguments
1289 });
1290 });
1291 }
1292
1293 let wasNoOp = true;
1294
1295 if (isStarted()) {
1296 appChangeUnderway = true;
1297 return performAppChanges();
1298 } else {
1299 return loadApps();
1300 }
1301
1302 function loadApps() {
1303 return Promise.resolve().then(() => {
1304 const loadPromises = getAppsToLoad().map(toLoadPromise);
1305
1306 if (loadPromises.length > 0) {
1307 wasNoOp = false;
1308 }
1309
1310 return Promise.all(loadPromises).then(callAllEventListeners) // there are no mounted apps, before start() is called, so we always return []
1311 .then(() => []).catch(err => {
1312 callAllEventListeners();
1313 throw err;
1314 });
1315 });
1316 }
1317
1318 function performAppChanges() {
1319 return Promise.resolve().then(() => {
1320 window.dispatchEvent(new customEvent("single-spa:before-routing-event", getCustomEventDetail()));
1321 const unloadPromises = getAppsToUnload().map(toUnloadPromise);
1322 const unmountUnloadPromises = getAppsToUnmount().map(toUnmountPromise).map(unmountPromise => unmountPromise.then(toUnloadPromise));
1323 const allUnmountPromises = unmountUnloadPromises.concat(unloadPromises);
1324
1325 if (allUnmountPromises.length > 0) {
1326 wasNoOp = false;
1327 }
1328
1329 const unmountAllPromise = Promise.all(allUnmountPromises);
1330 const appsToLoad = getAppsToLoad();
1331 /* We load and bootstrap apps while other apps are unmounting, but we
1332 * wait to mount the app until all apps are finishing unmounting
1333 */
1334
1335 const loadThenMountPromises = appsToLoad.map(app => {
1336 return toLoadPromise(app).then(toBootstrapPromise).then(app => {
1337 return unmountAllPromise.then(() => toMountPromise(app));
1338 });
1339 });
1340
1341 if (loadThenMountPromises.length > 0) {
1342 wasNoOp = false;
1343 }
1344 /* These are the apps that are already bootstrapped and just need
1345 * to be mounted. They each wait for all unmounting apps to finish up
1346 * before they mount.
1347 */
1348
1349
1350 const mountPromises = getAppsToMount().filter(appToMount => appsToLoad.indexOf(appToMount) < 0).map(appToMount => {
1351 return toBootstrapPromise(appToMount).then(() => unmountAllPromise).then(() => toMountPromise(appToMount));
1352 });
1353
1354 if (mountPromises.length > 0) {
1355 wasNoOp = false;
1356 }
1357
1358 return unmountAllPromise.catch(err => {
1359 callAllEventListeners();
1360 throw err;
1361 }).then(() => {
1362 /* Now that the apps that needed to be unmounted are unmounted, their DOM navigation
1363 * events (like hashchange or popstate) should have been cleaned up. So it's safe
1364 * to let the remaining captured event listeners to handle about the DOM event.
1365 */
1366 callAllEventListeners();
1367 return Promise.all(loadThenMountPromises.concat(mountPromises)).catch(err => {
1368 pendingPromises.forEach(promise => promise.reject(err));
1369 throw err;
1370 }).then(finishUpAndReturn);
1371 });
1372 });
1373 }
1374
1375 function finishUpAndReturn() {
1376 const returnValue = getMountedApps();
1377 pendingPromises.forEach(promise => promise.resolve(returnValue));
1378
1379 try {
1380 const appChangeEventName = wasNoOp ? "single-spa:no-app-change" : "single-spa:app-change";
1381 window.dispatchEvent(new customEvent(appChangeEventName, getCustomEventDetail()));
1382 window.dispatchEvent(new customEvent("single-spa:routing-event", getCustomEventDetail()));
1383 } catch (err) {
1384 /* We use a setTimeout because if someone else's event handler throws an error, single-spa
1385 * needs to carry on. If a listener to the event throws an error, it's their own fault, not
1386 * single-spa's.
1387 */
1388 setTimeout(() => {
1389 throw err;
1390 });
1391 }
1392 /* Setting this allows for subsequent calls to reroute() to actually perform
1393 * a reroute instead of just getting queued behind the current reroute call.
1394 * We want to do this after the mounting/unmounting is done but before we
1395 * resolve the promise for the `reroute` function.
1396 */
1397
1398
1399 appChangeUnderway = false;
1400
1401 if (peopleWaitingOnAppChange.length > 0) {
1402 /* While we were rerouting, someone else triggered another reroute that got queued.
1403 * So we need reroute again.
1404 */
1405 const nextPendingPromises = peopleWaitingOnAppChange;
1406 peopleWaitingOnAppChange = [];
1407 reroute(nextPendingPromises);
1408 }
1409
1410 return returnValue;
1411 }
1412 /* We need to call all event listeners that have been delayed because they were
1413 * waiting on single-spa. This includes haschange and popstate events for both
1414 * the current run of performAppChanges(), but also all of the queued event listeners.
1415 * We want to call the listeners in the same order as if they had not been delayed by
1416 * single-spa, which means queued ones first and then the most recent one.
1417 */
1418
1419
1420 function callAllEventListeners() {
1421 pendingPromises.forEach(pendingPromise => {
1422 callCapturedEventListeners(pendingPromise.eventArguments);
1423 });
1424 callCapturedEventListeners(eventArguments);
1425 }
1426
1427 function getCustomEventDetail() {
1428 const result = {
1429 detail: {}
1430 };
1431
1432 if (eventArguments && eventArguments[0]) {
1433 result.detail.originalEvent = eventArguments[0];
1434 }
1435
1436 return result;
1437 }
1438}
1439
1440let started = false;
1441function start(opts) {
1442 started = true;
1443
1444 if (opts && opts.urlRerouteOnly) {
1445 setUrlRerouteOnly(opts.urlRerouteOnly);
1446 }
1447
1448 if (isInBrowser) {
1449 reroute();
1450 }
1451}
1452function isStarted() {
1453 return started;
1454}
1455
1456if (isInBrowser) {
1457 setTimeout(() => {
1458 if (!started) {
1459 console.warn(formatErrorMessage(1, `singleSpa.start() has not been called, 5000ms after single-spa was loaded. Before start() is called, apps can be declared and loaded, but not bootstrapped or mounted.`));
1460 }
1461 }, 5000);
1462}
1463
1464var devtools = {
1465 getRawAppData,
1466 reroute,
1467 NOT_LOADED,
1468 toLoadPromise,
1469 toBootstrapPromise,
1470 unregisterApplication
1471};
1472
1473if (isInBrowser && window.__SINGLE_SPA_DEVTOOLS__) {
1474 window.__SINGLE_SPA_DEVTOOLS__.exposedMethods = devtools;
1475}
1476
1477export { BOOTSTRAPPING, LOADING_SOURCE_CODE, LOAD_ERROR, MOUNTED, MOUNTING, NOT_BOOTSTRAPPED, NOT_LOADED, NOT_MOUNTED, SKIP_BECAUSE_BROKEN, UNMOUNTING, UPDATING, addErrorHandler, checkActivityFunctions, ensureJQuerySupport, getAppNames, getAppStatus, getMountedApps, mountRootParcel, navigateToUrl, registerApplication, removeErrorHandler, setBootstrapMaxTime, setMountMaxTime, setUnloadMaxTime, setUnmountMaxTime, start, triggerAppChange, unloadApplication };
1478//# sourceMappingURL=single-spa.dev.js.map