-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Hooks don't work correctly in a mixed CJS/ESM codebase #4648
Comments
You can use a |
Per https://preactjs.com/guide/v10/getting-started#aliasing-in-webpack, that's a Webpack option, right? Neither my linked reproduction nor my actual application are using Webpack, or indeed any bundler at all (at least for SSR). |
Sadly that's a limitation then that you won't be able to resolve, in deno and others you can use a concept like import-maps 😅 |
Is Preact not committed to supporting Node as a runtime? The Preact docs all make it seem like Node is supported, and don't even seem to mention Deno or other runtimes. If Node isn't supported, I'd be happy to open a PR clarifying that in the docs. |
This has nothing to do with supporting Node or not and yes we do support Node. The problem is mixing ESM + CJS in a way that you're running into the classic dual package hazard problem. It's a general problem with mixing ESM + CJS and would happen with any other dependency in your setup too. It's not specific to Preact. The solution to this is to fix your setup. That will also avoid running into the dual package hazard with other libraries in the future potentially. |
It's not actually clear to me what's broken with my setup. I'm using Node and Preact exactly as specified in your documentation. I don't think it should be required to switch to a different runtime or use a bundler. Yes, my example is contrived as I shared above, but that's just a minimal reproduction. In practice, it's (sort of, kind of) an issue with using other packages that themselves depends on React/Preact, and whether or not they're publishing CJS or ESM. In practice, I can't control how third parties publish their code. Even if I convince There might be some confusion about what the dual-package hazard is. It's generally accepted to be an issue with how packages publish their code, and thus on package maintainers to fix. It is not generally an issue of using a package "incorrectly", which this description of the dual-package hazard calls out. To be clear, I'm not faulting/blaming you for this. Dual-publishing ESM and CJS can be really really tricky to get right, which is why I wanted to file this issue! I'm even willing to try to open a PR fixing this, as I really like Preact and want to continue using it in my project. Hopefully it's clear how this is an issue with the way Preact is publishing itself, and if you're open to a PR, I'd love to take a shot at it. |
What's broken is precisely what you noted in your issue: hooks require Preact to be a singleton and yet multiple copies are being loaded. If you cannot author your app in a way to avoid this, then yes, you will need to turn to bundlers and/or other tools that can enforce this for you.
Unfortunately that is not the case here. We intentionally provide outputs in a variety of formats to allow users to use Preact in as many configurations and environments as possible and we do so purely, i.e., without facade modules that don't tend to be valid outside of Node (such as ESM importing CJS, this will fall over in the browser). Externalizing state is also not possible for size, perf, and plain UX reasons. Additionally, As such, we have to say this is an issue of not using Preact correctly, you must load only one copy at once. |
Thanks for the explanation, I guess I'll have to switch to React to take advantage of the broader ecosystem. Would you accept a PR adding this limitation to the docs so that others don't have to discover it independently? |
Potentially, but we'd frame it instead as a usage error and how users should correct it, not a limitation. Having separate ESM & CJS copies of the same module (& module version) in an app, after all, should never be intentional. We don't really have a page set up for that at the moment though. IIRC there's an open issue for adding that sort of thing but we haven't yet gotten around to it. |
I'd propose something like this, added to https://preactjs.com/guide/v10/getting-started#aliasing-in-node:
This would have been enough to quickly steer me in another direction. |
I opened a PR to the docs with the above text: preactjs/preact-www#1234 |
Wouldn't the simplest solution here be to use a version of Node that fixes the dual package hazard? (the last two majors IIRC) |
@developit can you say more about that? I keep up with the Node releases and I don't recall any changes to module resolution that would have resolved this issue. FWIW, I ran my linked reproduction in Node 20, 22, and 23, and I observed the same broken behavior in each. |
Describe the bug
I have a codebase that uses mixed native ESM and CJS. In reality, this is the situation:
react-boostrap
, which internally uses CJS (they also try to publish native ESM, but did so incorrectly, so I can't use that).@preact/compat
to ensurereact-bootstrap
usespreact
instead ofreact
. I set things up with the instructions here: github.com:nwalters512/preact-esm-repro.gitHowever, it can be reproduced in isolation without either
@preact/compat
orreact-bootstrap
.To Reproduce
I created a reproduction here: https://github.com/nwalters512/preact-esm-repro
Clone this repository:
Install dependencies:
Build the project:
Run the project:
Observe an error like the following:
Expected behavior
I would expect to see no error, and see a string logged like the following:
Additional details
I spent a small amount of time digging into this. From what I can tell, this is a classic case of the dual package hazard.
hooks/src/index.js
relies on internal state, e.g.currentComponent
, to function correctly. That file also registers itself with Preact'soptions
object. However, in practice, there are two versions of that file in the published package:node_modules/preact/hooks/dist/index.js
andnode_modules/preact/hooks/dist/index.mjs
. Both of these files end up being loaded in my reproduction: the former byBar.cts
, and the latter byFoo.ts
. They can't both register their callbacks intooptions
, so one version of the hooks module won't get the callbacks necessary to setcurrentComponent
, hence the error seen above.The text was updated successfully, but these errors were encountered: