toastdotdev / toast

The best place to stack your JAM. Toast is a Jamstack framework
153 stars 13 forks source link

[idea] how could a clear "only do this at build time" API work? #57

Open ChristopherBiscardi opened 3 years ago

ChristopherBiscardi commented 3 years ago

originally created by @jlengstorf at https://github.com/ChristopherBiscardi/toast/issues/30

it would be great if there was a clear way to specify which work in the app should only happen during build time. this could be something like Next's getStaticProps, or something entirely different

11ty does an interesting thing where you basically execute arbitrary Node and dump the output into its data layer, which is flexible to the point of being potentially very confusing, but might be worth exploring as a true "pre-processing" step instead of mixing into the React logic directly?

going galaxy brain for a second, I feel like there are a couple opportunities in this API design:

  1. a clear way to feed static data into React apps. we could sidestep this entirely by generating JSON files instead of worrying about a custom data layer, or we could auto-insert a data prop into components (or something different; I'd love to hear how this is handled in other frameworks, languages, etc. to see if there are some existing patterns that are really intuitive)
  2. a clear way to register custom data into the static data layer. Gatsby's GraphQL approach is cool because it has a clear API, but it's A Lot™ to get ramped up on, so that's probably more than we need. 11ty and Next let you write whatever code you want and just fire that back into your pages, which is extremely flexible but not super approachable for someone who doesn't have a lot of JavaScript experience. I feel like finding the goldlocks zone of "structured enough to be teachable/predictable" and "flexible enough that I can just add data" is where the magic lies, but I also think that this could be its own project that emerges after seeing how people are adding and using this data in the wild for a while

I'm kinda braindumping here in hopes of getting smarter people than me thinking about this, because I know what I want the outcome to be, but I don't have strong opinions on how it's actually done 😅

ChristopherBiscardi commented 3 years ago

It looks like with React Server Components, that React is using thing.server.js, thing.client.js and thing.js for server, client, and shared components respectively. This allows static prop generation on the server through hook-like things.

The react-fetch hook-thing is built on top of unstable_getCacheForType from react on the client, which is a facade that makes it look like fetch's public API (replacing the implementation not just with env-specific fetch implementations, but also the React wrapping code), just as react-fs and react-pg are facades that make them look like pg and the filesystem

import {unstable_getCacheForType} from 'react';

react-pg and react-fs are server-only while react-fetch can operate in both contexts. Given a server component that uses react-{pg,fs,fetch}, the behavior is similar to next's getStaticProps/getServerSideProps, but on the component level. Which is no surprise, Next was the framework the rsc preview videos kept mentioning and has been closer than other projects to the React team for some time now (for ex: the react-fast-refresh work).


.server.js components don't get sent to the client, but the results do, where they get merged into the client.

M1:{"id":"./src/SearchField.client.js","chunks":["client5"],"name":""}
M2:{"id":"./src/EditButton.client.js","chunks":["client1"],"name":""}
S3:"react.suspense"
J0:["$","div",null,{"className":"main","children":[["$","section",null,{"className":"col sidebar","children":[["$","section",null,{"className":"sidebar-header","children":[["$","img",null,{"className":"logo","src":"logo.svg","width":"22px","height":"20px","alt":"","role":"presentation"}],["$","strong",null,{"children":"React Notes"}]]}],["$","section",null,{"className":"sidebar-menu","role":"menubar","children":[["$","@1",null,{}],["$","@2",null,{"noteId":null,"children":"New"}]]}],["$","nav",null,{"children":["$","$3",null,{"fallback":["$","div",null,{"children":["$","ul",null,{"className":"notes-list skeleton-container","children":[["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}]]}]}],"children":"@4"}]}]]}],["$","section","1",{"className":"col note-viewer","children":["$","$3",null,{"fallback":["$","div",null,{"className":"note skeleton-container","role":"progressbar","aria-busy":"true","children":[["$","div",null,{"className":"note-header","children":[["$","div",null,{"className":"note-title skeleton","style":{"height":"3rem","width":"65%","marginInline":"12px 1em"}}],["$","div",null,{"className":"skeleton skeleton--button","style":{"width":"8em","height":"2.5em"}}]]}],["$","div",null,{"className":"note-preview","children":[["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}],["$","div",null,{"className":"skeleton v-stack","style":{"height":"1.5em"}}]]}]]}],"children":"@5"}]}]]}]
J5:["$","div",null,{"className":"note","children":[["$","div",null,{"className":"note-header","children":[["$","h1",null,{"className":"note-title","children":"testing"}],["$","div",null,{"className":"note-menu","role":"menubar","children":[["$","small",null,{"className":"note-updated-at","role":"status","children":["Last updated on ","28 Dec 2020 at 8:15 PM"]}],["$","@2",null,{"noteId":1,"children":"Edit"}]]}]]}],["$","div",null,{"className":"note-preview","children":["$","div",null,{"className":"text-with-markdown","dangerouslySetInnerHTML":{"__html":"<p>this</p>\n"}}]}]]}]
M6:{"id":"./src/SidebarNote.client.js","chunks":["client6"],"name":""}
J4:["$","ul",null,{"className":"notes-list","children":[["$","li","1",{"children":["$","@6",null,{"id":1,"title":"testing","expandedChildren":["$","p",null,{"className":"sidebar-note-excerpt","children":"this"}],"children":["$","header",null,{"className":"sidebar-note-header","children":[["$","strong",null,{"children":"testing"}],["$","small",null,{"children":"8:15 PM"}]]}]}]}]]}]

rsc are entirely built on webpack to support next (and remix, and anything else in the shape of next/built on webpack). While this may change the only "alternative implementation" I've seen mentioned is for parcel.

ChristopherBiscardi commented 3 years ago

Vue is also experimenting with this sort of "server only/disappearing components" work as noted by this thread that talks about tree-shaking imports for render functions that then get stringified for the browser.

Svelte has also long claimed to be "the disappearing framework" but usage is a distant third to vue and react and it's different enough that I'm not sure the implementation details are useful in those contexts.