UNPKG

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