Skip to content

Commit

Permalink
Build: Make internal core compatible with native ESM
Browse files Browse the repository at this point in the history
* 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 #1551.
  • Loading branch information
Krinkle committed Jul 6, 2024
1 parent 52f37b8 commit 95105aa
Show file tree
Hide file tree
Showing 26 changed files with 125 additions and 102 deletions.
12 changes: 6 additions & 6 deletions src/assert.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/browser/browser-runner.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
45 changes: 21 additions & 24 deletions src/core.js
Original file line number Diff line number Diff line change
@@ -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 = {};

Expand Down Expand Up @@ -144,6 +143,4 @@ function begin () {
}).then(unblockAndAdvanceQueue);
}

exportQUnit(QUnit);

export default QUnit;
6 changes: 3 additions & 3 deletions src/core/config.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/core/hooks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import config from './config';
import config from './config.js';

function makeAddGlobalHook (hookName) {
return function addGlobalHook (callback) {
Expand Down
4 changes: 2 additions & 2 deletions src/core/logging.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
10 changes: 5 additions & 5 deletions src/core/on-uncaught-exception.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
16 changes: 8 additions & 8 deletions src/core/processing-queue.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
43 changes: 33 additions & 10 deletions src/core/stacktrace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/utilities.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/diff.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/dump.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/equiv.js
Original file line number Diff line number Diff line change
@@ -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']);

Expand Down
4 changes: 2 additions & 2 deletions src/events.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
2 changes: 1 addition & 1 deletion src/export.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/logger.js
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
6 changes: 3 additions & 3 deletions src/module.js
Original file line number Diff line number Diff line change
@@ -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 = [];

Expand Down
9 changes: 6 additions & 3 deletions src/qunit.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/reporters/ConsoleReporter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { console } from '../globals';
import { console } from '../globals.js';

export default class ConsoleReporter {
constructor (runner, options = {}) {
Expand Down
12 changes: 6 additions & 6 deletions src/reporters/HtmlReporter.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/reporters/PerfReporter.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/reporters/TapReporter.js
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand Down
2 changes: 1 addition & 1 deletion src/reports/suite.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { performance } from '../core/utilities';
import { performance } from '../core/utilities.js';

export default class SuiteReport {
constructor (name, parentSuite) {
Expand Down
2 changes: 1 addition & 1 deletion src/reports/test.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
22 changes: 11 additions & 11 deletions src/test.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/urlparams.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { window } from './globals';
import { window } from './globals.js';

function getUrlParams () {
const urlParams = Object.create(null);
Expand Down

0 comments on commit 95105aa

Please sign in to comment.