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

Separate SSR app render and HTML transform phases with client build in between #267

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

gryphonmyers
Copy link
Contributor

This PR restructures the build script to run two separate queues.

SSR app render

First, we run vue/server-renderer for each route. When this queue completes, we will have the raw HTML output rendered out into files within the vite-ssg-temp dir. Why did I bother to write this HTML out to files, you ask? One goal of this PR is to reduce the likelihood of OOM errors. Since some static sites can grow to include hundreds or thousands of routes, each with their own initialState payload , responsible memory usage is crucial to avoid scalability issues. By writing the HTML to files, we avoid holding these very large strings in memory. We can load each HTML string as we need it during the next step.

HTML Transforms

With all the vue/server-renderer output in temp files, we can now read the HTML for each route and apply all the built in transformations (formatting, preload, critters, etc) in a separate queue.

Why Do This

As mentioned above, part of the reasoning here is to improve stability of the build job by keeping some of these large strings in temp files instead of clogging up memory.

Another reason is to accommodate operations occurring during SSR that may affect the client distributable. At present, the client build occurs first, then all SSR renders occur afterward. This means that the SSR render process cannot write / alter files that would potentially be desired in the client build, as the client build has already been run.

For example: say my SSR application keeps track of all network requests run during build and writes those payloads to json files. I could then use dynamic imports in my client application to resolve those json files as a caching layer, right? Except here's the problem: vite doesn't know about those json files because they got written during the SSR build, so the json files will be missing from the distributable. My PR solves this by first running all the SSR renders, then running the client build. So by the time the client build runs, all the SSR renders have run.


// Now that the SSR apps have run (which may have created client assets), we can run the client build.
buildLog('Build for client...')
await viteBuild(mergeConfig(viteConfig, {
Copy link
Member

@userquin userquin Jul 12, 2022

Choose a reason for hiding this comment

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

should we reset process.env.VITE_SSG and process.env.SSR before running the client build?

EDIT: ppl can use import.meta.env.SSR and import.meta.env.VITE_SSG in their code.

Copy link
Member

@userquin userquin Jul 12, 2022

Choose a reason for hiding this comment

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

how about deregistering jsdom?

EDIT: check this line https://github.com/antfu/vite-ssg/blob/main/src/client/index.ts#L24

Copy link
Member

Choose a reason for hiding this comment

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

Is it really necessary to reverse the build order?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we are to support use cases such as the one I have provided, then yes, reversing the build order is required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

how about deregistering jsdom?

EDIT: check this line https://github.com/antfu/vite-ssg/blob/main/src/client/index.ts#L24

how about deregistering jsdom?

EDIT: check this line https://github.com/antfu/vite-ssg/blob/main/src/client/index.ts#L24

I'm not sure I understand the feedback. That line looks like it's checking for the existence of the window object in order to determine whether it should do a client build. Nothing about that would be changed by my PR, as far as I can tell.

@gryphonmyers
Copy link
Contributor Author

Here is a visual example to demonstrate (with pseudocode) the use case that this PR makes possible:

export async function performCachedRequest(request) {
  if (import.meta.env.SSR) {
    if (isInCache(request)) {
      return JSON.parse(await fs.readFile(`/generated/cache/${getCacheKey(request)}.json`)).toString('utf-8'))
    } else {
      const response = await makeRequest(request)
      await fs.writeFile(`../generated/cache/${getCacheKey(request)}.json`, JSON.stringify(response))
      return response;
    }
  }
  return import(`../generated/cache/${getCacheKey(request)}`)
}
<template><div>{{ myData.title }}</div><template>
<script>
import { performCachedRequest } from './perform-cached-request'

export default {
  async setup() {
    const myData = await performCachedRequest({ my: 'request' })
    
    return { myData }
  }
}
</script>

It's an extremely basic caching mechanism that utilizes local files to achieve something not unlike the asyncData caching in Nuxt, but in a way less opinionated way. As you run the site build, it will run makeRequest as it is rendering out all the routes, but when you then load the built site in browser, those same requests will instead be served by a dynamic import of the JSON file containing the cached response for that request.

Now, in order for this example to work, vite would have to bundle up all the files in ../generated/cache so that they are available in the distributable when the client code tries to run the dynamic import. At present, this does not work: vite-ssg will generate the cache files after the client bundle has already been built, so all the cache files will be missing from the client build. My PR solves this by running the client build after the SSR app has been rendered for all routes.

I will note that it does not seem like there was a particular reason the client build was being run first, and I don't think changing this carries any negative consequences. It just required some minor restructuring to make it possible.

@gryphonmyers
Copy link
Contributor Author

@antfu if you wouldn't mind reviewing - thanks!

@antfu
Copy link
Member

antfu commented Aug 29, 2022

Haven't looking deep into it, but it seems to break my site https://github.com/antfu/antfu.me. Could you help checking out which change is breaking it? Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants