preactjs / signals

Manage state with style in every framework
https://preactjs.com/blog/introducing-signals/
MIT License
3.71k stars 91 forks source link

How to make sure that runtime included? #388

Closed XantreDev closed 1 year ago

XantreDev commented 1 year ago

I am developling libraries that can be accepted via react/preact. Each package has part that can be used without framework specific adapter, and some parts like hooks, that can be consumed with each of framework. I don't want to create 3 package for each package with framework specific imports, sounds dummy. How to be sure that user install framework specific bindings?

rschristian commented 1 year ago

You don't need to create separate packages per framework, you can just use subpath exports instead? I.e.,:

import ... from 'my-pkg';
import ... from 'my-pkg/preact';
import ... from 'my-pkg/react';
XantreDev commented 1 year ago

I think the good way is too promise user to add import '@preact/signals' or import '@preact/signals-react' in the root of project and just use @preact/signals-core

rschristian commented 1 year ago

If you're worried about the user not installing the bindings you could attempt to import the module and trigger an error that way, for sure.

XantreDev commented 1 year ago

I know but there are two runtimes for preact and react. I can't import both, because user at most of the time uses one of them. So here is two options:

And in case of pnpm we should ask user to use public-hoist-pattern[]=@preact/signals-core (because its will be under @preact/signals dir, and will create duplication of runtime)

rschristian commented 1 year ago

Oh, you don't want to provide multiple exports?

It's still doable, just use a try / catch w/ dynamic imports (import()) to ensure at least one of them is available.

XantreDev commented 1 year ago

You meaning this? packagename/preact packagename/react

Its could be ok, but not convenient for users. But I'm using this packages inside others in monorepo

XantreDev commented 1 year ago

I thinks that code writen for signals should be interoperable between preact/react. So it would be better to core a peer dependency

rschristian commented 1 year ago

You meaning this?
packagename/preact packagename/react

That's what I suggested first, yes, but if you don't like subpath exports, again, you could always use dynamic imports to guess and check which is available. A bit hacky, but will work. To demonstrate:

let useSignal;
try {
    ({ useSignal } = await import('@preact/signals'));
} catch (e) {
    if (e.code !== 'ERR_MODULE_NOT_FOUND') throw e;
    try {
        ({ useSignal } = await import('@preact/signals-react'));
    } catch (e) {
        if (e.code !== 'ERR_MODULE_NOT_FOUND') throw e;
        throw new Error('Could not find valid signals package');
    }
}
XantreDev commented 1 year ago

I don't think it a good way, because someone can have both runtime packages (for example exposed from other dependency with flat node modules). So we will install extra runtime (for preact, for example, that will rely on this inside component and throw while putting signal to jsx, because both runtimes mutates signal prototype and this behavior will depend from imports order)

XantreDev commented 1 year ago

Seems to be it's good way to provide undefined behavior. I really like this signals implementation, but for more wide adoption it should provide a way to safely write libraries

rschristian commented 1 year ago

I think it's pretty unlikely that a user should ever have both installed (unintentionally is another thing), but just throwing it out there. It's an option, up to you to decide what fits your needs best.

Edit: there is a very safe way, and that is peer deps & subpath exports. It's absolutely standard practice these days, but if you don't want to use them, you'll have to get a bit more creative.