Skip to content

Commit

Permalink
Add support for SSR (#1255)
Browse files Browse the repository at this point in the history
Co-authored-by: Patrick Jakubik <[email protected]>
Co-authored-by: Roni Costa <[email protected]>
Co-authored-by: Ryan Russell <[email protected]>
Co-authored-by: HiDeoo <[email protected]>
Co-authored-by: Murali Manohar Varma <[email protected]>
Co-authored-by: Marcelo Cardoso <[email protected]>
Co-authored-by: Aaron Siddhartha Mondal <[email protected]>
Co-authored-by: Chris Swithinbank <[email protected]>
Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
10 people authored Sep 6, 2024
1 parent 20cbf3b commit 6f3202b
Show file tree
Hide file tree
Showing 48 changed files with 1,004 additions and 146 deletions.
7 changes: 7 additions & 0 deletions .changeset/ninety-singers-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astrojs/starlight': patch
---

Improves performance of computing the last updated times from Git history.

Instead of executing `git` for each docs page, it is now executed twice regardless of the number of pages.
5 changes: 5 additions & 0 deletions .changeset/quiet-penguins-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Fixes last updated times on projects with custom `srcDir`
16 changes: 16 additions & 0 deletions .changeset/six-phones-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@astrojs/starlight': minor
---

Adds support for server-rendered Starlight pages.

When building a project with `hybrid` or `server` output mode, a new `prerender` option on Starlight config can be set to `false` to make all Starlight pages be rendered on-demand:

```ts
export default defineConfig({
output: 'server',
integrations: [starlight({
prerender: false
})],
})
```
4 changes: 2 additions & 2 deletions docs/src/content/docs/manual-setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,6 @@ In the future, we plan to support this use case better to avoid the need for the
### Use Starlight with SSR
You can use Starlight alongside custom on-demand rendered pages in your project by following the [“On-demand Rendering Adapters”](https://docs.astro.build/en/guides/server-side-rendering/) guide in Astro’s docs.
To enable SSR, follow the [“On-demand Rendering Adapters”](https://docs.astro.build/en/guides/server-side-rendering/) guide in Astro’s docs to add a server adapter to your Starlight project.
Currently, documentation pages generated by Starlight are always prerendered regardless of your project's output mode. We hope to be able to support on-demand rendering for Starlight pages soon.
Documentation pages generated by Starlight are pre-rendered by default regardless of your project's output mode. To opt out of pre-rendering your Starlight pages, set the [`prerender` config option](/reference/configuration/#prerender) to `false`.
12 changes: 12 additions & 0 deletions docs/src/content/docs/reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,18 @@ Define whether Starlight’s default site search provider [Pagefind](https://pag
Set to `false` to disable indexing your site with Pagefind.
This will also hide the default search UI if in use.

Pagefind cannot be enabled when the [`prerender`](#prerender) option is set to `false`.

### `prerender`

**type:** `boolean`
**default:** `true`

Define whether Starlight pages should be pre-rendered to static HTML or on-demand rendered by an [SSR adapter](https://docs.astro.build/en/guides/server-side-rendering/).

Starlight pages are pre-rendered by default.
If you are using an SSR adapter and want to render Starlight pages on demand, set `prerender: false`.

### `head`

**type:** [`HeadConfig[]`](#headconfig)
Expand Down
4 changes: 2 additions & 2 deletions packages/starlight/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Astro generates this during tests, but we want to ignore it.
src/env.d.ts
__tests__/**/env.d.ts
env.d.ts
__tests__/**/types.d.ts
.astro
5 changes: 3 additions & 2 deletions packages/starlight/__e2e__/collection-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { expect, testFactory } from './test-utils';

// This fixture contains a space in the directory so that we have a smoke test for building
// Starlight projects with pathnames like this, which are a common source of bugs.
const test = await testFactory('./fixtures/custom src-dir/');
const test = testFactory('./fixtures/custom src-dir/');

test('builds a custom page using the `<StarlightPage>` component and a custom `srcDir`', async ({
page,
starlight,
getProdServer,
}) => {
const starlight = await getProdServer();
await starlight.goto('/custom');

await expect(page.getByText('Hello')).toBeVisible();
Expand Down
10 changes: 10 additions & 0 deletions packages/starlight/__e2e__/fixtures/git/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This fixture is used for the E2E tests related to Git where
# a new repo is created. Nested repos do not inherit the .gitignore
# options from the parent repo, so even though these files are
# already ignored on the package they have to be re-declared here.
# Do not delete this file or the Git tests will try to index and
# and commit all the node_modules and generated files from the test.
env.d.ts
.astro
/node_modules/
/dist/
11 changes: 11 additions & 0 deletions packages/starlight/__e2e__/fixtures/git/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config';

export default defineConfig({
integrations: [
starlight({
title: 'Git',
pagefind: false,
}),
],
});
9 changes: 9 additions & 0 deletions packages/starlight/__e2e__/fixtures/git/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@e2e/git",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/starlight": "workspace:*",
"astro": "^4.15.3"
}
}
6 changes: 6 additions & 0 deletions packages/starlight/__e2e__/fixtures/git/src/content/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineCollection } from 'astro:content';
import { docsSchema } from '@astrojs/starlight/schema';

export const collections = {
docs: defineCollection({ schema: docsSchema() }),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Home Page
lastUpdated: true
---

Home page content
25 changes: 25 additions & 0 deletions packages/starlight/__e2e__/fixtures/ssr/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import node from '@astrojs/node';

const prerendering = process.env.STARLIGHT_PRERENDER === 'yes';

export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
compressHTML: false, // for easier debugging
// Output to different folders and expose on different ports
// on each case so the servers don't conflict with
// each other during tests.
outDir: prerendering ? 'dist' : 'build',
server: {
port: prerendering ? 4322 : 4321,
},
integrations: [
starlight({
title: 'SSR',
prerender: prerendering,
pagefind: false,
}),
],
});
10 changes: 10 additions & 0 deletions packages/starlight/__e2e__/fixtures/ssr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@e2e/ssr",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/node": "^8.3.2",
"@astrojs/starlight": "workspace:*",
"astro": "^4.15.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
function checkServer() {
try {
Astro.clientAddress;
return true;
} catch {
// Accessing `clientAddress` during build time
// fails, so it is not SSR.
return false;
}
}
---

<div id="server-check">{checkServer() ? 'On server' : 'Not server'}</div>
6 changes: 6 additions & 0 deletions packages/starlight/__e2e__/fixtures/ssr/src/content/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineCollection } from 'astro:content';
import { docsSchema } from '@astrojs/starlight/schema';

export const collections = {
docs: defineCollection({ schema: docsSchema() }),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Not Found
template: splash
lastUpdate: true
---

import ServerCheck from '../../component/ServerCheck.astro';

<ServerCheck />
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Content
lastUpdate: true
---

Example page
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: Server Check
lastUpdate: true
---

import ServerCheck from '../../component/ServerCheck.astro';

<ServerCheck />
18 changes: 18 additions & 0 deletions packages/starlight/__e2e__/fixtures/ssr/src/content/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Home Page
template: splash
lastUpdate: true
hero:
title: Make your docs shine with Starlight
tagline: Everything you need to build a stellar documentation website. Fast, accessible, and easy-to-use.
actions:
- text: Get started
icon: right-arrow
variant: primary
link: /getting-started/
- text: View on GitHub
icon: external
link: https://github.com/withastro/starlight
---

Home page content
36 changes: 36 additions & 0 deletions packages/starlight/__e2e__/git.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { makeTestRepo } from '../__tests__/git-utils';
import { expect, testFactory } from './test-utils';
import { rm } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { join } from 'node:path';

const repoPath = fileURLToPath(new URL('./fixtures/git/', import.meta.url));

const test = testFactory(repoPath);

test.beforeAll(async () => {
// Clears existing nested repo to account for previously interrupted tests.
await rm(join(repoPath, '.git'), { recursive: true, force: true });
const testRepo = makeTestRepo(repoPath);

testRepo.commitAllChanges('Add home page', '2024-02-03');
});

test.afterAll(async () => {
// Remove nested repo after test runs
await rm(join(repoPath, '.git'), { recursive: true, force: true });
});

test('include last updated date from git in the footer', async ({ page, getProdServer }) => {
const starlight = await getProdServer();
await starlight.goto('/');

await expect(page.locator('footer')).toContainText('Last updated: Feb 3, 2024');
});

test('include git information while developing', async ({ page, makeServer }) => {
const starlight = await makeServer('dev', { mode: 'dev' });
await starlight.goto('/');

await expect(page.locator('footer')).toContainText('Last updated: Feb 3, 2024');
});
66 changes: 66 additions & 0 deletions packages/starlight/__e2e__/ssr.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect, testFactory } from './test-utils';
import assert from 'node:assert';
import { parseHTML } from 'linkedom';

const test = testFactory('./fixtures/ssr/');

test.beforeEach(() => {
delete process.env.STARLIGHT_PRERENDER;
});

test('Render page on the server', async ({ page, getProdServer }) => {
const starlight = await getProdServer();
await starlight.goto('/demo');

await expect(page.locator('#server-check')).toHaveText('On server');
});

test('Render 404 page on the server', async ({ page, getProdServer }) => {
const starlight = await getProdServer();
await starlight.goto('/not-found');

await expect(page.locator('#server-check')).toHaveText('On server');
});

test('SSR mode renders the same content page as prerendering', async ({
getProdServer,
makeServer,
}) => {
const starlight = await getProdServer();
const ssrContent = await starlight.goto('/content').then((res) => res?.text());
assert(ssrContent);

process.env.STARLIGHT_PRERENDER = 'yes';
const prerenderStarlight = await makeServer('prerender');
const prerenderContent = await prerenderStarlight.goto('/content').then((res) => res?.text());
assert(prerenderContent);

expectEquivalentHTML(prerenderContent, ssrContent);
});

test('SSR mode renders the same splash page as prerendering', async ({
getProdServer,
makeServer,
}) => {
const starlight = await getProdServer();
const ssrContent = await starlight.goto('/').then((res) => res?.text());
assert(ssrContent);

process.env.STARLIGHT_PRERENDER = 'yes';
const prerenderStarlight = await makeServer('prerender');
const prerenderContent = await prerenderStarlight.goto('/').then((res) => res?.text());
assert(prerenderContent);

expectEquivalentHTML(prerenderContent, ssrContent);
});

function expectEquivalentHTML(a: string, b: string) {
expect(getNormalizedHTML(a)).toEqual(getNormalizedHTML(b));
}

function getNormalizedHTML(html: string) {
const window = parseHTML(html);
window.document.querySelectorAll('script[src]').forEach((el) => el.setAttribute('src', ''));
window.document.querySelectorAll('link[href]').forEach((el) => el.setAttribute('href', ''));
return window.toString();
}
Loading

0 comments on commit 6f3202b

Please sign in to comment.