aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/node.js.yml4
-rw-r--r--Gruntfile.js6
-rw-r--r--README.md6
-rw-r--r--build/tasks/build.js30
-rw-r--r--package.json3
-rw-r--r--src/selector-native.js88
-rw-r--r--test/data/testinit.js23
-rw-r--r--test/unit/selector.js19
-rw-r--r--test/unit/support.js9
9 files changed, 161 insertions, 27 deletions
diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index 4ea1e23cc..c2c02ce9f 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -25,6 +25,10 @@ jobs:
NODE_VERSION: "16.x"
NPM_SCRIPT: "test:no-deprecated"
BROWSERS: "ChromeHeadless"
+ - NAME: "Browser tests: selector-native build, Chrome stable"
+ NODE_VERSION: "16.x"
+ NPM_SCRIPT: "test:selector-native"
+ BROWSERS: "ChromeHeadless"
- NAME: "Browser tests: ES modules build, Chrome stable"
NODE_VERSION: "16.x"
NPM_SCRIPT: "test:esmodules"
diff --git a/Gruntfile.js b/Gruntfile.js
index 687b3215c..3a56cb4e4 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -61,8 +61,7 @@ module.exports = function( grunt ) {
all: {
dest: "dist/jquery.js",
minimum: [
- "core",
- "selector"
+ "core"
],
// Exclude specified modules if the module matching the key is removed
@@ -75,7 +74,8 @@ module.exports = function( grunt ) {
remove: [ "ajax", "effects", "queue", "core/ready" ],
include: [ "core/ready-no-deferred" ]
},
- event: [ "deprecated/ajax-event-alias", "deprecated/event" ]
+ event: [ "deprecated/ajax-event-alias", "deprecated/event" ],
+ selector: [ "css/hiddenVisibleSelectors", "effects/animatedSelector" ]
}
}
},
diff --git a/README.md b/README.md
index 92092013d..4b4cb873f 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,12 @@ Some example modules that can be excluded are:
- **exports/global**: Exclude the attachment of global jQuery variables ($ and jQuery) to the window.
- **exports/amd**: Exclude the AMD definition.
+As a special case, you may also replace the full jQuery `selector` module by using a special flag `grunt custom:-selector`.
+
+- **selector**: The full jQuery selector engine. When this module is excluded, it is replaced by a rudimentary selector engine based on the browser's `querySelectorAll` method that does not support jQuery selector extensions or enhanced semantics. See the [selector-native.js](https://github.com/jquery/jquery/blob/main/src/selector-native.js) file for details.
+
+*Note*: Excluding the full `selector` module will also exclude all jQuery selector extensions (such as `effects/animatedSelector` and `css/hiddenVisibleSelectors`).
+
The build process shows a message for each dependent module it excludes or includes.
##### AMD name
diff --git a/build/tasks/build.js b/build/tasks/build.js
index e344f991b..61ec670fb 100644
--- a/build/tasks/build.js
+++ b/build/tasks/build.js
@@ -128,11 +128,11 @@ module.exports = function( grunt ) {
* Adds the specified module to the excluded or included list, depending on the flag
* @param {String} flag A module path relative to
* the src directory starting with + or - to indicate
- * whether it should included or excluded
+ * whether it should be included or excluded
*/
const excluder = flag => {
let additional;
- const m = /^(\+|\-|)([\w\/-]+)$/.exec( flag );
+ const m = /^(\+|-|)([\w\/-]+)$/.exec( flag );
const exclude = m[ 1 ] === "-";
const module = m[ 2 ];
@@ -150,10 +150,16 @@ module.exports = function( grunt ) {
// These are the removable dependencies
// It's fine if the directory is not there
try {
- excludeList(
- fs.readdirSync( `${ srcFolder }/${ module }` ),
- module
- );
+
+ // `selector` is a special case as we don't just remove
+ // the module, but we replace it with `selector-native`
+ // which re-uses parts of the `src/selector` folder.
+ if ( module !== "selector" ) {
+ excludeList(
+ fs.readdirSync( `${ srcFolder }/${ module }` ),
+ module
+ );
+ }
} catch ( e ) {
grunt.verbose.writeln( e );
}
@@ -232,14 +238,14 @@ module.exports = function( grunt ) {
// Remove the comma for anonymous defines
setOverride( `${ srcFolder }/exports/amd.js`,
read( "exports/amd.js" )
- .replace( /(\s*)"jquery"(\,\s*)/,
+ .replace( /(\s*)"jquery"(,\s*)/,
amdName ? "$1\"" + amdName + "\"$2" : "" ) );
}
grunt.verbose.writeflags( excluded, "Excluded" );
grunt.verbose.writeflags( included, "Included" );
- // Indicate a Slim build without listing all of the exclusions
+ // Indicate a Slim build without listing all the exclusions
// to save space.
if ( isPureSlim ) {
version += " slim";
@@ -260,7 +266,13 @@ module.exports = function( grunt ) {
// Replace excluded modules with empty sources.
for ( const module of excluded ) {
- setOverride( `${ srcFolder }/${ module }.js`, "" );
+ setOverride(
+ `${ srcFolder }/${ module }.js`,
+
+ // The `selector` module is not removed, but replaced
+ // with `selector-native`.
+ module === "selector" ? read( "selector-native.js" ) : ""
+ );
}
}
diff --git a/package.json b/package.json
index 9be880bdd..84a375ac6 100644
--- a/package.json
+++ b/package.json
@@ -77,8 +77,9 @@
"test:esmodules": "grunt && grunt karma:esmodules",
"test:amd": "grunt && grunt karma:amd",
"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 && grunt && grunt test:slow && grunt karma:main && grunt karma:esmodules && grunt karma:amd",
+ "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 && grunt karma:amd",
"jenkins": "npm run test:browserless"
},
"commitplease": {
diff --git a/src/selector-native.js b/src/selector-native.js
new file mode 100644
index 000000000..0e2e48d81
--- /dev/null
+++ b/src/selector-native.js
@@ -0,0 +1,88 @@
+/*
+ * Optional limited selector module for custom builds.
+ *
+ * Note that this DOES NOT SUPPORT many documented jQuery
+ * features in exchange for its smaller size:
+ *
+ * * Attribute not equal selector (!=)
+ * * Positional selectors (:first; :eq(n); :odd; etc.)
+ * * Type selectors (:input; :checkbox; :button; etc.)
+ * * State-based selectors (:animated; :visible; :hidden; etc.)
+ * * :has(selector)
+ * * :not(complex selector)
+ * * custom selectors via jQuery extensions
+ * * Leading combinators (e.g., $collection.find("> *"))
+ * * Reliable functionality on XML fragments
+ * * Requiring all parts of a selector to match elements under context
+ * (e.g., $div.find("div > *") now matches children of $div)
+ * * Matching against non-elements
+ * * Reliable sorting of disconnected nodes
+ * * querySelectorAll bug fixes (e.g., unreliable :focus on WebKit)
+ *
+ * If any of these are unacceptable tradeoffs, either use the full
+ * selector engine or customize this stub for the project's specific
+ * needs.
+ */
+
+import jQuery from "./core.js";
+import document from "./var/document.js";
+import documentElement from "./var/documentElement.js";
+import whitespace from "./var/whitespace.js";
+
+// The following utils are attached directly to the jQuery object.
+import "./selector/contains.js";
+import "./selector/escapeSelector.js";
+import "./selector/uniqueSort.js";
+
+// Support: IE 9 - 11+
+// IE requires a prefix.
+var matches = documentElement.matches || documentElement.msMatchesSelector;
+
+jQuery.extend( {
+ find: function( selector, context, results, seed ) {
+ var elem, nodeType,
+ i = 0;
+
+ results = results || [];
+ context = context || document;
+
+ // Same basic safeguard as in the full selector module
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ // Early return if context is not an element, document or document fragment
+ if ( ( nodeType = context.nodeType ) !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+ return [];
+ }
+
+ if ( seed ) {
+ while ( ( elem = seed[ i++ ] ) ) {
+ if ( jQuery.find.matchesSelector( elem, selector ) ) {
+ results.push( elem );
+ }
+ }
+ } else {
+ jQuery.merge( results, context.querySelectorAll( selector ) );
+ }
+
+ return results;
+ },
+ expr: {
+ attrHandle: {},
+ match: {
+ bool: new RegExp( "^(?:checked|selected|async|autofocus|autoplay|controls|defer" +
+ "|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped)$", "i" ),
+ needsContext: new RegExp( "^" + whitespace + "*[>+~]" )
+ }
+ }
+} );
+
+jQuery.extend( jQuery.find, {
+ matches: function( expr, elements ) {
+ return jQuery.find( expr, null, null, elements );
+ },
+ matchesSelector: function( elem, expr ) {
+ return matches.call( elem, expr );
+ }
+} );
diff --git a/test/data/testinit.js b/test/data/testinit.js
index 650042b2a..6503b70a5 100644
--- a/test/data/testinit.js
+++ b/test/data/testinit.js
@@ -301,17 +301,6 @@ if ( !window.__karma__ ) {
QUnit.isSwarm = ( QUnit.urlParams.swarmURL + "" ).indexOf( "http" ) === 0;
QUnit.basicTests = ( QUnit.urlParams.module + "" ) === "basic";
-// Says whether jQuery positional selector extensions are supported.
-// A full selector engine is required to support them as they need to be evaluated
-// left-to-right. Remove that property when support for positional selectors is dropped.
-QUnit.jQuerySelectorsPos = true;
-
-// Says whether jQuery selector extensions are supported. Change that to `false`
-// if your custom jQuery versions relies more on native qSA.
-// This doesn't include support for positional selectors (see above).
-// TODO do we want to keep this or just assume support for jQuery extensions?
-QUnit.jQuerySelectors = true;
-
// Support: IE 11+
// A variable to make it easier to skip specific tests in IE, mostly
// testing integrations with newer Web features not supported by it.
@@ -388,6 +377,18 @@ this.loadTests = function() {
// Get testSubproject from testrunner first
require( [ parentUrl + "test/data/testrunner.js" ], function() {
+
+ // Says whether jQuery positional selector extensions are supported.
+ // A full selector engine is required to support them as they need to
+ // be evaluated left-to-right. Remove that property when support for
+ // positional selectors is dropped.
+ QUnit.jQuerySelectorsPos = includesModule( "selector" );
+
+ // Says whether jQuery selector extensions are supported. Change that
+ // to `false` if your custom jQuery versions relies more on native qSA.
+ // This doesn't include support for positional selectors (see above).
+ QUnit.jQuerySelectors = includesModule( "selector" );
+
var i = 0,
tests = [
// A special module with basic tests, meant for not fully
diff --git a/test/unit/selector.js b/test/unit/selector.js
index 47837d6ce..4f50412e7 100644
--- a/test/unit/selector.js
+++ b/test/unit/selector.js
@@ -1,7 +1,7 @@
QUnit.module( "selector", {
beforeEach: function() {
this.safari = /\bsafari\b/i.test( navigator.userAgent ) &&
- !/\bchrome\b/i.test( navigator.userAgent );
+ !/\b(?:headless)?chrome\b/i.test( navigator.userAgent );
},
afterEach: moduleTeardown
} );
@@ -1908,6 +1908,21 @@ QUnit.testUnlessIE( "jQuery.contains within <template/> doesn't throw (gh-5147)"
assert.ok( true, "Didn't throw" );
} );
+QUnit.test( "find in document fragments", function( assert ) {
+ assert.expect( 1 );
+
+ var elem,
+ nonnodes = jQuery( "#nonnodes" ).contents(),
+ fragment = document.createDocumentFragment();
+
+ nonnodes.each( function() {
+ fragment.appendChild( this );
+ } );
+
+ elem = jQuery( fragment ).find( "#nonnodesElement" );
+ assert.strictEqual( elem.length, 1, "Selection works" );
+} );
+
QUnit.test( "jQuery.uniqueSort", function( assert ) {
assert.expect( 14 );
@@ -2156,7 +2171,7 @@ QUnit.test( "jQuery.escapeSelector", function( assert ) {
assert.equal( jQuery.escapeSelector( "\uD834" ), "\uD834", "Doesn't escape lone high surrogate" );
} );
-QUnit.test( "custom pseudos", function( assert ) {
+QUnit[ QUnit.jQuerySelectors ? "test" : "skip" ]( "custom pseudos", function( assert ) {
assert.expect( 6 );
try {
diff --git a/test/unit/support.js b/test/unit/support.js
index e3a7778c4..a3122e052 100644
--- a/test/unit/support.js
+++ b/test/unit/support.js
@@ -55,7 +55,7 @@ testIframe(
);
( function() {
- var expected,
+ var expected, browserKey,
userAgent = window.navigator.userAgent,
expectedMap = {
ie_11: {
@@ -80,6 +80,13 @@ testIframe(
}
};
+ // Make the selector-native build pass tests.
+ for ( browserKey in expectedMap ) {
+ if ( !includesModule( "selector" ) ) {
+ delete expectedMap[ browserKey ].cssSupportsSelector;
+ }
+ }
+
if ( document.documentMode ) {
expected = expectedMap.ie_11;
} else if ( /chrome/i.test( userAgent ) ) {