Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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" ]
}
}
},
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 21 additions & 9 deletions build/tasks/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just WebStorm complaining that the escape was not necessary.

const exclude = m[ 1 ] === "-";
const module = m[ 2 ];

Expand All @@ -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 );
}
Expand Down Expand Up @@ -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";
Expand All @@ -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" ) : ""
);
}
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
88 changes: 88 additions & 0 deletions src/selector-native.js
Original file line number Diff line number Diff line change
@@ -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 );
}
} );
23 changes: 12 additions & 11 deletions test/data/testinit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
19 changes: 17 additions & 2 deletions test/unit/selector.js
Original file line number Diff line number Diff line change
@@ -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 );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests in CI are running on Chrome Headless which has a different user agent, making this.safari === true. This is what caused the original test failures; it was just a test issue.

},
afterEach: moduleTeardown
} );
Expand Down Expand Up @@ -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 );

Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 8 additions & 1 deletion test/unit/support.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ testIframe(
);

( function() {
var expected,
var expected, browserKey,
userAgent = window.navigator.userAgent,
expectedMap = {
ie_11: {
Expand All @@ -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 ) ) {
Expand Down