-
Notifications
You must be signed in to change notification settings - Fork 68
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
Dual package hazard #509
Comments
Hey @fubhy unfortunately I think this might be an issue with the ecosystem. But, can you share a bit more details with what happened? What CJS library was it that pulled in the CJS export, etc.? Dropping CJS would be nice, but not sure we can without some tests to ensure we're not breaking a bunch of bundlers out there. I can maybe look into this with our connect-es-integration repo to see how things would behave. |
Hey @smaye81. Sorry for the late reply! I can see how it might be difficult for y'all to entirely drop CJS support now that it's already "live" with both ESM & CJS. Maybe, instead of dropping it entirely you'd consider publishing the ESM package as a CJS wrapper instead? That would eliminate the dual package hazard effectively too... Admittedly at a bit of a maintenance burden in terms additional build & build config shenanigans. Particularly for Given how "low level" particularly the "@bufbuild/protobuf" is for anything that has anything to do with connect, I'd be willing to bet that this is destined to happen more frequently as adoption increases. Especially as people might start to publish & reuse protos & utils via shared packages. A single misconfigured (or intentionally CJS-only) package and you run into this. Mind you: this happened inside a single (admittedly large) monorepo with only local lib & app packages for me and it was already hard to debug ;-) |
More info on how to effectively avoid dual package hazard can be found here: https://esbuild.github.io/api/#how-conditions-work And this PR from Mateusz adds some more too: esbuild/esbuild.github.io#40 |
... This part in particular:
This should work without issue or extra hassles because |
@smaye81 Would you accept a PR (across all |
I'm open to a discussion on it, but let's wait until people are back from vacation, etc. (ideally next week at the earliest). Don't want to make any big decisions without a proper review. |
Switching to ESM wrappers has the downside of effectively disabling tree-shaking as the ESM wrapper imports the CJS file. See https://nodejs.org/api/packages.html#dual-package-hazard |
Not if you also provide a esm version for bundlers. |
Basically you author the "exports": {
".": {
"types": "./dist/index.d.ts",
"module": "./dist/esm/index.js", // ESM (for bundlers)
"import": "./dist/proxy/index.mjs", // ESM -> CJS Wrapper (proxies to dist/cjs/index.js)
"default": "./dist/cjs/index.js" // CJS
}
} This way, you don't get the dual package hazard whilst retaining tree-shaking capabilities for bundlers. |
@fubhy, apologies for the late response. An ESM wrapper for Node.js in combination with true ESM for the I do wonder how well it is supported in bundlers, because the fallback-behavior would come with a pretty big bump in bundle size. Do you know any popular packages that use this setup? |
Hey @timostamm, no problem. I know that @Andarist has been neck deep in this problem space. I did a quick search through his packages and found that e.g. https://www.npmjs.com/package/react-textarea-autosize uses a similar setup. This one seems to be generated through https://preconstruct.tools/ |
Full disclosure: I use this setup in Emotion and it works exceptionally well. It's not that I didn't run into any problems though but they were really really minor and I went that extra mile to fix those issues at the root (for example in Vite). There is literally one really really edge case that I'm aware of in Next.js: vercel/next.js#49898 . It doesn't affect most applications, it's related to a very specific mix of CJS & ESM in the app code~ and so far only one project has reported it. Even though Material UI reported it - they only observed it in their own docs-related setup and it was not reported by their users. We were examining this case and it looks like a bug in Next.js and not like the issue with this setup. Other than that... I'm not aware of any issues with it :) |
We just made the change for all relevant packages and ran some tests in connectrpc/examples-es#1002. It's obviously a bit difficult testing it exhaustively, but I can confirm that the popular bundlers tested resolve as expected. (Thanks to @smaye81 and @paul-sachs for testing). |
Fixes #305... again again. The `esm` wrapper (as described in bufbuild/protobuf-es#509) creates problems when using a peer dependency that defines it's exports without an import proxy (in our case, react-query). # Current situation Our exports look like: ```json { "exports": { ".": { "node": { "import": "./dist/proxy/index.js", "require": "./dist/cjs/index.js" }, "module": "./dist/esm/index.js", "import": "./dist/proxy/index.js", "require": "./dist/cjs/index.js" } } } ``` The above meant the following: - When the **application** imported `QueryClientProvider` from `@tanstack/react-query`, it resolved to the `esm` version of the context - When the **library** imported `useQuery` from `@tanstack/react-query`, it resolved instead to the `cjs` version of the package (since NextJS SSR is considered a "node" environment) This mismatch between the esm and cjs versions causes a problem detecting context # Alternatives considered We could expose our own hooks/components with a one-to-one mapping from `@tanstack/react-query` and require our own `QueryClientProvider` to be initialized. This would bypass these kinds of issues, but introduce more setup and potential for confusion. It would mean that using `react-query` for anything non-connect related would require an additional query client provider which MAY actually be the same client depending on where (node/browser) it's running. # Reason we chose this solution By not using an `ESM` wrapper, we avoid these possible import mismatches. It does introduce the possibility of dual package hazard with class instances (where instanceof is used, like with ConnectError) but we don't use any of that in connect-query so we can be relatively safe in that regard.
I just ran into the dual package hazard problem with
@bufbuild/protobuf
and the exported WKT types. I was checkinginstanceof
on a Timestap type and that failed because the object instance I got was from the CJS export (through a CJS library) and I was comparing it againstTimestamp
via my local ESM import.Personally, I think it's time to drop CJS altogether. Top-level await has been available for quite some time now and is a perfectly acceptable escape hatch for anyone who needs to import ESM modules into CJS via dynamic import.
The text was updated successfully, but these errors were encountered: