From 95105aad6200f844d78bdb240c618d95b869f06e Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Sat, 6 Jul 2024 22:11:54 +0100 Subject: [PATCH] Build: Make internal core compatible with native ESM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add explicit file extensions as per ESM standard, instead of relying on Node.js-specific require() resolution, or Rollup-specific ESM resolution for Node.js compat. * As minor prep for external ESM support, move UMD export to the /src/qunit.js entrypoint. I considered adding an `export` statement to the entrypoint, but this interfers with generating the CJS distribution with Rollup the way we do today. - Adding `export default` to /src/qunit.js, creates a Rollup error about `output: none` for input files that perform exports. - Setting `output: defaults` in Rollup config, creates a warning about having an export while using `format: iife` but having set no name for it. - Adding a name would change the output in a meaningful way, namely it would create an (implied global) variable in the form of `var QUnit = (function () { … return QUnit; }());` - Creating such variable using `var` instead of as `window.QUnit` means that it cannot be unset via `delete window.QUnit` which breaks certain test, and is out of the scope for this pathc. * Fix fragile code in stracktrace.js that previously worked only because of Babel transformations masking a violation of the Temporal Dead Zone between `const fileName` and the functions it uses to compute that value. ``` $ node --experimental-detect-module Welcome to Node.js v21.1.0. Type ".help" for more information. > await import("./src/qunit.js"); Uncaught ReferenceError: Cannot access 'fileName' before initialization at extractStacktrace (file:///Users/krinkle/Development/qunit/src/core/stacktrace.js:51:5) at sourceFromStacktrace (file:///Users/krinkle/Development/qunit/src/core/stacktrace.js:81:10) at file:///Users/krinkle/Development/qunit/src/core/stacktrace.js:35:19 at … at async REPL ``` After this: ``` > await import("./src/qunit.js"); //> null > (await import("./src/core.js")).default; //> { version: …, module: …, test: … } ``` Ref https://github.com/qunitjs/qunit/issues/1551. --- src/assert.js | 12 ++++----- src/browser/browser-runner.js | 4 +-- src/core.js | 45 +++++++++++++++---------------- src/core/config.js | 6 ++--- src/core/hooks.js | 2 +- src/core/logging.js | 4 +-- src/core/on-uncaught-exception.js | 10 +++---- src/core/processing-queue.js | 16 +++++------ src/core/stacktrace.js | 43 ++++++++++++++++++++++------- src/core/utilities.js | 2 +- src/diff.js | 2 +- src/dump.js | 4 +-- src/equiv.js | 4 +-- src/events.js | 4 +-- src/export.js | 2 +- src/logger.js | 2 +- src/module.js | 6 ++--- src/qunit.js | 9 ++++--- src/reporters/ConsoleReporter.js | 2 +- src/reporters/HtmlReporter.js | 12 ++++----- src/reporters/PerfReporter.js | 4 +-- src/reporters/TapReporter.js | 4 +-- src/reports/suite.js | 2 +- src/reports/test.js | 2 +- src/test.js | 22 +++++++-------- src/urlparams.js | 2 +- 26 files changed, 125 insertions(+), 102 deletions(-) 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);