Skip to content
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

Update module resolution to NodeNext #10620

Closed
wants to merge 3 commits into from

Conversation

TrevinAvery
Copy link
Contributor

Checklist:

  • If this PR contains changes to the library itself (not necessary for e.g. docs updates), please include a changeset (see CONTRIBUTING.md)
  • If this PR is a new feature, please reference an issue where a consensus about the design was reached (not necessary for small changes)
  • Make sure all of the significant new logic is covered by tests

This PR addresses #9890 and this feature request by updating the entire package to use NodeNext module resolution and add the exports field to all package.json files in the distribution.

There are two major changes that had to be made:

  1. Updating all relative imports.
    The NodeNext module resolution enforces the latest ESM requirements, which includes using the full file name for import statements. This means that for importing a file, the import must end in .js, and for importing a directory, the index.js file must be named explicitly. All changes in the src and docs directories should be exclusively one of these two changes. The files in the config directory also needed these changes, but required additional work as well.

  2. Updating configuration files to TypeScript.
    There was a bit of complexity in getting the package to build with the new imports while also getting the tests to pass.

    1. Jest could not understand the new imports unless it was run as ESM, which meant moving the config file to TypeScript.
    2. But now, all files run in the tests could not import entryPoints unless it was also TypeScript.
    3. Then, the rollup script could no longer import the entryPoints file, so it also had to be moved to TypeScript.
    4. To make this work, I had to remove the config/tsconfig.json file and modify the one at the root of the package because rollup does not have an option to configure which tsconfig.json file to use.
    5. To make everything build as it did before, I created tsconfig.build.json and configured Jest and tsc to use that file.
    6. I updated the rollup script to convert all require('*/index.js') to require('*/<bundleName>.cjs').
    7. Finally, I added the exports field to each package.json file in the prepareDist.ts file.

This change should be fully backwards compatible, with no noticeable change to its external API, with the only exception that it should be correctly imported in any project using NodeNext. For this reason, I have not included a change set. If this is needed, I'd be happy to add one.

Since this is such a large code change, I am looking for some general feedback and would be happy to address anything I may have missed.

@changeset-bot
Copy link

changeset-bot bot commented Mar 3, 2023

⚠️ No Changeset found

Latest commit: 0f1e306

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify
Copy link

netlify bot commented Mar 3, 2023

Deploy Preview for apollo-client-docs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 0f1e306
🔍 Latest deploy log https://app.netlify.com/sites/apollo-client-docs/deploys/64023180ac2807000864cbd8
😎 Deploy Preview https://deploy-preview-10620--apollo-client-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

@benjamn
Copy link
Member

benjamn commented Mar 8, 2023

@TrevinAvery It seems like many (44) tests are failing because of these changes. Can you investigate further?

@TrevinAvery
Copy link
Contributor Author

TrevinAvery commented Mar 8, 2023 via email

@phryneas
Copy link
Member

phryneas commented Mar 9, 2023

Finally, I added the exports field to each package.json file in the prepareDist.ts file.

This change should be fully backwards compatible, with no noticeable change to its external API, with the only exception that it should be correctly imported in any project using NodeNext. For this reason, I have not included a change set. If this is needed, I'd be happy to add one.

Unfortunately, adding an exports field to any package.json is very much a breaking change - a lot of bundlers out there will completely fail on that. (Other packages have tried this and rolled back again, over and over).

We can only ship something like that in a major - and that won't happen in the next few months.

The only compromise I could think of here would be adding a @apollo/client/esm subfolder that recreates the whole folder structure of the package, only containing package.json files that contain exports fields that point at the actually distributed code.

Could you try if that angle would also solve your problems?

@TrevinAvery
Copy link
Contributor Author

@phryneas My understanding is that if a bundler doesn't know how to use exports, it will just default to using main and/or module as it did before. Thus, I have left those fields unchanged.

Also, since using exports limits the entry points to exactly those listed, I made sure to include an entry for every value in the entryPoints file, with three variations each to account for every way one of those files may have been imported:

  1. ./path
  2. ./path/index
  3. ./path/index.js

To my understanding, and what I've read in the node.js docs, this should cover all the bases.

Questions:

  1. Is there a bundler that breaks because it doesn't recognize exports, or possibly interprets it wrong?
  2. Is there a list of specifically supported bundlers?
  3. Is there something I'm missing or misunderstanding?

@phryneas
Copy link
Member

@TrevinAvery I would have to dig in years of twitter threads to pull out specific examples, but there are reasons. Once in a while, someone tries it and things start breaking left and right. Every time in a fun, new and unexpected way. I believe one of the last cases was immer that added exports in a patch release.
That's fine in a major, it's not in a minor.

As a result, in the library author community, we widely see adding exports as a reason to put out a new major version.

I'll still try to answer the questions:

  1. I would be most afraid of a bundler that worked without exports after the change recognizes exports, but handles it in a slightly different way than before.
  2. Unfortunately not, so we have to care about breaking bundlers we don't even know about. (Which, again, would be fine in a major and not fine in a minor)
  3. You're not really misunderstanding anything, everybody looks at it the way you do first because it seems easy in the docs. Then you release something with enough users, and reality gets back to you. This is generally an absolutely horrifying minefield.
    Just as an example of the complexity involved here, here is a fun little twitter thread about the fun one of the TypeScript maintainers has to deal with: https://twitter.com/atcb/status/1634653474041503744

@TrevinAvery
Copy link
Contributor Author

TrevinAvery commented Mar 15, 2023 via email

@phryneas
Copy link
Member

My suggestion would be that you evaluate a @apollo/client/esm subfolder that contains a skeleton structure of package.json files - that way, we could add some level of support without having to wait for the next major release.

Revisiting the packaging and bundling is definitely on the Roadmap for the next release - so in that regards, there is nothing to be done :)

@phryneas
Copy link
Member

While I really appreciate the PR, I believe except for the exports field in package.json, most changes that were suggested here are already part of the repo by now (e.g. via #10994).

When we get to the point of adding an exports field, we will also have a ton of extra work ahead of us - a lot of bundling changes to get a true dual-emit with explicit type declarations for .cjs and .mjs files, as well as normal .js files to stay compatible with webpack 4.
So I guess at that point, those exports changes are likely to be generated by new tooling (like tshy) we will have to pick up.

As a result (and because rebasing all of the work in here is probably going to be a nightmare), I will close this PR for now and will revisit the topic when we target a new major version.

I hope that's okay with you @TrevinAvery. I want to repeat myself here - I really appreciate the work you put into this!

@phryneas phryneas closed this Sep 14, 2023
@phryneas phryneas mentioned this pull request Sep 14, 2023
2 tasks
@basicdays
Copy link

@TrevinAvery For what it's worth, I have a temporary workaround that can be used with patch-package or yarn patch over here: #9976 (comment)

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants