diff --git a/.changeset/early-melons-thank.md b/.changeset/early-melons-thank.md new file mode 100644 index 000000000000..c12a20de413c --- /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 21af34e1d280..484fe6a8d36f 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 b7f5abb07cec..7996f1161d6d 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 000000000000..d69e57975a64 --- /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 000000000000..288dc2562432 --- /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 000000000000..0ecb2d8587b0 --- /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 000000000000..dcded99ccf63 --- /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 000000000000..1adee317378b --- /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 000000000000..601b05ccaa79 --- /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 000000000000..447b2cf07a5d --- /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 +--- + + + + home +

Latest Blog

+

{latestBlog.data.title}

+

posted: {latestBlog.data.date.toLocaleString()}

+
+

Older blogs

+ { + blogs.map((b) => ( + <> +

{b.data.title}

+

posted: {b.data.date.toLocaleString()}

+ + )) + } + + 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 000000000000..6b24144d999e --- /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 +--- + + + + other page +

Latest Blog

+

{latestBlog.data.title}

+

posted: {latestBlog.data.date.toLocaleString()}

+
+

Older blogs

+ { + blogs.map((b) => ( + <> +

{b.data.title}

+

posted: {b.data.date.toLocaleString()}

+ + )) + } + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40e008fd35a8..91b6cf92ca3e 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: