From 8be4c0e4f89d6c8f780e5937a0534921d8c7815e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Micha=C5=82=20Go=C5=82=C4=99biowski-Owczarek?= Date: Mon, 10 Jul 2023 19:14:08 +0200 Subject: [PATCH] Build: Add `exports` to package.json, export slim & esm builds Summary of the changes: * define the `exports` field in `package.json`; `jQuery` & `$` are also exported as named exports in ESM builds now * declare `"type": "module"` globally except for the `build` folder * add the `--esm` option to `grunt custom`, generating jQuery as an ECMAScript module into the `dist-module` folder * expand `node_smoke_tests` to test the slim & ESM builds and their various combinations; also, test both jQuery loaded via a path to the file as well as from module specifiers that should be parsed via the `exports` feature * add details about ESM usage to the release package README * run `compare_size` on all built minified files; don't run it anymore on unminified files where they don't provide lots of value * remove the remove_map_comment task; SWC doesn't insert the `//# sourceMappingURL=` pragma by default so there's nothing to strip Fixes gh-4592 Closes gh-5255 --- .eslintignore | 7 + .gitignore | 11 +- .npmignore | 2 +- Gruntfile.js => Gruntfile.cjs | 78 ++++-- README.md | 26 +- build/fixtures/README.md | 138 ++++++++++- build/package.json | 3 + build/release.js | 23 +- build/release/cdn.js | 229 ++++++++++-------- build/tasks/build.js | 43 ++-- build/tasks/dist.js | 25 +- build/tasks/node_smoke_tests.js | 66 +++-- build/tasks/promises_aplus_tests.js | 4 +- build/tasks/sourcemap.js | 17 -- dist-module/.eslintrc.json | 45 ++++ dist-module/jquery.node-module-wrapper.js | 4 + .../jquery.node-module-wrapper.slim.js | 4 + dist-module/package.json | 3 + dist/.eslintrc.json | 13 +- dist/package.json | 3 + package.json | 38 ++- src/.eslintrc.json | 24 ++ src/wrapper-esm.js | 41 ++++ src/wrapper.js | 1 + test/data/mock.php | 2 +- ...ockserver.js => middleware-mockserver.cjs} | 0 test/node_smoke_tests/.eslintrc.json | 13 - test/node_smoke_tests/commonjs/.eslintrc.json | 13 + .../commonjs/document_missing.cjs | 15 ++ .../commonjs/document_passed.cjs | 16 ++ .../commonjs/iterable_with_native_symbol.cjs | 14 ++ .../lib/ensure_global_not_created.cjs} | 4 +- .../commonjs/lib/ensure_iterability_es6.cjs | 25 ++ .../lib/ensure_jquery.cjs} | 4 +- .../commonjs/lib/jquery-module-specifier.cjs | 22 ++ .../commonjs/window_present_originally.cjs | 19 ++ test/node_smoke_tests/document_missing.js | 11 - test/node_smoke_tests/document_passed.js | 12 - .../document_present_originally.js | 15 -- .../iterable_with_native_symbol.js | 8 - .../lib/ensure_iterability_es6.js | 25 -- test/node_smoke_tests/module/.eslintrc.json | 13 + .../module/document_missing.js | 13 + .../module/document_passed.js | 14 ++ .../module/iterable_with_native_symbol.js | 12 + .../module/lib/ensure_global_not_created.js | 11 + .../module/lib/ensure_iterability_es6.js | 21 ++ .../module/lib/ensure_jquery.js | 7 + .../module/lib/jquery-module-specifier.js | 21 ++ .../module/window_present_originally.js | 17 ++ .../{deferred.js => deferred.cjs} | 0 .../{when.js => when.cjs} | 0 52 files changed, 887 insertions(+), 308 deletions(-) rename Gruntfile.js => Gruntfile.cjs (83%) create mode 100644 build/package.json delete mode 100644 build/tasks/sourcemap.js create mode 100644 dist-module/.eslintrc.json create mode 100644 dist-module/jquery.node-module-wrapper.js create mode 100644 dist-module/jquery.node-module-wrapper.slim.js create mode 100644 dist-module/package.json create mode 100644 dist/package.json create mode 100644 src/wrapper-esm.js rename test/{middleware-mockserver.js => middleware-mockserver.cjs} (100%) delete mode 100644 test/node_smoke_tests/.eslintrc.json create mode 100644 test/node_smoke_tests/commonjs/.eslintrc.json create mode 100644 test/node_smoke_tests/commonjs/document_missing.cjs create mode 100644 test/node_smoke_tests/commonjs/document_passed.cjs create mode 100644 test/node_smoke_tests/commonjs/iterable_with_native_symbol.cjs rename test/node_smoke_tests/{lib/ensure_global_not_created.js => commonjs/lib/ensure_global_not_created.cjs} (82%) create mode 100644 test/node_smoke_tests/commonjs/lib/ensure_iterability_es6.cjs rename test/node_smoke_tests/{lib/ensure_jquery.js => commonjs/lib/ensure_jquery.cjs} (77%) create mode 100644 test/node_smoke_tests/commonjs/lib/jquery-module-specifier.cjs create mode 100644 test/node_smoke_tests/commonjs/window_present_originally.cjs delete mode 100644 test/node_smoke_tests/document_missing.js delete mode 100644 test/node_smoke_tests/document_passed.js delete mode 100644 test/node_smoke_tests/document_present_originally.js delete mode 100644 test/node_smoke_tests/iterable_with_native_symbol.js delete mode 100644 test/node_smoke_tests/lib/ensure_iterability_es6.js create mode 100644 test/node_smoke_tests/module/.eslintrc.json create mode 100644 test/node_smoke_tests/module/document_missing.js create mode 100644 test/node_smoke_tests/module/document_passed.js create mode 100644 test/node_smoke_tests/module/iterable_with_native_symbol.js create mode 100644 test/node_smoke_tests/module/lib/ensure_global_not_created.js create mode 100644 test/node_smoke_tests/module/lib/ensure_iterability_es6.js create mode 100644 test/node_smoke_tests/module/lib/ensure_jquery.js create mode 100644 test/node_smoke_tests/module/lib/jquery-module-specifier.js create mode 100644 test/node_smoke_tests/module/window_present_originally.js rename test/promises_aplus_adapters/{deferred.js => deferred.cjs} (100%) rename test/promises_aplus_adapters/{when.js => when.cjs} (100%) diff --git a/.eslintignore b/.eslintignore index 2d52f5cda..6c5cdceba 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,13 @@ node_modules dist/** !dist/jquery.js !dist/jquery.min.js +!dist/jquery.slim.js +!dist/jquery.slim.min.js +dist-module/** +!dist-module/jquery.module.js +!dist-module/jquery.module.min.js +!dist-module/jquery.slim.module.js +!dist-module/jquery.slim.module.min.js test/data/jquery-1.9.1.js test/data/badcall.js test/data/badjson.js diff --git a/.gitignore b/.gitignore index 6cb83bb6e..1d43584d8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,18 @@ package-lock.json npm-debug.log* -# Ignore everything in dist folder except for eslint config +# Ignore everything in `dist` folder except for the ESLint config /dist/* !/dist/.eslintrc.json +!/dist/package.json + +# Ignore everything in the `dist-module` folder except for the ESLint config, +# package.json & Node module wrapper files +/dist-module/* +!/dist-module/.eslintrc.json +!/dist-module/package.json +!/dist-module/jquery.node-module-wrapper.js +!/dist-module/jquery.node-module-wrapper.slim.js /external /node_modules diff --git a/.npmignore b/.npmignore index 7fff27ce4..8c96b584a 100644 --- a/.npmignore +++ b/.npmignore @@ -9,4 +9,4 @@ /external /speed /test -/Gruntfile.js +/Gruntfile.cjs diff --git a/Gruntfile.js b/Gruntfile.cjs similarity index 83% rename from Gruntfile.js rename to Gruntfile.cjs index 11260d350..51f36a8e4 100644 --- a/Gruntfile.js +++ b/Gruntfile.cjs @@ -13,19 +13,19 @@ module.exports = function( grunt ) { } const fs = require( "fs" ); + const { spawn } = require( "child_process" ); const gzip = require( "gzip-js" ); - const nodeV14OrNewer = !/^v1[0-3]\./.test( process.version ); + const nodeV16OrNewer = !/^v1[0-5]\./.test( process.version ); const nodeV17OrNewer = !/^v1[0-6]\./.test( process.version ); const customBrowsers = process.env.BROWSERS && process.env.BROWSERS.split( "," ); - // Support: Node.js <14 - // Skip running tasks that dropped support for Node.js 10 or 12 - // in this Node version. + // Support: Node.js <16 + // Skip running tasks that dropped support for old Node.js in these Node versions. function runIfNewNode( task ) { - return nodeV14OrNewer ? task : "print_old_node_message:" + task; + return nodeV16OrNewer ? task : "print_old_node_message:" + task; } - if ( nodeV14OrNewer ) { + if ( nodeV16OrNewer ) { const playwright = require( "playwright-webkit" ); process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath(); } @@ -34,11 +34,27 @@ module.exports = function( grunt ) { grunt.option( "filename", "jquery.js" ); } + grunt.option( "dist-folder", grunt.option( "esm" ) ? "dist-module" : "dist" ); + + const builtJsFiles = [ + "dist/jquery.js", + "dist/jquery.min.js", + "dist/jquery.slim.js", + "dist/jquery.slim.min.js", + "dist-module/jquery.module.js", + "dist-module/jquery.module.min.js", + "dist-module/jquery.slim.module.js", + "dist-module/jquery.slim.module.min.js" + ]; + + const builtJsMinFiles = builtJsFiles + .filter( filepath => filepath.endsWith( ".min.js" ) ); + grunt.initConfig( { pkg: grunt.file.readJSON( "package.json" ), dst: readOptionalJSON( "dist/.destination.json" ), compare_size: { - files: [ "dist/jquery.js", "dist/jquery.min.js" ], + files: builtJsMinFiles, options: { compress: { gz: function( contents ) { @@ -122,7 +138,7 @@ module.exports = function( grunt ) { // We have to explicitly declare "src" property otherwise "newer" // task wouldn't work properly :/ dist: { - src: [ "dist/jquery.js", "dist/jquery.min.js" ] + src: builtJsFiles }, dev: { src: [ @@ -142,9 +158,10 @@ module.exports = function( grunt ) { `!${ filePath }` ), - // Explicitly ignore `dist/` as it could be unignored by - // the above `.eslintignore` parsing. - "!dist/**/*.js" + // Explicitly ignore `dist/` & `dist-module/` as it could be unignored + // by the above `.eslintignore` parsing. + "!dist/**/*.js", + "!dist-module/**/*.js" ] } }, @@ -195,7 +212,7 @@ module.exports = function( grunt ) { { "middleware:mockserver": [ "factory", - require( "./test/middleware-mockserver.js" ) + require( "./test/middleware-mockserver.cjs" ) ] } ], @@ -319,12 +336,15 @@ module.exports = function( grunt ) { minify: { all: { files: { - "dist/<%= grunt.option('filename').replace('.js', '.min.js') %>": - "dist/<%= grunt.option('filename') %>" + [ "<%= grunt.option('dist-folder') %>/" + + "<%= grunt.option('filename').replace(/\\.js$/, '.min.js') %>" ]: + "<%= grunt.option('dist-folder') %>/<%= grunt.option('filename') %>" }, options: { sourceMap: { - filename: "dist/<%= grunt.option('filename').replace('.js', '.min.map') %>", + filename: "<%= grunt.option('dist-folder') %>/" + + "<%= grunt.option('filename')" + + ".replace(/\\.js$/, '.min.map') %>", // The map's `files` & `sources` property are set incorrectly, fix // them via overrides from the task config. @@ -338,7 +358,7 @@ module.exports = function( grunt ) { }, swc: { format: { - ecma: 5, + ecma: grunt.option( "esm" ) ? 2015 : 5, asciiOnly: true, comments: false, preamble: "/*! jQuery v4.0.0-pre | " + @@ -346,7 +366,7 @@ module.exports = function( grunt ) { "jquery.org/license */\n" }, compress: { - ecma: 5, + ecma: grunt.option( "esm" ) ? 2015 : 5, hoist_funs: false, loops: false }, @@ -359,7 +379,7 @@ module.exports = function( grunt ) { // Load grunt tasks from NPM packages require( "load-grunt-tasks" )( grunt, { - pattern: nodeV14OrNewer ? [ "grunt-*" ] : [ "grunt-*", "!grunt-eslint" ] + pattern: nodeV16OrNewer ? [ "grunt-*" ] : [ "grunt-*", "!grunt-eslint" ] } ); // Integrate jQuery specific tasks @@ -370,6 +390,20 @@ module.exports = function( grunt ) { grunt.log.writeln( "Old Node.js detected, running the task \"" + task + "\" skipped..." ); } ); + grunt.registerTask( "build-all-variants", + "Build all variants of the full/slim build & a script/ESM one", + function() { + const done = this.async(); + + spawn( "npm run build-all-variants", { + stdio: "inherit", + shell: true + } ) + .on( "close", code => { + done( code === 0 ); + } ); + } ); + grunt.registerTask( "print_jsdom_message", () => { grunt.log.writeln( "Node.js 17 or newer detected, skipping jsdom tests..." ); } ); @@ -393,7 +427,7 @@ module.exports = function( grunt ) { runIfNewNode( "newer:eslint:dist" ) ] ); - grunt.registerTask( "test:fast", runIfNewNode( "node_smoke_tests" ) ); + grunt.registerTask( "test:fast", [ "node_smoke_tests:commonjs:jquery" ] ); grunt.registerTask( "test:slow", [ runIfNewNode( "promises_aplus_tests" ), @@ -419,7 +453,6 @@ module.exports = function( grunt ) { "build:*:*", runIfNewNode( "newer:eslint:dev" ), "newer:minify", - "remove_map_comment", "dist:*", "qunit_fixture", "compare_size" @@ -427,10 +460,7 @@ module.exports = function( grunt ) { grunt.registerTask( "default", [ runIfNewNode( "eslint:dev" ), - "build:*:*", - "minify", - "remove_map_comment", - "dist:*", + "build-all-variants", "test:prepare", runIfNewNode( "eslint:dist" ), "test:fast", diff --git a/README.md b/README.md index cab646fcd..8d9ff0a93 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ The build process shows a message for each dependent module it excludes or inclu ##### AMD name -As an option, you can set the module name for jQuery's AMD definition. By default, it is set to "jquery", which plays nicely with plugins and third-party libraries, but there may be cases where you'd like to change this. Simply set the `"amd"` option: +As an option, you can set the module name for jQuery's AMD definition. By default, it is set to "jquery", which plays nicely with plugins and third-party libraries, but there may be cases where you'd like to change this. Simply pass it to the `--amd` parameter: ```bash grunt custom --amd="custom-name" @@ -123,6 +123,30 @@ Or, to define anonymously, set the name to an empty string. grunt custom --amd="" ``` +##### File name + +The default name for the built jQuery file is `jquery.js`; it is placed under the `dist/` directory. It's possible to change the file name using the `--filename` parameter: + +```bash +grunt custom:slim --filename="jquery.slim.js" +``` + +This would create a slim version of jQuery and place it under `dist/jquery.slim.js`. In fact, this is exactly the command we use to generate the slim jQuery during the release process. + +##### ECMAScript Module (ESM) mode + +By default, jQuery generates a regular script JavaScript file. You can also generate an ECMAScript module exporting `jQuery` as the default export using the `--esm` parameter: + +```bash +grunt custom --esm +``` + +The default is `script` but you can also pass it explicitly via `--no-esm`: + +```bash +grunt custom --no-esm +``` + #### Custom Build Examples To create a custom build, first check out the version: diff --git a/build/fixtures/README.md b/build/fixtures/README.md index 360eb1c5c..a12e6121c 100644 --- a/build/fixtures/README.md +++ b/build/fixtures/README.md @@ -19,9 +19,78 @@ Below are some of the most common ways to include jQuery. ``` -#### Webpack / Browserify / Babel +or, to use the jQuery ECMAScript module: -There are several ways to use [Webpack](https://webpack.js.org/), [Browserify](http://browserify.org/) or [Babel](https://babeljs.io/). For more information on using these tools, please refer to the corresponding project's documentation. In the script, including jQuery will usually look like this: +```html + +``` + +Sometimes you don’t need AJAX, or you prefer to use one of the many standalone libraries that focus on AJAX requests. And often it is simpler to use a combination of CSS, class manipulation or the Web Animations API. Similarly, many projects opt into relying on native browser promises instead of jQuery Deferreds. Along with the regular version of jQuery that includes the `ajax`, `callbacks`, `deferred`, `effects` & `queue` modules, we’ve released a “slim” version that excludes these modules. You can load it as a regular script: + +```html + +``` + +or as a module: + +```html + +``` + +#### Import maps + +To avoid repeating long import paths that change on each jQuery release, you can use import maps - they are now supported in every modern browser. Put the following script tag before any ` +``` + +Now, the following will work to get the full version: + +```html + +``` + +and the following to get the slim one: + +```html + +``` + +The advantage of these specific mappings is they match the ones embedded in the jQuery npm package, providing better interoperability between the environments. + +You can also use jQuery from npm even in the browser setup. Read along for more details. + +### Using jQuery from npm + +There are several ways to use jQuery from npm. One is to use a build tool like [Webpack](https://webpack.js.org/), [Browserify](http://browserify.org/) or [Babel](https://babeljs.io/). For more information on using these tools, please refer to the corresponding project's documentation. + +Another way is to use jQuery directly in Node.js. See the [Node.js pre-requisites](#nodejs-pre-requisites) section for more details on the Node.js-specific part of this. + +To install the jQuery npm package, invoke: + +```sh +npm install jquery +``` + +In the script, including jQuery will usually look like this: ```js import $ from "jquery"; @@ -30,10 +99,30 @@ import $ from "jquery"; If you need to use jQuery in a file that's not an ECMAScript module, you can use the CommonJS syntax: ```js -var $ = require( "jquery" ); +const $ = require( "jquery" ); ``` -#### AMD (Asynchronous Module Definition) +#### Individual modules + +jQuery is authored in ECMAScript modules; it's also possible to use them directly. They are contained in the `src/` folder; inspect the package contents to see what's there. Full file names are required, including the `.js` extension. + +Be aware that this is an advanced & low-level interface, and we don't consider it stable, even between minor or patch releases - this is especially the case for modules in subdirectories or `src/`. If you rely on it, verify your setup before updating jQuery. + +All top-level modules, i.e. files directly in the `src/` directory export jQuery. Importing multiple modules will all attach to the same jQuery instance. + +Remember that some modules have other dependencies (e.g. the `event` module depends on the `selector` one) so in some cases you may get more than you expect. + +Example usage: + +```js +import $ from "jquery/src/css.js"; // adds the `.css()` method +import "jquery/src/event.js"; // adds the `.on()` method; pulls "selector" as a dependency +$( ".toggle" ).on( "click", function() { + $( this ).css( "color", "red" ); +} ); +``` + +### AMD (Asynchronous Module Definition) AMD is a module format built for the browser. For more information, we recommend [require.js' documentation](https://requirejs.org/docs/whyamd.html). @@ -43,18 +132,49 @@ define( [ "jquery" ], function( $ ) { } ); ``` -### Node +Node.js doesn't understand AMD natively so this method is mostly used in a browser setup. -To include jQuery in [Node](https://nodejs.org/), first install with npm. +### Node.js pre-requisites -```sh -npm install jquery +For jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/jsdom/jsdom). This can be useful for testing purposes. + +jQuery checks for a `window` global with a `document` property and - if one is not present, as is the default in Node.js - it returns a factory accepting a `window` as a parameter instead. + +To `import` jQuery using this factory, use the following: + +```js +import { JSDOM } from "jsdom"; +const { window } = new JSDOM( "" ); +import jQueryFactory from "jquery"; +const $ = jQueryFactory( window ); ``` -For jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/jsdom/jsdom). This can be useful for testing purposes. +or, if you use `require`: ```js const { JSDOM } = require( "jsdom" ); const { window } = new JSDOM( "" ); const $ = require( "jquery" )( window ); ``` + +If the `window` global is present at the moment of the `import` or `require` of `"jquery"`, it will resolve to a jQuery instance, as in the browser. You can set such a global manually to simulate the behavior; with `import`: + +```js +import { JSDOM } from "jsdom"; +const { window } = new JSDOM( "" ); +globalThis.window = window; +const { default: $ } = await import( "jquery" ); +``` + +or with `require`: + +```js +const { JSDOM } = require( "jsdom" ); +const { window } = new JSDOM( "" ); +globalThis.window = window; +const $ = require( "jquery" ); +``` + +#### Slim build in Node.js + +To use the slim build of jQuery in Node.js, use `"jquery/slim"` instead of `"jquery"` in both `require` or `import` calls above. diff --git a/build/package.json b/build/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/build/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/build/release.js b/build/release.js index 379f7de0a..f4509931a 100644 --- a/build/release.js +++ b/build/release.js @@ -10,7 +10,13 @@ module.exports = function( Release ) { "dist/jquery.min.map", "dist/jquery.slim.js", "dist/jquery.slim.min.js", - "dist/jquery.slim.min.map" + "dist/jquery.slim.min.map", + "dist-module/jquery.module.js", + "dist-module/jquery.module.min.js", + "dist-module/jquery.module.min.map", + "dist-module/jquery.slim.module.js", + "dist-module/jquery.slim.module.min.js", + "dist-module/jquery.slim.module.min.map" ]; const filesToCommit = [ ...distFiles, @@ -45,12 +51,8 @@ module.exports = function( Release ) { * @param {Function} callback */ generateArtifacts: function( callback ) { - Release.exec( "npx grunt", "Grunt command failed" ); - Release.exec( - "npx grunt custom:slim --filename=jquery.slim.js && " + - "npx grunt remove_map_comment --filename=jquery.slim.js", - "Grunt custom failed" - ); + Release.exec( "npx grunt" ); + cdn.makeReleaseCopies( Release ); Release._setSrcVersion(); callback( filesToCommit ); @@ -72,10 +74,9 @@ module.exports = function( Release ) { * Publish to distribution repo and npm * @param {Function} callback */ - dist: function( callback ) { - cdn.makeArchives( Release, function() { - dist( Release, distFiles, callback ); - } ); + dist: async callback => { + await cdn.makeArchives( Release ); + dist( Release, distFiles, callback ); } } ); }; diff --git a/build/release/cdn.js b/build/release/cdn.js index 0cbea4a7a..a75ad7303 100644 --- a/build/release/cdn.js +++ b/build/release/cdn.js @@ -1,130 +1,163 @@ "use strict"; -var - fs = require( "fs" ), - shell = require( "shelljs" ), - path = require( "path" ), - os = require( "os" ), - - cdnFolder = "dist/cdn", - - releaseFiles = { - "jquery-VER.js": "dist/jquery.js", - "jquery-VER.min.js": "dist/jquery.min.js", - "jquery-VER.min.map": "dist/jquery.min.map", - "jquery-VER.slim.js": "dist/jquery.slim.js", - "jquery-VER.slim.min.js": "dist/jquery.slim.min.js", - "jquery-VER.slim.min.map": "dist/jquery.slim.min.map" - }, - - googleFilesCDN = [ - "jquery.js", "jquery.min.js", "jquery.min.map", - "jquery.slim.js", "jquery.slim.min.js", "jquery.slim.min.map" - ], - - msFilesCDN = [ - "jquery-VER.js", "jquery-VER.min.js", "jquery-VER.min.map", - "jquery-VER.slim.js", "jquery-VER.slim.min.js", "jquery-VER.slim.min.map" - ]; +const fs = require( "fs" ); +const shell = require( "shelljs" ); +const path = require( "path" ); +const os = require( "os" ); + +const cdnFolderContainer = "dist/cdn"; +const cdnFolderVersioned = `${ cdnFolderContainer }/versioned`; +const cdnFolderUnversioned = `${ cdnFolderContainer }/unversioned`; + +const versionedReleaseFiles = { + "jquery-@VER.js": "dist/jquery.js", + "jquery-@VER.min.js": "dist/jquery.min.js", + "jquery-@VER.min.map": "dist/jquery.min.map", + "jquery-@VER.slim.js": "dist/jquery.slim.js", + "jquery-@VER.slim.min.js": "dist/jquery.slim.min.js", + "jquery-@VER.slim.min.map": "dist/jquery.slim.min.map", + "jquery-@VER.module.js": "dist-module/jquery.module.js", + "jquery-@VER.module.min.js": "dist-module/jquery.module.min.js", + "jquery-@VER.module.min.map": "dist-module/jquery.module.min.map", + "jquery-@VER.slim.module.js": "dist-module/jquery.slim.module.js", + "jquery-@VER.slim.module.min.js": "dist-module/jquery.slim.module.min.js", + "jquery-@VER.slim.module.min.map": "dist-module/jquery.slim.module.min.map" +}; + +const unversionedReleaseFiles = { + "jquery.js": "dist/jquery.js", + "jquery.min.js": "dist/jquery.min.js", + "jquery.min.map": "dist/jquery.min.map", + "jquery.slim.js": "dist/jquery.slim.js", + "jquery.slim.min.js": "dist/jquery.slim.min.js", + "jquery.slim.min.map": "dist/jquery.slim.min.map", + "jquery.module.js": "dist-module/jquery.module.js", + "jquery.module.min.js": "dist-module/jquery.module.min.js", + "jquery.module.min.map": "dist-module/jquery.module.min.map", + "jquery.slim.module.js": "dist-module/jquery.slim.module.js", + "jquery.slim.module.min.js": "dist-module/jquery.slim.module.min.js", + "jquery.slim.module.min.map": "dist-module/jquery.slim.module.min.map" +}; /** * Generates copies for the CDNs */ function makeReleaseCopies( Release ) { - shell.mkdir( "-p", cdnFolder ); - - Object.keys( releaseFiles ).forEach( function( key ) { - var text, - builtFile = releaseFiles[ key ], - unpathedFile = key.replace( /VER/g, Release.newVersion ), - releaseFile = cdnFolder + "/" + unpathedFile; - - if ( /\.map$/.test( releaseFile ) ) { - - // Map files need to reference the new uncompressed name; - // assume that all files reside in the same directory. - // "file":"jquery.min.js" ... "sources":["jquery.js"] - text = fs.readFileSync( builtFile, "utf8" ) - .replace( /"file":"([^"]+)"/, - "\"file\":\"" + unpathedFile.replace( /\.min\.map/, ".min.js\"" ) ) - .replace( /"sources":\["([^"]+)"\]/, - "\"sources\":[\"" + unpathedFile.replace( /\.min\.map/, ".js" ) + "\"]" ); - fs.writeFileSync( releaseFile, text ); - } else if ( builtFile !== releaseFile ) { - shell.cp( "-f", builtFile, releaseFile ); - } + [ + { filesMap: versionedReleaseFiles, cdnFolder: cdnFolderVersioned }, + { filesMap: unversionedReleaseFiles, cdnFolder: cdnFolderUnversioned } + ].forEach( ( { filesMap, cdnFolder } ) => { + shell.mkdir( "-p", cdnFolder ); + + Object.keys( filesMap ).forEach( key => { + let text; + const builtFile = filesMap[ key ]; + const unpathedFile = key.replace( /@VER/g, Release.newVersion ); + const releaseFile = cdnFolder + "/" + unpathedFile; + + if ( /\.map$/.test( releaseFile ) ) { + + // Map files need to reference the new uncompressed name; + // assume that all files reside in the same directory. + // "file":"jquery.min.js" ... "sources":["jquery.js"] + text = fs.readFileSync( builtFile, "utf8" ) + .replace( /"file":"([^"]+)"/, + "\"file\":\"" + unpathedFile.replace( /\.min\.map/, ".min.js\"" ) ) + .replace( /"sources":\["([^"]+)"\]/, + "\"sources\":[\"" + unpathedFile.replace( /\.min\.map/, ".js" ) + "\"]" ); + fs.writeFileSync( releaseFile, text ); + } else if ( builtFile !== releaseFile ) { + shell.cp( "-f", builtFile, releaseFile ); + } + } ); + } ); } -function makeArchives( Release, callback ) { +async function makeArchives( Release ) { Release.chdir( Release.dir.repo ); - function makeArchive( cdn, files, callback ) { - if ( Release.preRelease ) { - console.log( "Skipping archive creation for " + cdn + "; this is a beta release." ); - callback(); - return; - } + async function makeArchive( { cdn, filesMap, cdnFolder } ) { + return new Promise( ( resolve, reject ) => { + if ( Release.preRelease ) { + console.log( "Skipping archive creation for " + cdn + "; this is a beta release." ); + resolve(); + return; + } - console.log( "Creating production archive for " + cdn ); + console.log( "Creating production archive for " + cdn ); - var i, sum, result, - archiver = require( "archiver" )( "zip" ), - md5file = cdnFolder + "/" + cdn + "-md5.txt", - output = fs.createWriteStream( + let i, sum, result; + const archiver = require( "archiver" )( "zip" ); + const md5file = cdnFolder + "/" + cdn + "-md5.txt"; + const output = fs.createWriteStream( cdnFolder + "/" + cdn + "-jquery-" + Release.newVersion + ".zip" - ), - rmd5 = /[a-f0-9]{32}/, - rver = /VER/; + ); + const rmd5 = /[a-f0-9]{32}/; + const rver = /@VER/; - output.on( "close", callback ); + output.on( "close", resolve ); - output.on( "error", function( err ) { - throw err; - } ); + output.on( "error", err => { + reject( err ); + } ); - archiver.pipe( output ); + archiver.pipe( output ); - files = files.map( function( item ) { - return "dist" + ( rver.test( item ) ? "/cdn" : "" ) + "/" + - item.replace( rver, Release.newVersion ); - } ); + let finalFilesMap = Object.create( null ); + for ( const [ releaseFile, builtFile ] of Object.entries( filesMap ) ) { + finalFilesMap[ releaseFile.replace( rver, Release.newVersion ) ] = builtFile; + } - if ( os.platform() === "win32" ) { - sum = []; - for ( i = 0; i < files.length; i++ ) { - result = Release.exec( - "certutil -hashfile " + files[ i ] + " MD5", "Error retrieving md5sum" - ); - sum.push( rmd5.exec( result )[ 0 ] + " " + files[ i ] ); + const files = Object + .keys( filesMap ) + .map( item => `${ cdnFolder }/${ + item.replace( rver, Release.newVersion ) + }` ); + + if ( os.platform() === "win32" ) { + sum = []; + for ( i = 0; i < files.length; i++ ) { + result = Release.exec( + "certutil -hashfile " + files[ i ] + " MD5", "Error retrieving md5sum" + ); + sum.push( rmd5.exec( result )[ 0 ] + " " + files[ i ] ); + } + sum = sum.join( "\n" ); + } else { + sum = Release.exec( "md5 -r " + files.join( " " ), "Error retrieving md5sum" ); } - sum = sum.join( "\n" ); - } else { - sum = Release.exec( "md5 -r " + files.join( " " ), "Error retrieving md5sum" ); - } - fs.writeFileSync( md5file, sum ); - files.push( md5file ); - - files.forEach( function( file ) { - archiver.append( fs.createReadStream( file ), - { name: path.basename( file ) } ); - } ); + fs.writeFileSync( md5file, sum ); + files.push( md5file ); + + files.forEach( file => { + archiver.append( fs.createReadStream( file ), + { name: path.basename( file ) } ); + } ); - archiver.finalize(); + archiver.finalize(); + } ); } - function buildGoogleCDN( callback ) { - makeArchive( "googlecdn", googleFilesCDN, callback ); + async function buildGoogleCDN() { + await makeArchive( { + cdn: "googlecdn", + filesMap: unversionedReleaseFiles, + cdnFolder: cdnFolderUnversioned + } ); } - function buildMicrosoftCDN( callback ) { - makeArchive( "mscdn", msFilesCDN, callback ); + async function buildMicrosoftCDN() { + await makeArchive( { + cdn: "mscdn", + filesMap: versionedReleaseFiles, + cdnFolder: cdnFolderVersioned + } ); } - buildGoogleCDN( function() { - buildMicrosoftCDN( callback ); - } ); + await buildGoogleCDN(); + await buildMicrosoftCDN(); } module.exports = { diff --git a/build/tasks/build.js b/build/tasks/build.js index e1dd39993..79498d012 100644 --- a/build/tasks/build.js +++ b/build/tasks/build.js @@ -17,27 +17,33 @@ module.exports = function( grunt ) { return grunt.file.read( `${ srcFolder }/${ fileName }` ); }; - // Catch `// @CODE` and subsequent comment lines event if they don't start - // in the first column. - const wrapper = read( "wrapper.js" ) - .split( /[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/ ); - const inputFileName = "jquery.js"; const inputRollupOptions = { input: `${ srcFolder }/${ inputFileName }` }; - 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", + function getOutputRollupOptions( { + esm = false + } = {} ) { + const wrapperFileName = `wrapper${ esm ? "-esm" : "" }.js`; + + // Catch `// @CODE` and subsequent comment lines event if they don't start + // in the first column. + const wrapper = read( wrapperFileName ) + .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*/, "" ) + }; + } - intro: wrapper[ 0 ] - .replace( /\n*$/, "" ), - outro: wrapper[ 1 ] - .replace( /^\n*/, "" ) - }; const fileOverrides = new Map(); function getOverride( filePath ) { @@ -62,6 +68,8 @@ module.exports = function( grunt ) { const flags = this.flags; const optIn = flags[ "*" ]; let name = grunt.option( "filename" ); + const esm = !!grunt.option( "esm" ); + const distFolder = grunt.option( "dist-folder" ); const minimum = this.data.minimum; const removeWith = this.data.removeWith; const excluded = []; @@ -185,7 +193,7 @@ module.exports = function( grunt ) { // Filename can be passed to the command line using // command line options // e.g. grunt build --filename=jquery-custom.js - name = name ? `dist/${ name }` : this.data.dest; + name = name ? `${ distFolder }/${ name }` : this.data.dest; // append commit id to version if ( process.env.COMMIT ) { @@ -296,6 +304,9 @@ module.exports = function( grunt ) { plugins: [ rollupFileOverrides( fileOverrides ) ] } ); + const outputRollupOptions = + getOutputRollupOptions( { esm } ); + const { output: [ { code } ] } = await bundle.generate( outputRollupOptions ); const compiledContents = code diff --git a/build/tasks/dist.js b/build/tasks/dist.js index f19929b89..36ce38b00 100644 --- a/build/tasks/dist.js +++ b/build/tasks/dist.js @@ -1,17 +1,18 @@ "use strict"; module.exports = function( grunt ) { - var fs = require( "fs" ), - filename = grunt.option( "filename" ), - distpaths = [ - "dist/" + filename, - "dist/" + filename.replace( ".js", ".min.map" ), - "dist/" + filename.replace( ".js", ".min.js" ) - ]; + const fs = require( "fs" ); + const filename = grunt.option( "filename" ); + const distFolder = grunt.option( "dist-folder" ); + const distPaths = [ + `${ distFolder }/${ filename }`, + `${ distFolder }/${ filename.replace( ".js", ".min.js" ) }`, + `${ distFolder }/${ filename.replace( ".js", ".min.map" ) }` + ]; // Process files for distribution grunt.registerTask( "dist", function() { - var stored, flags, paths, nonascii; + let stored, flags, paths, nonascii; // Check for stored destination paths // ( set in dist/.destination.json ) @@ -28,9 +29,9 @@ module.exports = function( grunt ) { // Ensure the dist files are pure ASCII nonascii = false; - distpaths.forEach( function( filename ) { - var i, c, - text = fs.readFileSync( filename, "utf8" ); + distPaths.forEach( function( filename ) { + let i, c; + const text = fs.readFileSync( filename, "utf8" ); // Ensure files use only \n for line endings, not \r\n if ( /\x0d\x0a/.test( text ) ) { @@ -54,7 +55,7 @@ module.exports = function( grunt ) { // Optionally copy dist files to other locations paths.forEach( function( path ) { - var created; + let created; if ( !/\/$/.test( path ) ) { path += "/"; diff --git a/build/tasks/node_smoke_tests.js b/build/tasks/node_smoke_tests.js index 574a63b4a..7edbac881 100644 --- a/build/tasks/node_smoke_tests.js +++ b/build/tasks/node_smoke_tests.js @@ -3,29 +3,49 @@ module.exports = ( grunt ) => { const fs = require( "fs" ); const spawnTest = require( "./lib/spawn_test.js" ); - const testsDir = "./test/node_smoke_tests/"; - const nodeSmokeTests = []; - - // Fire up all tests defined in test/node_smoke_tests/*.js in spawned sub-processes. - // All the files under test/node_smoke_tests/*.js are supposed to exit with 0 code - // on success or another one on failure. Spawning in sub-processes is - // important so that the tests & the main process don't interfere with - // each other, e.g. so that they don't share the require cache. - - fs.readdirSync( testsDir ) - .filter( ( testFilePath ) => - fs.statSync( testsDir + testFilePath ).isFile() && - /\.js$/.test( testFilePath ) - ) - .forEach( ( testFilePath ) => { - const taskName = `node_${ testFilePath.replace( /\.js$/, "" ) }`; - - grunt.registerTask( taskName, function() { - spawnTest( this.async(), `node "test/node_smoke_tests/${ testFilePath }"` ); - } ); + const nodeV16OrNewer = !/^v1[0-5]\./.test( process.version ); + + grunt.registerTask( "node_smoke_tests", function( moduleType, jQueryModuleSpecifier ) { + if ( + ( moduleType !== "commonjs" && moduleType !== "module" ) || + !jQueryModuleSpecifier + ) { + grunt.fatal( "Use `node_smoke_tests:commonjs:JQUERY` " + + "or `node_smoke_tests:module:JQUERY.\n" + + "JQUERY can be `jquery`, `jquery/slim` or a path to any of them." ); + } + + if ( !nodeV16OrNewer ) { + grunt.log.writeln( "Old Node.js detected, running the task " + + `"node_smoke_tests:${ moduleType }:${ jQueryModuleSpecifier }" skipped...` ); + return; + } + + const testsDir = `./test/node_smoke_tests/${ moduleType }`; + const nodeSmokeTests = []; - nodeSmokeTests.push( taskName ); - } ); + // Fire up all tests defined in test/node_smoke_tests/*.js in spawned sub-processes. + // All the files under test/node_smoke_tests/*.js are supposed to exit with 0 code + // on success or another one on failure. Spawning in sub-processes is + // important so that the tests & the main process don't interfere with + // each other, e.g. so that they don't share the `require` cache. + + fs.readdirSync( testsDir ) + .filter( ( testFilePath ) => + fs.statSync( `${ testsDir }/${ testFilePath }` ).isFile() && + /\.[cm]?js$/.test( testFilePath ) + ) + .forEach( ( testFilePath ) => { + const taskName = `node_${ testFilePath.replace( /\.[cm]?js$/, "" ) }:${ moduleType }:${ jQueryModuleSpecifier }`; + + grunt.registerTask( taskName, function() { + spawnTest( this.async(), `node "${ testsDir }/${ + testFilePath }" ${ jQueryModuleSpecifier }` ); + } ); + + nodeSmokeTests.push( taskName ); + } ); - grunt.registerTask( "node_smoke_tests", nodeSmokeTests ); + grunt.task.run( nodeSmokeTests ); + } ); }; diff --git a/build/tasks/promises_aplus_tests.js b/build/tasks/promises_aplus_tests.js index cb1a4fddb..1bbeff08e 100644 --- a/build/tasks/promises_aplus_tests.js +++ b/build/tasks/promises_aplus_tests.js @@ -10,7 +10,7 @@ module.exports = grunt => { grunt.registerTask( "promises_aplus_tests:deferred", function() { spawnTest( this.async(), "\"" + __dirname + "/../../node_modules/.bin/promises-aplus-tests\"" + - " test/promises_aplus_adapters/deferred.js" + + " test/promises_aplus_adapters/deferred.cjs" + " --reporter dot" + " --timeout " + timeout ); @@ -19,7 +19,7 @@ module.exports = grunt => { grunt.registerTask( "promises_aplus_tests:when", function() { spawnTest( this.async(), "\"" + __dirname + "/../../node_modules/.bin/promises-aplus-tests\"" + - " test/promises_aplus_adapters/when.js" + + " test/promises_aplus_adapters/when.cjs" + " --reporter dot" + " --timeout " + timeout ); diff --git a/build/tasks/sourcemap.js b/build/tasks/sourcemap.js deleted file mode 100644 index 8b9c248e0..000000000 --- a/build/tasks/sourcemap.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; - -var fs = require( "fs" ); - -module.exports = function( grunt ) { - var config = grunt.config( "minify.all.files" ); - grunt.registerTask( "remove_map_comment", function() { - var minLoc = grunt.config.process( Object.keys( config )[ 0 ] ); - - // Remove the source map comment; it causes way too many problems. - // The map file is still generated for manual associations - // https://github.com/jquery/jquery/issues/1707 - var text = fs.readFileSync( minLoc, "utf8" ) - .replace( /\/\/# sourceMappingURL=\S+/, "" ); - fs.writeFileSync( minLoc, text ); - } ); -}; diff --git a/dist-module/.eslintrc.json b/dist-module/.eslintrc.json new file mode 100644 index 000000000..33335abd8 --- /dev/null +++ b/dist-module/.eslintrc.json @@ -0,0 +1,45 @@ +{ + "root": true, + + "parserOptions": { + "ecmaVersion": 2015, + "sourceType": "module" + }, + + "globals": { + "define": false, + "Symbol": false + }, + + "overrides": [ + { + "files": "jquery{,.slim}.module.min.js", + + "parserOptions": { + "ecmaVersion": 2015, + "sourceType": "module" + } + }, + + { + "files": "jquery{,.slim}.module.js", + "extends": "../.eslintrc-browser.json", + + "parserOptions": { + "ecmaVersion": 2015, + "sourceType": "module" + }, + + "rules": { + + // That is okay for the built version + "no-multiple-empty-lines": "off", + + // When custom compilation is used, the version string + // can get large. Accept that in the built version. + "max-len": "off", + "one-var": "off" + } + } + ] +} diff --git a/dist-module/jquery.node-module-wrapper.js b/dist-module/jquery.node-module-wrapper.js new file mode 100644 index 000000000..bda9eb3d7 --- /dev/null +++ b/dist-module/jquery.node-module-wrapper.js @@ -0,0 +1,4 @@ +import jQuery from "../dist/jquery.js"; +const $ = jQuery; +export { jQuery, $ }; +export default jQuery; diff --git a/dist-module/jquery.node-module-wrapper.slim.js b/dist-module/jquery.node-module-wrapper.slim.js new file mode 100644 index 000000000..900f32fc2 --- /dev/null +++ b/dist-module/jquery.node-module-wrapper.slim.js @@ -0,0 +1,4 @@ +import jQuery from "../dist/jquery.slim.js"; +const $ = jQuery; +export { jQuery, $ }; +export default jQuery; diff --git a/dist-module/package.json b/dist-module/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/dist-module/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/dist/.eslintrc.json b/dist/.eslintrc.json index 7f7df317c..8c8003239 100644 --- a/dist/.eslintrc.json +++ b/dist/.eslintrc.json @@ -14,12 +14,23 @@ "overrides": [ { - "files": "jquery.js", + "files": "jquery{,.slim}.min.js", + + "parserOptions": { + "ecmaVersion": 5, + "sourceType": "script" + } + }, + + { + "files": "jquery{,.slim}.js", "extends": "../.eslintrc-browser.json", "rules": { + // That is okay for the built version "no-multiple-empty-lines": "off", + // When custom compilation is used, the version string // can get large. Accept that in the built version. "max-len": "off", diff --git a/dist/package.json b/dist/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/dist/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/package.json b/package.json index aef026591..f2ef725b3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,32 @@ "title": "jQuery", "description": "JavaScript library for DOM operations", "version": "4.0.0-pre", + "type": "module", + "exports": { + ".": { + "node": { + "module": "./dist-module/jquery.module.js", + "import": "./dist-module/jquery.node-module-wrapper.js", + "require": "./dist/jquery.js" + }, + "production": "./dist-module/jquery.module.min.js", + "development": "./dist-module/jquery.module.js", + "script": "./dist/jquery.min.js", + "default": "./dist-module/jquery.module.min.js" + }, + "./slim": { + "node": { + "module": "./dist-module/jquery.slim.module.js", + "import": "./dist-module/jquery.node-module-wrapper.slim.js", + "require": "./dist/jquery.slim.js" + }, + "production": "./dist-module/jquery.slim.module.min.js", + "development": "./dist-module/jquery.slim.module.js", + "script": "./dist/jquery.slim.min.js", + "default": "./dist-module/jquery.slim.module.min.js" + }, + "./src/*.js": "./src/*.js" + }, "main": "dist/jquery.js", "homepage": "https://jquery.com", "author": { @@ -71,15 +97,21 @@ "testswarm": "1.1.2" }, "scripts": { - "build": "npm install && grunt", + "build": "npm install && npm run build-all-variants", + "build-all-variants": "grunt custom:slim --esm --filename=jquery.slim.module.js && grunt custom --esm --filename=jquery.module.js && grunt custom:slim --filename=jquery.slim.js && grunt custom", "start": "grunt watch", - "test:browserless": "grunt && grunt test:slow", + "test:browserless": "grunt && npm run test:node_smoke_tests && grunt test:slow", "test:browser": "grunt && grunt karma:main", "test:esmodules": "grunt && grunt karma:esmodules", "test:no-deprecated": "grunt test:prepare && grunt custom:-deprecated && grunt karma:main", "test:selector-native": "grunt test:prepare && grunt custom:-selector && grunt karma:main", "test:slim": "grunt test:prepare && grunt custom:slim && grunt karma:main", - "test": "npm run test:slim && npm run test:no-deprecated && npm run test:selector-native && grunt && grunt test:slow && grunt karma:main && grunt karma:esmodules", + "test:node_smoke_tests:full-module": "grunt node_smoke_tests:module:./dist-module/jquery.module.js && grunt node_smoke_tests:module:jquery", + "test:node_smoke_tests:full-commonjs": "grunt node_smoke_tests:commonjs:./dist/jquery.js && grunt node_smoke_tests:commonjs:jquery", + "test:node_smoke_tests:slim-module": "grunt node_smoke_tests:module:./dist-module/jquery.slim.module.js && grunt node_smoke_tests:module:jquery/slim", + "test:node_smoke_tests:slim-commonjs": "grunt node_smoke_tests:commonjs:./dist/jquery.slim.js && grunt node_smoke_tests:commonjs:jquery/slim", + "test:node_smoke_tests": "npm run test:node_smoke_tests:full-module && npm run test:node_smoke_tests:slim-module && npm run test:node_smoke_tests:full-commonjs && npm run test:node_smoke_tests:slim-commonjs", + "test": "npm run test:browserless && npm run test:slim && npm run test:no-deprecated && npm run test:selector-native && grunt && grunt test:slow && grunt karma:main && grunt karma:esmodules", "jenkins": "npm run test:browserless" }, "commitplease": { diff --git a/src/.eslintrc.json b/src/.eslintrc.json index f1b596dff..c2b08fb93 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -57,6 +57,30 @@ } }, + { + "files": "wrapper-esm.js", + "parserOptions": { + "ecmaVersion": 2015, + "sourceType": "module" + }, + "globals": { + "jQuery": false + }, + "rules": { + "no-unused-vars": "off", + "indent": [ "error", "tab", { + // Unlike other codes, "wrapper.js" is implemented in UMD. + // So it required a specific exception for jQuery's UMD + // Code Style. This makes that indentation check is not + // performed for 1 depth of outer FunctionExpressions + "ignoredNodes": [ + "Program > FunctionDeclaration > *" + ] + } ], + "import/no-unused-modules": "off" + } + }, + { "files": "exports/amd.js", "globals": { diff --git a/src/wrapper-esm.js b/src/wrapper-esm.js new file mode 100644 index 000000000..00ff2995c --- /dev/null +++ b/src/wrapper-esm.js @@ -0,0 +1,41 @@ +/*! + * jQuery JavaScript Library v@VERSION + * https://jquery.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: @DATE + */ +// For ECMAScript module environments where a proper `window` +// is present, execute the factory and get jQuery. +// For environments that do not have a `window` with a `document` +// (such as Node.js), expose a factory as module.exports. +// This accentuates the need for the creation of a real `window`. +// e.g. var jQuery = require("jquery")(window); +// See ticket trac-14549 for more info. +var jQueryOrJQueryFactory = typeof window !== "undefined" && window.document ? + jQueryFactory( window, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return jQueryFactory( w ); + }; + +function jQueryFactory( window, noGlobal ) { + +// @CODE +// build.js inserts compiled jQuery here + +return jQuery; + +} + +export { + jQueryOrJQueryFactory as jQuery, + jQueryOrJQueryFactory as $ +}; + +export default jQueryOrJQueryFactory; diff --git a/src/wrapper.js b/src/wrapper.js index 8a3dc53e0..fa8240e1e 100644 --- a/src/wrapper.js +++ b/src/wrapper.js @@ -42,4 +42,5 @@ // build.js inserts compiled jQuery here return jQuery; + } ); diff --git a/test/data/mock.php b/test/data/mock.php index 1955f56fc..61e40c3fc 100644 --- a/test/data/mock.php +++ b/test/data/mock.php @@ -1,7 +1,7 @@ { + jQueryFactory( {} ); +}, /jQuery requires a window with a document/ ); + +ensureGlobalNotCreated( module.exports ); diff --git a/test/node_smoke_tests/commonjs/document_passed.cjs b/test/node_smoke_tests/commonjs/document_passed.cjs new file mode 100644 index 000000000..4cf280109 --- /dev/null +++ b/test/node_smoke_tests/commonjs/document_passed.cjs @@ -0,0 +1,16 @@ +"use strict"; + +const { JSDOM } = require( "jsdom" ); + +const { ensureJQuery } = require( "./lib/ensure_jquery.cjs" ); +const { ensureGlobalNotCreated } = require( "./lib/ensure_global_not_created.cjs" ); +const { getJQueryModuleSpecifier } = require( "./lib/jquery-module-specifier.cjs" ); + +const { window } = new JSDOM( "" ); + +const jQueryModuleSpecifier = getJQueryModuleSpecifier(); +const jQueryFactory = require( jQueryModuleSpecifier ); +const jQuery = jQueryFactory( window ); + +ensureJQuery( jQuery ); +ensureGlobalNotCreated( module.exports ); diff --git a/test/node_smoke_tests/commonjs/iterable_with_native_symbol.cjs b/test/node_smoke_tests/commonjs/iterable_with_native_symbol.cjs new file mode 100644 index 000000000..8fce34e06 --- /dev/null +++ b/test/node_smoke_tests/commonjs/iterable_with_native_symbol.cjs @@ -0,0 +1,14 @@ +"use strict"; + +const process = require( "node:process" ); + +if ( typeof Symbol === "undefined" ) { + console.log( "Symbols not supported, skipping the test..." ); + process.exit(); +} + +const { ensureIterability } = require( "./lib/ensure_iterability_es6.cjs" ); +const { getJQueryModuleSpecifier } = require( "./lib/jquery-module-specifier.cjs" ); + +const jQueryModuleSpecifier = getJQueryModuleSpecifier(); +ensureIterability( jQueryModuleSpecifier ); diff --git a/test/node_smoke_tests/lib/ensure_global_not_created.js b/test/node_smoke_tests/commonjs/lib/ensure_global_not_created.cjs similarity index 82% rename from test/node_smoke_tests/lib/ensure_global_not_created.js rename to test/node_smoke_tests/commonjs/lib/ensure_global_not_created.cjs index 95db62226..5eee11d21 100644 --- a/test/node_smoke_tests/lib/ensure_global_not_created.js +++ b/test/node_smoke_tests/commonjs/lib/ensure_global_not_created.cjs @@ -1,6 +1,6 @@ "use strict"; -const assert = require( "assert" ); +const assert = require( "node:assert" ); // Ensure the jQuery property on global/window/module.exports/etc. was not // created in a CommonJS environment. @@ -12,4 +12,4 @@ const ensureGlobalNotCreated = ( ...args ) => { } ); }; -module.exports = ensureGlobalNotCreated; +module.exports = { ensureGlobalNotCreated }; diff --git a/test/node_smoke_tests/commonjs/lib/ensure_iterability_es6.cjs b/test/node_smoke_tests/commonjs/lib/ensure_iterability_es6.cjs new file mode 100644 index 000000000..eb4914758 --- /dev/null +++ b/test/node_smoke_tests/commonjs/lib/ensure_iterability_es6.cjs @@ -0,0 +1,25 @@ +"use strict"; + +const assert = require( "node:assert" ); +const { JSDOM } = require( "jsdom" ); + +const { ensureJQuery } = require( "./ensure_jquery.cjs" ); + +const ensureIterability = ( jQueryModuleSpecifier ) => { + const { window } = new JSDOM( "" ); + + const jQueryFactory = require( jQueryModuleSpecifier ); + const jQuery = jQueryFactory( window ); + const elem = jQuery( "
" ); + + ensureJQuery( jQuery ); + + let result = ""; + for ( const node of elem ) { + result += node.nodeName; + } + + assert.strictEqual( result, "DIVSPANA", "for-of works on jQuery objects" ); +}; + +module.exports = { ensureIterability }; diff --git a/test/node_smoke_tests/lib/ensure_jquery.js b/test/node_smoke_tests/commonjs/lib/ensure_jquery.cjs similarity index 77% rename from test/node_smoke_tests/lib/ensure_jquery.js rename to test/node_smoke_tests/commonjs/lib/ensure_jquery.cjs index 5b7c064f1..be23e9900 100644 --- a/test/node_smoke_tests/lib/ensure_jquery.js +++ b/test/node_smoke_tests/commonjs/lib/ensure_jquery.cjs @@ -1,6 +1,6 @@ "use strict"; -const assert = require( "assert" ); +const assert = require( "node:assert" ); // Check if the object we got is the jQuery object by invoking a basic API. const ensureJQuery = ( jQuery ) => { @@ -8,4 +8,4 @@ const ensureJQuery = ( jQuery ) => { "jQuery.expando was not detected, the jQuery bootstrap process has failed" ); }; -module.exports = ensureJQuery; +module.exports = { ensureJQuery }; diff --git a/test/node_smoke_tests/commonjs/lib/jquery-module-specifier.cjs b/test/node_smoke_tests/commonjs/lib/jquery-module-specifier.cjs new file mode 100644 index 000000000..216660d80 --- /dev/null +++ b/test/node_smoke_tests/commonjs/lib/jquery-module-specifier.cjs @@ -0,0 +1,22 @@ +"use strict"; + +const path = require( "node:path" ); + +const ROOT_DIR = path.resolve( __dirname, "..", "..", "..", ".." ); + +// If `jQueryModuleSpecifier` is a real relative path, make it absolute +// to make sure it resolves to the same file inside utils from +// a subdirectory. Otherwise, leave it as-is as we may be testing `exports` +// so we need input as-is. +const getJQueryModuleSpecifier = () => { + const jQueryModuleInputSpecifier = process.argv[ 2 ]; + if ( !jQueryModuleInputSpecifier ) { + throw new Error( "jQuery module specifier not passed" ); + } + + return jQueryModuleInputSpecifier.startsWith( "." ) ? + path.resolve( ROOT_DIR, jQueryModuleInputSpecifier ) : + jQueryModuleInputSpecifier; +}; + +module.exports = { getJQueryModuleSpecifier }; diff --git a/test/node_smoke_tests/commonjs/window_present_originally.cjs b/test/node_smoke_tests/commonjs/window_present_originally.cjs new file mode 100644 index 000000000..908fcb940 --- /dev/null +++ b/test/node_smoke_tests/commonjs/window_present_originally.cjs @@ -0,0 +1,19 @@ +"use strict"; + +const { JSDOM } = require( "jsdom" ); + +const { ensureJQuery } = require( "./lib/ensure_jquery.cjs" ); +const { ensureGlobalNotCreated } = require( "./lib/ensure_global_not_created.cjs" ); +const { getJQueryModuleSpecifier } = require( "./lib/jquery-module-specifier.cjs" ); + +const jQueryModuleSpecifier = getJQueryModuleSpecifier(); + +const { window } = new JSDOM( "" ); + +// Set the window global. +globalThis.window = window; + +const jQuery = require( jQueryModuleSpecifier ); + +ensureJQuery( jQuery ); +ensureGlobalNotCreated( module.exports, window ); diff --git a/test/node_smoke_tests/document_missing.js b/test/node_smoke_tests/document_missing.js deleted file mode 100644 index 0f6a3f78b..000000000 --- a/test/node_smoke_tests/document_missing.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const assert = require( "assert" ); -const ensureGlobalNotCreated = require( "./lib/ensure_global_not_created" ); -const jQueryFactory = require( "../../dist/jquery.js" ); - -assert.throws( () => { - jQueryFactory( {} ); -}, /jQuery requires a window with a document/ ); - -ensureGlobalNotCreated( module.exports ); diff --git a/test/node_smoke_tests/document_passed.js b/test/node_smoke_tests/document_passed.js deleted file mode 100644 index b1154d3a3..000000000 --- a/test/node_smoke_tests/document_passed.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; - -const { JSDOM } = require( "jsdom" ); - -const { window } = new JSDOM( "" ); - -const ensureJQuery = require( "./lib/ensure_jquery" ); -const ensureGlobalNotCreated = require( "./lib/ensure_global_not_created" ); -const jQuery = require( "../../dist/jquery.js" )( window ); - -ensureJQuery( jQuery ); -ensureGlobalNotCreated( module.exports ); diff --git a/test/node_smoke_tests/document_present_originally.js b/test/node_smoke_tests/document_present_originally.js deleted file mode 100644 index 89b0c7bd8..000000000 --- a/test/node_smoke_tests/document_present_originally.js +++ /dev/null @@ -1,15 +0,0 @@ -"use strict"; - -const { JSDOM } = require( "jsdom" ); - -const { window } = new JSDOM( "" ); - -// Pretend the window is a global. -global.window = window; - -const ensureJQuery = require( "./lib/ensure_jquery" ); -const ensureGlobalNotCreated = require( "./lib/ensure_global_not_created" ); -const jQuery = require( "../../dist/jquery.js" ); - -ensureJQuery( jQuery ); -ensureGlobalNotCreated( module.exports, window ); diff --git a/test/node_smoke_tests/iterable_with_native_symbol.js b/test/node_smoke_tests/iterable_with_native_symbol.js deleted file mode 100644 index 3376ebdc5..000000000 --- a/test/node_smoke_tests/iterable_with_native_symbol.js +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; - -if ( typeof Symbol === "undefined" ) { - console.log( "Symbols not supported, skipping the test..." ); - process.exit(); -} - -require( "./lib/ensure_iterability_es6" )(); diff --git a/test/node_smoke_tests/lib/ensure_iterability_es6.js b/test/node_smoke_tests/lib/ensure_iterability_es6.js deleted file mode 100644 index a948f1996..000000000 --- a/test/node_smoke_tests/lib/ensure_iterability_es6.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; - -const assert = require( "assert" ); - -const ensureIterability = () => { - const { JSDOM } = require( "jsdom" ); - - const { window } = new JSDOM( "" ); - - let i; - const ensureJQuery = require( "./ensure_jquery" ); - const jQuery = require( "../../../dist/jquery.js" )( window ); - const elem = jQuery( "
" ); - let result = ""; - - ensureJQuery( jQuery ); - - for ( i of elem ) { - result += i.nodeName; - } - - assert.strictEqual( result, "DIVSPANA", "for-of works on jQuery objects" ); -}; - -module.exports = ensureIterability; diff --git a/test/node_smoke_tests/module/.eslintrc.json b/test/node_smoke_tests/module/.eslintrc.json new file mode 100644 index 000000000..3a13e1732 --- /dev/null +++ b/test/node_smoke_tests/module/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "root": true, + + "extends": "../../../.eslintrc-node.json", + + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "env": { + "es2022": true + } +} diff --git a/test/node_smoke_tests/module/document_missing.js b/test/node_smoke_tests/module/document_missing.js new file mode 100644 index 000000000..9394af796 --- /dev/null +++ b/test/node_smoke_tests/module/document_missing.js @@ -0,0 +1,13 @@ +import assert from "node:assert"; + +import { ensureGlobalNotCreated } from "./lib/ensure_global_not_created.js"; +import { getJQueryModuleSpecifier } from "./lib/jquery-module-specifier.js"; + +const jQueryModuleSpecifier = getJQueryModuleSpecifier(); +const { default: jQueryFactory } = await import( jQueryModuleSpecifier ); + +assert.throws( () => { + jQueryFactory( {} ); +}, /jQuery requires a window with a document/ ); + +ensureGlobalNotCreated(); diff --git a/test/node_smoke_tests/module/document_passed.js b/test/node_smoke_tests/module/document_passed.js new file mode 100644 index 000000000..7af5a7972 --- /dev/null +++ b/test/node_smoke_tests/module/document_passed.js @@ -0,0 +1,14 @@ +import { JSDOM } from "jsdom"; + +import { ensureJQuery } from "./lib/ensure_jquery.js"; +import { ensureGlobalNotCreated } from "./lib/ensure_global_not_created.js"; +import { getJQueryModuleSpecifier } from "./lib/jquery-module-specifier.js"; + +const { window } = new JSDOM( "" ); + +const jQueryModuleSpecifier = getJQueryModuleSpecifier(); +const { default: jQueryFactory } = await import( jQueryModuleSpecifier ); +const jQuery = jQueryFactory( window ); + +ensureJQuery( jQuery ); +ensureGlobalNotCreated(); diff --git a/test/node_smoke_tests/module/iterable_with_native_symbol.js b/test/node_smoke_tests/module/iterable_with_native_symbol.js new file mode 100644 index 000000000..3417e8ac0 --- /dev/null +++ b/test/node_smoke_tests/module/iterable_with_native_symbol.js @@ -0,0 +1,12 @@ +import process from "node:process"; + +import { ensureIterability } from "./lib/ensure_iterability_es6.js"; +import { getJQueryModuleSpecifier } from "./lib/jquery-module-specifier.js"; + +if ( typeof Symbol === "undefined" ) { + console.log( "Symbols not supported, skipping the test..." ); + process.exit(); +} + +const jQueryModuleSpecifier = getJQueryModuleSpecifier(); +await ensureIterability( jQueryModuleSpecifier ); diff --git a/test/node_smoke_tests/module/lib/ensure_global_not_created.js b/test/node_smoke_tests/module/lib/ensure_global_not_created.js new file mode 100644 index 000000000..d7648bd68 --- /dev/null +++ b/test/node_smoke_tests/module/lib/ensure_global_not_created.js @@ -0,0 +1,11 @@ +import assert from "node:assert"; + +// Ensure the jQuery property on global/window/module "this"/etc. was not +// created in a CommonJS environment. +// `global` is always checked in addition to passed parameters. +export const ensureGlobalNotCreated = ( ...args ) => { + [ ...args, global ].forEach( function( object ) { + assert.strictEqual( object.jQuery, undefined, + "A jQuery global was created in a module environment." ); + } ); +}; diff --git a/test/node_smoke_tests/module/lib/ensure_iterability_es6.js b/test/node_smoke_tests/module/lib/ensure_iterability_es6.js new file mode 100644 index 000000000..8893267a0 --- /dev/null +++ b/test/node_smoke_tests/module/lib/ensure_iterability_es6.js @@ -0,0 +1,21 @@ +import assert from "node:assert"; +const { JSDOM } = await import( "jsdom" ); + +const { ensureJQuery } = await import( "./ensure_jquery.js" ); + +export const ensureIterability = async( jQueryModuleSpecifier ) => { + const { window } = new JSDOM( "" ); + + const { default: jQueryFactory } = await import( jQueryModuleSpecifier ); + const jQuery = jQueryFactory( window ); + const elem = jQuery( "
" ); + + ensureJQuery( jQuery ); + + let result = ""; + for ( const node of elem ) { + result += node.nodeName; + } + + assert.strictEqual( result, "DIVSPANA", "for-of works on jQuery objects" ); +}; diff --git a/test/node_smoke_tests/module/lib/ensure_jquery.js b/test/node_smoke_tests/module/lib/ensure_jquery.js new file mode 100644 index 000000000..d07624ebd --- /dev/null +++ b/test/node_smoke_tests/module/lib/ensure_jquery.js @@ -0,0 +1,7 @@ +import assert from "node:assert"; + +// Check if the object we got is the jQuery object by invoking a basic API. +export const ensureJQuery = ( jQuery ) => { + assert( /^jQuery/.test( jQuery.expando ), + "jQuery.expando was not detected, the jQuery bootstrap process has failed" ); +}; diff --git a/test/node_smoke_tests/module/lib/jquery-module-specifier.js b/test/node_smoke_tests/module/lib/jquery-module-specifier.js new file mode 100644 index 000000000..39aff9fd4 --- /dev/null +++ b/test/node_smoke_tests/module/lib/jquery-module-specifier.js @@ -0,0 +1,21 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const dirname = path.dirname( fileURLToPath( import.meta.url ) ); + +const ROOT_DIR = path.resolve( dirname, "..", "..", "..", ".." ); + +// If `jQueryModuleSpecifier` is a real relative path, make it absolute +// to make sure it resolves to the same file inside utils from +// a subdirectory. Otherwise, leave it as-is as we may be testing `exports` +// so we need input as-is. +export const getJQueryModuleSpecifier = () => { + const jQueryModuleInputSpecifier = process.argv[ 2 ]; + if ( !jQueryModuleInputSpecifier ) { + throw new Error( "jQuery module specifier not passed" ); + } + + return jQueryModuleInputSpecifier.startsWith( "." ) ? + path.resolve( ROOT_DIR, jQueryModuleInputSpecifier ) : + jQueryModuleInputSpecifier; +}; diff --git a/test/node_smoke_tests/module/window_present_originally.js b/test/node_smoke_tests/module/window_present_originally.js new file mode 100644 index 000000000..ab60947e8 --- /dev/null +++ b/test/node_smoke_tests/module/window_present_originally.js @@ -0,0 +1,17 @@ +import { JSDOM } from "jsdom"; + +import { ensureJQuery } from "./lib/ensure_jquery.js"; +import { ensureGlobalNotCreated } from "./lib/ensure_global_not_created.js"; +import { getJQueryModuleSpecifier } from "./lib/jquery-module-specifier.js"; + +const jQueryModuleSpecifier = getJQueryModuleSpecifier(); + +const { window } = new JSDOM( "" ); + +// Set the window global. +globalThis.window = window; + +const { default: jQuery } = await import( jQueryModuleSpecifier ); + +ensureJQuery( jQuery ); +ensureGlobalNotCreated( window ); diff --git a/test/promises_aplus_adapters/deferred.js b/test/promises_aplus_adapters/deferred.cjs similarity index 100% rename from test/promises_aplus_adapters/deferred.js rename to test/promises_aplus_adapters/deferred.cjs diff --git a/test/promises_aplus_adapters/when.js b/test/promises_aplus_adapters/when.cjs similarity index 100% rename from test/promises_aplus_adapters/when.js rename to test/promises_aplus_adapters/when.cjs -- 2.39.5