Open braebo opened 1 year ago
Hi @FractalHQ
Can you please explain why you think these are necessary/appropriate? Maybe provide links to supporting docs/articles?
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:
Here is the documentation: https://nodejs.org/api/packages.html#conditional-exports
And the section specific to this particular kerrfuffle: https://nodejs.org/api/packages.html#writing-dual-packages-while-avoiding-or-minimizing-hazards
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.
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')
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'
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'
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.
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.
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.
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?
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.
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: https://github.com/ds300/patch-package/issues/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.
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
@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:
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?
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.
I believe these fields need to be added to your
package.json
: