Skip to content

WIP (example) Core: Define the exports & module fields in package.json #5122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

mgol
Copy link
Member

@mgol mgol commented Sep 21, 2022

Summary

module is needed for Parcel which still doesn't support exports: parcel-bundler/parcel#4155

I prepared this after reading https://nodejs.org/api/packages.html and https://webpack.js.org/guides/package-exports/. Important information is:

  • jQuery can be loaded & used both in the browser and in Node.js
  • jQuery is stateful so we need to make sure it's not loaded twice by accident (vide the Dual package hazard section from Node.js docs)
  • Bundling using popular bundlers (Webpack, Parcel, Vite, Browserify, etc.) should be possible

This draft contains files as they'd exist in a final package. Of course, the final PR would be different, files would be generated during the build and only included in published packages but I included them in the PR so that it can be experimented with.

Ref gh-4592

Checklist

`module` is needed for Parcel which still doesn't support `exports`:
parcel-bundler/parcel#4155
@mgol mgol changed the title Core: Define the exports & module fields in package.json WIP (example) Core: Define the exports & module fields in package.json Sep 21, 2022
@mgol
Copy link
Member Author

mgol commented Sep 21, 2022

An example script showing how it works in Node.js:

'use strict';

// Get a `window` implementation from jsdom and assign it to the global.
import jsdom from 'jsdom';
const { JSDOM } = jsdom;
const { window } = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
globalThis.window = window;

// Log the jQuery slim version
const jQuerySlim = (await import('jquery/slim')).default;
console.log('jQuery slim version:', jQuerySlim?.fn?.jquery);

// Log the contents of the internal cssExpand array.
const cssExpand = (await import('jquery/src/css/var/cssExpand.js')).default;
console.log('jQuery cssExpand array:', cssExpand);

To run this example, save it as test.js in an npm project, install jsdom as a dependency and link node_modules/jquery to a clone of this branch - and run node test.js in a console.

"main": "dist/jquery.js",
"module": "dist/jquery.mjs",
Copy link
Member Author

Choose a reason for hiding this comment

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

Parcel doesn't support package.json exports so this field is still needed:
parcel-bundler/parcel#4155

Comment on lines +24 to +25
"./src/*.js": "./src/*.js",
"./amd/*.js": "./amd/*.js"
Copy link
Member Author

@mgol mgol Sep 21, 2022

Choose a reason for hiding this comment

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

These entries are tricky. Technically, we don't guarantee anything about files in those directories, they can come and go even in patch releases. On the other hand, we've been telling people that for advanced cases they can use our modules, taking into account these restrictions.

src contains jQuery source written in ES modules, amd those modules transpiled to AMD as jQuery 3.x & older used AMD as the source format and we wanted to still make it available to people.

The modules in src are compatible with direct usage in a browser without any bundler as well:

<script type="module">
	import jQuery from "./src/jquery.js";
	/* do someting with jQuery */
</script>

Comment on lines +8 to +23
".": {
"node": {
"module": "./dist/jquery.mjs",
"import": "./dist/jquery-node-wrapper.mjs",
"default": "./dist/jquery.js"
},
"browser": "./dist/jquery.mjs"
},
"./slim": {
"node": {
"module": "./dist/jquery.slim.mjs",
"import": "./dist/jquery-node-wrapper.slim.mjs",
"default": "./dist/jquery.slim.js"
},
"browser": "./dist/jquery.slim.mjs"
},
Copy link
Member Author

Choose a reason for hiding this comment

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

@mgol
Copy link
Member Author

mgol commented Sep 21, 2022

@guybedford @jkrems @MylesBorins could you have a look?

"import": "./dist/jquery-node-wrapper.mjs",
"default": "./dist/jquery.js"
},
"browser": "./dist/jquery.mjs"
Copy link

Choose a reason for hiding this comment

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

You probably want to add the require and default in case, that should cover most cases. I'm not sure if "browser" is needed if you already have default, but also can't hurt.

Suggested change
"browser": "./dist/jquery.mjs"
"require": "./dist/jquery.js",
"browser": "./dist/jquery.mjs",
"default": "./dist/jquery.mjs"

Choose a reason for hiding this comment

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

I would also recommend flipping around the file extensions. Use .cjs for the CommonJS version and .js for all other versions, and add "type": "module" to the package.json. I think it’s still the case that many servers/CDNs don’t serve the proper MIME type for .mjs files, so the browser version of jQuery (even the browser ESM version) should absolutely have a .js file extension for maximum compatibility.

Copy link

@aduh95 aduh95 Sep 21, 2022

Choose a reason for hiding this comment

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

I think it’s still the case that many servers/CDNs don’t serve the proper MIME type for .mjs files

It's been years since I've since this issue, not sure if it's worth it. (One could argue that we should be using .mjs to maximize compatibility because Node.js v10.x never got support for "type": "module"). Note that the .js files are not CJS but AMD FWIW.

Choose a reason for hiding this comment

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

It’s been years since I’ve since this issue, not sure if it’s worth it.

I think jQuery’s enormous scope justifies being careful. There’s no benefit to .mjs/.js over .cjs/.js. Node 10 doesn’t support "exports" at all; if you want to explicitly provide a Node 10-and-below ESM build, an .mjs file somewhere just for those consumers could be included. I would still save all the others as .js files as .js works everywhere.

Copy link
Member Author

Choose a reason for hiding this comment

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

The only reason we test on Node 10 is our Jenkins instance cannot handle anything newer. I'd like to get us away from it, though, so that shouldn't hopefully be a blocker.

Our goal is to only support Node.js versions that are supported by upstream, i.e. now 14+.

Copy link
Member Author

Choose a reason for hiding this comment

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

@GeoffreyBooth I'd like to understand where every entry comes from. Why do we need require, browser & default for non-Node.js cases?

Choose a reason for hiding this comment

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

I’d like to understand where every entry comes from

They’re documented here: https://nodejs.org/api/packages.html#conditional-exports

Why do we need require, browser & default for non-Node.js cases?

Others should chime in, especially @guybedford, but my understanding is that while Node reads the conditions as appropriate at runtime, the other conditions (such as "browser") are intended for bundlers. So when a build tool is creating a browser build, it’ll use the jQuery file listed under the "browser" field.

Build tools can also build for Node, of course, in which case they’ll read the other conditions too. When preparing a Node build, if jQuery is referenced via a require() call, the "require" condition will be chosen, and so on. When nothing more specific matches, "default" is chosen (which is why it must always be last).

Copy link

Choose a reason for hiding this comment

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

Why do we need require […] for non-Node.js cases?

E.g. if someone uses require() to write browser focused code. If you set browser to point to the ESM file, depending on the builder it may or may not work (i.e. it would work only if the bundler supports requiring ES modules).

"import": "./dist/jquery-node-wrapper.slim.mjs",
"default": "./dist/jquery.slim.js"
},
"browser": "./dist/jquery.slim.mjs"
Copy link

Choose a reason for hiding this comment

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

Same here.

Suggested change
"browser": "./dist/jquery.slim.mjs"
"require": "./dist/jquery.slim.js",
"browser": "./dist/jquery.slim.mjs",
"default": "./dist/jquery.slim.mjs"

@ollie-iterators
Copy link

Shouldn't this be put in the 4.0.0 milestone?

@mgol
Copy link
Member Author

mgol commented Apr 14, 2023

@ollie-iterators We have #4592 for tracking purposes; this PR is a POC of what the compiled bundles could look like; the final code will not include any built files and will need code to actually generate them so it will be a different PR.

@mgol
Copy link
Member Author

mgol commented May 22, 2023

I created a PR adding exports support & the ESM build at #5255. This WIP is no longer needed, the PR contains all the relevant details with test cases.

One important change from this POC is that I ended up just defining a single entry point for Node.js. This is because our compiled ESM version still only has a default export with the value of module.exports from the regular file - which is just what Node.js does natively if you import a CommonJS module from an ESM one. Using a single file allows to avoid the dual package hazard altogether.

@mgol mgol closed this May 22, 2023
@mgol mgol deleted the package-exports-example branch July 10, 2023 18:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants