ProjectEvergreen / greenwood

Greenwood is your full-stack workbench for the web, focused on supporting modern web standards and development to help you create your next project.
https://www.greenwoodjs.io
MIT License
95 stars 9 forks source link

custom response and constructor props for SSR pages #1177

Open thescientist13 opened 8 months ago

thescientist13 commented 8 months ago

Summary

Coming out of #952 , where in we introduced Web Server Components, that took in an instance of Request as a constructor prop

export default class PostPage extends HTMLElement {
  constructor(request) {
    super();

    const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
    this.postId = params.get('id');
  }

  async connectedCallback() {
    const { postId } = this;
    const post = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`).then(resp => resp.json());
    const { id, title, body } = post;

    this.innerHTML = `
      <h1>Fetched Post ID: ${id}</h1>
      <h2>${title}</h2>
      <p>${body}</p>
    `;
  }
}

We would want to make sure we could compliment this with the ability to "customize" the response, in addition the already provided HTML. This would be useful for pages, especially in the case of response headers, for setting things like:

In addition, being able to customize constructor props would be nice, to encompass all that data fetching / massaging outside of the component definition, where things could awkward within the limitation of a synchronous constructor and could make unit testing easier through dependency injection.

Details

As seen in #880, I think a custom "loader" function could be used for a double-purpose here, in that the loader function can be a mechanic for hydration, in that the user can return some JSON serialiaziable data, and we can inline that into the HTML of the page as well as pass it in as a custom constructor prop.

export default class PostPage extends HTMLElement {
  constructor({ post }) {
    super();

    this.post = post;
  }

  async connectedCallback() {
    const { id, title, body } = this.post;

    this.innerHTML = `
      <h1>Fetched Post ID: ${id}</h1>
      <h2>${title}</h2>
      <p>${body}</p>
    `;
  }
}

export async function loader(request) {
  const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
  const postId = params.get('id');
  const post = await fetch(`https://example.com/posts/${postId}`).then(resp => resp.json());

  return { post };
}

From there, perhaps we can also define an instance of Response to be returned, and then we can just extract / merge everything but the body, so that something like this could be possible?

export async function loader(request) {
  const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
  const postId = params.get('id');
  const post = await fetch(`https://example.com/posts/${postId}`).then(resp => resp.json());

  return {
    props: { post },
    response: new Response('', {
      headers: new Headers({
        'Cache-Control': 'max-age=604800'
      })
    })
  };
}

I'm not quite sure how to reconcile this with #880 where all we care about is the custom prop, so not sure we can get this all from a Response, or maybe we can just pass in a mutable reference to the response being managed by Greenwood? 🤔

export async function loader(request, response) {
  const params = new URLSearchParams(request.url.slice(request.url.indexOf('?')));
  const postId = params.get('id');
  const post = await fetch(`https://example.com/posts/${postId}`).then(resp => resp.json());

  response.headers.set( 'Cache-Control', 'max-age=604800');

  return { post },
}

The pilot for this can be Lit's SSR hydration support added in https://github.com/ProjectEvergreen/greenwood/pull/1201