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

RFC: Microsite v2.0.0 #163

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
93 changes: 93 additions & 0 deletions rfcs/001-microsite-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# RFC: Microsite v2.0.0

Microsite v1.0.0 was released in December 2020. Conceptually, it was my first attempt to unify several strands of work on "Partial Hydration" into a single, well-optimized tool.

In March 2021, I was hired to work on [Astro](https://github.com/snowpackjs/astro), a framework-agnostic tool that built on many of the ideas I was exploring with Microsite.

I've learned so much while working on Astro. Now seems like as good a time as any to apply those lessons to Microsite.

## JSX Compiler

The core of Microsite v2.0.0 would be built around a custom JSX compiler. Conceptually, this would be similar to [Solid](https://www.solidjs.com/)'s approach with [`dom-expressions`](http://npm.im/dom-expressions)—leverage JSX/TSX as the defacto standard for authoring Markup in JavaScript, but compile JSX to a non-standard, highly optimized output.

Instead of the `withHydrate` HOC, this compiler would be able to leverage a hydration directive directly inside of JSX. The compiler output would be highly optimized for SSR speed, relying on strings rather than a server-side VDOM.

It's possible that hydration directives wouldn't even be necessary if we can detect whether a component uses `on*` event listeners or `use*` hooks. In that case, I'd default to `visible` hydration.

```tsx
// Input
/* imports */

const Input = () => {
return (
<>
<Head>
<seo.title>Microsite v2.0.0</seo.title>

<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="alternate icon" href="/favicon.ico" />
</Head>

<main>
<div class="stack">
<h1>Hello world!</h1>
</div>

<Counter @hydrate={{ method: 'visible' }} />
</main>
</>
)
}

// Output
import { ssr, component, escape } from 'microsite/internal';
/* imports */

const Output = () => {
return ssr`
${component(Head, null, ssr`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Not to overly focus on implementation details right away, but I like the use of ssr as a tagged literal. It suggests, among other things, a potential solution to one of Astro’s rough edges: rendered HTML and raw strings being the same type at build-render time.

    That said, I’m wondering about potential issues supporting that in a children position. Does ssr also return a component?

  2. I think component used this way may be challenging when supporting Solid, where its equivalent is createComponent(() => Component, props). If Microsite is going to have something similar, I think following Solid’s lead here is probably the most flexible option.

    Alternatively, this might be something better left to per-JSX library configuration, not dissimilar to various pragmas.

${component(seo.title, null, ssr`Microsite v2.0.0`)}
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="alternate icon" href="/favicon.ico" />
`)}
<main>
<div class="stack">
<h1>Hello world!</h1>
</div>

${component(Counter, { '@hydrate': { method: 'visible' } })}
</main>
`
}
```

## Framework Agnostic

Switching the majority of JSX compilation to a custom compiler would also open the door for other JSX-based frameworks. Preact would be the primary one, but Solid would be supported as well.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love that this is an explicit goal! Because they’re both quite different in practice, I’d like to consider them both co-equal “primary” targets. This would help to ensure design and APIs are flexible enough to achieve this goal without major discrepancies.


> I'm not interested in supporting React unless it is aliased to `preact/compat`.

### Open Questions
- Framework detection
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, Microsite may want to consider sidestepping this issue entirely, by limiting projects to one framework per package. Multi-framework support in Astro is neat (in the “wow it’s wild you can do that” sense), but I suspect it’s less of a must-have than generalized JSX partial hydration.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a very good point and simplifies things a lot!

- Framework JSX compilation

## Custom Rendering

Microsite would have its own internal `renderToString` that resolves everything to a string of HTML.
`component` would be able to render the component instance to a string of HTML and generate the final hydration script.

### Streaming Rendering?

We could potentially return a generator function to enable streaming responses by exposing something like `renderToStream`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another case where we’re probably going to want to anticipate component in (potentially nested) props positions. I think it’s doable but I definitely want to make note of it early on.


## `esbuild`

`esbuild` is pretty amazing, and there's also a Deno module. Assuming we could build an HMR engine for `esbuild`, do we really need all the extra weight that Snowpack/Vite bring along?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏👏👏

And while we’re on that topic, I think it might be good to be very explicit from the start about how v2 lives alongside the rest of the esbuild ecosystem. Which is to say, I think Microsite v2 should be designed to be a pipeable esbuild plugin, rather than a build step outside of or adjacent to esbuild.

Longer term, I’d like to also consider providing compiler APIs for additional AST transforms. This is currently a huge deoptimization/pain point with esbuild’s design. If you agree with this goal, I think it would be best to be ahead of the curve, by designing AST and transform as separate plugins even if they’re exposed as a single plugin by default.


## Deno

I really like [Deno](https://deno.land/) and Microsite should run on Deno. We probably _shouldn't_ drop Node support, but...

If the compiler was written in Rust, we could ship `microsite` as a stand-alone executable that uses [`deno_core`](https://docs.rs/deno_core/0.98.0/deno_core/) under the hood. The idea is extremely appealing!

Using `deno deploy` with no build step sounds amazing.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏👏👏 no further comment on this!