Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content Layer #11360

Merged
merged 71 commits into from
Aug 14, 2024
Merged

Content Layer #11360

merged 71 commits into from
Aug 14, 2024

Conversation

ascorbic
Copy link
Contributor

@ascorbic ascorbic commented Jun 27, 2024

This is the work-in-progress branch for the Content Layer
The Content Layer API is a new way to handle content and data in Astro. It builds upon content collections, taking them beyond local files in src/content and allowing you to fetch content from anywhere, including remote APIs, or files anywhere in your project. As well as being more powerful, the Content Layer is designed to be more performant, helping sites scale to thousands of pages. Data is cached between builds and updated incrementally. Markdown parsing is also 5-10x faster, with similar scale reductions in memory. While the feature is experimental and subject to breaking changes, we invite you to try it today and let us know how it works for you.

Enabling content layer

To enable, add the contentLayer flag to the experimental object in your Astro config:

{
	experimental: {
		contentLayer: true,
	}
}

Using the content layer

Tip

The content layer APIs are based on content collections, so you can learn more about them in the content collection docs. Any differences are highlighted below.

To use content layer, create a collection in src/content/config.ts with a loader property. For local files where there is one entry per file, use the glob() loader. You can put your content files anywhere, but not in src/content because these would be handled by the current content collections instead. In this example the files are in src/data.

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
  // By default the ID is a slug, generated from the path of the file relative to `base`
  loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
  }),
});

export const collections = { blog };

You can load multiple entries from a single JSON file using the file() loader. In this case the data must either be an array of objects, which each contain an id property, or an object where each key is the ID.

Array syntax:

[
  {
    "id": "labrador-retriever",
    "breed": "Labrador Retriever",
    "size": "Large",
    "origin": "Canada",
    "lifespan": "10-12 years",
    "temperament": [
      "Friendly",
      "Active",
      "Outgoing"
    ]
  },
  {
    "id": "german-shepherd",
    "breed": "German Shepherd",
    "size": "Large",
    "origin": "Germany",
    "lifespan": "9-13 years",
    "temperament": [
      "Loyal",
      "Intelligent",
      "Confident"
    ]
  }
]

Object syntax:

{
  "labrador-retriever": {
    "breed": "Labrador Retriever",
    "size": "Large",
    "origin": "Canada",
    "lifespan": "10-12 years",
    "temperament": [
      "Friendly",
      "Active",
      "Outgoing"
    ]
  },
  "german-shepherd": {
    "breed": "German Shepherd",
    "size": "Large",
    "origin": "Germany",
    "lifespan": "9-13 years",
    "temperament": [
      "Loyal",
      "Intelligent",
      "Confident"
    ]
  }
}

The collection is then defined using the file() loader:

import { defineCollection, z } from 'astro:content';
import { file } from 'astro/loaders';

const dogs = defineCollection({
	loader: file('src/data/dogs.json'),
	schema: z.object({
		id: z.string(),
    breed: z.string(),
		size: z.string(),
		origin: z.string(),
		lifespan: z.string(),
		temperament: z.array(z.string()),
	}),
});

export const collections = { dogs };

The collection can be queried in the same way as content collections:

import { getCollection, getEntry } from 'astro:content';

// Get all entries from a collection.
// Requires the name of the collection as an argument.
const allBlogPosts = await getCollection('blog');

// Get a single entry from a collection.
// Requires the name of the collection and ID
const labradorData = await getEntry('dogs', 'labrador-retriever');

Rendering content

Entries generated from markdown or MDX can be rendered directly to a page using the render() function.

Warning

The syntax for rendering content layer collections is different from existing content collections syntax.

---
import { getEntry, render } from 'astro:content';

const post = await getEntry('blog', Astro.params.slug);

const { Content, headings } = await render(entry);
---

<Content />

Creating a content layer loader

Content layer loaders aren't restricted to just loading local files. You can also use loaders to load or generate content from anywhere. The simplest type of loader is an async function that returns an array of objects, each of which has an id:

const countries = defineCollection({
  loader: async () => {
    const response = await fetch("https://restcountries.com/v3.1/all");
    const data = await response.json();
    // Must return an array of entries with an id property, or an object with IDs as keys and entries as values
    return data.map((country) => ({
      id: country.cca3,
      ...country,
    }));
  },
  // optionally add a schema
  // schema: z.object...
});

export const collections = { countries };

For more advanced loading logic, you can define an object loader. This allows incremental updates and conditional loading, and gives full access to the data store. See the API in the draft RFC.

Migrating a markdown or MDX collection to content layer

If you have used content collections, content layer will be very familiar as most of the APIs are the same. You can try converting an existing content collection to content layer if it uses markdown, MDX or JSON, with these steps:

  1. Move the collection folder out of src/content. This is so it won't be handled as a current content collection. This example assumes the content has been moved to src/data. The config.ts file must remain in src/content.
  2. Edit the collection definition. The collection should not have type set, and needs a loader defined.
import { defineCollection, z } from 'astro:content';
+ import { glob } from 'astro/loaders';

const blog = defineCollection({
  // For content layer you do not define a `type`
- type: 'content',
+ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),	
  schema: z.object({
		title: z.string(),
		description: z.string(),
		pubDate: z.coerce.date(),
		updatedDate: z.coerce.date().optional(),
	}),
});
  1. Change references from slug to id. Content layer collections do not have a slug field. You should use id instead, which has the same syntax.
---
export async function getStaticPaths() {
	const posts = await getCollection('blog');
	return posts.map((post) => ({
-   params: { slug: post.slug },
+   params: { slug: post.id },
    props: post,
	}));
}
---
  1. Switch to the new render() function. Entries no longer have a render() method, as they are now serializable plain objects. Instead, import the render() function from astro:content.
---
- import { getEntry } from 'astro:content';
+ import { getEntry, render } from 'astro:content';

  const post = await getEntry('blog', params.slug);

- const { Content, headings } = await post.render();
+ const { Content, headings } = await render(entry);
---

<Content />

The getEntryBySlug and getDataEntryByID functions are deprecated and cannot be used with content layer collections. Instead, use getEntry, which is a drop-in replacement for both.

Learn more

To see the full API look at the draft RFC and share your feedback on the feature and API.

Copy link

changeset-bot bot commented Jun 27, 2024

🦋 Changeset detected

Latest commit: fdd7b83

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ascorbic ascorbic marked this pull request as draft June 27, 2024 08:23
@github-actions github-actions bot added the semver: minor Change triggers a `minor` release label Jun 27, 2024
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is blocked because it contains a minor changeset. A reviewer will merge this at the next release if approved.

* wip

* wip

* wip

* Update demo

* Add meta

* wip

* Add file loader

* Add schema validation

* Remove log

* Changeset

* Format

* Lockfile

* Fix type

* Handle loading for data store JSON

* Use rollup util to import JSON

* Fix types

* Format

* Add tests

* Changes from review
@github-actions github-actions bot added the pkg: astro Related to the core `astro` package (scope) label Jun 28, 2024
ascorbic added 7 commits June 28, 2024 09:09
* wip

* wip

* wip

* Update demo

* Add meta

* wip

* Add file loader

* Add schema validation

* Remove log

* Changeset

* Format

* Lockfile

* Fix type

* Handle loading for data store JSON

* Use rollup util to import JSON

* Fix types

* Format

* Add tests

* Changes from review

* Sync content layer in dev
* fix: watch for content layer changes

* Add test
* wip

* Add simple loader

* Fix type guard

* Tighten loader schema

* Add loader function to type
@github-actions github-actions bot added the pkg: example Related to an example package (scope) label Jul 8, 2024
* feat: add glob loader

* Enable watching and fix paths

* Store the full entry object, not just data

* Add generateId support

* Fix test

* Rename loaders to sync

* Refacctor imports

* Use getEntry

* Format

* Fix import

* Remove type from output

* Windows path

* Add test for absolute path

* Update lockfile

* Debugging windows

* Allow file URL for base dir

* Reset time limit
@github-actions github-actions bot added the pr: docs A PR that includes documentation for review label Jul 10, 2024
ascorbic added 5 commits July 17, 2024 20:38
* feat: add glob loader

* Enable watching and fix paths

* Store the full entry object, not just data

* Add generateId support

* Fix test

* Rename loaders to sync

* Refacctor imports

* Use getEntry

* Format

* Fix import

* Remove type from output

* Windows path

* Add test for absolute path

* Update lockfile

* Debugging windows

* Allow file URL for base dir

* Reset time limit

* wip: add markdown rendering to content layer

* use cached entries

* CLean up types

* Instrument more of the build

* Add digest helper

* Add comments

* Make image extraction work

* feat: image support for content layer (#11469)

* wip

* wip

* Add image to benchmark

* Stub assets if missing

* Resolve assets in data

* Ignore virtual module

* Format

* rm log

* Handle images when using cached data

* Fix CCC

* Add a comment

* Changes from review

* Format

* Use relative paths for asset files

* Pass all md props to getImage

* Ensure dotastro dir exists

* Fix tests

* Changes from review

* Don't use temp array in getcollection

* Add error handling

* Format

* Handle paths that are already relative

* Dedupe sync runs

* Fix syncing in dev

* Changes from review

* Windows paths ftw

* feat(content-layer): support references in content layer (#11494)

* Support references in content layer

* Fix utf8 rendering

* Warn for invalid entries

* Fix test

* lol windows paths

* Remove assertion
@ematipico ematipico marked this pull request as ready for review August 12, 2024 09:53
ematipico and others added 5 commits August 12, 2024 10:54
* Update content layer flag docs

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* More markdoc

* Typo

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
* Add changeset for content layer experimental release

* Update changeset

* Update .changeset/smooth-chicken-wash.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
ascorbic and others added 10 commits August 14, 2024 09:22
* feat: make inline config 1st arg

* fix: run config done in sync

* feat: start working on injectTypes

* feat: write files

* feat: adapt core features

* feat: migrate db to injectTypes

* feat: special db handling

* feat: update settings instead of workarounds

* fix: create dotAstroDir

* feat: refactor sync tests

* fix: path

* fix: paths

* chore: add comments

* feat: overwrite content file if exists

* chore: remove unused db env related code

* feat: use dotAstroDir for settings

* chore: simplify astro env sync

* feat: use dotAstroDir for preferences

* feat: handle db in integration api

* chore: reorganize

* feat: format

* feat: add test

* Discard changes to examples/basics/astro.config.mjs

* Discard changes to examples/basics/package.json

* Discard changes to pnpm-lock.yaml

* chore: remove test files

* feat: update examples dts

* fix: dts

* chore: changesets

* fix: indentation

* Apply suggestions from code review

Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>

* Apply suggestions from code review

Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>

* chore: format

* Update packages/astro/src/integrations/hooks.ts

* Update .changeset/mean-horses-kiss.md

* feat: remove formatting

* feat: handle fs errors

* feat: remove astro:db special path handling

* feat: add fs error

* Update packages/astro/src/content/types-generator.ts

* Update .changeset/mean-horses-kiss.md

* Update errors-data.ts

* Update .changeset/mean-horses-kiss.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/mean-horses-kiss.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
* feat: add type to infer input type of collection

* refactor:

* feat: generate json schema for content too

* feat: generate a manifest of all the collections

* refactor: unnecessary type

* fix: only add content collections to manifest

* chore: changeset

* fix: generate file URLs

* fix: flag it properly

* fix: save in lower case

* docs: add jsdoc to experimental option

* nit: move function out

* fix: match vscode flag name

* Update packages/astro/src/@types/astro.ts

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update packages/astro/src/@types/astro.ts

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update serious-pumas-run.md

* test: add tests

* Add content layer support

* Apply suggestions from code review

* fix: test

* Update .changeset/serious-pumas-run.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Apply suggestions from code review

* Remove check for json

---------

Co-authored-by: Matt Kane <m@mk.gg>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
* fix: code component was missing support for meta string

Fixed #11604

* Create odd-buttons-pay.md

* <Code>: add reference link for meta prop

* Apply suggestions from code review

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/odd-buttons-pay.md

* Update .changeset/odd-buttons-pay.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
* wip

* done i think

* Add changeset

* Use hook instead

* Reorder hooks [skip ci]

* Update .changeset/eleven-pens-glow.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Fix run

* Fix link

* Add link

Co-authored-by: Sarah Rainsberger <sarah11918@users.noreply.github.com>

* More accurate migration [skip ci]

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Sarah Rainsberger <sarah11918@users.noreply.github.com>
* wip

* done

* Add changeset

* Format

* Update

* Fix houston

* Fix test

* Fix test
@github-actions github-actions bot added pkg: integration Related to any renderer integration (scope) pkg: create-astro Related to the `create-astro` package (scope) labels Aug 14, 2024
@github-actions github-actions bot removed pkg: integration Related to any renderer integration (scope) pkg: create-astro Related to the `create-astro` package (scope) labels Aug 14, 2024
message: (err as Error).message,
...AstroErrorData.ContentLayerWriteError,
});
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Princesseuh Will this surface the actual FS error message to the user?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the cause, yes! It's shown in both our CLI and our overlay, though the styling isn't quite amazing

@ascorbic ascorbic merged commit a79a8b0 into main Aug 14, 2024
12 of 14 checks passed
@ascorbic ascorbic deleted the content-layer branch August 14, 2024 10:49
This was referenced Aug 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: astro Related to the core `astro` package (scope) pkg: example Related to an example package (scope) pr: docs A PR that includes documentation for review semver: minor Change triggers a `minor` release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants