express-graphql icon indicating copy to clipboard operation
express-graphql copied to clipboard

Support native ESM via .mjs.

Open jaydenseric opened this issue 6 years ago • 10 comments

Node.js natively supports ESM via .mjs (currently behind the --experimental-modules flag, soon to be unflagged).

At the moment, consumers using native ESM, graphql and express-graphql are in a conundrum (see https://github.com/graphql/express-graphql/issues/425). When they use graphql the ESM .mjs modules run. When express-graphql internally uses graphql, the CJS .js files run. graphql thinks there are multiple versions being used and throws a tantrum. This issue will be mitigated by supporting native ESM across both packages.

  • Swapped deprecated babel-preset-es2015 for babel-preset-env.
  • Added package.json engines field.
  • Configured babel-preset-env to target the Node.js version defined in package.json engines field.
  • Kept Babel at v6, but moved config into a v7 ready .babelrc.js file for dynamic config via env.
  • Tidied the package.json scripts, added an ESM build step that generates .mjs dist files. Follows the lead of https://github.com/graphql/graphql-js/pull/1244, although I do things quite different for my own projects with Babel v7.
  • Updated package.json main field for ESM/CJS cross compatibility.
  • Added a package.json module field for tree-shaking bundlers.

I didn't upgrade Babel to v7 to limit the scope of this PR. It would be a good idea though.

jaydenseric avatar May 02 '18 14:05 jaydenseric

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed.

If you have received this in error or have any questions, please contact us at [email protected]. Thanks!

facebook-github-bot avatar May 02 '18 14:05 facebook-github-bot

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

facebook-github-bot avatar May 02 '18 14:05 facebook-github-bot

Don't merge yet, I'm noticing that dist/index.mjs has a few erroneous module.exports in it for some reason.

jaydenseric avatar May 02 '18 14:05 jaydenseric

Looks great 🎉

Don't merge yet, I'm noticing that dist/index.mjs has a few erroneous module.exports in it for some reason.

Please ping me when you solve this issue or if you need help with it. I was planning on doing 0.7.0 at the end of this week and would be great to also include this feature.

Tidied the package.json scripts, added an ESM build step that generates .mjs dist files. Follows the lead of graphql/graphql-js#1244, although I do things quite different for my own projects with Babel v7.

If you have time you can help to migrate graphql-js: https://github.com/graphql/graphql-js/issues/1266 It would be ideal to first update it and than all other graphql/* projects.

Added a package.json module field for tree-shaking bundlers.

Maybe it also make sense to add "sideEffects": false simular to https://github.com/graphql/graphql-js/pull/1312

Added package.json engines field.

No need to support 4.0 since it's End-of-life so you can set it to 6.0 https://github.com/nodejs/Release

IvanGoncharov avatar May 03 '18 09:05 IvanGoncharov

As a breaking change, I updated the exports to be only named for ESM/CJS cross compatibility and consistency.

For a native ESM environment:

import { graphqlHTTP, getGraphQLParams } from 'express-graphql'

For a legacy CJS environment:

- const graphqlHTTP = require('express-graphql')
- const getGraphQLParams = graphqlHTTP.getGraphQLParams
+ const { graphqlHTTP, getGraphQLParams } = require('express-graphql')

The old way the additional getGraphQLParams export was added onto the graphqlHTTP function was pretty weird anyway, and now the import and require look consistent.

There is no concept of mixed default/named exports in CJS, only a babel convention of a .default export. If we had mixed, this is what the API would look like:

// ESM:
import graphqlHTTP, { getGraphQLParams } from 'express-graphql'

// CJS:
const { default: graphqlHTTP, getGraphQLParams } = require('express-graphql')

jaydenseric avatar May 07 '18 02:05 jaydenseric

As a breaking change, I updated the exports to be only named for ESM/CJS cross compatibility and consistency.

@jaydenseric I would prefer not make such breaking changes especially since the absolute majority of users don't use getGraphQLParams function. Can we make it backward compatible using babel-plugin-add-module-exports?

IvanGoncharov avatar May 07 '18 09:05 IvanGoncharov

@IvanGoncharov

Can we make it backward compatible using babel-plugin-add-module-exports?

For starters, that plugin does not, and likely will not support babel v7.

The best way forward is to tweak the API to properly support both environments. As a best practice, no npm package should have mixed named/default exports. Only default, or only named. Only default is probably an inferior option if you expect your API to grow, as you can't export more stuff without a breaking change to named exports.

The new API would be consistent with graphql and graphql-relay, which only have named exports.

This issue is a dealbreaker for me and others I know. Because Apollo does not support native ESM either I've begun work on a new GraphQL server package.

jaydenseric avatar May 23 '18 05:05 jaydenseric

(currently behind the --experimental-modules flag, soon to be unflagged).

Hi 👋 ! Node core and Node Module WG member here. There is no set date for when --experimental-modules will be unflagged. There's also no guarantee that what has shipped now is what will be in the future. For example, there are several discussions in the Node Module WG on how to approach implementation. One such discussion is to ship a maximally minimal implementation that would require package-maps for resolving bare specifiers (no longer using the Node module resolution algorithm).

Adopting .mjs with the idea that its current support is solid (for production use) at this stage is bit premature and may cause headache for users. Not only ecosystem hurdles like #425 or how Webpack locks down bundled .mjs bits making your code more difficult to mock, stub, and spy. But also if/when changes do happen to Node's --experimental-modules your ESM story will be various shades of broken.

The --experimental-modules flag really does mean experimental. It's something you should pause and consider at least before going all in as the route for your ESM support strategy today.

jdalton avatar Jul 07 '18 15:07 jdalton

Things were moving too slow so I published graphql-api-koa, and have been enjoying writing resolvers in native ESM for a month now without issue.

jaydenseric avatar Jul 08 '18 21:07 jaydenseric

We're about a month away from Node 12 going into LTS. What happened to this feature?

FredrikZeiner avatar Sep 29 '19 14:09 FredrikZeiner