Skip to content

Commit

Permalink
"Virtual" pages prototype (#1175)
Browse files Browse the repository at this point in the history
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
  • Loading branch information
3 people committed Feb 16, 2024
1 parent 2cb3578 commit dd11b95
Show file tree
Hide file tree
Showing 20 changed files with 928 additions and 77 deletions.
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@astrojs/starlight": "workspace:*",
"@lunariajs/core": "^0.0.25",
"@types/culori": "^2.0.0",
"astro": "^4.3.4",
"astro": "^4.3.5",
"culori": "^3.2.0",
"sharp": "^0.32.5"
},
Expand Down
38 changes: 2 additions & 36 deletions docs/src/content/docs/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,43 +83,9 @@ Open this URL to start browsing your site.

Starlight is ready for you to add new content, or bring your existing files!

#### File formats
Add new pages to your site by creating Markdown files in the `src/content/docs/` directory.

Starlight supports authoring content in Markdown and MDX with no configuration required.
You can add support for Markdoc by installing the experimental [Astro Markdoc integration](https://docs.astro.build/en/guides/integrations-guide/markdoc/).

#### Add pages

Add new pages to your site by creating `.md` or `.mdx` files in `src/content/docs/`.
Use sub-folders to organize your files and to create multiple path segments.

For example, the following file structure will generate pages at `example.com/hello-world` and `example.com/guides/faq`:

import FileTree from '~/components/file-tree.astro';

<FileTree>

- src/
- content/
- docs/
- guides/
- faq.md
- hello-world.md

</FileTree>

#### Type-safe frontmatter

All Starlight pages share a customizable [common set of frontmatter properties](/reference/frontmatter/) to control how the page appears:

```md
---
title: Hello, World!
description: This is a page in my Starlight-powered site
---
```

If you forget anything important, Starlight will let you know.
Read more about file-based routing and support for MDX and Markdoc files in the [“Pages”](/guides/pages/) guide.

### Next steps

Expand Down
151 changes: 151 additions & 0 deletions docs/src/content/docs/guides/pages.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
title: Pages
description: Learn how to create and manage your documentation site’s pages with Starlight.
sidebar:
order: 1
---

Starlight generates your site’s HTML pages based on your content, with flexible options provided via Markdown frontmatter.
In addition, Starlight projects have full access to [Astro’s powerful page generation tools](https://docs.astro.build/en/basics/astro-pages/).
This guide shows how page generation works in Starlight.

## Content pages

### File formats

Starlight supports authoring content in Markdown and MDX with no configuration required.
You can add support for Markdoc by installing the experimental [Astro Markdoc integration](https://docs.astro.build/en/guides/integrations-guide/markdoc/).

### Add pages

Add new pages to your site by creating `.md` or `.mdx` files in `src/content/docs/`.
Use sub-folders to organize your files and to create multiple path segments.

For example, the following file structure will generate pages at `example.com/hello-world` and `example.com/reference/faq`:

import FileTree from '~/components/file-tree.astro';

<FileTree>

- src/
- content/
- docs/
- hello-world.md
- reference/
- faq.md

</FileTree>

### Type-safe frontmatter

All Starlight pages share a customizable [common set of frontmatter properties](/reference/frontmatter/) to control how the page appears:

```md
---
title: Hello, World!
description: This is a page in my Starlight-powered site
---
```

If you forget anything important, Starlight will let you know.

## Custom pages

For advanced use cases, you can add custom pages by creating a `src/pages/` directory.
The `src/pages/` directory uses [Astro's file-based routing](https://docs.astro.build/en/basics/astro-pages/#file-based-routing) and includes support for `.astro` files amongst other page formats.
This is helpful if you need to build pages with a completely custom layout or generate a page from an alternative data source.

For example, this project mixes Markdown content in `src/content/docs/` with Astro and HTML routes in `src/pages/`:

<FileTree>

- src/
- content/
- docs/
- hello-world.md
- pages/
- custom.astro
- archived.html

</FileTree>

Read more in the [“Pages” guide in the Astro docs](https://docs.astro.build/en/basics/astro-pages/).

### Using Starlight’s design in custom pages

To use the Starlight layout in custom pages, wrap your page content with the `<StarlightPage />` component.
This can be helpful if you are generating content dynamically but still want to use Starlight’s design.

```astro
---
// src/pages/custom-page/example.astro
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
import CustomComponent from './CustomComponent.astro';
---
<StarlightPage frontmatter={{ title: 'My custom page' }}>
<p>This is a custom page with a custom component:</p>
<CustomComponent />
</StarlightPage>
```

#### Props

The `<StarlightPage />` component accepts the following props.

##### `frontmatter` (required)

**type:** `StarlightPageFrontmatter`

Set the [frontmatter properties](/reference/frontmatter/) for this page, similar to frontmatter in Markdown pages.
The [`title`](/reference/frontmatter/#title-required) property is required and all other properties are optional.

The following properties differ from Markdown frontmatter:

- The [`slug`](/reference/frontmatter/#slug) property is not supported and is automatically set based on the custom page’s URL.
- The [`editUrl`](/reference/frontmatter/#editurl) option requires a URL to display an edit link.
- The [`sidebar`](/reference/frontmatter/#sidebar) property is not supported. In Markdown frontmatter, this option allows customization of [autogenerated link groups](/reference/configuration/#sidebar), which is not applicable to pages using the `<StarlightPage />` component.

{/* ##### `sidebar` */}

{/* **type:** `SidebarEntry[] | undefined` */}
{/* **default:** the sidebar generated based on the [global `sidebar` config](/reference/configuration/#sidebar) */}

{/* Provide a custom site navigation sidebar for this page. */}
{/* If not set, the page will use the default global sidebar. */}

##### `hasSidebar`

**type:** `boolean`
**default:** `false` if [`frontmatter.template`](/reference/frontmatter/#template) is `'splash'`, otherwise `true`

Control whether or not the sidebar should be displayed on this page.

##### `headings`

**type:** `{ depth: number; slug: string; text: string }[]`
**default:** `[]`

Provide an array of all the headings on this page.
Starlight will generate the page table of contents from these headings if provided.

##### `dir`

**type:** `'ltr' | 'rtl'`
**default:** the writing direction for the current locale

Set the writing direction for this page’s content.

##### `lang`

**type:** `string`
**default:** the language of the current locale

Set the BCP-47 language tag for this page’s content, e.g. `en`, `zh-CN`, or `pt-BR`.

##### `isFallback`

**type:** `boolean`
**default:** `false`

Indicate if this page is using [fallback content](/guides/i18n/#fallback-content) because there is no translation for the current language.
2 changes: 1 addition & 1 deletion examples/basics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dependencies": {
"@astrojs/starlight": "^0.18.1",
"astro": "^4.3.4",
"astro": "^4.3.5",
"sharp": "^0.32.5"
}
}
2 changes: 1 addition & 1 deletion examples/tailwind/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@astrojs/starlight": "^0.18.1",
"@astrojs/starlight-tailwind": "^2.0.1",
"@astrojs/tailwind": "^5.1.0",
"astro": "^4.3.4",
"astro": "^4.3.5",
"sharp": "^0.32.5",
"tailwindcss": "^3.4.1"
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.1",
"@size-limit/file": "^8.2.4",
"astro": "^4.3.4",
"astro": "^4.3.5",
"prettier": "^3.0.0",
"prettier-plugin-astro": "^0.13.0",
"size-limit": "^8.2.4"
Expand Down
35 changes: 34 additions & 1 deletion packages/starlight/__tests__/basics/slugs.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, expect, test } from 'vitest';
import { describe, expect, test, vi } from 'vitest';
import {
localeToLang,
localizedId,
localizedSlug,
slugToLocaleData,
slugToParam,
slugToPathname,
urlToSlug,
} from '../../utils/slugs';

describe('slugToLocaleData', () => {
Expand Down Expand Up @@ -76,3 +77,35 @@ describe('localizedSlug', () => {
expect(localizedSlug('test', undefined)).toBe('test');
});
});

describe('urlToSlug', () => {
test('returns slugs with `build.output: "directory"`', () => {
expect(urlToSlug(new URL('https://example.com'))).toBe('');
expect(urlToSlug(new URL('https://example.com/slug'))).toBe('slug');
expect(urlToSlug(new URL('https://example.com/dir/page/'))).toBe('dir/page');
expect(urlToSlug(new URL('https://example.com/dir/sub-dir/page/'))).toBe('dir/sub-dir/page');
});

test('returns slugs with `build.output: "file"`', () => {
expect(urlToSlug(new URL('https://example.com/index.html'))).toBe('');
expect(urlToSlug(new URL('https://example.com/slug.html'))).toBe('slug');
expect(urlToSlug(new URL('https://example.com/dir/page/index.html'))).toBe('dir/page');
expect(urlToSlug(new URL('https://example.com/dir/sub-dir/page.html'))).toBe(
'dir/sub-dir/page'
);
});

// It is currently not possible to test this as stubbing BASE_URL is not supported due to
// `vite-plugin-env` controlling it and the lack of a way to pass in an Astro config using
// `getViteConfig()` from `astro/config`.
test.todo('returns slugs with a custom `base` option', () => {
vi.stubEnv('BASE_URL', '/base/');
expect(urlToSlug(new URL('https://example.com/base'))).toBe('');
expect(urlToSlug(new URL('https://example.com/base/slug'))).toBe('slug');
expect(urlToSlug(new URL('https://example.com/base/dir/page/'))).toBe('dir/page');
expect(urlToSlug(new URL('https://example.com/base/dir/sub-dir/page/'))).toBe(
'dir/sub-dir/page'
);
vi.unstubAllEnvs();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { assert, expect, test, vi } from 'vitest';
import {
generateStarlightPageRouteData,
type StarlightPageProps,
} from '../../utils/starlight-page';

vi.mock('virtual:starlight/collection-config', async () => {
const { z } = await vi.importActual<typeof import('astro:content')>('astro:content');
return (await import('../test-utils')).mockedCollectionConfig({
extend: z.object({
// Make the built-in description field required.
description: z.string(),
// Add a new optional field.
category: z.string().optional(),
}),
});
});

const starlightPageProps: StarlightPageProps = {
frontmatter: { title: 'This is a test title' },
};

test('throws a validation error if a built-in field required by the user schema is not passed down', async () => {
expect.assertions(3);

try {
await generateStarlightPageRouteData({
props: starlightPageProps,
url: new URL('https://example.com/test-slug'),
});
} catch (error) {
assert(error instanceof Error);
const lines = error.message.split('\n');
// The first line should be a user-friendly error message describing the exact issue and the second line should be
// the missing description field.
expect(lines).toHaveLength(2);
const [message, missingField] = lines;
expect(message).toMatchInlineSnapshot(
`"Invalid frontmatter props passed to the \`<StarlightPage/>\` component."`
);
expect(missingField).toMatchInlineSnapshot(`"**description**: Required"`);
}
});

test('returns new field defined in the user schema', async () => {
const category = 'test category';
const data = await generateStarlightPageRouteData({
props: {
...starlightPageProps,
frontmatter: {
...starlightPageProps.frontmatter,
description: 'test description',
// @ts-expect-error - Custom field defined in the user schema.
category,
},
},
url: new URL('https://example.com/test-slug'),
});
// @ts-expect-error - Custom field defined in the user schema.
expect(data.entry.data.category).toBe(category);
});
Loading

0 comments on commit dd11b95

Please sign in to comment.