Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
|
75661c6a04 | ||
|
976b6ccf6e | ||
|
d8608bb947 |
24 changed files with 427 additions and 39 deletions
5
.changeset/mighty-bags-hope.md
Normal file
5
.changeset/mighty-bags-hope.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Client-side routing via Outlet component
|
|
@ -1,4 +1,10 @@
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
import node from "@astrojs/node";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({});
|
export default defineConfig({
|
||||||
|
output: "server",
|
||||||
|
adapter: node({
|
||||||
|
mode: "standalone"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/node": "^5.1.1",
|
||||||
"astro": "^2.4.1"
|
"astro": "^2.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
examples/minimal/src/component/Deep.astro
Normal file
3
examples/minimal/src/component/Deep.astro
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div id="deep">
|
||||||
|
<slot />
|
||||||
|
</div>
|
10
examples/minimal/src/component/Tabs.astro
Normal file
10
examples/minimal/src/component/Tabs.astro
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
const { tab } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a class:list={{ active: tab === '1' }} href="/a">Tab 1</a></li>
|
||||||
|
<li><a class:list={{ active: tab === '2' }} href="/a/2">Tab 2</a></li>
|
||||||
|
<li><a class:list={{ active: tab === '3' }} href="/a/3">Tab 3</a></li>
|
||||||
|
</ul>
|
||||||
|
<div>Tab {tab}</div>
|
1
examples/minimal/src/content/posts/intro.md
Normal file
1
examples/minimal/src/content/posts/intro.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello world!
|
1
examples/minimal/src/env.d.ts
vendored
1
examples/minimal/src/env.d.ts
vendored
|
@ -1 +1,2 @@
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
/// <reference types="astro/client" />
|
/// <reference types="astro/client" />
|
||||||
|
|
135
examples/minimal/src/layouts/Default.astro
Normal file
135
examples/minimal/src/layouts/Default.astro
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
---
|
||||||
|
import { Outlet } from 'astro/components';
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>Astro</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>My Website</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<Outlet id="nav">
|
||||||
|
<slot name="nav" />
|
||||||
|
</Outlet>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{() => new Promise((resolve) => setTimeout(resolve, 1000))}
|
||||||
|
<Outlet id="main">
|
||||||
|
<slot />
|
||||||
|
</Outlet>
|
||||||
|
{() => new Promise((resolve) => setTimeout(resolve, 1000))}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
nav ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
a.active {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getOutlets() {
|
||||||
|
const query = "//comment()[contains(., 'astro:outlet')]";
|
||||||
|
const comments = document.evaluate(
|
||||||
|
query,
|
||||||
|
document,
|
||||||
|
null,
|
||||||
|
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const outlets: Record<string, Range> = {};
|
||||||
|
let fragment = [];
|
||||||
|
let stack = 0;
|
||||||
|
for (let i = 0; i < comments.snapshotLength; i++) {
|
||||||
|
const comment = comments.snapshotItem(i);
|
||||||
|
const end = comment.textContent?.trim()?.[0] === '/';
|
||||||
|
if (!end) {
|
||||||
|
if (stack === 0) fragment.push(comment);
|
||||||
|
stack++;
|
||||||
|
} else {
|
||||||
|
stack--;
|
||||||
|
if (stack === 0) {
|
||||||
|
const start = fragment[0];
|
||||||
|
const end = comment;
|
||||||
|
const range = new Range();
|
||||||
|
range.setStartAfter(start);
|
||||||
|
range.setEndBefore(end);
|
||||||
|
outlets[start.textContent.replace('astro:outlet', '').trim()] = range;
|
||||||
|
fragment.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outlets;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function streamToRange(response: Response, range: Range) {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
range.deleteContents();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const doc = document.implementation.createHTMLDocument();
|
||||||
|
doc.write('<streaming-root>');
|
||||||
|
const root = doc.querySelector('streaming-root');
|
||||||
|
|
||||||
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
||||||
|
let init = false;
|
||||||
|
const mo = new MutationObserver((entries) => {
|
||||||
|
if (!init) {
|
||||||
|
range.deleteContents();
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
for (const entry of entries) {
|
||||||
|
for (const child of entry.addedNodes) {
|
||||||
|
range.insertNode(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mo.observe(root, { childList: true });
|
||||||
|
|
||||||
|
let result = await reader.read();
|
||||||
|
while (!result.done) {
|
||||||
|
doc.write(result.value);
|
||||||
|
result = await reader.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation.addEventListener('navigate', (event) => {
|
||||||
|
event.intercept({
|
||||||
|
async handler() {
|
||||||
|
async function update() {
|
||||||
|
const promises: any[] = [];
|
||||||
|
for (const [outlet, range] of Object.entries(getOutlets())) {
|
||||||
|
console.log({ outlet });
|
||||||
|
promises.push(
|
||||||
|
fetch(event.destination.url, {
|
||||||
|
headers: { 'x-astro-outlet': outlet },
|
||||||
|
}).then((res) => streamToRange(res, range))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
// if ('startViewTransition' in document) {
|
||||||
|
// await document.startViewTransition(() => update());
|
||||||
|
// } else {
|
||||||
|
await update();
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
22
examples/minimal/src/pages/a/[tab].astro
Normal file
22
examples/minimal/src/pages/a/[tab].astro
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
import Layout from '../../layouts/Default.astro';
|
||||||
|
import Tab from '../../component/Tabs.astro';
|
||||||
|
const { tab = '' } = Astro.params;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<div>
|
||||||
|
<h2>Page A</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tab slot="tabs" tab={tab} />
|
||||||
|
|
||||||
|
<nav slot="nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/a" class="active">Page A</a></li>
|
||||||
|
<li><a href="/b">Page B</a></li>
|
||||||
|
<li><a href="/c">Page C</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</Layout>
|
21
examples/minimal/src/pages/a/index.astro
Normal file
21
examples/minimal/src/pages/a/index.astro
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
import Layout from '../../layouts/Default.astro';
|
||||||
|
import Tab from '../../component/Tabs.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<div>
|
||||||
|
<h2>Page A</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tab slot="tabs" tab="1" />
|
||||||
|
|
||||||
|
<nav slot="nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/a" class="active">Page A</a></li>
|
||||||
|
<li><a href="/b">Page B</a></li>
|
||||||
|
<li><a href="/c">Page C</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</Layout>
|
20
examples/minimal/src/pages/b.astro
Normal file
20
examples/minimal/src/pages/b.astro
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
import Layout from '../layouts/Default.astro';
|
||||||
|
import Deep from '../component/Deep.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<Deep>
|
||||||
|
<h2>Page B</h2>
|
||||||
|
<p>No delay</p>
|
||||||
|
</Deep>
|
||||||
|
|
||||||
|
<nav slot="nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/a">Page A</a></li>
|
||||||
|
<li><a href="/b" class="active">Page B</a></li>
|
||||||
|
<li><a href="/c">Page C</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</Layout>
|
22
examples/minimal/src/pages/c.astro
Normal file
22
examples/minimal/src/pages/c.astro
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
import Layout from '../layouts/Default.astro';
|
||||||
|
import { setTimeout as sleep } from 'node:timers/promises';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout>
|
||||||
|
<div>
|
||||||
|
<h2>Page C</h2>
|
||||||
|
<p>This is fast</p>
|
||||||
|
{sleep(1000)}
|
||||||
|
<p>This is slow</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav slot="nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/a">Page A</a></li>
|
||||||
|
<li><a href="/b">Page B</a></li>
|
||||||
|
<li><a href="/c" class="active">Page C</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</Layout>
|
|
@ -1,15 +1,26 @@
|
||||||
---
|
---
|
||||||
|
import Layout from '../layouts/Default.astro';
|
||||||
|
|
||||||
|
import { getEntryBySlug } from 'astro:content';
|
||||||
|
|
||||||
|
const entry = await getEntryBySlug('posts', 'intro');
|
||||||
|
const { Content } = await entry.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<Layout>
|
||||||
<head>
|
<div>
|
||||||
<meta charset="utf-8" />
|
<h2>Home</h2>
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<p>Streaming partials!</p>
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
<meta name="generator" content={Astro.generator} />
|
<Content />
|
||||||
<title>Astro</title>
|
</div>
|
||||||
</head>
|
|
||||||
<body>
|
<nav slot="nav">
|
||||||
<h1>Astro</h1>
|
<ul>
|
||||||
</body>
|
<li><a href="/" class="active">Home</a></li>
|
||||||
</html>
|
<li><a href="/a">Page A</a></li>
|
||||||
|
<li><a href="/b">Page B</a></li>
|
||||||
|
<li><a href="/c">Page C</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</Layout>
|
||||||
|
|
|
@ -31,6 +31,11 @@ const { title, description } = Astro.props;
|
||||||
addEventListener('load', () => document.documentElement.classList.add('loaded'));
|
addEventListener('load', () => document.documentElement.classList.add('loaded'));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import listen from 'micromorph/spa';
|
||||||
|
listen();
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--_placeholder-bg: linear-gradient(transparent, transparent);
|
--_placeholder-bg: linear-gradient(transparent, transparent);
|
||||||
|
|
14
packages/astro/components/Outlet.astro
Normal file
14
packages/astro/components/Outlet.astro
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = Astro.props;
|
||||||
|
|
||||||
|
// @ts-ignore untyped internals
|
||||||
|
$$result.outlets.set(id, () => Astro.slots.render('default'));
|
||||||
|
---
|
||||||
|
|
||||||
|
<Fragment set:html={`<!--astro:outlet ${id}-->`} /><slot /><Fragment
|
||||||
|
set:html={`<!--/astro:outlet ${id}-->`}
|
||||||
|
/>
|
|
@ -1,2 +1,3 @@
|
||||||
export { default as Code } from './Code.astro';
|
export { default as Code } from './Code.astro';
|
||||||
export { default as Debug } from './Debug.astro';
|
export { default as Debug } from './Debug.astro';
|
||||||
|
export { default as Outlet } from './Outlet.astro';
|
||||||
|
|
|
@ -113,11 +113,11 @@
|
||||||
"test:e2e:match": "playwright test -g"
|
"test:e2e:match": "playwright test -g"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^1.4.0",
|
"@astrojs/compiler": "^1.4.1",
|
||||||
"@astrojs/language-server": "^1.0.0",
|
"@astrojs/language-server": "^0.28.3",
|
||||||
"@astrojs/markdown-remark": "^2.2.0",
|
"@astrojs/markdown-remark": "^2.1.4",
|
||||||
"@astrojs/telemetry": "^2.1.1",
|
"@astrojs/telemetry": "^2.1.0",
|
||||||
"@astrojs/webapi": "^2.1.1",
|
"@astrojs/webapi": "^2.1.0",
|
||||||
"@babel/core": "^7.18.2",
|
"@babel/core": "^7.18.2",
|
||||||
"@babel/generator": "^7.18.2",
|
"@babel/generator": "^7.18.2",
|
||||||
"@babel/parser": "^7.18.4",
|
"@babel/parser": "^7.18.4",
|
||||||
|
|
|
@ -1750,6 +1750,7 @@ export interface SSRMetadata {
|
||||||
hasDirectives: Set<string>;
|
hasDirectives: Set<string>;
|
||||||
hasRenderedHead: boolean;
|
hasRenderedHead: boolean;
|
||||||
headInTree: boolean;
|
headInTree: boolean;
|
||||||
|
request: Request;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1775,7 +1776,9 @@ export interface SSRResult {
|
||||||
links: Set<SSRElement>;
|
links: Set<SSRElement>;
|
||||||
componentMetadata: Map<string, SSRComponentMetadata>;
|
componentMetadata: Map<string, SSRComponentMetadata>;
|
||||||
propagators: Map<AstroComponentFactory, AstroComponentInstance>;
|
propagators: Map<AstroComponentFactory, AstroComponentInstance>;
|
||||||
|
outletPropagators: Map<string, any>;
|
||||||
extraHead: Array<string>;
|
extraHead: Array<string>;
|
||||||
|
outlets: Map<string, any>;
|
||||||
cookies: AstroCookies | undefined;
|
cookies: AstroCookies | undefined;
|
||||||
createAstro(
|
createAstro(
|
||||||
Astro: AstroGlobalPartial,
|
Astro: AstroGlobalPartial,
|
||||||
|
|
|
@ -163,7 +163,9 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
links: args.links ?? new Set<SSRElement>(),
|
links: args.links ?? new Set<SSRElement>(),
|
||||||
componentMetadata,
|
componentMetadata,
|
||||||
propagators: new Map(),
|
propagators: new Map(),
|
||||||
|
outletPropagators: new Map(),
|
||||||
extraHead: [],
|
extraHead: [],
|
||||||
|
outlets: new Map<string, any>(),
|
||||||
scope: 0,
|
scope: 0,
|
||||||
cookies,
|
cookies,
|
||||||
/** This function returns the `Astro` faux-global */
|
/** This function returns the `Astro` faux-global */
|
||||||
|
@ -260,6 +262,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
||||||
hasRenderedHead: false,
|
hasRenderedHead: false,
|
||||||
hasDirectives: new Set(),
|
hasDirectives: new Set(),
|
||||||
headInTree: false,
|
headInTree: false,
|
||||||
|
request
|
||||||
},
|
},
|
||||||
response,
|
response,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ function validateArgs(args: unknown[]): args is Parameters<AstroComponentFactory
|
||||||
if (!args[0] || typeof args[0] !== 'object') return false;
|
if (!args[0] || typeof args[0] !== 'object') return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string): AstroComponentFactory {
|
function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string, hash?: string): AstroComponentFactory {
|
||||||
const name = moduleId?.split('/').pop()?.replace('.astro', '') ?? '';
|
const name = moduleId?.split('/').pop()?.replace('.astro', '') ?? '';
|
||||||
const fn = (...args: Parameters<AstroComponentFactory>) => {
|
const fn = (...args: Parameters<AstroComponentFactory>) => {
|
||||||
if (!validateArgs(args)) {
|
if (!validateArgs(args)) {
|
||||||
|
@ -22,6 +22,7 @@ function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string): Astr
|
||||||
// Add a flag to this callback to mark it as an Astro component
|
// Add a flag to this callback to mark it as an Astro component
|
||||||
fn.isAstroComponentFactory = true;
|
fn.isAstroComponentFactory = true;
|
||||||
fn.moduleId = moduleId;
|
fn.moduleId = moduleId;
|
||||||
|
fn.hash = hash;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,20 +30,23 @@ interface CreateComponentOptions {
|
||||||
factory: AstroComponentFactory;
|
factory: AstroComponentFactory;
|
||||||
moduleId?: string;
|
moduleId?: string;
|
||||||
propagation?: PropagationHint;
|
propagation?: PropagationHint;
|
||||||
|
hash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createComponentWithOptions(opts: CreateComponentOptions) {
|
function createComponentWithOptions(opts: CreateComponentOptions) {
|
||||||
const cb = baseCreateComponent(opts.factory, opts.moduleId);
|
const cb = baseCreateComponent(opts.factory, opts.moduleId);
|
||||||
cb.propagation = opts.propagation;
|
cb.propagation = opts.propagation;
|
||||||
|
cb.hash = opts.hash;
|
||||||
return cb;
|
return cb;
|
||||||
}
|
}
|
||||||
// Used in creating the component. aka the main export.
|
// Used in creating the component. aka the main export.
|
||||||
export function createComponent(
|
export function createComponent(
|
||||||
arg1: AstroComponentFactory | CreateComponentOptions,
|
arg1: AstroComponentFactory | CreateComponentOptions,
|
||||||
moduleId?: string
|
moduleId?: string,
|
||||||
|
hash?: string
|
||||||
) {
|
) {
|
||||||
if (typeof arg1 === 'function') {
|
if (typeof arg1 === 'function') {
|
||||||
return baseCreateComponent(arg1, moduleId);
|
return baseCreateComponent(arg1, moduleId, hash);
|
||||||
} else {
|
} else {
|
||||||
return createComponentWithOptions(arg1);
|
return createComponentWithOptions(arg1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export interface AstroComponentFactory {
|
||||||
isAstroComponentFactory?: boolean;
|
isAstroComponentFactory?: boolean;
|
||||||
moduleId?: string | undefined;
|
moduleId?: string | undefined;
|
||||||
propagation?: PropagationHint;
|
propagation?: PropagationHint;
|
||||||
|
hash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAstroComponentFactory(obj: any): obj is AstroComponentFactory {
|
export function isAstroComponentFactory(obj: any): obj is AstroComponentFactory {
|
||||||
|
|
|
@ -81,6 +81,12 @@ export function createAstroComponentInstance(
|
||||||
) {
|
) {
|
||||||
validateComponentProps(props, displayName);
|
validateComponentProps(props, displayName);
|
||||||
const instance = new AstroComponentInstance(result, props, slots, factory);
|
const instance = new AstroComponentInstance(result, props, slots, factory);
|
||||||
|
if (result._metadata.request.headers.has('x-astro-outlet')) {
|
||||||
|
for (const [name, slot] of Object.entries(slots)) {
|
||||||
|
result.outletPropagators.set(name, slot);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
if (isAPropagatingComponent(result, factory) && !result.propagators.has(factory)) {
|
if (isAPropagatingComponent(result, factory) && !result.propagators.has(factory)) {
|
||||||
result.propagators.set(factory, instance);
|
result.propagators.set(factory, instance);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,22 @@ async function bufferHeadContent(result: SSRResult) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recursively calls component instances that might have slots to be propagated up.
|
||||||
|
async function bufferSlottedContent(result: SSRResult, outlet: string) {
|
||||||
|
const iterator = result.outletPropagators.entries();
|
||||||
|
while (true) {
|
||||||
|
const { value: [name, slot] = [], done } = iterator.next();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const returnValue = await slot(result);
|
||||||
|
// TODO: exhaust all expressions
|
||||||
|
if (name === outlet) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function renderPage(
|
export async function renderPage(
|
||||||
result: SSRResult,
|
result: SSRResult,
|
||||||
componentFactory: AstroComponentFactory | NonAstroPageComponent,
|
componentFactory: AstroComponentFactory | NonAstroPageComponent,
|
||||||
|
@ -131,7 +147,68 @@ export async function renderPage(
|
||||||
result.componentMetadata.get(componentFactory.moduleId!)?.containsHead ?? false;
|
result.componentMetadata.get(componentFactory.moduleId!)?.containsHead ?? false;
|
||||||
const factoryReturnValue = await componentFactory(result, props, children);
|
const factoryReturnValue = await componentFactory(result, props, children);
|
||||||
const factoryIsHeadAndContent = isHeadAndContent(factoryReturnValue);
|
const factoryIsHeadAndContent = isHeadAndContent(factoryReturnValue);
|
||||||
if (isRenderTemplateResult(factoryReturnValue) || factoryIsHeadAndContent) {
|
if (result._metadata.request.headers.get('x-astro-outlet') && (isRenderTemplateResult(factoryReturnValue) || factoryIsHeadAndContent)) {
|
||||||
|
const outlet = result._metadata.request.headers.get('x-astro-outlet')!;
|
||||||
|
result.scripts.clear();
|
||||||
|
await bufferSlottedContent(result, outlet);
|
||||||
|
|
||||||
|
console.log(result.outlets);
|
||||||
|
|
||||||
|
if (!result.outlets.get(outlet)) {
|
||||||
|
let init = result.response;
|
||||||
|
let headers = new Headers(init.headers);
|
||||||
|
let response = createResponse(null, { ...init, headers, status: 404 });
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
let init = result.response;
|
||||||
|
let headers = new Headers(init.headers);
|
||||||
|
let body: BodyInit;
|
||||||
|
|
||||||
|
if (streaming) {
|
||||||
|
body = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
async function read() {
|
||||||
|
try {
|
||||||
|
const template = result.outlets.get(outlet);
|
||||||
|
for await (const chunk of renderAstroTemplateResult(template)) {
|
||||||
|
const bytes = chunkToByteArray(result, chunk);
|
||||||
|
controller.enqueue(bytes);
|
||||||
|
}
|
||||||
|
controller.close();
|
||||||
|
} catch (e) {
|
||||||
|
// We don't have a lot of information downstream, and upstream we can't catch the error properly
|
||||||
|
// So let's add the location here
|
||||||
|
if (AstroError.is(e) && !e.loc) {
|
||||||
|
e.setLocation({
|
||||||
|
file: route?.component,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
read();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let iterable = (async function* () {
|
||||||
|
for (const [key, slot] of result.outlets) {
|
||||||
|
yield `<astro-outlet id="${key}">\n`;
|
||||||
|
for await (const chunk of renderAstroTemplateResult(slot)) {
|
||||||
|
const bytes = chunkToByteArray(result, chunk);
|
||||||
|
yield bytes;
|
||||||
|
}
|
||||||
|
yield '\n</astro-outlet>\n\n';
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
body = await iterableToHTMLBytes(result, iterable);
|
||||||
|
headers.set('Content-Length', body.byteLength.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = createResponse(body, { ...init, headers });
|
||||||
|
return response;
|
||||||
|
} else if (isRenderTemplateResult(factoryReturnValue) || factoryIsHeadAndContent) {
|
||||||
// Wait for head content to be buffered up
|
// Wait for head content to be buffered up
|
||||||
await bufferHeadContent(result);
|
await bufferHeadContent(result);
|
||||||
const templateResult = factoryIsHeadAndContent
|
const templateResult = factoryIsHeadAndContent
|
||||||
|
|
50
pnpm-lock.yaml
generated
50
pnpm-lock.yaml
generated
|
@ -375,6 +375,9 @@ importers:
|
||||||
|
|
||||||
examples/minimal:
|
examples/minimal:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@astrojs/node':
|
||||||
|
specifier: ^5.1.1
|
||||||
|
version: link:../../packages/integrations/node
|
||||||
astro:
|
astro:
|
||||||
specifier: ^2.4.1
|
specifier: ^2.4.1
|
||||||
version: link:../../packages/astro
|
version: link:../../packages/astro
|
||||||
|
@ -532,19 +535,19 @@ importers:
|
||||||
packages/astro:
|
packages/astro:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler':
|
'@astrojs/compiler':
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.1
|
||||||
version: 1.4.0
|
version: 1.4.1
|
||||||
'@astrojs/language-server':
|
'@astrojs/language-server':
|
||||||
specifier: ^1.0.0
|
specifier: ^0.28.3
|
||||||
version: 1.0.0
|
version: 0.28.3
|
||||||
'@astrojs/markdown-remark':
|
'@astrojs/markdown-remark':
|
||||||
specifier: ^2.2.0
|
specifier: ^2.1.4
|
||||||
version: link:../markdown/remark
|
version: link:../markdown/remark
|
||||||
'@astrojs/telemetry':
|
'@astrojs/telemetry':
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.0
|
||||||
version: link:../telemetry
|
version: link:../telemetry
|
||||||
'@astrojs/webapi':
|
'@astrojs/webapi':
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.0
|
||||||
version: link:../webapi
|
version: link:../webapi
|
||||||
'@babel/core':
|
'@babel/core':
|
||||||
specifier: ^7.18.2
|
specifier: ^7.18.2
|
||||||
|
@ -5390,20 +5393,22 @@ packages:
|
||||||
sisteransi: 1.0.5
|
sisteransi: 1.0.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@astrojs/compiler@1.4.0:
|
/@astrojs/compiler@0.31.4:
|
||||||
resolution: {integrity: sha512-Vav3a32Ct+omowV9X9kDM2ghWAvFdjZkv5BdvBjZCKYbFVT6//IZApDIVbHI1UPuLuD2sKyLWx2T+E7clqUJdg==}
|
resolution: {integrity: sha512-6bBFeDTtPOn4jZaiD3p0f05MEGQL9pw2Zbfj546oFETNmjJFWO3nzHz6/m+P53calknCvyVzZ5YhoBLIvzn5iw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@astrojs/language-server@1.0.0:
|
/@astrojs/compiler@1.4.1:
|
||||||
resolution: {integrity: sha512-oEw7AwJmzjgy6HC9f5IdrphZ1GVgfV/+7xQuyf52cpTiRWd/tJISK3MsKP0cDkVlfodmNABNFnAaAWuLZEiiiA==}
|
resolution: {integrity: sha512-aXAxapNWZwGN41P+Am/ma/2kAzKOhMNaY6YuvLkUHFv+UZkmDHD6F0fE1sQA2Up0bLjgPQa1VQzoAaii5tZWaA==}
|
||||||
|
|
||||||
|
/@astrojs/language-server@0.28.3:
|
||||||
|
resolution: {integrity: sha512-fPovAX/X46eE2w03jNRMpQ7W9m2mAvNt4Ay65lD9wl1Z5vIQYxlg7Enp9qP225muTr4jSVB5QiLumFJmZMAaVA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 1.4.0
|
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
|
||||||
'@vscode/emmet-helper': 2.8.8
|
'@vscode/emmet-helper': 2.8.8
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
prettier-plugin-astro: 0.8.0
|
prettier-plugin-astro: 0.7.2
|
||||||
synckit: 0.8.5
|
source-map: 0.7.4
|
||||||
vscode-css-languageservice: 6.2.5
|
vscode-css-languageservice: 6.2.5
|
||||||
vscode-html-languageservice: 5.0.5
|
vscode-html-languageservice: 5.0.5
|
||||||
vscode-languageserver: 8.1.0
|
vscode-languageserver: 8.1.0
|
||||||
|
@ -8433,7 +8438,7 @@ packages:
|
||||||
/@ts-morph/common@0.16.0:
|
/@ts-morph/common@0.16.0:
|
||||||
resolution: {integrity: sha512-SgJpzkTgZKLKqQniCjLaE3c2L2sdL7UShvmTmPBejAKd2OKV/yfMpQ2IWpAuA+VY5wy7PkSUaEObIqEK6afFuw==}
|
resolution: {integrity: sha512-SgJpzkTgZKLKqQniCjLaE3c2L2sdL7UShvmTmPBejAKd2OKV/yfMpQ2IWpAuA+VY5wy7PkSUaEObIqEK6afFuw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-glob: 3.2.11
|
fast-glob: 3.2.12
|
||||||
minimatch: 5.1.6
|
minimatch: 5.1.6
|
||||||
mkdirp: 1.0.4
|
mkdirp: 1.0.4
|
||||||
path-browserify: 1.0.1
|
path-browserify: 1.0.1
|
||||||
|
@ -15244,14 +15249,25 @@ packages:
|
||||||
fast-diff: 1.2.0
|
fast-diff: 1.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/prettier-plugin-astro@0.7.2:
|
||||||
|
resolution: {integrity: sha512-mmifnkG160BtC727gqoimoxnZT/dwr8ASxpoGGl6EHevhfblSOeu+pwH1LAm5Qu1MynizktztFujHHaijLCkww==}
|
||||||
|
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
|
||||||
|
dependencies:
|
||||||
|
'@astrojs/compiler': 0.31.4
|
||||||
|
prettier: 2.8.8
|
||||||
|
sass-formatter: 0.7.6
|
||||||
|
synckit: 0.8.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/prettier-plugin-astro@0.8.0:
|
/prettier-plugin-astro@0.8.0:
|
||||||
resolution: {integrity: sha512-kt9wk33J7HvFGwFaHb8piwy4zbUmabC8Nu+qCw493jhe96YkpjscqGBPy4nJ9TPy9pd7+kEx1zM81rp+MIdrXg==}
|
resolution: {integrity: sha512-kt9wk33J7HvFGwFaHb8piwy4zbUmabC8Nu+qCw493jhe96YkpjscqGBPy4nJ9TPy9pd7+kEx1zM81rp+MIdrXg==}
|
||||||
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
|
engines: {node: ^14.15.0 || >=16.0.0, pnpm: '>=7.14.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 1.4.0
|
'@astrojs/compiler': 1.4.1
|
||||||
prettier: 2.8.8
|
prettier: 2.8.8
|
||||||
sass-formatter: 0.7.6
|
sass-formatter: 0.7.6
|
||||||
synckit: 0.8.5
|
synckit: 0.8.5
|
||||||
|
dev: true
|
||||||
|
|
||||||
/prettier@2.8.8:
|
/prettier@2.8.8:
|
||||||
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
|
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
|
||||||
|
|
Loading…
Add table
Reference in a new issue