diff options
12 files changed, 139 insertions, 3 deletions
diff --git a/.changeset/early-melons-thank.md b/.changeset/early-melons-thank.md new file mode 100644 index 000000000..c12a20de4 --- /dev/null +++ b/.changeset/early-melons-thank.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Updates `getCollection()` to always return a cloned array diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index 21af34e1d..484fe6a8d 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -79,8 +79,7 @@ export function createGetCollection({ // Cache `getCollection()` calls in production only // prevents stale cache in development if (!import.meta.env?.DEV && cacheEntriesByCollection.has(collection)) { - // Always return a new instance so consumers can safely mutate it - entries = [...cacheEntriesByCollection.get(collection)!]; + entries = cacheEntriesByCollection.get(collection)!; } else { const limit = pLimit(10); entries = await Promise.all( @@ -115,7 +114,9 @@ export function createGetCollection({ if (typeof filter === 'function') { return entries.filter(filter); } else { - return entries; + // Clone the array so users can safely mutate it. + // slice() is faster than ...spread for large arrays. + return entries.slice(); } }; } diff --git a/packages/astro/test/content-collections.test.js b/packages/astro/test/content-collections.test.js index b7f5abb07..7996f1161 100644 --- a/packages/astro/test/content-collections.test.js +++ b/packages/astro/test/content-collections.test.js @@ -370,4 +370,26 @@ describe('Content Collections', () => { assert.equal($('script').attr('src').startsWith('/docs'), true); }); }); + + describe('Mutation', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/content-collections-mutation/', + }); + await fixture.build(); + }); + + it('Does not mutate cached collection', async () => { + const html = await fixture.readFile('/index.html'); + const index = cheerio.load(html)('h2:first').text(); + const html2 = await fixture.readFile('/another_page/index.html'); + const anotherPage = cheerio.load(html2)('h2:first').text(); + + assert.equal(index, anotherPage); + }); + + }); + }); diff --git a/packages/astro/test/fixtures/content-collections-mutation/astro.config.mjs b/packages/astro/test/fixtures/content-collections-mutation/astro.config.mjs new file mode 100644 index 000000000..d69e57975 --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/astro.config.mjs @@ -0,0 +1,6 @@ +import mdx from '@astrojs/mdx'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + integrations: [mdx()], +}); diff --git a/packages/astro/test/fixtures/content-collections-mutation/package.json b/packages/astro/test/fixtures/content-collections-mutation/package.json new file mode 100644 index 000000000..288dc2562 --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/content-collections-mutation", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/mdx": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/first.md b/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/first.md new file mode 100644 index 000000000..0ecb2d858 --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/first.md @@ -0,0 +1,6 @@ +--- +title: "First Blog" +date: 2024-04-05 +--- + +First blog content. diff --git a/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/second.md b/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/second.md new file mode 100644 index 000000000..dcded99cc --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/second.md @@ -0,0 +1,6 @@ +--- +title: "Second Blog" +date: 2024-04-06 +--- + +Second blog content. diff --git a/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/third.md b/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/third.md new file mode 100644 index 000000000..1adee3173 --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/src/content/blog/third.md @@ -0,0 +1,6 @@ +--- +title: "Third Blog" +date: 2024-04-07 +--- + +Third blog content. diff --git a/packages/astro/test/fixtures/content-collections-mutation/src/content/config.ts b/packages/astro/test/fixtures/content-collections-mutation/src/content/config.ts new file mode 100644 index 000000000..601b05cca --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/src/content/config.ts @@ -0,0 +1,16 @@ +// 1. Import utilities from `astro:content` +import { z, defineCollection } from "astro:content"; + +// 2. Define a `type` and `schema` for each collection +const blogCollection = defineCollection({ + type: "content", // v2.5.0 and later + schema: z.object({ + title: z.string(), + date: z.date(), + }), +}); + +// 3. Export a single `collections` object to register your collection(s) +export const collections = { + blog: blogCollection, +}; diff --git a/packages/astro/test/fixtures/content-collections-mutation/src/pages/another_page.astro b/packages/astro/test/fixtures/content-collections-mutation/src/pages/another_page.astro new file mode 100644 index 000000000..447b2cf07 --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/src/pages/another_page.astro @@ -0,0 +1,25 @@ +--- +import { getCollection } from "astro:content"; +const blogs = await getCollection("blog"); +blogs.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()); // sort by date most recent first +const latestBlog = blogs.splice(0, 1)[0]; // modifies the collection +--- + +<html> + <body> + <a href="/">home</a> + <h1>Latest Blog</h1> + <h2>{latestBlog.data.title}</h2> + <p>posted: {latestBlog.data.date.toLocaleString()}</p> + <br /> + <h2>Older blogs</h2> + { + blogs.map((b) => ( + <> + <h3>{b.data.title}</h3> + <p>posted: {b.data.date.toLocaleString()}</p> + </> + )) + } + </body> +</html> diff --git a/packages/astro/test/fixtures/content-collections-mutation/src/pages/index.astro b/packages/astro/test/fixtures/content-collections-mutation/src/pages/index.astro new file mode 100644 index 000000000..6b24144d9 --- /dev/null +++ b/packages/astro/test/fixtures/content-collections-mutation/src/pages/index.astro @@ -0,0 +1,25 @@ +--- +import { getCollection } from "astro:content"; +const blogs = await getCollection("blog"); +blogs.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()); // sort by date most recent first +const latestBlog = blogs.splice(0, 1)[0]; // modifies the collection +--- + +<html> + <body> + <a href="/another_page">other page</a> + <h1>Latest Blog</h1> + <h2>{latestBlog.data.title}</h2> + <p>posted: {latestBlog.data.date.toLocaleString()}</p> + <br /> + <h2>Older blogs</h2> + { + blogs.map((b) => ( + <> + <h3>{b.data.title}</h3> + <p>posted: {b.data.date.toLocaleString()}</p> + </> + )) + } + </body> +</html> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40e008fd3..91b6cf92c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2599,6 +2599,15 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/content-collections-mutation: + dependencies: + '@astrojs/mdx': + specifier: workspace:* + version: link:../../../../integrations/mdx + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/content-collections-with-config-mjs: dependencies: astro: |