Compare commits

...

6 commits

Author SHA1 Message Date
Matthew Phillips
53cd065c96
Merge branch 'main' into fragments 2023-10-05 14:47:28 -04:00
Matthew Phillips
69b6dbed54
Merge branch 'main' into fragments 2023-10-05 13:58:30 -04:00
Matthew Phillips
b224b31140 debuggin 2023-10-05 13:45:02 -04:00
Matthew Phillips
6b10fbce54 Linting 2023-10-05 11:04:42 -04:00
Matthew Phillips
b81f816912 Add a changeset 2023-10-05 10:59:26 -04:00
Matthew Phillips
a6f42fa4d7 Fragment support 2023-10-05 10:54:40 -04:00
11 changed files with 92 additions and 4 deletions

View file

@ -0,0 +1,18 @@
---
'astro': minor
---
Page Fragments
Any page components can now be identified as *fragments*, allowing you fetch them in the client in order to replace only parts of the page. Fragments are used in conjuction with a partial rendering library, like htmx or Stimulus or even just jQuery.
Pages marked as fragments do not have a `doctype` or any head content included in the rendered result. You can mark any page as a fragment by setting this option:
```astro
---
export const fragment = true;
---
<li>This is a single list item.</li>
```

View file

@ -1520,6 +1520,7 @@ export type AsyncRendererComponentFn<U> = (
export interface ComponentInstance {
default: AstroComponentFactory;
css?: string[];
fragment?: boolean;
prerender?: boolean;
/**
* Only used for logging if deprecated drafts feature is used
@ -2191,6 +2192,7 @@ export interface SSRResult {
*/
clientDirectives: Map<string, string>;
compressHTML: boolean;
fragment: boolean;
/**
* Only used for logging
*/

View file

@ -49,6 +49,7 @@ export async function renderPage({ mod, renderContext, env, cookies }: RenderPag
clientDirectives: env.clientDirectives,
compressHTML: env.compressHTML,
request: renderContext.request,
fragment: !!mod.fragment,
site: env.site,
scripts: renderContext.scripts,
ssr: env.ssr,

View file

@ -31,6 +31,7 @@ export interface CreateResultArgs {
renderers: SSRLoadedRenderer[];
clientDirectives: Map<string, string>;
compressHTML: boolean;
fragment: boolean;
resolve: (s: string) => Promise<string>;
/**
* Used for `Astro.site`
@ -155,6 +156,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
renderers: args.renderers,
clientDirectives: args.clientDirectives,
compressHTML: args.compressHTML,
fragment: args.fragment,
pathname: args.pathname,
cookies,
/** This function returns the `Astro` faux-global */

View file

@ -33,7 +33,7 @@ export async function renderToString(
// Automatic doctype insertion for pages
if (isPage && !renderedFirstPageChunk) {
renderedFirstPageChunk = true;
if (!/<!doctype html/i.test(String(chunk))) {
if (!result.fragment && !/<!doctype html/i.test(String(chunk))) {
const doctype = result.compressHTML ? '<!DOCTYPE html>' : '<!DOCTYPE html>\n';
str += doctype;
}
@ -84,7 +84,7 @@ export async function renderToReadableStream(
// Automatic doctype insertion for pages
if (isPage && !renderedFirstPageChunk) {
renderedFirstPageChunk = true;
if (!/<!doctype html/i.test(String(chunk))) {
if (!result.fragment && !/<!doctype html/i.test(String(chunk))) {
const doctype = result.compressHTML ? '<!DOCTYPE html>' : '<!DOCTYPE html>\n';
controller.enqueue(encoder.encode(doctype));
}

View file

@ -77,13 +77,13 @@ function stringifyChunk(
}
}
case 'head': {
if (result._metadata.hasRenderedHead) {
if (result._metadata.hasRenderedHead || result.fragment) {
return '';
}
return renderAllHeadContent(result);
}
case 'maybe-head': {
if (result._metadata.hasRenderedHead || result._metadata.headInTree) {
if (result._metadata.hasRenderedHead || result._metadata.headInTree || result.fragment) {
return '';
}
return renderAllHeadContent(result);

View file

@ -0,0 +1,5 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
});

View file

@ -0,0 +1,8 @@
{
"name": "@test/fragments",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,4 @@
---
export const fragment = true;
---
<li>This is a single line item</li>

View file

@ -0,0 +1,42 @@
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
describe('Fragments', () => {
/** @type {import('./test-utils.js').Fixture} */
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/fragments/',
});
});
describe('dev', () => {
/** @type {import('./test-utils.js').DevServer} */
let devServer;
before(async () => {
devServer = await fixture.startDevServer();
});
after(async () => {
await devServer.stop();
});
it('is only the written HTML', async () => {
const html = await fixture.fetch('/fragments/item/').then((res) => res.text());
expect(html.startsWith('<li>')).to.equal(true);
});
});
describe('build', () => {
before(async () => {
await fixture.build();
});
it('is only the written HTML', async () => {
const html = await fixture.readFile('/fragments/item/index.html');
expect(html.startsWith('<li>')).to.equal(true);
});
});
});

View file

@ -2689,6 +2689,12 @@ importers:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/fragments:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/get-static-paths-pages:
dependencies:
astro: