Skip to content

Commit

Permalink
Merge branch 'alternate/cache-definition-instead-of-render' of https:…
Browse files Browse the repository at this point in the history
…//github.com/astro-expansion/domain-expansion into alternate/cache-definition-instead-of-render
  • Loading branch information
louisescher committed Dec 22, 2024
2 parents 967c560 + 68d8384 commit fdf72e1
Show file tree
Hide file tree
Showing 16 changed files with 865 additions and 364 deletions.
12 changes: 12 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"mode": "pre",
"tag": "beta",
"initialVersions": {
"docs": "0.0.1",
"@domain-expansion/astro": "0.0.0",
"playground": "0.0.1"
},
"changesets": [
"sharp-lemons-drop"
]
}
5 changes: 5 additions & 0 deletions .changeset/sharp-lemons-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@domain-expansion/astro": minor
---

Initial beta release
8 changes: 8 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# docs

## 0.0.2-beta.0

### Patch Changes

- Updated dependencies [76702d0]
- @domain-expansion/astro@0.1.0-beta.0
4 changes: 2 additions & 2 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import catppuccin from "starlight-theme-catppuccin";
import domainExpansion from '@domain-expansion/astro';
// import domainExpansion from '@domain-expansion/astro';

import node from '@astrojs/node';
import starlightImageZoomPlugin from 'starlight-image-zoom';
Expand All @@ -11,7 +11,7 @@ import starlightImageZoomPlugin from 'starlight-image-zoom';
export default defineConfig({
site: 'https://domainexpansion.gg',
integrations: [
domainExpansion(),
// domainExpansion(),
starlight({
title: 'Domain Expansion',
social: {
Expand Down
5 changes: 3 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"name": "docs",
"type": "module",
"version": "0.0.1",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "astro dev",
"start": "node ./dist/server/entry.mjs",
Expand All @@ -12,7 +13,7 @@
"dependencies": {
"@astrojs/node": "^9.0.0",
"@astrojs/starlight": "^0.30.0",
"@domain-expansion/astro": "workspace:",
"@domain-expansion/astro": "workspace:^",
"astro": "^5.0.2",
"sharp": "^0.32.5",
"starlight-image-zoom": "^0.9.0",
Expand Down
108 changes: 90 additions & 18 deletions docs/src/content/docs/actual-explanation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
title: An actual explanation of what is going on here
---

After reading the Tale of the Three Mages, you might be a little confused, so here's an actual explanation of how
After reading the Tale of the Three Mages, you might be a little confused, so here's an actual explanation of how
Domain Expansion works under the hood.

## How Astro builds your site

Whenever you run `astro build`, Astro will essentially "request" your components internally and save the
resulting HTML. This is done using a function called `$$createComponent`. This function takes in a callback
Whenever you run `astro build`, Astro will essentially "request" your components internally and save the
resulting HTML. This is done using a function called `$$createComponent`. This function takes in a callback
(the compiled version of your component) and turns it into an instance of a component. That instance is then called
each time the component is rendered, with your properties, slots and so on.
each time the component is rendered, with your properties, slots and so on.
You can see how this looks internally in the Astro runtime [here](https://live-astro-compiler.vercel.app/):

```ts
Expand All @@ -32,23 +32,33 @@ import {
renderTransition as $$renderTransition,
createTransitionScope as $$createTransitionScope,
renderScript as $$renderScript,

} from "astro/runtime/server/index.js";
import Foo from './Foo.astro';
import Bar from './Bar.astro';
import Foo from "./Foo.astro";
import Bar from "./Bar.astro";

const $$stdin = $$createComponent(($$result, $$props, $$slots) => {

return $$render`${$$maybeRenderHead($$result)}<div>
${$$renderComponent($$result,'Foo',Foo,{},{"default": () => $$render`
const $$stdin = $$createComponent(
($$result, $$props, $$slots) => {
return $$render`${$$maybeRenderHead($$result)}<div>
${$$renderComponent(
$$result,
"Foo",
Foo,
{},
{
default: () => $$render`
Domain Expansion
`,})}
${$$renderComponent($$result,'Bar',Bar,{"baz":"tizio"})}
`,
}
)}
${$$renderComponent($$result, "Bar", Bar, { baz: "tizio" })}
</div>`;
}, '<stdin>', undefined);
},
"<stdin>",
undefined
);
export default $$stdin;

```

You can see how the `$$createComponent` function takes in the callback, which returns a few
template tags, essentially the rendered components.

Expand All @@ -64,9 +74,71 @@ The cache is saved in `node_modules/.domain-expansion`.
## What about assets?

Astro has built-in image optimization. That built-in image optimization adds the resulting asset to your build
output based on calls to the [`getImage` function](https://docs.astro.build/en/guides/images/#generating-images-with-getimage).
That function is also used in the [`<Image />`](https://docs.astro.build/en/guides/images/#display-optimized-images-with-the-image--component)
output based on calls to the [`getImage` function](https://docs.astro.build/en/guides/images/#generating-images-with-getimage).
That function is also used in the [`<Image />`](https://docs.astro.build/en/guides/images/#display-optimized-images-with-the-image--component)
and [`<Picture />`](https://docs.astro.build/en/reference/modules/astro-assets/#picture-)
components. Domain Expansion detects when that function is called and also adds the parameters that the function
was called with to the cache. Whenever we reconstruct a component from the cache, we "replay" all calls to `getImage`
was called with to the cache. Whenever we reconstruct a component from the cache, we "replay" all calls to `getImage`
such that the image service is called just as if the component was rendered normally.

## Zero-cost on SSR

Astro builds the server code once for both prerendered and on-demand pages. The prerendered pages are generated
by running the same render code that you'll deploy to your server during build time with the requests for the
pages that should be prerendered. This means that if we simply transform Astro or your own code for bundling it
would also try to save and load caches on the server, adding a lot of code to your deployed bundle and severely
restricting your hosting platforms (by requiring both a Node.js runtime and a writable file-system).

Instead of that approach, Domain Expansion adds minimal code to your bundle. It adds one internal module that is
essentially just this:

```ts
export const domainExpansionComponents = globalThis[{{internal component symbol}}] ?? ((fn) => fn);
export const domainExpansionAssets = globalThis[{{internal assets symbol}}] ?? ((fn) => fn);
```

Then it modify the definition of Astro's `createComponent` and `getImage` functions:

```ts ins={2-3,6} del={1,5}
function createComponent(...) {
import {domainExpansionComponents as $$domainExpansion} from '<internal module>';
const createComponent = $$domainExpansion(function createComponent(...) {
...
}
});
```
```ts ins={1} del={1,5}
export const getImage = async (...) => ...;
import {domainExpansionAssets as $$domainExpansion} from '<internal module>';
export const getImage = $$domainExpansion(async (...) => ...);
```
When your server is running, those wrappers will just return the original functions, so there is no change in behavior
for on-demand pages and the extra code shipped is just those 4 lines (2 definitions and 2 imports) and the wrapping.
During build, the render code runs in the same V8 isolate as the build process. This allows Domain Expansion to set a
different wrapper to be used only during build without shipping that code in the bundle.
### Bundling duplicates implementation
Astro has a bunch of classes and functions exported from `astro/runtime`. The runtime is bundled in the project by Vite.
This means that the instance used in the render code is not the same that an integration can import from `astro/runtime`,
it's the same code but in two modules so `value instanceof RuntimeClass` doesn't work since those are different, albeit
functionally identical, classes. We also need to reconstruct instances of those classes defined inside the bundle when
loading from cache, but again we can't import them.
To solve this problem, Domain Expansion also injects a little bit of extra code sending a reference to the runtime classes
back from the bundle into the build integration while bundle is loaded. The code looks like this:
```ts
import {someRuntimeFunction, SomeRuntimeClass} from 'astro/runtime/something';

Object.assign(
globalThis[<runtime transfer symbol>] ?? {},
{someRuntimeFunction, SomeRuntimeClass},
);
```
For this, in the Domain Expansion integration code, we add an empty object to the global scope under a private symbol
and it gets populated with the values from within the bundle.
6 changes: 0 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,5 @@
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@changesets/cli": "^2.27.10"
},
"pnpm": {
"patchedDependencies": {
"astro": "patches/astro.patch",
"@astrojs/starlight": "patches/@astrojs__starlight.patch"
}
}
}
7 changes: 7 additions & 0 deletions package/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @domain-expansion/astro

## 0.1.0-beta.0

### Minor Changes

- 76702d0: Initial beta release
4 changes: 2 additions & 2 deletions package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ You can now edit files in `package`. Please note that making changes to those fi

## Licensing

[MIT Licensed](https://github.com/astro-expansion/domain-expansion/blob/main/LICENSE). Made with ❤️ by [the Domain Expansion](https://github.com/TODO:).
[MIT Licensed](https://github.com/astro-expansion/domain-expansion/blob/main/LICENSE). Made with ❤️ by [the Domain Expansion](https://domainexpansion.gg).

## Acknowledgements

- [Luiz Ferraz](https://github.com/Fryuni)
- [Louis Escher](https://github.com/louisescher)
- [Reuben Tier](https://github.com/theotterlord)
- [Reuben Tier](https://github.com/theotterlord)
2 changes: 1 addition & 1 deletion package/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@domain-expansion/astro",
"version": "0.0.0",
"version": "0.1.0-beta.0",
"description": "Expanding™ the™ bits™ since™ 2024-12-12™",
"contributors": [
"Luiz Ferraz (https://github.com/Fryuni)",
Expand Down
9 changes: 5 additions & 4 deletions package/src/renderCaching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { RenderDestination } from "astro/runtime/server/render/common.js";
import type { PersistedMetadata } from "./renderFileStore.ts";
import type { getImage } from "astro/assets";
import { Lazy } from "@inox-tools/utils/lazy";
import { isDeepStrictEqual } from "util";
import { isDeepStrictEqual, types } from "util";
import { AsyncLocalStorage } from "async_hooks";

type GetImageFn = typeof getImage;
Expand Down Expand Up @@ -66,10 +66,11 @@ export const makeCaching = (cache: Cache, root: string, routeEntrypoints: string

if (slots !== undefined && Object.keys(slots).length > 0) return factory(result, props, slots);

const resolvedProps = Object.fromEntries(await Promise.all(
// TODO: Handle edge-cases involving Object.defineProperty
const resolvedProps = Object.fromEntries((await Promise.all(
Object.entries(props)
.map(async ([key, value]) => [key, await value])
));
.map(async ([key, value]) => [key, types.isProxy(value) ? undefined : await value])
)).filter((_key, value) => !!value));

// We need to delete this because otherwise scopes from outside of a component can be globally
// restricted to the inside of a child component through a slot and to support that the component
Expand Down
Loading

0 comments on commit fdf72e1

Please sign in to comment.