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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
6c80a1e
Empty commit
ascorbic Jun 27, 2024
0f807d3
Changeset
ascorbic Jun 26, 2024
9561a07
feat: add Content Layer loader (#11334)
ascorbic Jun 28, 2024
a66df94
fix: sync content layer in dev (#11365)
ascorbic Jun 28, 2024
f9a3998
feat: add typegen for loaders (#11358)
ascorbic Jun 28, 2024
322f8cc
fix: watch for content layer changes (#11371)
ascorbic Jun 28, 2024
4dc8c01
Merge branch 'main' into content-layer
ascorbic Jun 28, 2024
e4e670f
Merge branch 'main' into content-layer
ascorbic Jul 1, 2024
a957a74
feat: adds simple loader (#11386)
ascorbic Jul 1, 2024
8662f8b
Merge branch 'main' into content-layer
ascorbic Jul 2, 2024
608a814
Merge branch 'main' into content-layer
ascorbic Jul 8, 2024
6d60438
Reinstall vitest
ascorbic Jul 8, 2024
eb28c15
feat: add glob loader (#11398)
ascorbic Jul 10, 2024
456b1c3
Merge branch 'main' into content-layer
ascorbic Jul 12, 2024
e70f252
Merge branch 'main' into content-layer
ascorbic Jul 17, 2024
d19b15e
Merge branch 'main' into content-layer
ascorbic Jul 17, 2024
4149aaf
Merge branch 'main' into content-layer
ascorbic Jul 18, 2024
da24013
Merge branch 'main' into content-layer
ascorbic Jul 19, 2024
15b74c4
Merge branch 'main' into content-layer
ascorbic Jul 22, 2024
f7562f6
feat: add markdown rendering to content layer (#11440)
ascorbic Jul 22, 2024
4230c11
chore: fix content layer types (#11527)
ascorbic Jul 23, 2024
7fe3e85
Merge branch 'main' into content-layer
ascorbic Jul 23, 2024
84b0608
Lockfile
ascorbic Jul 23, 2024
f093238
Clean content layer with `--force` (#11541)
ascorbic Jul 24, 2024
bb36a2d
Fixes to content layer render types (#11558)
ascorbic Jul 26, 2024
69ea091
Merge branch 'main' into content-layer
ascorbic Jul 26, 2024
e553a4c
Lockfile
ascorbic Jul 26, 2024
0f85d69
Merge branch 'main' into content-layer
ascorbic Jul 26, 2024
73095cd
Merge branch 'main' into content-layer
ascorbic Jul 29, 2024
05f9df7
feat: use devalue to serialize content layer data (#11562)
ascorbic Jul 30, 2024
e8bd320
Merge branch 'main' into content-layer
ascorbic Jul 30, 2024
c366ae4
Support --force flag in sync and dev (#11581)
ascorbic Jul 31, 2024
2abc38b
Separate render function and merge content layer types (#11579)
ascorbic Aug 1, 2024
aecae03
fix: clear content layer cache if config has changed (#11591)
ascorbic Aug 1, 2024
ed78acf
Merge branch 'main' into content-layer
ascorbic Aug 5, 2024
1a79a50
fix: skip glob files in content dir (#11622)
ascorbic Aug 5, 2024
aa91648
Refactor content layer into shared instance (#11625)
ascorbic Aug 5, 2024
16dd91c
Merge branch 'main' into content-layer
ascorbic Aug 5, 2024
fb3efef
fix: support filters in content layer getCollection (#11631)
ascorbic Aug 6, 2024
ede3610
Throw when using deprecated getEntryByX functions with content layer …
ascorbic Aug 7, 2024
59992a6
Merge branch 'main' into content-layer
ascorbic Aug 7, 2024
e265805
Updates to content layer types and jsdocs (#11643)
ascorbic Aug 7, 2024
bd2684e
Add hot key to reload content layer (#11626)
ascorbic Aug 7, 2024
90168cc
Merge branch 'main' into content-layer
ascorbic Aug 7, 2024
aa2e2c5
feat: handle simple mdx rendering (#11633)
ematipico Aug 8, 2024
6723cd6
Merge remote-tracking branch 'origin/main' into content-layer
ematipico Aug 8, 2024
9aea0b4
run formatter
ematipico Aug 8, 2024
5f16f08
update lock file
ematipico Aug 8, 2024
8d6c27f
Lint
ascorbic Aug 8, 2024
598eb59
Add experimental content layer flag (#11652)
ascorbic Aug 9, 2024
acb6a7a
Merge branch 'main' into content-layer
ascorbic Aug 9, 2024
d13fb8c
Normalize render function return value (#11663)
ascorbic Aug 9, 2024
134fe2b
Add markdoc support to content layer (#11664)
ascorbic Aug 9, 2024
c515a85
Update benchmarks
ascorbic Aug 10, 2024
52e6c70
Merge remote-tracking branch 'origin/main' into content-layer
ematipico Aug 12, 2024
dc3c50a
update lock file
ematipico Aug 12, 2024
ad6eb00
Merge branch 'main' into content-layer
ascorbic Aug 12, 2024
9e05314
Update content layer flag docs (#11682)
ascorbic Aug 13, 2024
f1d8538
Add changeset for content layer experimental release (#11644)
ascorbic Aug 13, 2024
29d45cc
Merge branch 'main' into content-layer
ascorbic Aug 13, 2024
abf8f17
Merge branch 'main' into content-layer
ascorbic Aug 14, 2024
e801a36
Merge branch 'main' into content-layer
ascorbic Aug 14, 2024
e40043f
feat: injectTypes (#11551)
florian-lefebvre Aug 14, 2024
bad2580
Add file generation and flag for content intellisense (#11639)
Princesseuh Aug 14, 2024
39a1093
nit: use same filesystem error as injectTypes
Princesseuh Aug 14, 2024
50366eb
fix: code component was missing support for meta string (#11605)
jcayzac Aug 14, 2024
d1486f6
Deprecates exporting prerender with dynamic values (#11657)
bluwy Aug 14, 2024
7c25ddc
Use node parseArgs instead of yargs-parser and arg (#11645)
bluwy Aug 14, 2024
f874d55
[ci] format
ematipico Aug 14, 2024
c801745
resolve conflict
ematipico Aug 14, 2024
fdd7b83
Merge remote-tracking branch 'origin/main' into content-layer
ematipico Aug 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/fresh-fans-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@astrojs/db': minor
---

Changes how type generation works

The generated `.d.ts` file is now at a new location:

```diff
- .astro/db-types.d.ts
+ .astro/integrations/astro_db/db.d.ts
```

The following line can now be removed from `src/env.d.ts`:

```diff
- /// <reference path="../.astro/db-types.d.ts" />
```
35 changes: 35 additions & 0 deletions .changeset/mean-horses-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
'astro': minor
---

Adds a new [`injectTypes()` utility](https://docs.astro.build/en/reference/integrations-reference/#injecttypes-options) to the Integration API and refactors how type generation works

Use `injectTypes()` in the `astro:config:done` hook to inject types into your user's project by adding a new a `*.d.ts` file.

The `filename` property will be used to generate a file at `/.astro/integrations/<normalized_integration_name>/<normalized_filename>.d.ts` and must end with `".d.ts"`.

The `content` property will create the body of the file, and must be valid TypeScript.

Additionally, `injectTypes()` returns a URL to the normalized path so you can overwrite its content later on, or manipulate it in any way you want.

```js
// my-integration/index.js
export default {
name: 'my-integration',
'astro:config:done': ({ injectTypes }) => {
injectTypes({
filename: "types.d.ts",
content: "declare module 'virtual:my-integration' {}"
})
}
};
```

Codegen has been refactored. Although `src/env.d.ts` will continue to work as is, we recommend you update it:

```diff
- /// <reference types="astro/client" />
+ /// <reference path="../.astro/types.d.ts" />
- /// <reference path="../.astro/env.d.ts" />
- /// <reference path="../.astro/actions.d.ts" />
```
21 changes: 21 additions & 0 deletions .changeset/serious-pumas-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'astro': minor
---

Adds support for Intellisense features (e.g. code completion, quick hints) for your content collection entries in compatible editors under the `experimental.contentIntellisense` flag.

```js
import { defineConfig } from 'astro';

export default defineConfig({
experimental: {
contentIntellisense: true
}
})
```

When enabled, this feature will generate and add JSON schemas to the `.astro` directory in your project. These files can be used by the Astro language server to provide Intellisense inside content files (`.md`, `.mdx`, `.mdoc`).

Note that at this time, this also require enabling the `astro.content-intellisense` option in your editor, or passing the `contentIntellisense: true` initialization parameter to the Astro language server for editors using it directly.

See the [experimental content Intellisense docs](https://docs.astro.build/en/reference/configuration-reference/#experimentalcontentintellisense) for more information updates as this feature develops.
107 changes: 107 additions & 0 deletions .changeset/smooth-chicken-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
'astro': minor
---

Adds experimental support for the Content Layer API.

The new Content Layer API builds upon content collections, taking them beyond local files in `src/content/` and allowing you to fetch content from anywhere, including remote APIs. These new collections work alongside your existing content collections, and you can migrate them to the new API at your own pace. There are significant improvements to performance with large collections of local files.

### Getting started

To try out the new Content Layer API, enable it in your Astro config:

```js
import { defineConfig } from 'astro';

export default defineConfig({
experimental: {
contentLayer: true
}
})
```

You can then create collections in your `src/content/config.ts` using the Content Layer API.

### Loading your content

The core of the new Content Layer API is the loader, a function that fetches content from a source and caches it in a local data store. Astro 4.14 ships with built-in `glob()` and `file()` loaders to handle your local Markdown, MDX, Markdoc, and JSON files:

```ts {3,7}
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
// 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(),
publishDate: z.coerce.date(),
})
});

export const collections = { blog };
```

You can then query using the existing content collections functions, and enjoy a simplified `render()` function to display your content:

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

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

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

<Content />
```

### Creating a loader

You're not restricted to the built-in loaders – we hope you'll try building your own. You can fetch content from anywhere and return an array of entries:

```ts
// src/content/config.ts
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 to validate the data and make it type-safe for users
// 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. It also allows a loader to define its own schema, including generating it dynamically based on the source API. See the [the Content Layer API RFC](https://github.com/withastro/roadmap/blob/content-layer/proposals/0047-content-layer.md#loaders) for more details.

### Sharing your loaders

Loaders are better when they're shared. You can create a package that exports a loader and publish it to npm, and then anyone can use it on their site. We're excited to see what the community comes up with! To get started, [take a look at some examples](https://github.com/ascorbic/astro-loaders/). Here's how to load content using an RSS/Atom feed loader:

```ts
// src/content/config.ts
import { defineCollection } from "astro:content";
import { feedLoader } from "@ascorbic/feed-loader";

const podcasts = defineCollection({
loader: feedLoader({
url: "https://feeds.99percentinvisible.org/99percentinvisible",
}),
});

export const collections = { podcasts };
```

### Learn more

To find out more about using the Content Layer API, check out [the Content Layer RFC](https://github.com/withastro/roadmap/blob/content-layer/proposals/0047-content-layer.md) and [share your feedback](https://github.com/withastro/roadmap/pull/982).
2 changes: 1 addition & 1 deletion benchmark/bench/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function run(projectDir, outputFile) {
const outputFilePath = fileURLToPath(outputFile);

console.log('Building and benchmarking...');
await execaCommand(`node --expose-gc --max_old_space_size=256 ${astroBin} build`, {
await execaCommand(`node --expose-gc --max_old_space_size=10000 ${astroBin} build --silent`, {
cwd: root,
stdio: 'inherit',
env: {
Expand Down
Binary file added benchmark/make-project/image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions benchmark/make-project/markdown-cc1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import fs from 'node:fs/promises';
import { loremIpsumMd } from './_util.js';

/**
* @param {URL} projectDir
*/
export async function run(projectDir) {
await fs.rm(projectDir, { recursive: true, force: true });
await fs.mkdir(new URL('./src/pages/blog', projectDir), { recursive: true });
await fs.mkdir(new URL('./src/content/blog', projectDir), { recursive: true });
await fs.copyFile(new URL('./image.jpg', import.meta.url), new URL('./src/image.jpg', projectDir));

const promises = [];


for (let i = 0; i < 10000; i++) {
const content = `\
# Article ${i}

${loremIpsumMd}

![image ${i}](../../image.jpg)


`;
promises.push(
fs.writeFile(new URL(`./src/content/blog/article-${i}.md`, projectDir), content, 'utf-8')
);
}


await fs.writeFile(
new URL(`./src/pages/blog/[...slug].astro`, projectDir),
`\
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug }, props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />
`,
'utf-8'
);

await Promise.all(promises);

await fs.writeFile(
new URL('./astro.config.js', projectDir),
`\
import { defineConfig } from 'astro/config';

export default defineConfig({
});`,
'utf-8'
);
}
80 changes: 80 additions & 0 deletions benchmark/make-project/markdown-cc2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import fs from 'node:fs/promises';
import { loremIpsumMd } from './_util.js';

/**
* @param {URL} projectDir
*/
export async function run(projectDir) {
await fs.rm(projectDir, { recursive: true, force: true });
await fs.mkdir(new URL('./src/pages/blog', projectDir), { recursive: true });
await fs.mkdir(new URL('./data/blog', projectDir), { recursive: true });
await fs.mkdir(new URL('./src/content', projectDir), { recursive: true });
await fs.copyFile(new URL('./image.jpg', import.meta.url), new URL('./image.jpg', projectDir));

const promises = [];

for (let i = 0; i < 10000; i++) {
const content = `\
# Article ${i}

${loremIpsumMd}

![image ${i}](../../image.jpg)

`;
promises.push(
fs.writeFile(new URL(`./data/blog/article-${i}.md`, projectDir), content, 'utf-8')
);
}

await fs.writeFile(
new URL(`./src/content/config.ts`, projectDir),
/*ts */ `
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
loader: glob({ pattern: '*', base: './data/blog' }),
});

export const collections = { blog }

`
);

await fs.writeFile(
new URL(`./src/pages/blog/[...slug].astro`, projectDir),
`\
---
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.id }, props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await render(entry);

---
<h1>{entry.data.title}</h1>
<Content />
`,
'utf-8'
);

await Promise.all(promises);

await fs.writeFile(
new URL('./astro.config.js', projectDir),
`\
import { defineConfig } from 'astro/config';

export default defineConfig({
experimental: {
contentLayer: true
}
});`,
'utf-8'
);
}
Loading
Loading