toastdotdev / toast

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

Extensions to SetDataForSlug: mime types and ssr/client restrictions #50

Open ChristopherBiscardi opened 3 years ago

ChristopherBiscardi commented 3 years ago

SetDataForSlug

Currently the Rust types for setDataForSlug() as used in the JS environment look like this

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "mode")]
pub enum ModuleSpec {
    // users should see this as `component: null`
    #[serde(alias = "no-module")]
    NoModule,
    #[serde(alias = "filepath")]
    File {
        #[serde(alias = "value")]
        path: PathBuf,
    },
    #[serde(alias = "source")]
    Source {
        #[serde(alias = "value")]
        code: String,
    },
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct SetDataForSlug {
    /// /some/url or some/url
    pub slug: String,
    pub component: Option<ModuleSpec>,
    pub data: Option<serde_json::Value>,
    pub wrapper: Option<ModuleSpec>,
}

Module Spec

I'm going to ignore the slug for now, which gives us a JSON object for ModuleSpec that looks like one of these three options.

{
  mode: "no-module"
}
{
  mode: "filepath",
  value: "./some/file.js"
}
{
  mode: "source",
  value: "import { h } from 'preact'; export default props => <div>hi</div>"
}

This covers many needs. For example it allows:


So we cover "ways of acquiring content":

and currently assume that you're passing in a JavaScript type (a .js file or a js source string).

What it doesn't cover

SSR vs client-side

Possible extension

{
  mode: "filepath",
  value: "./some/file.js",
  server: true, // default
  client: true, // default
}
{
  mode: "filepath",
  value: "./some/file.js",
  client: false,
}
{
  mode: "filepath",
  value: "./some/file.js",
  server: false,
}

different content types

We also don't support other content types when used directly.

Possible extension

maybe we use mime types? This would require custom types for mdx and vue sfc but it would also ensure we don't conflict with existing future additions and accidentally create our own format

{
  mode: "filepath",
  value: "./some/file.mdx",
  mediaType: "text/mdx",
}
{
  mode: "filepath",
  value: "./some/file.html",
  mediaType: "text/html",
}
{
  mode: "filepath",
  value: "./some/file.js",
  mediaType: "application/js", // default
}
{
  mode: "filepath",
  value: "./some/file.md",
  mediaType: "text/markdown",
}
jlengstorf commented 3 years ago

I like this 💜

RE: different client/server rendering:

seems complicated but maybe valuable in the way of "use react-helmet on the server but omit it on the client" sort of way.

my use case: for a lot of my blog posts, I use MDX to enhance the posts with extra markup, but I don't need any interactivity at all — it would be great if I could tell Toast to only generate the HTML and skip the rehydration since I don't need it on those pages

ChristopherBiscardi commented 3 years ago

"different server and client-side components" in this description is meant to be

{
  server: {
      mode: "filepath",
      value: "./some/file.js",
  },
  client: {
      mode: "filepath",
      value: "./another/different/file.js",
  },
}

Which I'm not sure has much value. I guess it could if you wanted to run some arbitrary script on the client, but generate the html on the server.

it would be great if I could tell Toast to only generate the HTML and skip the rehydration

Would be:

{
  mode: "filepath",
  value: "./some/file.js",
  server: true, // default
  client: false
}
jlengstorf commented 3 years ago

in cases like I ran into with CJS/ESM compat problems, I can see this being a not-great-but-maybe-more-approachable-than-patch-package option?

client: false is exactly what I was hoping for! in that scenario, does it skip everything? page-wrapper, etc. to end up being equivalent to serving the page with JS disabled?

ChristopherBiscardi commented 3 years ago

Another note here: setDataForSlug currently focuses on pre-render-able content. That is, if you have .mdx content that is not page-level content (maybe about.mdx for an author's bio), it should not be pre-rendered.

setDataForSlug("/fragments/about", {
  prerender: false,
  component: {...},
  data: {...},
  wrapper: {...}
});

prerender precludes wrapper though (in the current model, if it's not prerendered, it wouldn't get wrapped), so it's awkward to have them both at the same level.


So it feels like setDataForSlug is getting pretty heavy. I still feel like learning "one API" is better than "five APIs" for users (one could imagine setData, setComponent, setPage, etc), but only if we can keep the options coherent (that is, so that two mutually exclusive options are not usable at the same time) as incoherent options would be confusing for users, on top of needing to understand the API itself.

A no-prerender API would also replace webpack loaders to an extent.