Skip to content

Commit

Permalink
Build: Add native ESM distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
Krinkle committed Aug 28, 2024
1 parent 86c5b20 commit e18fedb
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 123 deletions.
12 changes: 12 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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
21 changes: 19 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@
"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": {
"default": "./qunit/qunit.css"
}
},
"engines": {
"node": ">=18"
},
Expand Down Expand Up @@ -82,8 +99,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 @@ -8,44 +8,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,3 +1,6 @@
const path = require('path');
const { Module } = require('module');

// Depending on the exact usage, QUnit could be in one of several places, this
// function handles finding it.
module.exports = function requireQUnit (resolve = require.resolve) {
Expand All @@ -15,12 +18,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 18+ 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

0 comments on commit e18fedb

Please sign in to comment.