-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Render async SolidJS components (#6791)
* Render async SolidJS components * Add renderer-specific hydration script to allow for proper SolidJS hydration * Add support for Solid.js 1.8.x * Address documentation feedback * Rebuild pnpm lock file based on main branch * Address PR feedback from ematipico --------- Co-authored-by: Johannes Spohr <[email protected]> Co-authored-by: Florian Lefebvre <[email protected]>
- Loading branch information
1 parent
3b4e629
commit 3702104
Showing
24 changed files
with
605 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
--- | ||
'@astrojs/solid-js': major | ||
--- | ||
|
||
Render SolidJS components using [`renderToStringAsync`](https://www.solidjs.com/docs/latest#rendertostringasync). | ||
|
||
This changes the renderer of SolidJS components from `renderToString` to `renderToStringAsync`. It also injects the actual SolidJS hydration script generated by [`generateHydrationScript`](https://www.solidjs.com/guides/server#hydration-script), so that [`Suspense`](https://www.solidjs.com/docs/latest#suspense), [`ErrorBoundary`](https://www.solidjs.com/docs/latest#errorboundary) and similar components can be hydrated correctly. | ||
|
||
The server render phase will now wait for Suspense boundaries to resolve instead of always rendering the Suspense fallback. | ||
|
||
If you use the APIs [`createResource`](https://www.solidjs.com/docs/latest#createresource) or [`lazy`](https://www.solidjs.com/docs/latest#lazy), their functionalities will now be executed on the server side, not just the client side. | ||
|
||
This increases the flexibility of the SolidJS integration. Server-side components can now safely fetch remote data, call async Astro server functions like `getImage()` or load other components dynamically. Even server-only components that do not hydrate in the browser will benefit. | ||
|
||
It is very unlikely that a server-only component would have used the Suspense feature until now, so this should not be a breaking change for server-only components. | ||
|
||
This could be a breaking change for components that meet the following conditions: | ||
|
||
- The component uses Suspense APIs like `Suspense`, `lazy` or `createResource`, and | ||
- The component is mounted using a *hydrating* directive: | ||
- `client:load` | ||
- `client:idle` | ||
- `client:visible` | ||
- `client:media` | ||
|
||
These components will now first try to resolve the Suspense boundaries on the server side instead of the client side. | ||
|
||
If you do not want Suspense boundaries to be resolved on the server (for example, if you are using createResource to do an HTTP fetch that relies on a browser-side cookie), you may consider: | ||
|
||
- changing the template directive to `client:only` to skip server side rendering completely | ||
- use APIs like [isServer](https://www.solidjs.com/docs/latest/api#isserver) or `onMount()` to detect server mode and render a server fallback without using Suspense. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
packages/astro/test/fixtures/solid-component/src/components/Counter.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Based on reproduction from https://github.com/withastro/astro/issues/6912 | ||
|
||
import { For, Match, Switch } from 'solid-js'; | ||
|
||
export default function Counter(props) { | ||
return ( | ||
<For each={[1, 2, 3, 4]}> | ||
{(page) => { | ||
return ( | ||
<Switch> | ||
<Match when={page % 2 === 0}> | ||
<button | ||
onClick={() => { | ||
console.log(page); | ||
}} | ||
> | ||
even {page} | ||
</button> | ||
</Match> | ||
<Match when={page % 2 === 1}> | ||
<button | ||
onClick={() => { | ||
console.log(page); | ||
}} | ||
> | ||
odd {page} | ||
</button> | ||
</Match> | ||
</Switch> | ||
); | ||
}} | ||
</For> | ||
); | ||
} |
5 changes: 5 additions & 0 deletions
5
packages/astro/test/fixtures/solid-component/src/components/LazyCounter.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Based on reproduction from https://github.com/withastro/astro/issues/6912 | ||
|
||
import { lazy } from 'solid-js'; | ||
|
||
export const LazyCounter = lazy(() => import('./Counter')); |
70 changes: 70 additions & 0 deletions
70
packages/astro/test/fixtures/solid-component/src/components/async-components.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { createResource, createSignal, createUniqueId, ErrorBoundary, Show } from 'solid-js'; | ||
|
||
// It may be good to try short and long sleep times. | ||
// But short is faster for testing. | ||
const SLEEP_MS = 10; | ||
|
||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | ||
|
||
export function AsyncComponent(props) { | ||
const id = createUniqueId(); | ||
|
||
const [data] = createResource(async () => { | ||
// console.log("Start rendering async component " + props.title); | ||
await sleep(props.delay ?? SLEEP_MS); | ||
// console.log("Finish rendering async component " + props.title); | ||
return 'Async result for component id=' + id; | ||
}); | ||
|
||
const [show, setShow] = createSignal(false); | ||
|
||
return ( | ||
<div data-name="AsyncComponent" style={{ border: 'black solid 1px', padding: '4px' }}> | ||
{'title=' + (props.title ?? '(none)') + ' '} | ||
{'id=' + id + ' '} | ||
<span>{data()}</span>{' '} | ||
<button | ||
type="button" | ||
disabled={show()} | ||
onClick={() => { | ||
setShow(true); | ||
}} | ||
> | ||
Show children | ||
</button> | ||
{/* NOTE: The props.children are intentionally hidden by default | ||
to simulate a situation where hydration script might not | ||
be injected in the right spot. */} | ||
<Show when={show()}>{props.children ?? 'Empty'}</Show> | ||
</div> | ||
); | ||
} | ||
|
||
export function AsyncErrorComponent() { | ||
const [data] = createResource(async () => { | ||
await sleep(SLEEP_MS); | ||
throw new Error('Async error thrown!'); | ||
}); | ||
|
||
return <div>{data()}</div>; | ||
} | ||
|
||
export function AsyncErrorInErrorBoundary() { | ||
return ( | ||
<ErrorBoundary fallback={<div>Async error boundary fallback</div>}> | ||
<AsyncErrorComponent /> | ||
</ErrorBoundary> | ||
); | ||
} | ||
|
||
export function SyncErrorComponent() { | ||
throw new Error('Sync error thrown!'); | ||
} | ||
|
||
export function SyncErrorInErrorBoundary() { | ||
return ( | ||
<ErrorBoundary fallback={<div>Sync error boundary fallback</div>}> | ||
<SyncErrorComponent /> | ||
</ErrorBoundary> | ||
); | ||
} |
7 changes: 7 additions & 0 deletions
7
packages/astro/test/fixtures/solid-component/src/components/defer.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
import { AsyncComponent } from './async-components.jsx'; | ||
await new Promise((resolve) => setTimeout(resolve, Astro.props.delay)); | ||
--- | ||
|
||
<AsyncComponent client:load /> |
11 changes: 11 additions & 0 deletions
11
packages/astro/test/fixtures/solid-component/src/pages/deferred.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
import Defer from '../components/defer.astro'; | ||
--- | ||
|
||
<html> | ||
<head><title>Solid</title></head> | ||
<body> | ||
<Defer delay={50} /> | ||
<Defer delay={10} /> | ||
</body> | ||
</html> |
18 changes: 18 additions & 0 deletions
18
packages/astro/test/fixtures/solid-component/src/pages/nested.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
--- | ||
import { AsyncComponent } from '../components/async-components.jsx'; | ||
--- | ||
|
||
<html> | ||
<head><title>Nested Test</title></head> | ||
<body> | ||
<div> | ||
<AsyncComponent client:load title="level-a"> | ||
<AsyncComponent client:load title="level-a-a" /> | ||
<AsyncComponent client:load title="level-a-b"> | ||
<AsyncComponent client:load title="level-a-b-a" /> | ||
</AsyncComponent> | ||
<AsyncComponent client:load title="level-a-2" /> | ||
</AsyncComponent> | ||
</div> | ||
</body> | ||
</html> |
21 changes: 21 additions & 0 deletions
21
packages/astro/test/fixtures/solid-component/src/pages/ssr-client-load-throwing.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
--- | ||
import { | ||
AsyncErrorInErrorBoundary, | ||
SyncErrorInErrorBoundary, | ||
} from '../components/async-components.jsx'; | ||
--- | ||
|
||
<html> | ||
<head><title>Solid</title></head> | ||
<body> | ||
<div> | ||
<!-- | ||
Error boundary in hydrating component may generate scripts script: | ||
https://github.com/ryansolid/dom-expressions/blob/6746f048c4adf4d4797276f074dd2d487654796a/packages/dom-expressions/src/server.js#L24 | ||
So make sure that the hydration script is generated on this page. | ||
--> | ||
<AsyncErrorInErrorBoundary client:load /> | ||
<SyncErrorInErrorBoundary client:load /> | ||
</div> | ||
</body> | ||
</html> |
17 changes: 17 additions & 0 deletions
17
packages/astro/test/fixtures/solid-component/src/pages/ssr-client-load.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
--- | ||
import { LazyCounter } from '../components/LazyCounter.jsx'; | ||
import { AsyncComponent } from '../components/async-components.jsx'; | ||
--- | ||
|
||
<html> | ||
<head><title>Solid</title></head> | ||
<body> | ||
<div> | ||
<!-- client:load should generate exactly one hydration script per page --> | ||
<AsyncComponent client:load /> | ||
<AsyncComponent client:load /> | ||
<!-- Lazy copmonents should render consistently, even on first render. --> | ||
<LazyCounter client:load /> | ||
</div> | ||
</body> | ||
</html> |
24 changes: 24 additions & 0 deletions
24
packages/astro/test/fixtures/solid-component/src/pages/ssr-client-none-throwing.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
--- | ||
import { | ||
AsyncErrorInErrorBoundary, | ||
SyncErrorInErrorBoundary, | ||
// AsyncErrorComponent, | ||
// SyncErrorComponent, | ||
} from '../components/async-components.jsx'; | ||
--- | ||
|
||
<html> | ||
<head><title>Solid</title></head> | ||
<body> | ||
<div> | ||
<!-- Async errors should be caught by ErrorBoundary --> | ||
<AsyncErrorInErrorBoundary /> | ||
<!-- Sync errors should be caught by ErrorBoundary --> | ||
<SyncErrorInErrorBoundary /> | ||
|
||
<!-- Error not wrapped in ErrorBoundary should bubble up to Astro renderToStaticMarkup() function. --> | ||
<!-- <AsyncErrorComponent /> --> | ||
<!-- <SyncErrorComponent /> --> | ||
</div> | ||
</body> | ||
</html> |
Oops, something went wrong.