Skip to content

Core: Provide a package.json exports setup inspired by jQuery Core #566

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

Merged
merged 1 commit into from
Apr 14, 2025
Merged
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
5 changes: 1 addition & 4 deletions .github/workflows/browser-tests.yml
Original file line number Diff line number Diff line change
@@ -43,10 +43,7 @@ jobs:
run: npm install

- name: Run tests
run: |
npm run pretest
npm run test:unit -- -c jtr-local.yml \
--headless -b ${{ matrix.BROWSER }} -f plugin=${{ matrix.MIGRATE_VERSION }}
run: npm run test:browser -- -f plugin=${{ matrix.MIGRATE_VERSION }}

ie:
runs-on: windows-latest
3 changes: 3 additions & 0 deletions .github/workflows/browserstack.yml
Original file line number Diff line number Diff line change
@@ -58,6 +58,9 @@ jobs:
- name: Install dependencies
run: npm install

- name: Build jQuery
run: npm run build:all

- name: Pretest script
run: npm run pretest

27 changes: 17 additions & 10 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -5,32 +5,39 @@ on:
push:
branches-ignore: "dependabot/**"

env:
NODE_VERSION: 22.x

jobs:
node-smoke-test:
build-and-test:
runs-on: ubuntu-latest
name: Node smoke tests
name: ${{ matrix.NPM_SCRIPT }} - ${{ matrix.NAME }} (${{ matrix.NODE_VERSION }})
strategy:
fail-fast: false
matrix:
NAME: ["Node"]
NODE_VERSION: [18.x, 20.x, 22.x, 23.x]
NPM_SCRIPT: ["test:browserless"]
include:
- NAME: "Node"
NODE_VERSION: "22.x"
NPM_SCRIPT: "lint"
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
with:
node-version: ${{ env.NODE_VERSION }}
node-version: ${{ matrix.NODE_VERSION }}

- name: Cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock-${{ hashFiles('**/package-lock.json') }}
key: ${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-npm-lock-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-${{ env.NODE_VERSION }}-npm-lock-
${{ runner.os }}-node-${{ matrix.NODE_VERSION }}-npm-lock-

- name: Install dependencies
run: npm install

- name: Run Node smoke tests
run: npm run test:node_smoke_tests
- name: Run tests
run: npm run ${{ matrix.NPM_SCRIPT }}
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -11,9 +11,16 @@ CDN
.eslintcache

# Ignore built files in `dist` folder
# Leave package.json
# Leave the package.json and wrappers
/dist/*
!/dist/package.json
!/dist/wrappers

# Ignore built files in `dist-module` folder
# Leave the package.json and wrappers
/dist-module/*
!/dist-module/package.json
!/dist-module/wrappers

/external
/node_modules
45 changes: 45 additions & 0 deletions build/command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import yargs from "yargs/yargs";
import { build } from "./tasks/build.js";

const argv = yargs( process.argv.slice( 2 ) )
.version( false )
.command( {
command: "[options]",
describe: "Build a jQuery Migrate bundle"
} )
.option( "filename", {
alias: "f",
type: "string",
description:
"Set the filename of the built file. Defaults to jquery.js."
} )
.option( "dir", {
alias: "d",
type: "string",
description:
"Set the dir to which to output the built file. Defaults to /dist."
} )
.option( "version", {
alias: "v",
type: "string",
description:
"Set the version to include in the built file. " +
"Defaults to the version in package.json plus the " +
"short commit SHA and any excluded modules."
} )
.option( "watch", {
alias: "w",
type: "boolean",
description:
"Watch the source files and rebuild when they change."
} )
.option( "esm", {
type: "boolean",
description:
"Build an ES module (ESM) bundle. " +
"By default, a UMD bundle is built."
} )
.help()
.argv;

build( argv );
3 changes: 0 additions & 3 deletions build/tasks/build-default.js

This file was deleted.

3 changes: 0 additions & 3 deletions build/tasks/build-watch.js

This file was deleted.

73 changes: 54 additions & 19 deletions build/tasks/build.js
Original file line number Diff line number Diff line change
@@ -22,6 +22,33 @@ async function readJSON( filename ) {
return JSON.parse( await read( filename ) );
}

async function getOutputRollupOptions( {
esm = false
} = {} ) {
const wrapperFilePath = path.join( "src", `wrapper${
esm ? "-esm" : ""
}.js` );

const wrapperSource = await read( wrapperFilePath );

// Catch `// @CODE` and subsequent comment lines event if they don't start
// in the first column.
const wrapper = wrapperSource.split(
/[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/
);

return {

// The ESM format is not actually used as we strip it during the
// build, inserting our own wrappers; it's just that it doesn't
// generate any extra wrappers so there's nothing for us to remove.
format: "esm",

intro: wrapper[ 0 ].replace( /\n*$/, "" ),
outro: wrapper[ 1 ].replace( /^\n*/, "" )
};
}

async function writeCompiled( { code, dir, filename, version } ) {
const compiledContents = code

@@ -34,12 +61,12 @@ async function writeCompiled( { code, dir, filename, version } ) {

await writeFile( path.join( dir, filename ), compiledContents );
console.log( `[${ getTimestamp() }] ${ filename } v${ version } created.` );
await minify( { dir, filename, version } );
}

export async function build( {
dir = "dist",
filename = "jquery-migrate.js",
esm = false,
watch = false,
version
} = {} ) {
@@ -59,24 +86,8 @@ export async function build( {
}`;
}

// Catch `// @CODE` and subsequent comment lines event if they don't start
// in the first column.
const wrapperSrc = await read( "src/wrapper.js" );
const wrapper = wrapperSrc.split(
/[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/
);

const inputRollupOptions = {};
const outputRollupOptions = {

// The ESM format is not actually used as we strip it during
// the build; it's just that it doesn't generate any extra
// wrappers so there's nothing for us to remove.
format: "esm",

intro: wrapper[ 0 ].replace( /\n*$/, "" ),
outro: wrapper[ 1 ].replace( /^\n*/, "" )
};
const outputRollupOptions = await getOutputRollupOptions( { esm } );
const src = "src/migrate.js";

inputRollupOptions.input = path.resolve( src );
@@ -122,9 +133,33 @@ export async function build( {
} = await bundle.generate( outputRollupOptions );

await writeCompiled( { code, dir, filename, version } );
await minify( { dir, filename, version } );
}
}

export async function buildDefaultFiles( {
version = process.env.VERSION,
watch
} = {} ) {
await Promise.all( [
build( { version, watch } ),
build( {
dir: "dist-module",
filename: "jquery-migrate.module.js",
esm: true,
version,
watch
} )
] );

if ( watch ) {
console.log( "Watching files..." );
} else {
return compareSize( {
files: [ "dist/jquery-migrate.min.js" ]
files: [
"dist/jquery-migrate.min.js",
"dist-module/jquery-migrate.module.min.js"
]
} );
}
}
5 changes: 5 additions & 0 deletions dist-module/wrappers/jquery-migrate.node-module-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Node.js is able to import from a CommonJS module in an ESM one.
import jQuery from "../../dist/jquery-migrate.js";

export { jQuery, jQuery as $ };
export default jQuery;
5 changes: 5 additions & 0 deletions dist/wrappers/jquery-migrate.bundler-require-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";

// Bundlers are able to synchronously require an ESM module from a CommonJS one.
const { jQuery } = require( "../../dist-module/jquery-migrate.module.js" );
module.exports = jQuery;
54 changes: 51 additions & 3 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -12,7 +12,13 @@ export default [
},

{
files: [ "eslint.config.js", "build/**" ],
files: [
"eslint.config.js",
".release-it.cjs",
"build/**",
"test/node_smoke_tests/**",
"test/bundler_smoke_tests/**/*"
],
languageOptions: {
ecmaVersion: "latest",
globals: {
@@ -60,18 +66,44 @@ export default [
}
},

{
files: [
"src/wrapper.js",
"src/wrapper-esm.js",
"src/wrapper-factory.js",
"src/wrapper-factory-esm.js"
],
languageOptions: {
globals: {
jQuery: false
}
},
rules: {
"no-unused-vars": "off",
indent: [
"error",
"tab",
{

// This makes it so code within the wrapper is not indented.
ignoredNodes: [
"Program > FunctionDeclaration > *"
]
}
]
}
},

{
files: [ "src/wrapper.js" ],
languageOptions: {
sourceType: "script",
globals: {
jQuery: false,
define: false,
module: false
}
},
rules: {
"no-unused-vars": "off",
indent: [
"error",
"tab",
@@ -203,5 +235,21 @@ export default [
ecmaVersion: 5,
sourceType: "script"
}
},

{
files: [ "dist-module/**" ],
languageOptions: {
ecmaVersion: 2015,
sourceType: "module"
}
},

{
files: [ "dist/wrappers/*.js" ],
languageOptions: {
ecmaVersion: 2015,
sourceType: "commonjs"
}
}
];
1,165 changes: 1,164 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

42 changes: 32 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -2,9 +2,21 @@
"name": "jquery-migrate",
"title": "jQuery Migrate",
"description": "Migrate older jQuery code to jQuery 4.x",
"main": "dist/jquery-migrate.js",
"version": "4.0.0-pre",
"type": "module",
"exports": {
"node": {
"import": "./dist-module/wrappers/jquery-migrate.node-module-wrapper.js",
"default": "./dist/jquery-migrate.js"
},
"module": {
"import": "./dist-module/jquery-migrate.module.js",
"default": "./dist/wrappers/jquery-migrate.bundler-require-wrapper.js"
},
"import": "./dist-module/jquery-migrate.module.js",
"default": "./dist/jquery-migrate.js"
},
"main": "dist/jquery-migrate.js",
"homepage": "https://github.com/jquery/jquery-migrate",
"author": {
"name": "OpenJS Foundation and other contributors",
@@ -19,24 +31,32 @@
},
"license": "MIT",
"scripts": {
"build": "node build/tasks/build-default.js",
"build": "node ./build/command.js",
"build:all": "node --input-type=module -e \"import { buildDefaultFiles } from './build/tasks/build.js'; buildDefaultFiles()\"",
"build:clean": "rimraf --glob dist/*.{js,map} --glob dist-module/*.{js,map}",
"build:main": "node --input-type=module -e \"import { build } from './build/tasks/build.js'; build()\"",
"lint": "eslint --cache .",
"npmcopy": "node build/tasks/npmcopy.js",
"prepare": "husky",
"pretest": "npm run npmcopy && npm run build && npm run lint",
"start": "npm run npmcopy && node build/tasks/build-watch.js",
"test:browser": "npm run pretest && npm run test:unit -- -b chrome -b firefox --headless",
"test:ie": "npm run pretest && npm run test:unit -- -v -b ie",
"test:node_smoke_tests": "npm run pretest && node test/node_smoke_tests/smoke_tests.cjs",
"test:safari": "npm run pretest && npm run test:unit -- -v -b safari",
"pretest": "npm run npmcopy",
"start": "node --input-type=module -e \"import { buildDefaultFiles } from './build/tasks/build.js'; buildDefaultFiles({ watch: true })\"",
"test:browser": "npm run pretest && npm run build:all && npm run test:unit -- -b chrome -b firefox --headless",
"test:browserless": "npm run pretest && npm run build:all && node test/bundler_smoke_tests/run-jsdom-tests.js && node test/node_smoke_tests/node_smoke_tests.cjs",
"test:ie": "npm run pretest && npm run build:all && npm run test:unit -- -v -b ie",
"test:bundlers": "npm run pretest && npm run build:all && node test/bundler_smoke_tests/run-jsdom-tests.js",
"test:node_smoke_tests": "npm run pretest && npm run build:all && node test/node_smoke_tests/node_smoke_tests.cjs",
"test:safari": "npm run pretest && npm run build:all && npm run test:unit -- -v -b safari",
"test:server": "jtr serve",
"test:esm": "npm run pretest && npm run build:main && npm run test:unit -- -f plugin=esmodules --headless ",
"test:unit": "jtr",
"test": "npm run test:node_smoke_tests && npm run test:browser"
"test": "npm run build:all && npm run lint && npm run test:browserless && npm run test:browser && npm run test:esm"
},
"peerDependencies": {
"jquery": ">=4 <5"
},
"devDependencies": {
"@rollup/plugin-commonjs": "28.0.3",
"@rollup/plugin-node-resolve": "16.0.1",
"chalk": "5.4.1",
"commitplease": "3.2.0",
"enquirer": "2.4.1",
@@ -52,7 +72,9 @@
"qunit": "2.24.1",
"rollup": "4.34.8",
"sinon": "9.2.4",
"uglify-js": "3.19.3"
"uglify-js": "3.19.3",
"webpack": "5.98.0",
"yargs": "^17.7.2"
},
"keywords": [
"jquery",
21 changes: 21 additions & 0 deletions src/wrapper-esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*!
* jQuery Migrate - v@VERSION - @DATE
* Copyright OpenJS Foundation and other contributors
*/
import $ from "jquery";

// For ECMAScript module environments where a proper `window`
// is present, execute the factory and get jQuery.
function jQueryFactory( jQuery, window ) {

// @CODE
// build.js inserts compiled jQuery here

return jQuery;
}

var jQuery = jQueryFactory( $, window );

export { jQuery, jQuery as $ };

export default jQuery;
42 changes: 42 additions & 0 deletions test/bundler_smoke_tests/lib/run-rollup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { rollup } from "rollup";

import { loadConfigFile } from "rollup/loadConfigFile";
import path from "node:path";
import { fileURLToPath } from "node:url";

const dirname = path.dirname( fileURLToPath( import.meta.url ) );
const pureEsmConfigPath = path.resolve(
dirname, "..", "rollup-pure-esm.config.js" );
const commonJSConfigPath = path.resolve(
dirname, "..", "rollup-commonjs.config.js" );

// See https://rollupjs.org/javascript-api/#programmatically-loading-a-config-file
async function runRollup( name, configPath ) {

console.log( `Running Rollup, version: ${ name }` );

// options is an array of "inputOptions" objects with an additional
// "output" property that contains an array of "outputOptions".
// We generate a single output so the array only has one element.
const {
options: [ optionsObj ],
warnings
} = await loadConfigFile( configPath, {} );

// "warnings" wraps the default `onwarn` handler passed by the CLI.
// This prints all warnings up to this point:
warnings.flush();

const bundle = await rollup( optionsObj );
await Promise.all( optionsObj.output.map( bundle.write ) );

console.log( `Build completed: Rollup, version: ${ name }` );
}

export async function runRollupPureEsm() {
await runRollup( "pure ESM", pureEsmConfigPath );
}

export async function runRollupEsmAndCommonJs() {
await runRollup( "ESM + CommonJS", commonJSConfigPath );
}
21 changes: 21 additions & 0 deletions test/bundler_smoke_tests/lib/run-webpack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import webpack from "webpack";

// See https://webpack.js.org/api/node/#webpack
export async function runWebpack() {
return new Promise( async( resolve, reject ) => {
console.log( "Running Webpack" );

const { default: config } = await import( "../webpack.config.cjs" );

webpack( config, ( err, stats ) => {
if ( err || stats.hasErrors() ) {
console.error( "Errors detected during Webpack compilation" );
reject( err );
return;
}

console.log( "Build completed: Webpack" );
resolve();
} );
} );
}
13 changes: 13 additions & 0 deletions test/bundler_smoke_tests/lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const dirname = path.dirname( fileURLToPath( import.meta.url ) );
const TMP_BUNDLERS_DIR = path.resolve( dirname, "..", "tmp" );

export async function cleanTmpBundlersDir() {
await fs.rm( TMP_BUNDLERS_DIR, {
force: true,
recursive: true
} );
}
19 changes: 19 additions & 0 deletions test/bundler_smoke_tests/rollup-commonjs.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";

const dirname = path.dirname( fileURLToPath( import.meta.url ) );

export default {
input: `${ dirname }/src-esm-commonjs/main.js`,
output: {
dir: `${ dirname }/tmp/rollup-commonjs`,
format: "iife",
sourcemap: true
},
plugins: [
resolve(),
commonjs()
]
};
17 changes: 17 additions & 0 deletions test/bundler_smoke_tests/rollup-pure-esm.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import resolve from "@rollup/plugin-node-resolve";

const dirname = path.dirname( fileURLToPath( import.meta.url ) );

export default {
input: `${ dirname }/src-pure-esm/main.js`,
output: {
dir: `${ dirname }/tmp/rollup-pure-esm`,
format: "iife",
sourcemap: true
},
plugins: [
resolve()
]
};
73 changes: 73 additions & 0 deletions test/bundler_smoke_tests/run-jsdom-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import fs from "node:fs/promises";
import jsdom, { JSDOM } from "jsdom";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { runRollupEsmAndCommonJs, runRollupPureEsm } from "./lib/run-rollup.js";
import { runWebpack } from "./lib/run-webpack.js";
import { cleanTmpBundlersDir } from "./lib/utils.js";

const dirname = path.dirname( fileURLToPath( import.meta.url ) );

async function runJSDOMTest( { title, folder } ) {
console.log( "Running bundlers tests:", title );

const template = await fs.readFile( `${ dirname }/test.html`, "utf-8" );
const scriptSource = await fs.readFile(
`${ dirname }/tmp/${ folder }/main.js`, "utf-8" );

const html = template
.replace( /@TITLE\b/, () => title )
.replace( /@SCRIPT\b/, () => scriptSource );

const virtualConsole = new jsdom.VirtualConsole();
virtualConsole.sendTo( console );
virtualConsole.on( "assert", ( success ) => {
if ( !success ) {
process.exitCode = 1;
}
} );

new JSDOM( html, {
resources: "usable",
runScripts: "dangerously",
virtualConsole
} );

if ( process.exitCode === 0 || process.exitCode == null ) {
console.log( "Bundlers tests passed for:", title );
} else {
console.error( "Bundlers tests failed for:", title );
}
}

async function buildAndTest() {
await cleanTmpBundlersDir();

await Promise.all( [
runRollupPureEsm(),
runRollupEsmAndCommonJs(),
runWebpack()
] );

await Promise.all( [
runJSDOMTest( {
title: "Rollup with pure ESM setup",
folder: "rollup-pure-esm"
} ),

runJSDOMTest( {
title: "Rollup with ESM + CommonJS",
folder: "rollup-commonjs"
} ),

runJSDOMTest( {
title: "Webpack",
folder: "webpack"
} )
] );

// The directory won't be cleaned in case of failures; this may aid debugging.
await cleanTmpBundlersDir();
}

await buildAndTest();
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"use strict";

const $ = require( "jquery-migrate" );

module.exports.$required = $;
10 changes: 10 additions & 0 deletions test/bundler_smoke_tests/src-esm-commonjs/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { $ as $imported } from "jquery-migrate";

import { $required } from "./jquery-migrate-require.cjs";

console.assert( $required === $imported,
"Only one copy of full jQuery should exist" );
console.assert( /^jQuery/.test( $imported.expando ),
"jQuery.expando should be detected on full jQuery" );
console.assert( typeof $imported.migrateVersion === "string" && $imported.migrateVersion.length > 0,
"jQuery.migrateVersion was not detected, the jQuery Migrate bootstrap process has failed" );
6 changes: 6 additions & 0 deletions test/bundler_smoke_tests/src-pure-esm/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { $ } from "jquery-migrate";

console.assert( /^jQuery/.test( $.expando ),
"jQuery.expando should be detected on full jQuery" );
console.assert( typeof $.migrateVersion === "string" && $.migrateVersion.length > 0,
"jQuery.migrateVersion was not detected, the jQuery Migrate bootstrap process has failed" );
11 changes: 11 additions & 0 deletions test/bundler_smoke_tests/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html>
<head lang='en'>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width'>
<title>Bundlers tests: @TITLE</title>
</head>
<body>
<script>@SCRIPT</script>
</body>
</html>
9 changes: 9 additions & 0 deletions test/bundler_smoke_tests/webpack.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";

module.exports = {
entry: `${ __dirname }/src-esm-commonjs/main.js`,
output: {
filename: "main.js",
path: `${ __dirname }/tmp/webpack`
}
};
Original file line number Diff line number Diff line change
@@ -5,14 +5,12 @@ console.log( "Running Node.js smoke tests..." );
const assert = require( "node:assert/strict" );
const { JSDOM } = require( "jsdom" );

const { window } = new JSDOM( `<!DOCTYPE html><title>$</title>` );
const { window } = new JSDOM( "<!DOCTYPE html><title>$</title>" );

// Set the window global.
globalThis.window = window;

// Require jQuery Migrate. Since Migrate doesn't specify exports,
// `require( "jquery-migrate" )` won't work here.
const $ = require( "../.." );
const $ = require( "jquery-migrate" );

assert( /^jQuery/.test( $.expando ),
"jQuery.expando was not detected, the jQuery bootstrap process has failed" );