From fe4079f05ba21c0f3a167f8e3f55eff705506bd2 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 26 Oct 2023 10:14:10 -0400 Subject: [PATCH] Partials (#8755) * Fragment support * Add a changeset * Linting * debuggin * Rename to partial * Update the chagneset * Make work with mdx * Update .changeset/brave-pots-drop.md Co-authored-by: Sarah Rainsberger * Update .changeset/brave-pots-drop.md Co-authored-by: Sarah Rainsberger * Update .changeset/brave-pots-drop.md Co-authored-by: Sarah Rainsberger --------- Co-authored-by: Sarah Rainsberger --- .changeset/brave-pots-drop.md | 24 ++++++++++ packages/astro/src/@types/astro.ts | 2 + packages/astro/src/core/render/core.ts | 1 + packages/astro/src/core/render/result.ts | 2 + .../src/runtime/server/render/astro/render.ts | 4 +- .../astro/src/runtime/server/render/common.ts | 4 +- .../src/runtime/server/render/component.ts | 2 +- .../test/fixtures/partials/astro.config.mjs | 9 ++++ .../astro/test/fixtures/partials/package.json | 9 ++++ .../partials/src/pages/partials/docs.mdx | 5 ++ .../partials/src/pages/partials/item.astro | 4 ++ packages/astro/test/partials.test.js | 47 +++++++++++++++++++ pnpm-lock.yaml | 9 ++++ 13 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 .changeset/brave-pots-drop.md create mode 100644 packages/astro/test/fixtures/partials/astro.config.mjs create mode 100644 packages/astro/test/fixtures/partials/package.json create mode 100644 packages/astro/test/fixtures/partials/src/pages/partials/docs.mdx create mode 100644 packages/astro/test/fixtures/partials/src/pages/partials/item.astro create mode 100644 packages/astro/test/partials.test.js diff --git a/.changeset/brave-pots-drop.md b/.changeset/brave-pots-drop.md new file mode 100644 index 000000000000..54de404977ea --- /dev/null +++ b/.changeset/brave-pots-drop.md @@ -0,0 +1,24 @@ +--- +'astro': minor +--- + +Page Partials + +A page component can now be identified as a **partial** page, which will render its HTML content without including a `` declaration nor any `` content. + +A rendering library, like htmx or Stimulus or even just jQuery can access partial content on the client to dynamically update only parts of a page. + +Pages marked as partials do not have a `doctype` or any head content included in the rendered result. You can mark any page as a partial by setting this option: + + +```astro +--- +export const partial = true; +--- + +
  • This is a single list item.
  • +``` + +Other valid page files that can export a value (e.g. `.mdx`) can also be marked as partials. + +Read more about [Astro page partials](/en/core-concepts/astro-pages/#partials) in our documentation. diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 1bcbe20a9c47..778e5b739a30 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1562,6 +1562,7 @@ export type AsyncRendererComponentFn = ( export interface ComponentInstance { default: AstroComponentFactory; css?: string[]; + partial?: boolean; prerender?: boolean; /** * Only used for logging if deprecated drafts feature is used @@ -2234,6 +2235,7 @@ export interface SSRResult { */ clientDirectives: Map; compressHTML: boolean; + partial: boolean; /** * Only used for logging */ diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index d8c39ec1a212..f8889f47d116 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -49,6 +49,7 @@ export async function renderPage({ mod, renderContext, env, cookies }: RenderPag clientDirectives: env.clientDirectives, compressHTML: env.compressHTML, request: renderContext.request, + partial: !!mod.partial, site: env.site, scripts: renderContext.scripts, ssr: env.ssr, diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index 6f8ca930334c..b96628a5f8b9 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -31,6 +31,7 @@ export interface CreateResultArgs { renderers: SSRLoadedRenderer[]; clientDirectives: Map; compressHTML: boolean; + partial: boolean; resolve: (s: string) => Promise; /** * Used for `Astro.site` @@ -155,6 +156,7 @@ export function createResult(args: CreateResultArgs): SSRResult { renderers: args.renderers, clientDirectives: args.clientDirectives, compressHTML: args.compressHTML, + partial: args.partial, pathname: args.pathname, cookies, /** This function returns the `Astro` faux-global */ diff --git a/packages/astro/src/runtime/server/render/astro/render.ts b/packages/astro/src/runtime/server/render/astro/render.ts index 7091513c75f5..3b7cdc052622 100644 --- a/packages/astro/src/runtime/server/render/astro/render.ts +++ b/packages/astro/src/runtime/server/render/astro/render.ts @@ -33,7 +33,7 @@ export async function renderToString( // Automatic doctype insertion for pages if (isPage && !renderedFirstPageChunk) { renderedFirstPageChunk = true; - if (!/' : '\n'; str += doctype; } @@ -84,7 +84,7 @@ export async function renderToReadableStream( // Automatic doctype insertion for pages if (isPage && !renderedFirstPageChunk) { renderedFirstPageChunk = true; - if (!/' : '\n'; controller.enqueue(encoder.encode(doctype)); } diff --git a/packages/astro/src/runtime/server/render/common.ts b/packages/astro/src/runtime/server/render/common.ts index 03e9c830811e..e5a5d5e860e0 100644 --- a/packages/astro/src/runtime/server/render/common.ts +++ b/packages/astro/src/runtime/server/render/common.ts @@ -77,13 +77,13 @@ function stringifyChunk( } } case 'head': { - if (result._metadata.hasRenderedHead) { + if (result._metadata.hasRenderedHead || result.partial) { return ''; } return renderAllHeadContent(result); } case 'maybe-head': { - if (result._metadata.hasRenderedHead || result._metadata.headInTree) { + if (result._metadata.hasRenderedHead || result._metadata.headInTree || result.partial) { return ''; } return renderAllHeadContent(result); diff --git a/packages/astro/src/runtime/server/render/component.ts b/packages/astro/src/runtime/server/render/component.ts index 002f4ebafd06..a82b50ff8431 100644 --- a/packages/astro/src/runtime/server/render/component.ts +++ b/packages/astro/src/runtime/server/render/component.ts @@ -505,7 +505,7 @@ export async function renderComponentToString( // Automatic doctype and head insertion for pages if (isPage && !renderedFirstPageChunk) { renderedFirstPageChunk = true; - if (!/' : '\n'; str += doctype + head; } diff --git a/packages/astro/test/fixtures/partials/astro.config.mjs b/packages/astro/test/fixtures/partials/astro.config.mjs new file mode 100644 index 000000000000..b4dfec3ce2d7 --- /dev/null +++ b/packages/astro/test/fixtures/partials/astro.config.mjs @@ -0,0 +1,9 @@ +import { defineConfig } from 'astro/config'; +import mdx from '@astrojs/mdx'; + +// https://astro.build/config +export default defineConfig({ + integrations: [ + mdx() + ] +}); diff --git a/packages/astro/test/fixtures/partials/package.json b/packages/astro/test/fixtures/partials/package.json new file mode 100644 index 000000000000..347a6880318d --- /dev/null +++ b/packages/astro/test/fixtures/partials/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/partials", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/mdx": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/partials/src/pages/partials/docs.mdx b/packages/astro/test/fixtures/partials/src/pages/partials/docs.mdx new file mode 100644 index 000000000000..7d7850885c7c --- /dev/null +++ b/packages/astro/test/fixtures/partials/src/pages/partials/docs.mdx @@ -0,0 +1,5 @@ +export const partial = true; + +# This is heading + +and this is some text diff --git a/packages/astro/test/fixtures/partials/src/pages/partials/item.astro b/packages/astro/test/fixtures/partials/src/pages/partials/item.astro new file mode 100644 index 000000000000..500e7c98b439 --- /dev/null +++ b/packages/astro/test/fixtures/partials/src/pages/partials/item.astro @@ -0,0 +1,4 @@ +--- +export const partial = true; +--- +
  • This is a single line item
  • diff --git a/packages/astro/test/partials.test.js b/packages/astro/test/partials.test.js new file mode 100644 index 000000000000..6f1b5093899c --- /dev/null +++ b/packages/astro/test/partials.test.js @@ -0,0 +1,47 @@ +import { expect } from 'chai'; +import { loadFixture } from './test-utils.js'; + +describe('Partials', () => { + /** @type {import('./test-utils.js').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/partials/', + }); + }); + + 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('/partials/item/').then((res) => res.text()); + expect(html.startsWith('
  • ')).to.equal(true); + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('is only the written HTML', async () => { + const html = await fixture.readFile('/partials/item/index.html'); + expect(html.startsWith('
  • ')).to.equal(true); + }); + + it('Works with mdx', async () => { + const html = await fixture.readFile('/partials/docs/index.html'); + expect(html.startsWith('