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

package.json missing "type" / "exports" #548

Open
braebo opened this issue Jul 10, 2023 · 14 comments
Open

package.json missing "type" / "exports" #548

braebo opened this issue Jul 10, 2023 · 14 comments

Comments

@braebo
Copy link

braebo commented Jul 10, 2023

I believe these fields need to be added to your package.json:

"type": "module",
"exports": {
	"import": "./esm/index.js",
	"require": "./dist/gsap.js",
	"types": "./types/index.d.ts"
},
@jackdoyle
Copy link
Member

Hi @fractalhq

Can you please explain why you think these are necessary/appropriate? Maybe provide links to supporting docs/articles?

@braebo
Copy link
Author

braebo commented Aug 10, 2023

Hey! Sorry, it may not be that simple as I've yet to find time to fork and test it, but you can see more detailed errors regarding the module's configuration by using this great tool from one of the core Typescript engineers in charge of module resolution:

https://arethetypeswrong.github.io/[email protected]

@braebo
Copy link
Author

braebo commented Aug 10, 2023

@jackdoyle
Copy link
Member

Yeah, sorry, it's not at all clear to me what exactly the problem is or the specific solution required. Maybe I'm missing something obvious (to be honest, I don't have a lot of time to do extensive reading on it at the moment).

Have you tested your proposed solution? Do you have a repo that illustrates (in simple form) it broken and then it being resolved by your suggestion?

I've been burned before by adding/changing stuff in the package.json that might have seemed fine in one context but it broke things for many other people, so I'm very cautious about making tweaks like this.

@ivodolenc
Copy link

cjs

These pkg options (main,module,types) are intended for older versions of Node and TS (legacy versions) that used commonjs (require) modules.

// package.json
{
  "main": "./dist/index.js"
  "module": "./dist/index.mjs"
  "types": "./dist/index.d.ts"
}
// require
const { gsap } = require('gsap')

esm

The exports option is intended to be used in combination with the "type": "module" option for modern esm (import) modules.

At the moment, esm is the standard for new projects and all tools/frameworks seem to be following the migration to it.

// package.json
{
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/types/index.d.ts"
      "import": "./dist/esm/gsap-core.mjs",
      "require": "./dist/cjs/gsap-core.cjs",
    },
    "./ScrollTrigger": {
      "types": "./dist/types/scroll-trigger.d.ts"
      "import": "./dist/esm/scroll-trigger.mjs",
      "require": "./dist/cjs/scroll-trigger.cjs",
    },
    // ...
  }
}
// import
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'

ts

Also, in TypeScript projects, the moduleResolution option should be updated to newer versions such as Node16/NodeNext or Bundler.

// tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "NodeNext",
    // ...
  },
  // ...
}
// "moduleResolution": "Node" (legacy)

import { module } from './dir/subdir'
// "moduleResolution": "NodeNext"

import { module } from './dir/subdir/index.js'

breaking changes

Keep in mind that these changes are breaking-changes and the best practice is to create some sort of migration guidelines for users as these changes will break everything.

And to be clear, there are no changes to the production code (core or plugins). Changes are needed in the project/pkg setup.

At some point it will be necessary to migrate all the cjs projects to the new esm to keep up with the JS/TS ecosystem and I think that is inevitable.

For example, typescript-eslint completely dropped support for legacy versions and this is a pkg that has been downloaded over 22M times.

@jackdoyle
Copy link
Member

Thanks for the info, @ivodolenc

I think it would be very unwise for us to implement breaking changes at this point. With 12 million sites using GSAP, there are a lot of people that would probably be very upset if we imposed breaking changes like this. One CDN alone sometimes gets 11+ Billion requests for GSAP per month. And those are exclusively for the CJS files, not ES Modules. So if the change you're suggesting requires us to only provide ES Modules in the package, that's definitely problematic.

Is there something we can do that would help your scenario without implementing breaking changes?

It would also be helpful if you could summarize the problem that the current package.json is causing for you (and others), and an easy way to reproduce it in the most minimal way possible.

@ivodolenc
Copy link

I'm not saying you should do this at all, that's up to you, I'm just saying some info that might be helpful.

As for changes, it has to break at some point, I don't think much can be done for legacy versions.

If most of the major frameworks (next, nuxt, svelte, astro, etc) were to switch to esm-only in about 6-9 months, it's only a matter of time before all the other tools and packages follow that trend, that's just my opinion.

Here is more info for node versions:

It is also important to understand that it is a matter of the author and project organization.

When using packages like rollup, additional production formats like cjs, umd, etc. can always be generated with esm, so for example the end user using cdn will most likely not be affected.

@jackdoyle
Copy link
Member

Yep, we build in ESM and use Rollup to generate cjs.

So our package already delivers ESM...I guess I'm just a bit confused about what exactly the issue is here that causes any problems with the current setup. Are you just suggesting that we add the "exports" for every single JS file (which requires 3 entries for each)? And even doing that may cause breaking changes?

@ivodolenc
Copy link

If you are not familiar with the exports option, I suggest you first check it out and what is the best practice for modern npm+node+ts projects. This is essential before making any changes to the project.

It's was similar with TypeScript, breaking changes were made by the official team when they introduced module resolution NodeNext and the import system, all devs went crazy because of that move, but you get used to it.

It can all be complicated and confusing at first, but you should definitely take the time to research it in more detail.

The current gsap setup works fine, but it is adapted for older legacy versions that are no longer officially supported by node and typescript.

Latest active stable version of node is >=18. Version >=16 is under maintenance, but that means it's finished. More info here.

In version 18, esm is officialy stable and uses exports object to defines module main and subpaths entries.

So this can give you some solid guidance, for which versions you actually want to develop gsap further.

At some point, if you're going to migrate to newer versions of node and typescript, you'll need to implement an exports option in package.json (main, module and types options are no longer needed) and adjust your code to keep up with the changes.

@chalkygames123
Copy link

For those running into the following build error with Astro (and possibly Vite) projects that import GSAP modules, there is a workaround using patch-package to configure the type and exports fields.

Named export 'ScrollTrigger' not found. The requested module 'gsap/ScrollTrigger.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 'gsap/ScrollTrigger.js';
const { ScrollTrigger } = pkg;

Once you have installed patch-package, open the project's node_modules/gsap/package.json file in your editor and make the following changes:

 {
+  "type": "module",
+  "exports": {
+    ".": {
+      "import": {
+        "types": "./types/index.d.ts",
+        "default": "./index.js"
+      }
+    },
+    "./ScrollTrigger": {
+      "import": {
+        "types": "./types/scroll-trigger.d.ts",
+        "default": "./ScrollTrigger.js"
+      }
+    }
+    // and other plugins...
+  },
     "name": "gsap",
     "version": "3.12.5",
     "description": "GSAP is a framework-agnostic JavaScript animation library that turns developers into animation superheroes. Build high-performance animations that work in **every** major browser. Animate CSS, SVG, canvas, React, Vue, WebGL, colors, strings, motion paths, generic objects...anything JavaScript can touch! The ScrollTrigger plugin lets you create jaw-dropping scroll-based animations with minimal code. No other library delivers such advanced sequencing, reliability, and tight control while solving real-world problems on millions of sites. GSAP works around countless browser inconsistencies; your animations **just work**. At its core, GSAP is a high-speed property manipulator, updating values over time with extreme accuracy.",

Then run npx patch-package gsap --exclude '^$' to create the patch. The --exclude option here is required because patch-package ignores changes in package.json by default. See: ds300/patch-package#250

If you are also using @gsap/react, open node_modules/@gsap/react/package.json and make the following changes:

 {
+  "type": "module",
+  "exports": {
+    ".": {
+      "import": {
+        "types": "./types/index.d.ts",
+        "default": "./src/index.js"
+      }
+    }
+  },
   "name": "@gsap/react",
   "version": "2.1.0",
   "description": "Tools for using GSAP in React, like useGSAP() which is a drop-in replacement for useLayoutEffect()/useEffect()",

Then run npx patch-package @gsap/react --exclude '^$'.

Now, the build should work.

Note that you must recreate the patches every time you update the package versions.

@jackdoyle
Copy link
Member

Hi @chalkygames123 Thanks so much for the detailed solution. I'm just curious if you tried importing from the /dist/ directory. For environments that don't understand ES Modules, you should be able to just switch to the UMD versions like:

// ES Module 
import ScrollTrigger from "gsap/ScrollTrigger";

// UMD
import ScrollTrigger from "gsap/dist/ScrollTrigger"; // <-- in the /dist/ folder

@chalkygames123
Copy link

chalkygames123 commented Jan 29, 2024

@jackdoyle Ah, yes, I just tried it and it does work for sure, and I just found the documentation about that (https://gsap.com/docs/v3/Installation#faqs). That said, it is still just another workaround, I guess.

FYI, you may find publint useful for configuring npm packages correctly.

For example:

@jackdoyle
Copy link
Member

Thanks for the clarification. I'm curious - if this is added to the package.json (with no other changes), does it resolve your issue (without needing to import from /dist/)?

"type": "module"

I'm just a bit concerned that if the package technically has BOTH a module (default) version AND a commonjs (/dist/) version, I wouldn't want the /dist/ version to suddenly not work because it's forced everything to be interpreted as modules due to that type declaration. See what I mean?

@chalkygames123
Copy link

In that case, I will end up with getting the following error with import { gsap } from 'gsap';:

The requested module 'gsap' does not provide an export named 'gsap'

and with import gsap from 'gsap';:

The requested module 'gsap' does not provide an export named 'default'

For your concern, I would recommend reading the official Node.js docs, especially the "Dual CommonJS/ES module packages" and the "Conditional exports" sections.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants