Submitting here as an issue to reference it in the next WG agenda.
Some historical context puzzle pieces:
There was some discussion in Provide a clear path on how to support graphql-js 16 and 17 at the same time as a esm and cjs library author #3603
Unfortunately, there isn't a lot of documented discussion and I couldn't find any meeting notes for the above graphql-js-wg meeting.
Status Quo
With many tools like Vitest that try to use ESM (but also support CJS), the dual-package hazard still seems to be a real problem.
Many packages depending on graphql are still CJS only, so as soon as you try to start in an ESM world (which Vitest does) and import the wrong dependency, you end up in a mix of CJS and ESM, and the instanceof checks break on you.
That makes graphql very hard to use in modern setups.
Proposed solution
I want to suggest a bundling/publishing scheme that @Andarist is successfully using for many packages he maintains, e.g. XState or react-textarea-autosize.
Here's an example entrypoint from https://unpkg.com/browse/xstate@5.11.0/package.json
{
"main": "dist/xstate.cjs.js",
"module": "dist/xstate.esm.js",
".": {
"types": {
"import": "./dist/xstate.cjs.mjs",
"default": "./dist/xstate.cjs.js"
},
"development": {
"module": "./dist/xstate.development.esm.js",
"import": "./dist/xstate.development.cjs.mjs",
"default": "./dist/xstate.development.cjs.js"
},
"module": "./dist/xstate.esm.js",
"import": "./dist/xstate.cjs.mjs",
"default": "./dist/xstate.cjs.js"
},
}
Let's zoom in for a second:
{
".": {
"module": "./dist/xstate.esm.js",
"import": "./dist/xstate.cjs.mjs",
"default": "./dist/xstate.cjs.js"
}
}
Here, module would be picked up by bundlers, and import/default would be picked up by runtimes like node.
- xstate.esm.js is an ESM module. It will be picked up by bundlers for both
import and require - they don't care.
- xstate.cjs.js is a CommonJS module as you might expect - this would be picked up by
require calls in node.
- xstate.cjs.mjs is the actually interesting one. It's ESM, but it only re-exports the CommonJS module. It will be picked up in
node for import calls and its contents are:
export {
Actor,
// ...snip...
waitFor
} from "./xstate.cjs.js";
This ensures that bundlers can pick up a modern ESM build, but in runtime environments like node where the dual-package hazard exists, only one variant of the package exists. With current limitations, that is the CJS build.
I would suggest that we go forward with a packaging scheme like this for v17, as it could solve the dual package hazard, not lock out CJS users and enable gradual adoption of ESM (currently this is very painful and probably holding back a bunch of users from making the switch).
Once ESM is more of an option, v18 could still be ESM only.
From my understanding, the exports field has not been highly adopted when the current decisions around ESM were made, but by now, it is pretty widely supported.
PS:
A nice side effect of introducing an exports field would be that we could also use development and production conditions, making a process.env.NODE_ENV check necessary only in a fallback import that would be used if these conditions are not supported by a consumer.
That would be follow-up work, though.
Submitting here as an issue to reference it in the next WG agenda.
Some historical context puzzle pieces:
graphql-esmpackage and alatest-esmtag on thegraphqlpackage itselfgraphqlv17.There was some discussion in Provide a clear path on how to support graphql-js 16 and 17 at the same time as a esm and cjs library author #3603
Unfortunately, there isn't a lot of documented discussion and I couldn't find any meeting notes for the above graphql-js-wg meeting.
Status Quo
With many tools like Vitest that try to use ESM (but also support CJS), the dual-package hazard still seems to be a real problem.
Many packages depending on
graphqlare still CJS only, so as soon as you try to start in an ESM world (which Vitest does) and import the wrong dependency, you end up in a mix of CJS and ESM, and theinstanceofchecks break on you.That makes
graphqlvery hard to use in modern setups.Proposed solution
I want to suggest a bundling/publishing scheme that @Andarist is successfully using for many packages he maintains, e.g.
XStateorreact-textarea-autosize.Here's an example entrypoint from https://unpkg.com/browse/xstate@5.11.0/package.json
{ "main": "dist/xstate.cjs.js", "module": "dist/xstate.esm.js", ".": { "types": { "import": "./dist/xstate.cjs.mjs", "default": "./dist/xstate.cjs.js" }, "development": { "module": "./dist/xstate.development.esm.js", "import": "./dist/xstate.development.cjs.mjs", "default": "./dist/xstate.development.cjs.js" }, "module": "./dist/xstate.esm.js", "import": "./dist/xstate.cjs.mjs", "default": "./dist/xstate.cjs.js" }, }Let's zoom in for a second:
{ ".": { "module": "./dist/xstate.esm.js", "import": "./dist/xstate.cjs.mjs", "default": "./dist/xstate.cjs.js" } }Here,
modulewould be picked up by bundlers, andimport/defaultwould be picked up by runtimes likenode.importandrequire- they don't care.requirecalls innode.nodeforimportcalls and its contents are:This ensures that bundlers can pick up a modern ESM build, but in runtime environments like
nodewhere the dual-package hazard exists, only one variant of the package exists. With current limitations, that is the CJS build.I would suggest that we go forward with a packaging scheme like this for v17, as it could solve the dual package hazard, not lock out CJS users and enable gradual adoption of ESM (currently this is very painful and probably holding back a bunch of users from making the switch).
Once ESM is more of an option, v18 could still be ESM only.
From my understanding, the
exportsfield has not been highly adopted when the current decisions around ESM were made, but by now, it is pretty widely supported.PS:
A nice side effect of introducing an
exportsfield would be that we could also usedevelopmentandproductionconditions, making aprocess.env.NODE_ENVcheck necessary only in a fallback import that would be used if these conditions are not supported by a consumer.That would be follow-up work, though.