Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build: Add native ESM distribution #1798

Merged
merged 1 commit into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ module.exports = function (grunt) {
'src-css': {
src: 'src/core/qunit.css',
dest: 'qunit/qunit.css'
},
'src-export-cjs-wrappers': {
src: 'src/core/qunit-wrapper-bundler-require.js',
expand: true,
flatten: true,
dest: 'qunit/'
},
'src-export-esm-wrappers': {
src: 'src/core/qunit-wrapper-nodejs-module.js',
expand: true,
flatten: true,
dest: 'qunit/esm/'
}
},
search: {
Expand Down
19 changes: 17 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@
"LICENSE.txt"
],
"main": "qunit/qunit.js",
"exports": {
".": {
"node": {
"import": "./qunit/esm/qunit-wrapper-nodejs-module.js",
"default": "./qunit/qunit.js"
},
"module": {
"import": "./qunit/esm/qunit.module.js",
"default": "./qunit/qunit-wrapper-bundler-require.js"
},
"import": "./qunit/esm/qunit.module.js",
"default": "./qunit/qunit.js"
},
"./qunit/qunit.css": "./qunit/qunit.css"
},
"engines": {
"node": ">=18"
},
Expand Down Expand Up @@ -82,8 +97,8 @@
"tap-min": "^3.0.0"
},
"scripts": {
"build": "rollup -c && grunt copy:src-css",
"build-coverage": "rollup -c --environment BUILD_TARGET:coverage && grunt copy:src-css",
"build": "rollup -c && grunt copy",
"build-coverage": "rollup -c --environment BUILD_TARGET:coverage && grunt copy",
"build-dev": "node build/dev.js",
"benchmark": "npm install --silent --no-audit --prefix test/benchmark/ && node test/benchmark/micro.js",
"lint": "eslint --cache .",
Expand Down
110 changes: 71 additions & 39 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,76 @@ const replace = require('@rollup/plugin-replace');
const { replacements } = require('./build/dist-replace.js');
const isCoverage = process.env.BUILD_TARGET === 'coverage';

module.exports = {
input: 'src/core/qunit.js',
output: {
file: 'qunit/qunit.js',
sourcemap: isCoverage,
format: 'iife',
exports: 'none',
const banner = `/*!
* QUnit @VERSION
* https://qunitjs.com/
*
* Copyright OpenJS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*/`;

// eslint-disable-next-line no-multi-str
banner: '/*!\n\
* QUnit @VERSION\n\
* https://qunitjs.com/\n\
*\n\
* Copyright OpenJS Foundation and other contributors\n\
* Released under the MIT license\n\
* https://jquery.org/license\n\
*/'
},
plugins: [
replace({
preventAssignment: true,
delimiters: ['', ''],
...replacements
}),
nodeResolve(),
commonjs(),
babel({
babelHelpers: 'bundled',
babelrc: false,
presets: [
['@babel/preset-env', {
targets: {
ie: 11,
safari: 7,
node: 18
}
}]
]
})
]
const replacementOptions = {
preventAssignment: true,
delimiters: ['', ''],
...replacements
};

module.exports = [
{
input: 'src/core/qunit-commonjs.js',
output: {
file: 'qunit/qunit.js',
sourcemap: isCoverage,
format: 'iife',
exports: 'none',
banner: banner
},
plugins: [
replace(replacementOptions),
nodeResolve(),
commonjs(),
babel({
babelHelpers: 'bundled',
babelrc: false,
presets: [
['@babel/preset-env', {
targets: {
ie: 11,
safari: 7,
node: 18
}
}]
]
})
]
},
{
input: 'src/core/qunit.js',
output: {
file: 'qunit/esm/qunit.module.js',
format: 'es',
exports: 'named',
banner: banner
},
plugins: [
replace(replacementOptions),
nodeResolve(),
commonjs(),
babel({
babelHelpers: 'bundled',
babelrc: false,
presets: [
['@babel/preset-env', {
// No need to support IE11 in the ESM version,
// which means this will leave ES6 features mostly unchanged.
targets: {
safari: 10,
node: 18
}
}]
]
})
]
}
];
31 changes: 26 additions & 5 deletions src/cli/require-qunit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use strict';

const path = require('path');
const { Module } = require('module');
Krinkle marked this conversation as resolved.
Show resolved Hide resolved

/**
* Depending on the exact usage, QUnit could be in one of several places, this
* function handles finding it.
Expand All @@ -19,12 +22,30 @@ module.exports = function requireQUnit (resolve = require.resolve) {
// If the user (accidentally) ran the CLI command from their global
// install, then we prefer to stil use the qunit library file from the
// current project's dependency.
const localQUnitPath = resolve('qunit', {
//
// NOTE: We can't use require.resolve() because, despite it taking a 'paths'
// option, the resolution algorithm [1] is poisoned by current filename (i.e.
// this src/cli/require-qunit.js file). The documentation doesn't say it,
// but in practice the "paths" option only overrides how step 6 (LOAD_NODE_MODULES)
// traverses directories. It does not influence step 5 (LOAD_PACKAGE_SELF) which
// looks explicilty relative to the current file (regardless of `process.cwd`,
// and regardless of `paths` passed to require.resolve). This wasn't an issue
// until QUnit 3.0 because LOAD_PACKAGE_SELF only looks for cases where
// package.json uses "exports", which QUnit 3.0 adopted for ESM support.
//
// If this uses `requires.resolve(, paths:[cwd])` instead of
// `Module.createRequire(cwd).resolve()`, then this would always return
// the 'qunit' copy that this /src/cli/require-qunit.js file came from,
// regardless of the process.cwd(), which defeats the purpose of looking
// relative to process.cwd().
//
// This is covered by /test/cli/require-qunit-test.js
//
// [1]: https://nodejs.org/docs/latest-v18.x/api/modules.html#all-together
const localQUnitPath = Module.createRequire(
path.join(process.cwd(), 'fake.js')
).resolve('qunit');

// Support: Node 10. Explicitly check "node_modules" to avoid a bug.
// Fixed in Node 12+. See https://github.com/nodejs/node/issues/35367.
paths: [process.cwd() + '/node_modules', process.cwd()]
});
delete require.cache[localQUnitPath];
return require(localQUnitPath);
} catch (e) {
Expand Down
51 changes: 0 additions & 51 deletions src/core/export.js

This file was deleted.

12 changes: 12 additions & 0 deletions src/core/qunit-commonjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* global module, exports */
import QUnit from './qunit.js';

// For Node.js
if (typeof module !== 'undefined' && module && module.exports) {
module.exports = QUnit;
}

// For CommonJS with exports, but without module.exports, like Rhino
if (typeof exports !== 'undefined' && exports) {
exports.QUnit = QUnit;
}
9 changes: 9 additions & 0 deletions src/core/qunit-wrapper-bundler-require.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-env node */

// In a single bundler invocation, if different parts or dependencies
// of a project mix ESM and CJS, avoid a split-brain state by making
// sure both import and re-use the same instance via this wrapper.
//
// Bundlers generally allow requiring an ESM file from CommonJS.
const { QUnit } = require('./esm/qunit.module.js');
module.exports = QUnit;
42 changes: 42 additions & 0 deletions src/core/qunit-wrapper-nodejs-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// In a single Node.js process, if different parts or dependencies
// of a project mix ESM and CJS, avoid a split-brain state by making
// sure both import and re-use the same instance via this wrapper.
//
// Node.js 12+ can import a CommonJS file from ESM.
import QUnit from '../qunit.js';

export const {
assert,
begin,
config,
diff,
done,
dump,
equiv,
hooks,
is,
isLocal,
log,
module,
moduleDone,
moduleStart,
objectType,
on,
only,
onUncaughtException,
pushFailure,
reporters,
skip,
stack,
start,
test,
testDone,
testStart,
todo,
urlParams,
version
} = QUnit;

export { QUnit };

export default QUnit;
Loading