diff --git a/src/assert.js b/src/assert.js
index 4f9eb0c3c..a8aaae4e5 100644
--- a/src/assert.js
+++ b/src/assert.js
@@ -1,10 +1,10 @@
-import dump from './dump';
-import equiv from './equiv';
+import dump from './dump.js';
+import equiv from './equiv.js';
 
-import config from './core/config';
-import { objectType, objectValues, objectValuesSubset, errorString } from './core/utilities';
-import { sourceFromStacktrace } from './core/stacktrace';
-import { clearTimeout } from './globals';
+import config from './core/config.js';
+import { objectType, objectValues, objectValuesSubset, errorString } from './core/utilities.js';
+import { sourceFromStacktrace } from './core/stacktrace.js';
+import { clearTimeout } from './globals.js';
 
 class Assert {
   constructor (testContext) {
diff --git a/src/browser/browser-runner.js b/src/browser/browser-runner.js
index fa925cc2a..04fccc202 100644
--- a/src/browser/browser-runner.js
+++ b/src/browser/browser-runner.js
@@ -1,5 +1,5 @@
-import initFixture from './fixture';
-import initUrlConfig from './urlparams';
+import initFixture from './fixture.js';
+import initUrlConfig from './urlparams.js';
 
 export function initBrowser (QUnit, window, document) {
   // Report uncaught exceptions to QUnit.
diff --git a/src/core.js b/src/core.js
index 34d4ef0b9..9c684e46d 100644
--- a/src/core.js
+++ b/src/core.js
@@ -1,25 +1,24 @@
-import { window, document, setTimeout } from './globals';
-
-import equiv from './equiv';
-import dump from './dump';
-import { runSuite, module } from './module';
-import Assert from './assert';
-import Test, { test, pushFailure } from './test';
-import exportQUnit from './export';
-import reporters from './reporters';
-
-import config from './core/config';
-import hooks from './core/hooks';
-import { extend, objectType, is, performance } from './core/utilities';
-import { registerLoggingCallbacks, runLoggingCallbacks } from './core/logging';
-import { sourceFromStacktrace } from './core/stacktrace';
-import ProcessingQueue from './core/processing-queue';
-
-import { urlParams } from './urlparams';
-import { on, emit } from './events';
-import onUncaughtException from './core/on-uncaught-exception';
-import diff from './diff';
-import version from './version';
+import { window, document, setTimeout } from './globals.js';
+
+import equiv from './equiv.js';
+import dump from './dump.js';
+import { runSuite, module } from './module.js';
+import Assert from './assert.js';
+import Test, { test, pushFailure } from './test.js';
+import reporters from './reporters.js';
+
+import config from './core/config.js';
+import hooks from './core/hooks.js';
+import { extend, objectType, is, performance } from './core/utilities.js';
+import { registerLoggingCallbacks, runLoggingCallbacks } from './core/logging.js';
+import { sourceFromStacktrace } from './core/stacktrace.js';
+import ProcessingQueue from './core/processing-queue.js';
+
+import { urlParams } from './urlparams.js';
+import { on, emit } from './events.js';
+import onUncaughtException from './core/on-uncaught-exception.js';
+import diff from './diff.js';
+import version from './version.js';
 
 const QUnit = {};
 
@@ -144,6 +143,4 @@ function begin () {
   }).then(unblockAndAdvanceQueue);
 }
 
-exportQUnit(QUnit);
-
 export default QUnit;
diff --git a/src/core/config.js b/src/core/config.js
index 673cd61a6..9071bb730 100644
--- a/src/core/config.js
+++ b/src/core/config.js
@@ -1,6 +1,6 @@
-import { globalThis, process, sessionStorage } from '../globals';
-import { urlParams } from '../urlparams';
-import { extend } from './utilities';
+import { globalThis, process, sessionStorage } from '../globals.js';
+import { urlParams } from '../urlparams.js';
+import { extend } from './utilities.js';
 
 /**
  * Config object: Maintain internal state
diff --git a/src/core/hooks.js b/src/core/hooks.js
index 1a5249c3e..cbe78ebf8 100644
--- a/src/core/hooks.js
+++ b/src/core/hooks.js
@@ -1,4 +1,4 @@
-import config from './config';
+import config from './config.js';
 
 function makeAddGlobalHook (hookName) {
   return function addGlobalHook (callback) {
diff --git a/src/core/logging.js b/src/core/logging.js
index ae5e5bc59..6406fdd33 100644
--- a/src/core/logging.js
+++ b/src/core/logging.js
@@ -1,5 +1,5 @@
-import config from './config';
-import Promise from '../promise';
+import config from './config.js';
+import Promise from '../promise.js';
 
 // Register logging callbacks
 export function registerLoggingCallbacks (obj) {
diff --git a/src/core/on-uncaught-exception.js b/src/core/on-uncaught-exception.js
index 889fa2c1b..47d36d8f9 100644
--- a/src/core/on-uncaught-exception.js
+++ b/src/core/on-uncaught-exception.js
@@ -1,8 +1,8 @@
-import config from './config';
-import { runSuite } from '../module';
-import { sourceFromStacktrace } from './stacktrace';
-import { errorString } from './utilities';
-import { emit } from '../events';
+import config from './config.js';
+import { runSuite } from '../module.js';
+import { sourceFromStacktrace } from './stacktrace.js';
+import { errorString } from './utilities.js';
+import { emit } from '../events.js';
 
 /**
  * Handle a global error that should result in a failed test run.
diff --git a/src/core/processing-queue.js b/src/core/processing-queue.js
index a36b3011f..b8c4201a2 100644
--- a/src/core/processing-queue.js
+++ b/src/core/processing-queue.js
@@ -1,11 +1,11 @@
-import config from './config';
-import { extend, generateHash, performance } from './utilities';
-import { runLoggingCallbacks } from './logging';
-
-import Promise from '../promise';
-import { runSuite } from '../module';
-import { emit } from '../events';
-import { setTimeout } from '../globals';
+import config from './config.js';
+import { extend, generateHash, performance } from './utilities.js';
+import { runLoggingCallbacks } from './logging.js';
+
+import Promise from '../promise.js';
+import { runSuite } from '../module.js';
+import { emit } from '../events.js';
+import { setTimeout } from '../globals.js';
 
 /**
  * Creates a seeded "sample" generator which is used for randomizing tests.
diff --git a/src/core/stacktrace.js b/src/core/stacktrace.js
index 3798234ec..8ffa92a51 100644
--- a/src/core/stacktrace.js
+++ b/src/core/stacktrace.js
@@ -31,20 +31,43 @@
 //
 // See also:
 // - https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
-//
-const fileName = (sourceFromStacktrace(0) || '')
-  // Global replace, because a frame like localhost:4000/lib/qunit.js:1234:50,
-  // would otherwise (harmlessly, but uselessly) remove only the port (first match).
-  // https://github.com/qunitjs/qunit/issues/1769
-  .replace(/(:\d+)+\)?/g, '')
-  // Remove anything prior to the last slash (Unix/Windows) from the last frame,
-  // leaving only "qunit.js".
-  .replace(/.+[/\\]/, '');
+
+function qunitFileName () {
+  let error = new Error();
+  if (!error.stack) {
+    // Copy of sourceFromStacktrace() to avoid circular dependency
+    // Support: IE 11
+    try {
+      throw error;
+    } catch (err) {
+      error = err;
+    }
+  }
+  return (error.stack || '')
+    // Copy of extractStacktrace() to avoid circular dependency
+    // Support: V8/Chrome
+    .replace(/^error$\n/im, '')
+    .split('\n')[0]
+    // Global replace, because a frame like localhost:4000/lib/qunit.js:1234:50,
+    // would otherwise (harmlessly, but uselessly) remove only the port (first match).
+    // https://github.com/qunitjs/qunit/issues/1769
+    .replace(/(:\d+)+\)?/g, '')
+    // Remove anything prior to the last slash (Unix/Windows) from the last frame,
+    // leaving only "qunit.js".
+    .replace(/.+[/\\]/, '');
+}
+
+const fileName = qunitFileName();
+
 export function extractStacktrace (e, offset) {
   offset = offset === undefined ? 4 : offset;
 
   if (e && e.stack) {
     const stack = e.stack.split('\n');
+    // In Firefox and Safari, e.stack starts immediately with the first frame.
+    //
+    // In V8 (Chrome/Node.js), the stack starts first with a stringified error message,
+    // and the real stack starting on line 2.
     if (/^error$/i.test(stack[0])) {
       stack.shift();
     }
@@ -67,7 +90,7 @@ export function extractStacktrace (e, offset) {
 export function sourceFromStacktrace (offset) {
   let error = new Error();
 
-  // Support: Safari <=7, IE 11
+  // Support: IE 11
   // Not all browsers generate the `stack` property for `new Error()`
   // See also https://github.com/qunitjs/qunit/issues/636
   if (!error.stack) {
diff --git a/src/core/utilities.js b/src/core/utilities.js
index a9dc85999..12da5bc8c 100644
--- a/src/core/utilities.js
+++ b/src/core/utilities.js
@@ -1,4 +1,4 @@
-import { window } from '../globals';
+import { window } from '../globals.js';
 
 export const toString = Object.prototype.toString;
 export const hasOwn = Object.prototype.hasOwnProperty;
diff --git a/src/diff.js b/src/diff.js
index 296be6fe9..23b0047a6 100644
--- a/src/diff.js
+++ b/src/diff.js
@@ -1,5 +1,5 @@
 /* eslint-disable indent */
-import { escapeText } from './core/utilities';
+import { escapeText } from './core/utilities.js';
 
 /*
  * This file is a modified version of google-diff-match-patch's JavaScript implementation
diff --git a/src/dump.js b/src/dump.js
index 2f4174f42..5dcdaaee7 100644
--- a/src/dump.js
+++ b/src/dump.js
@@ -28,8 +28,8 @@
 // POSSIBILITY OF SUCH DAMAGE.
 // -------
 
-import config from './core/config';
-import { inArray, toString, is } from './core/utilities';
+import config from './core/config.js';
+import { inArray, toString, is } from './core/utilities.js';
 
 export default (function () {
   function quote (str) {
diff --git a/src/equiv.js b/src/equiv.js
index c67fcf4e6..8e512e1c8 100644
--- a/src/equiv.js
+++ b/src/equiv.js
@@ -1,5 +1,5 @@
-import { objectType } from './core/utilities';
-import { StringSet } from './globals';
+import { objectType } from './core/utilities.js';
+import { StringSet } from './globals.js';
 
 const BOXABLE_TYPES = new StringSet(['boolean', 'number', 'string']);
 
diff --git a/src/events.js b/src/events.js
index 5be7b5327..fe02b3a24 100644
--- a/src/events.js
+++ b/src/events.js
@@ -1,5 +1,5 @@
-import { inArray } from './core/utilities';
-import config from './core/config';
+import { inArray } from './core/utilities.js';
+import config from './core/config.js';
 
 const SUPPORTED_EVENTS = [
   'error',
diff --git a/src/export.js b/src/export.js
index cbf15b3c3..1802f45cc 100644
--- a/src/export.js
+++ b/src/export.js
@@ -1,5 +1,5 @@
 /* global module, exports */
-import { window, document, globalThis } from './globals';
+import { window, document, globalThis } from './globals.js';
 
 export default function exportQUnit (QUnit) {
   let exportedModule = false;
diff --git a/src/logger.js b/src/logger.js
index 2034387ea..0ab042d3b 100644
--- a/src/logger.js
+++ b/src/logger.js
@@ -1,4 +1,4 @@
-import { console } from './globals';
+import { console } from './globals.js';
 
 // Support: SpiderMonkey (mozjs 68+)
 // The console object has a log method, but no warn method.
diff --git a/src/module.js b/src/module.js
index 8990ca25c..f1cdef561 100644
--- a/src/module.js
+++ b/src/module.js
@@ -1,6 +1,6 @@
-import config from './core/config';
-import SuiteReport from './reports/suite';
-import { extend, generateHash, isAsyncFunction } from './core/utilities';
+import config from './core/config.js';
+import SuiteReport from './reports/suite.js';
+import { extend, generateHash, isAsyncFunction } from './core/utilities.js';
 
 const moduleStack = [];
 
diff --git a/src/qunit.js b/src/qunit.js
index 1f9cbccd5..47c8bf869 100644
--- a/src/qunit.js
+++ b/src/qunit.js
@@ -1,6 +1,9 @@
-import QUnit from './core';
-import { initBrowser } from './browser/browser-runner';
-import { window, document } from './globals';
+import QUnit from './core.js';
+import { initBrowser } from './browser/browser-runner.js';
+import { window, document } from './globals.js';
+import exportQUnit from './export.js';
+
+exportQUnit(QUnit);
 
 if (window && document) {
   initBrowser(QUnit, window, document);
diff --git a/src/reporters/ConsoleReporter.js b/src/reporters/ConsoleReporter.js
index 688664299..edf00ce3a 100644
--- a/src/reporters/ConsoleReporter.js
+++ b/src/reporters/ConsoleReporter.js
@@ -1,4 +1,4 @@
-import { console } from '../globals';
+import { console } from '../globals.js';
 
 export default class ConsoleReporter {
   constructor (runner, options = {}) {
diff --git a/src/reporters/HtmlReporter.js b/src/reporters/HtmlReporter.js
index 9bf4905cc..468862fba 100644
--- a/src/reporters/HtmlReporter.js
+++ b/src/reporters/HtmlReporter.js
@@ -1,9 +1,9 @@
-import { extend, errorString, escapeText } from '../core/utilities';
-import diff from '../diff';
-import dump from '../dump';
-import { window, document, navigator, StringMap } from '../globals';
-import { urlParams } from '../urlparams';
-import version from '../version';
+import { extend, errorString, escapeText } from '../core/utilities.js';
+import diff from '../diff.js';
+import dump from '../dump.js';
+import { window, document, navigator, StringMap } from '../globals.js';
+import { urlParams } from '../urlparams.js';
+import version from '../version.js';
 import fuzzysort from 'fuzzysort';
 
 const hasOwn = Object.prototype.hasOwnProperty;
diff --git a/src/reporters/PerfReporter.js b/src/reporters/PerfReporter.js
index 9a49c7dc0..aad625c0b 100644
--- a/src/reporters/PerfReporter.js
+++ b/src/reporters/PerfReporter.js
@@ -1,5 +1,5 @@
-import { window } from '../globals';
-import Logger from '../logger';
+import { window } from '../globals.js';
+import Logger from '../logger.js';
 
 // TODO: Consider using globalThis instead of window, so that the reporter
 // works for Node.js as well. As this can add overhead, we should make
diff --git a/src/reporters/TapReporter.js b/src/reporters/TapReporter.js
index b662965a4..c52ffff78 100644
--- a/src/reporters/TapReporter.js
+++ b/src/reporters/TapReporter.js
@@ -1,6 +1,6 @@
 import kleur from 'kleur';
-import { errorString } from '../core/utilities';
-import { console } from '../globals';
+import { errorString } from '../core/utilities.js';
+import { console } from '../globals.js';
 const hasOwn = Object.prototype.hasOwnProperty;
 
 /**
diff --git a/src/reports/suite.js b/src/reports/suite.js
index dae864303..621b4ad9b 100644
--- a/src/reports/suite.js
+++ b/src/reports/suite.js
@@ -1,4 +1,4 @@
-import { performance } from '../core/utilities';
+import { performance } from '../core/utilities.js';
 
 export default class SuiteReport {
   constructor (name, parentSuite) {
diff --git a/src/reports/test.js b/src/reports/test.js
index b58daf0be..865c9614b 100644
--- a/src/reports/test.js
+++ b/src/reports/test.js
@@ -1,4 +1,4 @@
-import { extend, performance } from '../core/utilities';
+import { extend, performance } from '../core/utilities.js';
 
 export default class TestReport {
   constructor (name, suite, options) {
diff --git a/src/test.js b/src/test.js
index a8d93bf36..7e56576f5 100644
--- a/src/test.js
+++ b/src/test.js
@@ -1,10 +1,10 @@
-import { globalThis, setTimeout, clearTimeout, StringMap } from './globals';
-import { emit } from './events';
-import Assert from './assert';
-import Logger from './logger';
-import Promise from './promise';
+import { globalThis, setTimeout, clearTimeout, StringMap } from './globals.js';
+import { emit } from './events.js';
+import Assert from './assert.js';
+import Logger from './logger.js';
+import Promise from './promise.js';
 
-import config from './core/config';
+import config from './core/config.js';
 import {
   diff,
   errorString,
@@ -13,12 +13,12 @@ import {
   hasOwn,
   inArray,
   performance
-} from './core/utilities';
-import { runLoggingCallbacks } from './core/logging';
-import { extractStacktrace, sourceFromStacktrace } from './core/stacktrace';
-import dump from './dump';
+} from './core/utilities.js';
+import { runLoggingCallbacks } from './core/logging.js';
+import { extractStacktrace, sourceFromStacktrace } from './core/stacktrace.js';
+import dump from './dump.js';
 
-import TestReport from './reports/test';
+import TestReport from './reports/test.js';
 
 export default function Test (settings) {
   this.expected = null;
diff --git a/src/urlparams.js b/src/urlparams.js
index dd0b69397..740875872 100644
--- a/src/urlparams.js
+++ b/src/urlparams.js
@@ -1,4 +1,4 @@
-import { window } from './globals';
+import { window } from './globals.js';
 
 function getUrlParams () {
   const urlParams = Object.create(null);