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

Development solid export condition #391

Closed
thetarnav opened this issue Jan 16, 2023 · 16 comments
Closed

Development solid export condition #391

thetarnav opened this issue Jan 16, 2023 · 16 comments
Labels
improve documentation Enhance existing documentation.

Comments

@thetarnav
Copy link
Contributor

thetarnav commented Jan 16, 2023

How should library authors set up export conditions to have separate entries for dev and prod? I'm making a tsup preset that would automatically configure those when building a library, this is just the last part that isn't working quite right.

This configuration works in the vite templates. dev.jsx is loaded in development and index.jsx in production when building.
But In solid-start the dev.jsx entry is taken in both cases.

Having a separate entry for the server works without issues though.

{
  "solid": {
    "development": "./dist/dev.jsx",
    "import": "./dist/index.jsx"
  },
  "development": {
    "import": {
      "types": "./dist/index.d.ts",
      "default": "./dist/dev.js"
    }
  },
  "import": {
    "types": "./dist/index.d.ts",
    "default": "./dist/index.js"
  }
}
@ryansolid
Copy link
Member

That is strange to say the least. I wouldn't expect a development condition showing up in prod. I believe the only place we touch this is in the vite-plugin-solid. https://github.com/solidjs/vite-plugin-solid/blob/master/src/index.ts#L327.. and in the adapters which definitely don't include "development"

@thetarnav
Copy link
Contributor Author

If I switch the order of conditions (prod above dev), the prod entry is taken both in dev and prod in SolidStart.
I believe I was using the default node adapter.

@ryansolid
Copy link
Member

I've noticed rollup node resolve plugin is kinda broken in the last 2 major versions.. can you check what version you are on. 13 seems fine. 14/15 not so much.

@thetarnav
Copy link
Contributor Author

thetarnav commented Jan 21, 2023

You're right. Overriding @rollup/plugin-node-resolve dependency to 13.3.0 fixed it. Solid exports are used accordingly in development/server/production.

@ryansolid
Copy link
Member

Yeah something is really broken here just don't know what.

@apatrida
Copy link

apatrida commented Oct 23, 2023

Seems like ark-ui is giving up on solid-start for now due to something related to this issue.
chakra-ui/ark#1363

@ryansolid
Copy link
Member

In setting up for SolidStarts next Beta Phase built on Nitro and Vinxi we are closing all PRs/Issues that will not be merged due to the system changing. If you feel your issue was closed by mistake. Feel free to re-open it after updating/testing against 0.4.x release. Thank you for your patience.

See solidjs/solid-start#1139 for more details.

@ryansolid ryansolid reopened this Dec 19, 2023
@ryansolid
Copy link
Member

I'm going to transfer this one to docs instead. General how to make a package for SSR is good thing to have.

@ryansolid ryansolid transferred this issue from solidjs/solid-start Dec 19, 2023
@trusktr
Copy link

trusktr commented Dec 20, 2023

The Node.js exports thing something to avoid (imo) because it is not part of the ES Module standard. I avoid it altogether, and find other means to do dev vs prod (f.e. set env vars, give devs the tools to configure whether their build will compile out the branches or not, but by default leave the branches in and don't shipped multiple formats of the code stripped and non-stripped).

They are not out in Node.js yet, but standard importmaps are a lot better because:

  1. end app author has full control of dependencies.
    • issues like node module resolution accidentally placing two versions of Solid into a bundle are easy to solve explicitly when using importmaps
    • wrong dist vs prod file being imported is no issue (the app author makes that choice)
    • individual files inside any package can be mapped to alternative implementations, etc.
  2. import maps will work everywhere, in a standard way (see the Node.js import maps tracking issue which is an important item on the loaders to-do list).
    • when Node comes out with importmaps, they will likely become supported as a standard by all build tools (tools like Webpack, etc, currently understand exports, but not import maps)

With import maps, users will be able to opt into dev vs prod in a more explicit way. This will be great because the current automatically-try-to-guess method embedded in the tooling can leave people stuck and feeling out of control when issues like that one from Chakra happen, especially in the many possible use cases for Solid.js outside of components (f.e. server apps, data manipulation, state management libraries, etc, where SSR, loading mock APIs, dev vs prod, etc, may not be concerns and these roadblocks get in the way of doing great things).

Here's a sample of importmap flexibility, where we can override a specific file from a package with another:

https://codepen.io/trusktr/pen/KKEPGYB?editors=1010

Keep an eye out for Node import maps, because this is something we should align with!

In the meantime, besides the above, this this 160-line-long exports field monster is a problem (in my opinion):

https://github.com/solidjs/solid/blob/51c94859098170c913c02a0fe1d9003d1664d130/packages/solid/package.json#L46-L206

If I could make the choice, it would be deleted, and Solid would ship vanilla standard ES modules, and then Solid would build the tooling around that instead of around Node.js, making Solid easily portable and more interoperable.

Building around Node.js is locking into non-standard ways of doing things that will not progress forward very well, especially for a library that is a client-side library, unlike Node.js packages that are intended for Node.js runtime only. The exports field is designed specifically for Node.js runtime libraries, and relying on this for a mostly-browser library is just not ideal.

The future is libraries/frameworks that simply work based on standards that are buildless-first, without any issues like the Chakra issue, but that have opt-in tooling to achieve what is needed (f.e. prod vs dev, srr vs client, etc).

I highly recommend (highly wish) for Solid.js to become as vanilla as possible, which not only simplifies things for users that want to use Solid.js in any way besides the default way with Solid's default tooling, but will also simplify maintenance.

There is a better way. People have been doing dev vs prod builds (etc) long before Node.js exports existed.

The ideal setup, imo

  • write branches in source code (ssr vs not, dev vs prod, etc) with default behavior based on runtime env vars (f.e. default to actual runtime without no-opping (not ssr) and default to dev)
  • compile TS from src/ into dist/ with declarations. Do not ship bundles.
  • do not use exports
  • set the main field to dist/index.js which is compiled from src/index.js (or dist/solid.js if the entry is src/solid.js, etc)
    • If core team still chooses that dev and prod files must be separate (I recommend avoiding this for reasons above and below), then at least default to main pointing to the dev file (still no exports field). Let tooling or import maps handle replacing the dev file with the prod file, while the package is still as close to plain ESM (just more files).
  • set the types field to dist/index.d.ts (or dist/solid.d.ts if the entry was src/solid.js, etc)
  • publish separate build tooling that takes this simple format and does what it needs to do (f.e. that compiles conditional branches based on env var for dev vs prod, ssr vs not, etc, or that replaces dev files with prod files)

As far as the library structure, that's it! You are done! Super simple.

Benefits:

  1. This works everywhere with the most minimal amount of friction. Something like Chakra's issue will never happen.
  2. A vanilla ES module client app can write a simple import map to point to where local solid-js is installed, and it just works
  3. the devtools experience is great with or without source maps (mostly the difference is just stripped types).
  4. Just about every single JS ecosystem modern build tool on the planet can import from this structure without failure, without any possible complication
  5. Any runtime can easily import from this (Deno, Node, Bun, etc)
  6. It is very very easy to go buildless on both server or client with this setup, with no complications
  7. Build tools can be added to achieve dev vs prod, ssr vs not, compile html template tags or not (like Lit's new compiler), with a 100% opt-in experience beyond the super basic package setup.
  8. It will be easier to make more integrations for Solid.js, even in build setups that are not so mainstream, because they don't need to implement all this conditional module lookup algos.
  9. Generally speaking, it will just be way easier for any tools in the world to simply consume these files.

If someone from core officially endorses trying out a simpler setup, I will be willing to help prototype it.

@trusktr
Copy link

trusktr commented Dec 20, 2023

Here's one of my packages (which as you might guess, follows those ideas):

https://github.com/lume/element

I've committed the dist/ folder so it is all easy to see from the repo (commiting dist/ is not on my list of recommendations, but for my needs this makes it easy to clone the lume super repo and everything works out of the box with no build, which is something I valued, and makes live vanilla demos like this one possible: https://raw.githack.com/lume/lume/develop/examples/gltf-model.html).

We can see that https://github.com/lume/element/blob/main/src/index.ts maps to https://github.com/lume/element/blob/main/dist/index.js along with sibling source map and declaration files. The same applies to other files.

main and types fields simply point to dist/index.js and dist/index.d.ts.

Other problems with Solid's setup I forgot to mention above

  • Developer intellisense experience is not as good as it can be. With solid-js, Go-To-Definition on a Solid API goes to a declaration file, but often we want to look at implementation.
    • Run Go-To-Defintion on a @lume/element API and you'll be taken to its source, with comments and everything. Useful for learning.
  • In solid-js is it not at all clear that a bunch of its code comes from a package called dom-expressions. That's because the dom-expressions package is copied into solid-js when building solid-js locally.
    • Solid's final code is far removed from the format of its source, which also makes learning more difficult.
    • Out of the box, due to this bespoke format, developers will not be able to simply Go To Definition to see original source files that might come from dom-expressions.
    • Development of dom-expressions is essentially hidden, outside of the solid-js repo.
    • With @lume/element, it is crystal clear where all code come from.
  • If someone wants to consume a specific piece of Solid.js, wants to import only that piece (for example, signals.ts), they cannot.
    • They will need to clone solid-js, and build it, and make a custom pipeline just to consume that.
    • If it were all just plain ESM, it would be super simple to consume a piece of Solid's APIs.
    • With @lume/element, just import from a specific file to get a piece of its API, and you will also get type definitions for the file by default because TypeScript by default looks for sibling declaration files.
  • people cannot easily replace a piece of Solid.js with an alternative implementation (like we can with plain ES and importmaps), they will have to fork Solid.js just to make a custom build of it.
    • With @lume/element we can easily replace its files. Ideally we split things out into small enough files that functionalities are self-contained, easy to replace.

I can think of more.

An ES module setup as close to vanilla as possible is simply the way to go. Maximal ease, portability, longevity, and interoperability.


Solid 2.0

Let's please 🙏 consider this for Solid 2.0. I see we're already going in the same direction:

https://github.com/solidjs/signals/blob/dcf7521abad59cacce53a881efd5191627cc46c6/package.json#L35C9-L45

@shiro
Copy link

shiro commented Dec 20, 2023

My 2 cents: Yesterday I went to inspect getRequestEvent, which took me to a declaration file, which had no reference to dom-expressions at all, so I had to go to github and use global search for it in solid start, solid and finally dom-expressions...

Not sure if other IDEs can do it, but at least the current behaviour with the TS language server for vim isn't great (I assume it's similar with other tools). If it's possible to go to the source directly, that would be great, also being able to consume just signals would be cool as well!

@trusktr
Copy link

trusktr commented Dec 20, 2023

Yep, it would be nice!

Not sure if other IDEs can do it

IDEs will never be able to do it because generic IDEs are not designed to specifically understand Solid's bespoke file copying pipeline. Only a hypothetical IDE plugin made by Solid team could enable it (wouldn't be worth it).

@aminya
Copy link

aminya commented Dec 20, 2023

do not use exports

@trusktr I agree with your suggestion to support import maps if someone is interested in using them, but I don't think it is a good idea to break the Nodejs exports. Nodejs has its own standard that many tools support. Breaking the tooling in each new version is a huge mistake that can hinder the adoption of newer versions.

@trusktr
Copy link

trusktr commented Dec 21, 2023

If Solid 2.0 will already be an ecosystem breaking change, and if all of Solid's prescribed tools that the team manages (f.e. app generators, Solid Start, any plugins for vite/babel/etc) are updated to work on 2.0, then this change will be for the better.

A clear migration path should be defined. I will gladly help write this.

Here's just one more example of problem with the current setup:

Screenshot 2023-12-20 at 9 06 58 PM

Which do I auto-complete? Devs should just see one option: "I just want X thing from solid.js".

This will be a philosphical question for core team: "how much does Solid.js want to be able to move forward, and how much does it want to stay full backwards compatible"?

React takes it to the extreme on one end of the spectrum at almost 100% backwards compatibility with one main notable change being the one to setState in class components when it went from sync to async, but that was basically all.

On the other hand, Three.js has the most simple package setup that will last forever, making maintenance as simple as possible, and it makes breaking ecosystem-changes when it determines it is for the better future (it has a 10 month deprecation cycle and anything can break in 10 months for the better), and provides a very nice migration guide along the way. And look at Three, it is doing great. The ecosystem is huge for being in a niche area as opposed to normal non-WebGL/GPU web development.

Solid.js started as a passion free time unpaid non-corporate project aiming to be one of the best and most innovative. Will Solid continue to strive for that? If so, breaking changes wiill be a must.

@aminya
Copy link

aminya commented Dec 21, 2023

I am all on for improvements and innovation, but I do not consider import maps superior to Nodejs exports. It's just a matter of different standards. Browsers sometimes have fallen behind the innovations of Nodejs, and I do not necessarily think import maps are inherently better. I respect your choice if you're on the team with "browsers and no local tooling", but it should be considered that many people are not, and there are many benefits from other perspectives.

So, a yay for innovation, but a nay for breaking things for the sake of a change.

@LadyBluenotes
Copy link
Member

Consolidated this into #473

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

No branches or pull requests

7 participants