natemoo-re / microsite

Do more with less JavaScript. Microsite is a smarter, performance-obsessed static site generator powered by Preact and Snowpack.

Home Page:https://npm.im/microsite

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Provide a public document context for SSG...

eyelidlessness opened this issue · comments

...or Page, or Main context, to avoid ambiguity.

~~~Context~~~ (ahem) use case

I’m currently trying to provide context to certain deep nodes which I expect to use on most/all pages, and which require an async initialization. (Specifically, to give credit to another couple awesome projects: Shiki and remark-shiki-twoslash.)

I’m also trying to use inline markdown with a template literal because I’m a weird person who wants the inverse of MDX: I want markdown in JSX, not the other way around, because it gives me all the cool Remark/Rehype stuff but with all the cool tooling that comes relatively for free in a .tsx file. Plus I’m kind of obsessive about collocation of component definitions which is why I put all that effort into getting Fela working.

I’ve tried setting up in a custom document or even in a page’s getStaticProps, then providing the (now synchronous) highlighter instance with a context provider, but that appears to be optimized out at build time. I’d essentially have to hydrate the entire page to keep it, or move a lot of boilerplate down through props.

Caveats filing the issue

  • As always I’m doing weird custom stuff so it’s entirely possible I’m fighting my own setup/not using tooling properly. I ran out of steam debugging and felt like it’s more productive to start a discussion.

  • This could potentially be addressed by #99 in ways I’ll handwave about to avoid derailing my own issue; I think this hypothetical API could be valuable and a better DX than yet another build plugin.

Proposal

I know there’s an internal doc context used at the SSG stage to provide various details for the final full HTML render, much like the internal head context.

I don’t want to overload this for end users, but provide a distinct (opt in of course) API for sites to define their own document/page context that’s guaranteed to be available during build/stripped out unless explicitly hydrated.

In fact I might even want to go one step further and suggest a general API/DX improvement over the Next/Gatsby style interface (which should be backwards compatible unless I’ve failed to think of something), and say that props supplied in a custom document or getStaticProps could automatically provide context consumable by deeper components. This would address very similar issues I had with those doc/page APIs that don’t allow per-component data fetching at build time.

Addressing problems and risks I can anticipate

  • More API, more to document, more to maintain and risk breakage; but this kind of API is especially useful and viable for a static-first library.

  • I’m not especially fond of context (or hooks) because they make reasoning about components harder; for the DX it’s a huge win, allowing more build-time work in the normal flow without more tooling.

  • The biggest risk I see is accidental deoptimization by making build-time stuff part of the bundle; there’s already safeguards in place distinguishing build/bundle and this could follow suit.

  • Type safety is a major concern for me, and anything automated/implicit could lead to magic and assumptions; definitely an argument against automating any of this, probably easier to target doc-global and/or a very specific API for context overall. Or a useDocument/usePage hook but I’m losing it even suggesting that 🙃

  • I already said the context API isn’t my favorite, is it actually right for this? I’m just trying to use prior art. Open question IMO.

I guess I should add that the alternatives are I discover I’ve configured myself into my own hell or I gotta put some babel into my mix. Both are real potentials

This is an interesting idea!

Two things stick out to me:

Type safety is a major concern for me, and anything automated/implicit could lead to magic and assumptions; definitely an argument against automating any of this, probably easier to target doc-global and/or a very specific API for context overall. Or a useDocument/usePage hook but I’m losing it even suggesting that 🙃

It would definitely be useful to have somewhere to place global Provider components for every page, since that's a really common use case and custom Document doesn't support it right now. I am thinking an entry point like Next's pages/_app.js? Users could add any custom Provider components there, which would allow them to manually pass down anything from Document.prepare, but Microsite wouldn't do it automatically.

Would that potentially cover this use case? One big issue I see is that it's not clear how those Providers should interact with hydration... Would these only be static? Would users understand that requirement?

In fact I might even want to go one step further and suggest a general API/DX improvement over the Next/Gatsby style interface (which should be backwards compatible unless I’ve failed to think of something), and say that props supplied in a custom document or getStaticProps could automatically provide context consumable by deeper components. This would address very similar issues I had with those doc/page APIs that don’t allow per-component data fetching at build time.

Something like a useStaticProps hook could be a really nice DX improvement!

I am thinking an entry point like Next's pages/_app.js? Users could add any custom Provider components there, which would allow them to manually pass down anything from Document.prepare, but Microsite wouldn't do it automatically.

The familiar API would definitely benefit newbies and adoption. I’m personally always confused by the different special page worlds and what’s allowed or not (though definitely have better reflexes knowing the Microsite internals). IMO it’s less important where it lives, more important that it’s clear how to use and has a clear type safe interface for use (so pages don’t get magic from app or doc or whatever).

Would that potentially cover this use case? One big issue I see is that it's not clear how those Providers should interact with hydration... Would these only be static? Would users understand that requirement?

Yes I think it would cover my use case!

As far as hydration I’m hesitant to suggest another server/client boundary. But this does have some additional risks I didn’t think about earlier, specifically serializability. Right now Next says you can’t even pass static props if they’re not primitive (they won’t even handle normal JSON-serialization values like Date or anything with a toJSON method which is IMO a bug). So my use case is dead in the water right there as I couldn’t pass a function much less a context. To be fair I think some serializable constraints are reasonable for hydration though I think Next is too restrictive.

I think the easiest approach would definitely be another server/client boundary but I’m so hesitant to introduce one. If that is the answer I’d want to make sure it has a clearly different call site. And here it really starts butting up against the type safety goal again :/

I’m gonna go do some hammock driven development on this and give it a good think instead of just braindumping. I’m glad you’re open to the idea!

Well that hammock really helped! Turns out I didn't need any kind of context or per-page or per-document setup for this. I just needed to make a couple changes in tsconfig.json to allow me to use top-level await!

Awesome! Closing for now but I will definitely be thinking about something like _app for other similar use cases.

Totally understand keeping the issues cleaned up but I’m gonna probably want to offer a PR documenting the solution I came up with for my static-only use case as soon as I can prove out that I can still hydrate the runtime for twoslash, once I have a content need for that functionality (so far I’ve been able to get away with just plain Shiki highlighting and I must say it’s wonderful!).

Not that I think it’s the final answer here but if it can save someone a few days of hunting for a solution when top-level await might be all they need I’d love to help there.

On that note at some point after launch I plan to break a significant amount of my site/build off into open source libraries and do some deep dive writing about my motivation for the set of tools I chose and how I got them to play nice.

Microsite is definitely going to be a central theme because it’s the core that let me get everything else I wanted for my project. Once I’m there I’d be happy for anything I release or post to be incorporated into examples or docs to help anyone else wanting any part of my weird combination of preferred DX/build priorities.