In my case, the key breakthrough was reducing everything to arbitrary constrainted emitters with a topological sort.
--
The build uses a GraphQL database to store the results (Prisma/Postgresql because it's great with Typescript).
The whole site is defined by arbitrary Data Sources that read from 0+ other sources and write 1+ unit of data in the database. A source can do anything, the only constant is it must define at least one thing in the database.
For example:
- list of files generated by the webpack build of a specific runtime js
- JSON version of a specific page
- http response to serve when a specific url is requested
---
A topological sort uses the constraints to deduce in which order to render data, for example:
- a page showing the 5 most recent articles can't be rendered before the articles themselves
- the french translation of an article cannot be rendered before the settings of the french website are defined
---
This way, a specific page (or type of pages, or even every page of a site variant) can explicit state it needs a specific js runtime, and the build knows to bundle it before any page that needs it.
Something like a cookie banner would be used by every page whereas a 3d model viewer webcomponent would only be used in specific pages.
---
You end up with a static site as consequence of rendering the "http response to serve when X page is requested" sources, which in turn called all the other sources it required.
The resulting database could even be available in production for dynamic pages that cannot be pre-rendered.