Repository: single-spa/single-spa (language: javascript)

Issue:
Drop IE11 support
See https://single-spa.js.org/blog/2023/11/27/single-spa-6#browser-support--ie11. A new major release of single-spa will be coming soon, to refactor to typescript. We should drop IE11 support at that time, too.

The assign.js/assign.ts file can be replaced with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign. The babel/rollup config can change as well. Probably other things to change, too


Discussion / hints:

Fix patch:
diff --git a/.babelrc.json b/.babelrc.json
index 9bd74527..4bdb87df 100644
--- a/.babelrc.json
+++ b/.babelrc.json
@@ -24,21 +24,6 @@
         "@babel/preset-typescript"
       ],
       "plugins": ["dynamic-import-node"]
-    },
-    "esm": {
-      "presets": [
-        [
-          "@babel/preset-env",
-          {
-            "modules": false,
-            "bugfixes": true,
-            "targets": {
-              "esmodules": true
-            }
-          }
-        ],
-        "@babel/preset-typescript"
-      ]
     }
   }
 }
diff --git a/.changeset/real-scissors-rest.md b/.changeset/real-scissors-rest.md
new file mode 100644
index 00000000..b9ee4e19
--- /dev/null
+++ b/.changeset/real-scissors-rest.md
@@ -0,0 +1,5 @@
+---
+"single-spa": major
+---
+
+Drop IE11 support. Change browserslist target
diff --git a/.changeset/sweet-vans-approve.md b/.changeset/sweet-vans-approve.md
new file mode 100644
index 00000000..37377a74
--- /dev/null
+++ b/.changeset/sweet-vans-approve.md
@@ -0,0 +1,5 @@
+---
+"single-spa": major
+---
+
+remove es2015 and es5 folders from published tarball
diff --git a/package.json b/package.json
index 7987ffed..3af25161 100644
--- a/package.json
+++ b/package.json
@@ -11,25 +11,25 @@
   "exports": {
     ".": {
       "development": {
-        "import": "./lib/es2015/esm/single-spa.dev.js",
-        "require": "./lib/es2015/umd/single-spa.dev.cjs",
+        "import": "./lib/esm/single-spa.dev.js",
+        "require": "./lib/umd/single-spa.dev.cjs",
         "types": "./typings/single-spa.d.ts"
       },
       "production": {
-        "import": "./lib/es2015/esm/single-spa.min.js",
-        "require": "./lib/es2015/umd/single-spa.min.cjs",
+        "import": "./lib/esm/single-spa.min.js",
+        "require": "./lib/umd/single-spa.min.cjs",
         "types": "./typings/single-spa.d.ts"
       },
       "default": {
-        "import": "./lib/es2015/esm/single-spa.min.js",
-        "require": "./lib/es2015/umd/single-spa.min.cjs",
+        "import": "./lib/esm/single-spa.min.js",
+        "require": "./lib/umd/single-spa.min.cjs",
         "types": "./typings/single-spa.d.ts"
       }
     },
     "./package.json": "./package.json",
     "./lib/*": "./lib/*"
   },
-  "module": "lib/es2015/esm/single-spa.min.js",
+  "module": "lib/esm/single-spa.min.js",
   "scripts": {
     "build": "pnpm run clean && concurrently pnpm:build:dev pnpm:build:prod pnpm:build:types",
     "build:prod": "rollup -c --environment ROLLUP_ENV:'production'",
@@ -116,11 +116,8 @@
     "typescript": "^5.3.3"
   },
   "browserslist": [
-    "ie >= 11",
-    "last 4 Safari major versions",
-    "last 10 Chrome major versions",
-    "last 10 Firefox major versions",
-    "last 4 Edge major versions"
+    ">5%",
+    "not ie <= 11"
   ],
   "ignore": [
     "examples",
diff --git a/rollup.config.js b/rollup.config.js
index 81a31d98..0d93ee8b 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -5,7 +5,7 @@ import analyzer from "rollup-plugin-analyzer";
 import replace from "@rollup/plugin-replace";
 import packageJson from "./package.json";
 
-const isProduction = process.env.NODE_ENV === "production";
+const isProduction = process.env.ROLLUP_ENV === "production";
 const useAnalyzer = process.env.ANALYZER === "analyzer";
 
 const replaceOpts = {
@@ -41,60 +41,23 @@ export default (async () => [
     input: `./src/single-spa${isProduction ? "" : ".profile"}.ts`,
     output: [
       {
-        file: `./lib/es5/umd/single-spa${isProduction ? ".min" : ".dev"}.cjs`,
+        file: `./lib/umd/single-spa${isProduction ? ".min" : ".dev"}.cjs`,
         format: "umd",
         name: "singleSpa",
         sourcemap: true,
-        banner: generateBanner("UMD ES5"),
+        banner: generateBanner("UMD"),
       },
       {
-        file: `./lib/es5/system/single-spa${isProduction ? ".min" : ".dev"}.js`,
-        format: "system",
-        sourcemap: true,
-        banner: generateBanner("SystemJS ES5"),
-      },
-      {
-        file: `./lib/es5/esm/single-spa${isProduction ? ".min" : ".dev"}.js`,
-        format: "esm",
-        sourcemap: true,
-        banner: generateBanner("ESM ES5"),
-      },
-    ],
-    plugins: [
-      replace(replaceOpts),
-      resolve(resolveOpts),
-      babel(babelOpts),
-      commonjs(),
-      isProduction &&
-        (await import("@rollup/plugin-terser")).terser(terserOpts),
-      useAnalyzer && analyzer(),
-    ],
-  },
-  {
-    input: `./src/single-spa${isProduction ? "" : ".profile"}.ts`,
-    output: [
-      {
-        file: `./lib/es2015/umd/single-spa${
-          isProduction ? ".min" : ".dev"
-        }.cjs`,
-        format: "umd",
-        name: "singleSpa",
-        sourcemap: true,
-        banner: generateBanner("UMD ES2015"),
-      },
-      {
-        file: `./lib/es2015/esm/single-spa${isProduction ? ".min" : ".dev"}.js`,
+        file: `./lib/esm/single-spa${isProduction ? ".min" : ".dev"}.js`,
         format: "esm",
         sourcemap: true,
-        banner: generateBanner("ES2015"),
+        banner: generateBanner("ESM"),
       },
       {
-        file: `./lib/es2015/system/single-spa${
-          isProduction ? ".min" : ".dev"
-        }.js`,
+        file: `./lib/system/single-spa${isProduction ? ".min" : ".dev"}.js`,
         format: "system",
         sourcemap: true,
-        banner: generateBanner("SystemJS ES2015"),
+        banner: generateBanner("SystemJS"),
       },
     ],
     plugins: [
@@ -107,7 +70,7 @@ export default (async () => [
       ),
       commonjs(),
       isProduction &&
-        (await import("@rollup/plugin-terser")).terser(
+        (await import("@rollup/plugin-terser")).default(
           Object.assign({}, terserOpts, {
             ecma: 6,
             module: true,
diff --git a/spec/apis/navigate-to-url.spec.ts b/spec/apis/navigate-to-url.spec.ts
index 9b1e6ccd..b83ec8c7 100644
--- a/spec/apis/navigate-to-url.spec.ts
+++ b/spec/apis/navigate-to-url.spec.ts
@@ -42,7 +42,7 @@ describe("navigateToUrl", function () {
     const a = document.createElement("a");
     a.setAttribute("href", hrefWithoutHash() + "#a/other");
     a.addEventListener("click", singleSpa.navigateToUrl);
-    // IE requires an element to be on the dom before click events will be fired.
+    // JSDOM requires an element to be on the dom before click events will be fired.
     document.body.appendChild(a);
     a.click();
     expect(location.hash).toBe("#a/other");
diff --git a/src/applications/apps.ts b/src/applications/apps.ts
index 93cd1b0b..ce61de52 100644
--- a/src/applications/apps.ts
+++ b/src/applications/apps.ts
@@ -103,7 +103,7 @@ export function getRawAppData(): InternalApplication[] {
   return [...apps];
 }
 
-export function getAppStatus(appName): AppOrParcelStatus {
+export function getAppStatus(appName): AppOrParcelStatus | null {
   const app = apps.find((app) => toName(app) === appName);
   return app ? app.status : null;
 }
@@ -226,7 +226,7 @@ export function unloadApplication(
   }
 
   const appUnloadInfo = getAppUnloadInfo(toName(app));
-  if (opts && opts.waitForUnmount) {
+  if (opts?.waitForUnmount) {
     // We need to wait for unmount before unloading the app
 
     if (appUnloadInfo) {
@@ -502,13 +502,8 @@ export function pathToActiveWhen(
   const regex = toDynamicPathValidatorRegex(path, exactMatch);
 
   return (location: Location) => {
-    // compatible with IE10
-    let origin = location.origin;
-    if (!origin) {
-      origin = `${location.protocol}//${location.host}`;
-    }
     const route = location.href
-      .replace(origin, "")
+      .replace(location.origin, "")
       .replace(location.search, "")
       .split("?")[0];
     return regex.test(route);
diff --git a/src/applications/timeouts.ts b/src/applications/timeouts.ts
index 8f20a990..a5bb56c0 100644
--- a/src/applications/timeouts.ts
+++ b/src/applications/timeouts.ts
@@ -1,4 +1,3 @@
-import { assign } from "../utils/assign";
 import { getProps } from "../lifecycles/prop.helpers";
 import { objectType, toName } from "./app.helpers";
 import { formatErrorMessage } from "./app-errors";
diff --git a/src/jquery-support.ts b/src/jquery-support.ts
index 2bc301fa..dab22f66 100644
--- a/src/jquery-support.ts
+++ b/src/jquery-support.ts
@@ -3,10 +3,8 @@ import { routingEventsListeningTo } from "./navigation/navigation-events";
 let hasInitialized: boolean = false;
 
 export function ensureJQuerySupport(jQuery: any = window.jQuery): void {
-  if (!jQuery) {
-    if (window.$ && window.$.fn && window.$.fn.jquery) {
-      jQuery = window.$;
-    }
+  if (!jQuery && window?.$?.fn?.jquery) {
+    jQuery = window.$;
   }
 
   if (jQuery && !hasInitialized) {
diff --git a/src/lifecycles/lifecycle.helpers.ts b/src/lifecycles/lifecycle.helpers.ts
index 44b0c2ca..efac72ee 100644
--- a/src/lifecycles/lifecycle.helpers.ts
+++ b/src/lifecycles/lifecycle.helpers.ts
@@ -1,4 +1,3 @@
-import { find } from "../utils/find";
 import {
   toName,
   AppOrParcelStatus,
@@ -15,7 +14,7 @@ export function validLifecycleFn(
 
   function isArrayOfFns(arr) {
     return (
-      Array.isArray(arr) && !find(arr, (item) => typeof item !== "function")
+      Array.isArray(arr) && !arr.find((item) => typeof item !== "function")
     );
   }
 }
diff --git a/src/navigation/navigation-events.ts b/src/navigation/navigation-events.ts
index 28cebe5b..22687028 100644
--- a/src/navigation/navigation-events.ts
+++ b/src/navigation/navigation-events.ts
@@ -1,5 +1,4 @@
 import { reroute } from "./reroute";
-import { find } from "../utils/find";
 import { formatErrorMessage } from "../applications/app-errors";
 import { isInBrowser } from "../utils/runtime-environment";
 import { StartOpts } from "../start";
@@ -164,8 +163,9 @@ export function patchHistoryApi(opts?: StartOpts) {
 
   // True by default, as a performance optimization that reduces
   // the number of extraneous popstate events
-  urlRerouteOnly =
-    opts && opts.hasOwnProperty("urlRerouteOnly") ? opts.urlRerouteOnly : true;
+  urlRerouteOnly = opts?.hasOwnProperty("urlRerouteOnly")
+    ? opts.urlRerouteOnly
+    : true;
 
   historyApiIsPatched = true;
 
@@ -182,7 +182,7 @@ export function patchHistoryApi(opts?: StartOpts) {
     if (typeof fn === "function") {
       if (
         routingEventsListeningTo.indexOf(eventName) >= 0 &&
-        !find(capturedEventListeners[eventName], (listener) => listener === fn)
+        !capturedEventListeners[eventName].find((listener) => listener === fn)
       ) {
         capturedEventListeners[eventName].push(fn);
         return;
@@ -194,7 +194,7 @@ export function patchHistoryApi(opts?: StartOpts) {
 
   window.removeEventListener = function (eventName, listenerFn) {
     if (typeof listenerFn === "function") {
-      if (routingEventsListeningTo.indexOf(eventName) >= 0) {
+      if (routingEventsListeningTo.includes(eventName)) {
         capturedEventListeners[eventName] = capturedEventListeners[
           eventName
         ].filter((fn) => fn !== listenerFn);
diff --git a/src/navigation/reroute.ts b/src/navigation/reroute.ts
index 41b22711..b2debc8c 100644
--- a/src/navigation/reroute.ts
+++ b/src/navigation/reroute.ts
@@ -120,7 +120,7 @@ export function reroute(
   function loadApps(): Promise<string[]> {
     return Promise.resolve().then(() => {
       const loadPromises = appsToLoad.map(toLoadPromise);
-      let succeeded;
+      let succeeded: boolean;
 
       return (
         Promise.all(loadPromises)
diff --git a/src/parcels/mount-parcel.ts b/src/parcels/mount-parcel.ts
index b6335e08..b431bc56 100644
--- a/src/parcels/mount-parcel.ts
+++ b/src/parcels/mount-parcel.ts
@@ -24,7 +24,7 @@ import { toUnmountPromise } from "../lifecycles/unmount";
 import { ensureValidAppTimeouts } from "../applications/timeouts";
 import { formatErrorMessage } from "../applications/app-errors";
 
-let parcelCount = 0;
+let parcelCount: number = 0;
 const rootParcels: ParcelOwner = { name: "rootParcels", parcels: {} };
 
 // This is a public api, exported to users of single-spa
diff --git a/src/utils/assign.ts b/src/utils/assign.ts
deleted file mode 100644
index e2c8fe1b..00000000
--- a/src/utils/assign.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// Object.assign() is not available in IE11. And the babel compiled output for object spread
-// syntax checks a bunch of Symbol stuff and is almost a kb. So this function is the smaller replacement.
-export function assign(...args: object[]): object {
-  for (let i = args.length - 1; i > 0; i--) {
-    for (let key in args[i]) {
-      if (key === "__proto__") {
-        continue;
-      }
-      args[i - 1][key] = args[i][key];
-    }
-  }
-
-  return args[0];
-}
diff --git a/src/utils/find.ts b/src/utils/find.ts
deleted file mode 100644
index 2f1e795b..00000000
--- a/src/utils/find.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/* the array.prototype.find polyfill on npmjs.com is ~20kb (not worth it)
- * and lodash is ~200kb (not worth it)
- */
-
-export function find(arr: any[], func: (item: any) => boolean): any | null {
-  for (let i = 0; i < arr.length; i++) {
-    if (func(arr[i])) {
-      return arr[i];
-    }
-  }
-
-  return null;
-}


Test patch: