🌐 AI搜索 & 代理 主页
Skip to content

Commit eb81726

Browse files
committed
Core: Move the factory to separate exports
Since versions 1.11.0/2.1.0, jQuery has used a module wrapper with one strange addition - in CommonJS environments, if a global `window` with a `document` was not present, jQuery exported a factory accepting a `window` implementation and returning jQuery. This approach created a number of problems: 1. Properly typing jQuery would be a nightmare as the exported value depends on the environment. In practice, typing definitions ignored the factory case. 2. Since we now use named exports for the jQuery module version, it felt weird to have `jQuery` and `$` pointing to the factory instead of real jQuery. Instead, for jQuery 4.0 we leverage the just added `exports` field in `package.json` to expose completely separate factory entry points: one for the full build, one for the slim one. Exports definitions for `./factory` & `./factory-slim` are simpler than for `.` and `./slim` - this is because it's a new entry point, we only expose a named export and so there's no issue with just pointing Node.js to the CommonJS version (we cannot use the module version for `import` from Node.js to avoid double package hazard). The factory entry points are also not meant for the Web browser which always has a proper `window` - and they'd be unfit for an inclusion in a regular script tag anyway. Because of that, we also don't generate minified versions of these entry points. The factory files are not pushed to the CDN since they are mostly aimed at Node.js.
1 parent f75daab commit eb81726

29 files changed

+277
-145
lines changed

.eslintignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ dist/**
66
!dist/jquery.min.js
77
!dist/jquery.slim.js
88
!dist/jquery.slim.min.js
9+
!dist/jquery.factory.js
10+
!dist/jquery.factory.slim.js
911
dist-module/**
1012
!dist-module/jquery.module.js
1113
!dist-module/jquery.module.min.js
1214
!dist-module/jquery.slim.module.js
1315
!dist-module/jquery.slim.module.min.js
16+
!dist-module/jquery.node-module-wrapper.js
17+
!dist-module/jquery.node-module-wrapper.slim.js
18+
!dist-module/jquery.factory.module.js
19+
!dist-module/jquery.factory.slim.module.js
1420
test/data/jquery-1.9.1.js
1521
test/data/badcall.js
1622
test/data/badjson.js

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package-lock.json
1212

1313
npm-debug.log*
1414

15-
# Ignore everything in `dist` folder except for the ESLint config
15+
# Ignore everything in `dist` folder except for the ESLint config & package.json
1616
/dist/*
1717
!/dist/.eslintrc.json
1818
!/dist/package.json

Gruntfile.cjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,14 @@ module.exports = function( grunt ) {
4141
"dist/jquery.min.js",
4242
"dist/jquery.slim.js",
4343
"dist/jquery.slim.min.js",
44+
"dist/jquery.factory.js",
45+
"dist/jquery.factory.slim.js",
4446
"dist-module/jquery.module.js",
4547
"dist-module/jquery.module.min.js",
4648
"dist-module/jquery.slim.module.js",
47-
"dist-module/jquery.slim.module.min.js"
49+
"dist-module/jquery.slim.module.min.js",
50+
"dist-module/jquery.factory.module.js",
51+
"dist-module/jquery.factory.slim.module.js",
4852
];
4953

5054
const builtJsMinFiles = builtJsFiles
@@ -427,7 +431,7 @@ module.exports = function( grunt ) {
427431
runIfNewNode( "newer:eslint:dist" )
428432
] );
429433

430-
grunt.registerTask( "test:fast", [ "node_smoke_tests:commonjs:jquery" ] );
434+
grunt.registerTask( "test:fast", [ "node_smoke_tests:factory:commonjs:jquery/factory" ] );
431435
grunt.registerTask( "test:slow", [
432436
runIfNewNode( "promises_aplus_tests" ),
433437

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,20 @@ The default is `script` but you can also pass it explicitly via `--no-esm`:
147147
grunt custom --no-esm
148148
```
149149

150+
##### Factory mode
151+
152+
By default, jQuery depends on a global `window`. For environments that don't have one, you can generate a factory build that exposes a function accepting `window` as a parameter that you can provide externally (see [`README` of the published package](build/fixtures/README.md) for usage instructions). You can generate such a factory using the `--factory` parameter:
153+
154+
```bash
155+
grunt custom --factory
156+
```
157+
158+
The default mode doesn't generate a factory, but you can also pass it explicitly via `--no-factory`:
159+
160+
```bash
161+
grunt custom --no-factory
162+
```
163+
150164
#### Custom Build Examples
151165

152166
To create a custom build, first check out the version:

build/fixtures/README.md

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,16 @@ Node.js doesn't understand AMD natively so this method is mostly used in a brows
136136

137137
### Node.js pre-requisites
138138

139-
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.
139+
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.
140140

141-
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.
141+
For Node-based environments that don't have a global `window`, jQuery exposes a dedicated `jquery/factory` entry point.
142142

143143
To `import` jQuery using this factory, use the following:
144144

145145
```js
146146
import { JSDOM } from "jsdom";
147147
const { window } = new JSDOM( "" );
148-
import jQueryFactory from "jquery";
148+
import { jQueryFactory } from "jquery/factory";
149149
const $ = jQueryFactory( window );
150150
```
151151

@@ -154,27 +154,10 @@ or, if you use `require`:
154154
```js
155155
const { JSDOM } = require( "jsdom" );
156156
const { window } = new JSDOM( "" );
157-
const $ = require( "jquery" )( window );
158-
```
159-
160-
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`:
161-
162-
```js
163-
import { JSDOM } from "jsdom";
164-
const { window } = new JSDOM( "" );
165-
globalThis.window = window;
166-
const { default: $ } = await import( "jquery" );
167-
```
168-
169-
or with `require`:
170-
171-
```js
172-
const { JSDOM } = require( "jsdom" );
173-
const { window } = new JSDOM( "" );
174-
globalThis.window = window;
175-
const $ = require( "jquery" );
157+
const { jQueryFactory } = require( "jquery/factory" );
158+
const $ = jQueryFactory( window );
176159
```
177160

178161
#### Slim build in Node.js
179162

180-
To use the slim build of jQuery in Node.js, use `"jquery/slim"` instead of `"jquery"` in both `require` or `import` calls above.
163+
To use the slim build of jQuery in Node.js, use `"jquery/slim"` instead of `"jquery"` in both `require` or `import` calls above. To use the slim build in Node.js with factory mode, use `jquery/factory-slim` instead of `jquery/factory`.

build/tasks/build.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,14 @@ module.exports = function( grunt ) {
2323
};
2424

2525
function getOutputRollupOptions( {
26-
esm = false
26+
esm = false,
27+
factory = false
2728
} = {} ) {
28-
const wrapperFileName = `wrapper${ esm ? "-esm" : "" }.js`;
29+
const wrapperFileName = `wrapper${
30+
factory ? "-factory" : ""
31+
}${
32+
esm ? "-esm" : ""
33+
}.js`;
2934

3035
// Catch `// @CODE` and subsequent comment lines event if they don't start
3136
// in the first column.
@@ -69,6 +74,7 @@ module.exports = function( grunt ) {
6974
const optIn = flags[ "*" ];
7075
let name = grunt.option( "filename" );
7176
const esm = !!grunt.option( "esm" );
77+
const factory = !!grunt.option( "factory" );
7278
const distFolder = grunt.option( "dist-folder" );
7379
const minimum = this.data.minimum;
7480
const removeWith = this.data.removeWith;
@@ -305,7 +311,7 @@ module.exports = function( grunt ) {
305311
} );
306312

307313
const outputRollupOptions =
308-
getOutputRollupOptions( { esm } );
314+
getOutputRollupOptions( { esm, factory } );
309315

310316
const { output: [ { code } ] } = await bundle.generate( outputRollupOptions );
311317

@@ -348,8 +354,16 @@ module.exports = function( grunt ) {
348354
const modules = args.length ?
349355
args[ 0 ].split( "," ).join( ":" ) :
350356
"";
357+
const factory = !!grunt.option( "factory" );
351358

352359
grunt.log.writeln( "Creating custom build...\n" );
353-
grunt.task.run( [ "build:*:*" + ( modules ? ":" + modules : "" ), "minify", "dist" ] );
360+
grunt.task.run( [
361+
"build:*:*" + ( modules ? ":" + modules : "" ),
362+
363+
// Don't minify factory files; they are not meant for the browser anyway.
364+
...( factory ? [] : [ "minify" ] ),
365+
366+
"dist"
367+
] );
354368
} );
355369
};

build/tasks/dist.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ module.exports = function( grunt ) {
44
const fs = require( "fs" );
55
const filename = grunt.option( "filename" );
66
const distFolder = grunt.option( "dist-folder" );
7+
const factory = !!grunt.option( "factory" );
78
const distPaths = [
89
`${ distFolder }/${ filename }`,
9-
`${ distFolder }/${ filename.replace( ".js", ".min.js" ) }`,
10-
`${ distFolder }/${ filename.replace( ".js", ".min.map" ) }`
10+
11+
// We don't minify factory files; they are not meant for the browser anyway.
12+
...( factory ? [] : [
13+
`${ distFolder }/${ filename.replace( ".js", ".min.js" ) }`,
14+
`${ distFolder }/${ filename.replace( ".js", ".min.map" ) }`
15+
] )
1116
];
1217

1318
// Process files for distribution

build/tasks/node_smoke_tests.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ module.exports = ( grunt ) => {
55
const spawnTest = require( "./lib/spawn_test.js" );
66
const nodeV16OrNewer = !/^v1[0-5]\./.test( process.version );
77

8-
grunt.registerTask( "node_smoke_tests", function( moduleType, jQueryModuleSpecifier ) {
8+
grunt.registerTask( "node_smoke_tests",
9+
function( libraryMode, moduleType, jQueryModuleSpecifier ) {
910
if (
11+
( libraryMode !== "regular" && libraryMode !== "factory" ) ||
1012
( moduleType !== "commonjs" && moduleType !== "module" ) ||
1113
!jQueryModuleSpecifier
1214
) {
13-
grunt.fatal( "Use `node_smoke_tests:commonjs:JQUERY` " +
14-
"or `node_smoke_tests:module:JQUERY.\n" +
15-
"JQUERY can be `jquery`, `jquery/slim` or a path to any of them." );
15+
grunt.fatal( "Use `node_smoke_tests:LIBRARY_MODE:MODULE_TYPE:JQUERY`.\n" +
16+
"LIBRARY_MODE can be `regular` or `factory`.\n" +
17+
"MODULE_TYPE can be `commonjs` or `module`.\n" +
18+
"JQUERY can be `jquery`, `jquery/slim` or a path to any of them."
19+
);
1620
}
1721

1822
if ( !nodeV16OrNewer ) {
@@ -21,7 +25,7 @@ module.exports = ( grunt ) => {
2125
return;
2226
}
2327

24-
const testsDir = `./test/node_smoke_tests/${ moduleType }`;
28+
const testsDir = `./test/node_smoke_tests/${ moduleType }/${ libraryMode }`;
2529
const nodeSmokeTests = [];
2630

2731
// Fire up all tests defined in test/node_smoke_tests/*.js in spawned sub-processes.

package.json

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@
2727
"script": "./dist/jquery.slim.min.js",
2828
"default": "./dist-module/jquery.slim.module.min.js"
2929
},
30+
"./factory": {
31+
"node": "./dist/jquery.factory.js",
32+
"default": "./dist-module/jquery.factory.module.js"
33+
},
34+
"./factory-slim": {
35+
"node": "./dist/jquery.factory.slim.js",
36+
"default": "./dist-module/jquery.factory.slim.module.js"
37+
},
3038
"./src/*.js": "./src/*.js"
3139
},
3240
"main": "dist/jquery.js",
@@ -98,20 +106,26 @@
98106
},
99107
"scripts": {
100108
"build": "npm install && npm run build-all-variants",
101-
"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",
109+
"build-all-variants": "grunt custom:slim --factory --esm --filename=jquery.factory.slim.module.js && grunt custom --factory --esm --filename=jquery.factory.module.js && grunt custom:slim --factory --filename=jquery.factory.slim.js && grunt custom --factory --filename=jquery.factory.js && 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",
102110
"start": "grunt watch",
103111
"test:browserless": "grunt && npm run test:node_smoke_tests && grunt test:slow",
104112
"test:browser": "grunt && grunt karma:main",
105113
"test:esmodules": "grunt && grunt karma:esmodules",
106114
"test:no-deprecated": "grunt test:prepare && grunt custom:-deprecated && grunt karma:main",
107115
"test:selector-native": "grunt test:prepare && grunt custom:-selector && grunt karma:main",
108116
"test:slim": "grunt test:prepare && grunt custom:slim && grunt karma:main",
109-
"test:node_smoke_tests:full-module": "grunt node_smoke_tests:module:./dist-module/jquery.module.js && grunt node_smoke_tests:module:jquery",
110-
"test:node_smoke_tests:full-commonjs": "grunt node_smoke_tests:commonjs:./dist/jquery.js && grunt node_smoke_tests:commonjs:jquery",
111-
"test:node_smoke_tests:slim-module": "grunt node_smoke_tests:module:./dist-module/jquery.slim.module.js && grunt node_smoke_tests:module:jquery/slim",
112-
"test:node_smoke_tests:slim-commonjs": "grunt node_smoke_tests:commonjs:./dist/jquery.slim.js && grunt node_smoke_tests:commonjs:jquery/slim",
113-
"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",
114-
"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",
117+
"test:node_smoke_tests:regular-full-module": "grunt node_smoke_tests:regular:module:./dist-module/jquery.module.js && grunt node_smoke_tests:regular:module:jquery",
118+
"test:node_smoke_tests:regular-slim-module": "grunt node_smoke_tests:regular:module:./dist-module/jquery.slim.module.js && grunt node_smoke_tests:regular:module:jquery/slim",
119+
"test:node_smoke_tests:regular-full-commonjs": "grunt node_smoke_tests:regular:commonjs:./dist/jquery.js && grunt node_smoke_tests:regular:commonjs:jquery",
120+
"test:node_smoke_tests:regular-slim-commonjs": "grunt node_smoke_tests:regular:commonjs:./dist/jquery.slim.js && grunt node_smoke_tests:regular:commonjs:jquery/slim",
121+
"test:node_smoke_tests:factory-full-module": "grunt node_smoke_tests:factory:module:./dist-module/jquery.factory.module.js && grunt node_smoke_tests:factory:module:jquery/factory",
122+
"test:node_smoke_tests:factory-slim-module": "grunt node_smoke_tests:factory:module:./dist-module/jquery.factory.slim.module.js && grunt node_smoke_tests:factory:module:jquery/factory-slim",
123+
"test:node_smoke_tests:factory-full-commonjs": "grunt node_smoke_tests:factory:commonjs:./dist/jquery.factory.js && grunt node_smoke_tests:factory:commonjs:jquery/factory",
124+
"test:node_smoke_tests:factory-slim-commonjs": "grunt node_smoke_tests:factory:commonjs:./dist/jquery.factory.slim.js && grunt node_smoke_tests:factory:commonjs:jquery/factory-slim",
125+
"test:node_smoke_tests:regular": "npm run test:node_smoke_tests:regular-full-module && npm run test:node_smoke_tests:regular-slim-module && npm run test:node_smoke_tests:regular-full-commonjs && npm run test:node_smoke_tests:regular-slim-commonjs",
126+
"test:node_smoke_tests:factory": "npm run test:node_smoke_tests:factory-full-module && npm run test:node_smoke_tests:factory-slim-module && npm run test:node_smoke_tests:factory-full-commonjs && npm run test:node_smoke_tests:factory-slim-commonjs",
127+
"test:node_smoke_tests": "npm run test:node_smoke_tests:regular && npm run test:node_smoke_tests:factory",
128+
"test": "npm run test:node_smoke_tests && npm run test:slim && npm run test:no-deprecated && npm run test:selector-native && grunt && grunt test:slow && grunt karma:main && grunt karma:esmodules",
115129
"jenkins": "npm run test:browserless"
116130
},
117131
"commitplease": {

src/.eslintrc.json

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
},
3333

3434
"overrides": [
35+
// Unlike other files, "wrapper*.js" ones contain placeholders for code
36+
// insertion, requiring some special rules, like relaxing indentation
37+
// requirements or allowun unused variables.
3538
{
3639
"files": "wrapper.js",
3740
"parserOptions": {
@@ -41,11 +44,6 @@
4144
"rules": {
4245
"no-unused-vars": "off",
4346
"indent": [ "error", "tab", {
44-
45-
// Unlike other codes, "wrapper.js" is implemented in UMD.
46-
// So it required a specific exception for jQuery's UMD
47-
// Code Style. This makes that indentation check is not
48-
// performed for 1 depth of outer FunctionExpressions
4947
"ignoredNodes": [
5048
"Program > ExpressionStatement > CallExpression > :last-child > *"
5149
]
@@ -69,10 +67,46 @@
6967
"rules": {
7068
"no-unused-vars": "off",
7169
"indent": [ "error", "tab", {
72-
// Unlike other codes, "wrapper.js" is implemented in UMD.
73-
// So it required a specific exception for jQuery's UMD
74-
// Code Style. This makes that indentation check is not
75-
// performed for 1 depth of outer FunctionExpressions
70+
"ignoredNodes": [
71+
"Program > FunctionDeclaration > *"
72+
]
73+
} ],
74+
"import/no-unused-modules": "off"
75+
}
76+
},
77+
78+
{
79+
"files": "wrapper-factory.js",
80+
"parserOptions": {
81+
"ecmaVersion": 5,
82+
"sourceType": "script"
83+
},
84+
"rules": {
85+
"no-unused-vars": "off",
86+
"indent": [ "error", "tab", {
87+
"ignoredNodes": [
88+
"Program > FunctionDeclaration > *"
89+
]
90+
} ]
91+
},
92+
"globals": {
93+
"jQuery": false,
94+
"module": true
95+
}
96+
},
97+
98+
{
99+
"files": "wrapper-factory-esm.js",
100+
"parserOptions": {
101+
"ecmaVersion": 2015,
102+
"sourceType": "module"
103+
},
104+
"globals": {
105+
"jQuery": false
106+
},
107+
"rules": {
108+
"no-unused-vars": "off",
109+
"indent": [ "error", "tab", {
76110
"ignoredNodes": [
77111
"Program > FunctionDeclaration > *"
78112
]

0 commit comments

Comments
 (0)