apollo-feature-requests icon indicating copy to clipboard operation
apollo-feature-requests copied to clipboard

Add Node/Browser ESM support to @apollo/client.

Open brainkim opened this issue 3 years ago β€’ 23 comments

This is a tracking issue for the work to add native ESM support for new Node versions and direct <script type="module"> references for the @apollo/client package.

This would probably mean adding "type": "module" to package.json files and potentially exports fields. It may also include changes to the build system and package artifacts. It is potentially a breaking change for unwitting developers who have imported files which we never intended to export.

Maybe this is a low priority, especially given most bundlers and front-end tools conflate and handle CommonJS and ESModules out of box, and I’m not sure what percentage of users import apollo-client from ESM-capable node versions. Maybe someone doing SSR?

Also note that this is a risky change to the codebase which will cause outages if we don’t dot our t’s and cross our i’s. See https://javascript.plainenglish.io/is-promise-post-mortem-cab807f18dcc, for instance.

@benjamn @hwillson @jcreighton

Related issues: #81 #83

brainkim avatar Mar 24 '21 15:03 brainkim

This could also be accomplished by switching all generated module files in the dist folder to the mjs file extension and changing "module": "index.js" to "module": "index.mjs" in the package.json file. This lets node import these files directly and should not break any build tools but would beak all import specifying the file extension:

import { ApolloClient } from '@apollo/client/core/ApolloClient.js';

This could be fixed by keeping the .js files and duplicating them with the .mjs extension.

I used this to get Apollo working in a esbuild project that only works with native ESM.

JanNitschke avatar Sep 14 '21 18:09 JanNitschke

Hi, this is problematic for us at Vulcan since we have moved our package library, that depends on @apollo/client, to ESM. Our library won't work in Next and users are facing similar issues in Svelte or Nuxt.

This StackOverflow question sums it up: https://stackoverflow.com/questions/70615613/apollo-client-named-export-remove-not-found We have the same issue with gql, useQuery etc. with no solution yet except maybe writing a custom Esbuild plugin to alter the import.

Next.js, which is based on Webpack 5, doesn't seem to support just having "module": "index.js", exports are needed instead. I could fix most issues by adding this to my @apollo/client package.json:

"exports": {
    ".": "./index.js",
    "./link/error": "./link/error/index.js"
  },

And also in ts-invariant package.json:

  "type": "module",
  "exports": {
    ".": "./lib/invariant.esm.js",
    "./process/index.js": "./process/index.js"
  },

And finally to ts-invariant/process package.json:

  "type": "module",

eric-burel avatar Mar 12 '22 09:03 eric-burel

Here is a gist: https://gist.github.com/eric-burel/543bdc809bcc62208deabdeb004db724 Tested with Apollo 3.5.10 Add node ./fix-apollo.js to postinstall package.json

eric-burel avatar Mar 12 '22 09:03 eric-burel

Any updates here? A year later it's still an issue when trying to use @apollo/client with something like Nuxt on Node 16

file:///home/fabis/Code/speckle/speckle-website-fe/node_modules/@apollo/client/utilities/globals/fix-graphql.js:1
import { remove } from "ts-invariant/process/index.js";
         ^^^^^^
SyntaxError: Named export 'remove' not found. The requested module 'ts-invariant/process/index.js' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'ts-invariant/process/index.js';
const { remove } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:127:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:191:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
    at async __instantiateModule__ (file:///home/fabis/Code/speckle/speckle-website-fe/.nuxt/dist/server/server.mjs:23402:3)

fabis94 avatar Apr 14 '22 10:04 fabis94

Any updates here? A year later it's still an issue when trying to use @apollo/client with something like Nuxt on Node 16

file:///home/fabis/Code/speckle/speckle-website-fe/node_modules/@apollo/client/utilities/globals/fix-graphql.js:1
import { remove } from "ts-invariant/process/index.js";
         ^^^^^^
SyntaxError: Named export 'remove' not found. The requested module 'ts-invariant/process/index.js' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'ts-invariant/process/index.js';
const { remove } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:127:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:191:5)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
    at async __instantiateModule__ (file:///home/fabis/Code/speckle/speckle-website-fe/.nuxt/dist/server/server.mjs:23402:3)

It seems that exports map is not an option for Apollo because it would break older applications, so the best hope seems to figure out why bundlers are not supporting this. Next has the same issue and rely on Webpack 5, I think that's also the case for Nuxt according to what I've read online, and maybe other frameworks

eric-burel avatar Apr 14 '22 11:04 eric-burel

@eric-burel Why not just convert the imports to the format shown in the error message?

fabis94 avatar Apr 14 '22 11:04 fabis94

@eric-burel Why not just convert the imports to the format shown in the error message?

@benjamn can maybe have more info

I am not sure it's that easy, when your package have a mix of ES modules that can in turn import modules that are either CJS or ESM, you are up to trouble. Technically you can change the way Apollo expose package via an export map in package.json (that's what I do and I have a setup that works ok, though I need this hot patch on postinstall), but I am not sure if it's the core issue, maybe some Webpack pros could help as well

eric-burel avatar Apr 14 '22 11:04 eric-burel

This breaks with Jest 28 as well, I had to reintroduce @apollo/client and ts-invariant in the build:

// in my jest config
  transformIgnorePatterns: [
    "/node_modules/(?!(@apollo/client|ts-invariant))",
    "^.+\\.module\\.(css|sass|scss)$",
  ],

and also extend my patch to provide a full export map for all internal packages of @apollo/client. Finally, I needed to import "gql" from "graphql-tag" instead of @apollo/client.

Demo: https://github.com/VulcanJS/vulcan-npm/pull/115

eric-burel avatar May 03 '22 14:05 eric-burel

I modified the package.json because I had to use esm only modules(ex: unified, remark ...)

// package.json
...
"type": "module",

a detour by applying it like this in the Jest test

// <rootDir>/lib/test/apollo.cjs
const apollo = require('@apollo/client/main');
const gql = require('graphql-tag');

module.exports = { ...apollo, gql };
// jest.config.js
...
moduleNameMapper: {
  '^@apollo/client$': '<rootDir>/lib/test/apollo',
},

with ts-jest

// jest.config.js
...
preset: 'ts-jest/presets/js-with-ts-esm',
globals: {
  'ts-jest': {
    tsconfig: 'tsconfig.json',
  },
},
// import { ApolloClient, InMemoryCache, NormalizedCacheObject, from, HttpLink } from '@apollo/client';
import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client/core';
import { from } from '@apollo/client/link/core';
import { HttpLink } from '@apollo/client/link/http';

momenthana avatar May 27 '22 03:05 momenthana

Would really appreciate an update on this. Currently using it with nuxt 3

stephenjason89 avatar Jul 12 '22 12:07 stephenjason89

Node 16 SSR ESM project: we point to the internal index.js in the package. Seems to work.

import { ApolloClient, from, gql } from '@apollo/client/core/index.js';
import { HttpLink } from '@apollo/client/link/http/index.js';
import { InMemoryCache } from '@apollo/client/cache/index.js';
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries/index.js';

underblob avatar Jul 22 '22 23:07 underblob

@underblob Thank you, this seems to work.

stephenjason89 avatar Jul 24 '22 07:07 stephenjason89

Here is a gist: https://gist.github.com/eric-burel/543bdc809bcc62208deabdeb004db724 Tested with Apollo 3.5.10 Add node ./fix-apollo.js to postinstall package.json

My patch doesn't work anymore with PNPM symlinking. I am able to provide open source repro, Apollo is really hard to use iin ESM packages...

Edit: ts-invariant 0.10+ changed its ESM module export name. Here is the up to date patch I use to build Next with @apollo/client imported from ESM dependencies: https://github.com/Devographics/Monorepo/blob/5b25381146ab7c5b69df72d9cdb1a0ec154a7864/surveyform/.vn/scripts/fix-apollo.js

eric-burel avatar Sep 08 '22 13:09 eric-burel

Node 16 SSR ESM project: we point to the internal index.js in the package. Seems to work.

import { ApolloClient, from, gql } from '@apollo/client/core/index.js';
import { HttpLink } from '@apollo/client/link/http/index.js';
import { InMemoryCache } from '@apollo/client/cache/index.js';
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries/index.js';

This works but indeed the sign that "@apollo/client/index.js" is not properly interpreted as an ES module in the first place. I have no idea how to force build system to take that into consideration. Maybe renaming to .mjs?

Edit: doesn't work actually, it will still consider relative import as CJS (eg choke on "../versions.js"), I have a (big) repro here: https://github.com/VulcanJS/vulcan-npm/pull/140

eric-burel avatar Sep 15 '22 07:09 eric-burel

This works but indeed the sign that "@apollo/client/index.js" is not properly interpreted as an ES module in the first place. I have no idea how to force build system to take that into consideration. Maybe renaming to .mjs?

If using esbuild you can tell it to prefer ESM exports with the mainFields option I believe

revmischa avatar Sep 15 '22 17:09 revmischa

It seems that exports map is not an option for Apollo because it would break older applications

What about doing a major release version, which is how is-promise solved this issue?

shahyar avatar Sep 21 '22 20:09 shahyar

Wanted to note a similar but unique challenge here:

I'm using graphql-codegen and the typescript-react-apollo plugin. This generates these imports:

import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
// ... codegen types and results

When I try to use any of the exported values from the codegen file in an ESM Typescript project (otherwise working), I receive this error:

import { gql } from '@apollo/client';
         ^^^
SyntaxError: Named export 'gql' not found. The requested module '@apollo/client' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '@apollo/client';
const { gql } = pkg;

Changing the imports as suggested makes it work, but I don't have control over the codegen plugin.

RyKilleen avatar Oct 01 '22 16:10 RyKilleen

I just really want tree shaking to work with @apollo/client. No matter what I do the entire thing always gets bundled in my app, and it affects lambda cold starts. It's so bad I am weighing the cost of switching gql clients.

I also am using graphql-codegen and the typescript-react-apollo plugin.

image

revmischa avatar Oct 01 '22 17:10 revmischa

Hi, I ran into this error as well. Will we get this fix soon?

ahnpnl avatar Dec 29 '22 21:12 ahnpnl

Isn't just the issue the fact that Typescript def looks like export * from './core'; instead of export * from './core/index.d.ts'; or something similar. TypeScript moduleResolution set to node16 or nodenext does not support import without file names. It doesn't discover the index magically anymore.

cedeber avatar Apr 20 '23 06:04 cedeber

TypeScript 5 will magically discover the index file if moduleResolution is set to 'bundler'

revmischa avatar Apr 20 '23 14:04 revmischa

TypeScript 5 will magically discover the index file if moduleResolution is set to 'bundler'

If someone sees this, this is what helped me resolve @apollo/client error in react-native. Thanks @revmischa

{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "types": ["react-native"],
    "module": "ES6",
    "moduleResolution": "bundler"
  }
}

Milutin-P avatar May 12 '23 15:05 Milutin-P

@Milutin-P worked like a charm! Thanks!

steniowagner avatar Oct 10 '23 21:10 steniowagner