diff --git a/packages/astro/src/content/consts.ts b/packages/astro/src/content/consts.ts index 269b5a27d09d..c1ac5ee70cab 100644 --- a/packages/astro/src/content/consts.ts +++ b/packages/astro/src/content/consts.ts @@ -3,11 +3,21 @@ export const CONTENT_RENDER_FLAG = 'astroRenderContent'; export const CONTENT_FLAG = 'astroContentCollectionEntry'; export const DATA_FLAG = 'astroDataCollectionEntry'; export const CONTENT_IMAGE_FLAG = 'astroContentImageFlag'; +export const CONTENT_MODULE_FLAG = 'astroContentModuleFlag'; export const VIRTUAL_MODULE_ID = 'astro:content'; export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID; export const DATA_STORE_VIRTUAL_ID = 'astro:data-layer-content'; export const RESOLVED_DATA_STORE_VIRTUAL_ID = '\0' + DATA_STORE_VIRTUAL_ID; + +// Used by the content layer to create a virtual module that loads the `modules.mjs`, a file created by the content layer +// to map modules that are renderer at runtime +export const MODULES_MJS_ID = 'astro:content-module-imports'; +export const MODULES_MJS_VIRTUAL_ID = '\0' + MODULES_MJS_ID; + +export const DEFERRED_MODULE = 'astro:content-layer-deferred-module'; + +// Used by the content layer to create a virtual module that loads the `assets.mjs` export const ASSET_IMPORTS_VIRTUAL_ID = 'astro:asset-imports'; export const ASSET_IMPORTS_RESOLVED_STUB_ID = '\0' + ASSET_IMPORTS_VIRTUAL_ID; export const LINKS_PLACEHOLDER = '@@ASTRO-LINKS@@'; @@ -21,11 +31,13 @@ export const CONTENT_FLAGS = [ DATA_FLAG, PROPAGATED_ASSET_FLAG, CONTENT_IMAGE_FLAG, + CONTENT_MODULE_FLAG, ] as const; export const CONTENT_TYPES_FILE = 'types.d.ts'; export const DATA_STORE_FILE = 'data-store.json'; export const ASSET_IMPORTS_FILE = 'assets.mjs'; +export const MODULES_IMPORTS_FILE = 'modules.mjs'; export const CONTENT_LAYER_TYPE = 'experimental_content'; diff --git a/packages/astro/src/content/content-layer.ts b/packages/astro/src/content/content-layer.ts index 8fe0ee3dd899..7d5a71192675 100644 --- a/packages/astro/src/content/content-layer.ts +++ b/packages/astro/src/content/content-layer.ts @@ -5,7 +5,12 @@ import type { FSWatcher } from 'vite'; import xxhash from 'xxhash-wasm'; import type { AstroSettings } from '../@types/astro.js'; import type { Logger } from '../core/logger/core.js'; -import { ASSET_IMPORTS_FILE, CONTENT_LAYER_TYPE, DATA_STORE_FILE } from './consts.js'; +import { + ASSET_IMPORTS_FILE, + CONTENT_LAYER_TYPE, + DATA_STORE_FILE, + MODULES_IMPORTS_FILE, +} from './consts.js'; import type { DataStore } from './data-store.js'; import type { LoaderContext } from './loaders/types.js'; import { getEntryDataAndImages, globalContentConfigObserver, posixRelative } from './utils.js'; @@ -208,6 +213,8 @@ export class ContentLayer { } const assetImportsFile = new URL(ASSET_IMPORTS_FILE, this.#settings.dotAstroDir); await this.#store.writeAssetImports(assetImportsFile); + const modulesImportsFile = new URL(MODULES_IMPORTS_FILE, this.#settings.dotAstroDir); + await this.#store.writeModuleImports(modulesImportsFile); logger.info('Synced content'); } } diff --git a/packages/astro/src/content/data-store.ts b/packages/astro/src/content/data-store.ts index 6660ef4bef2f..4baf71bfbcf3 100644 --- a/packages/astro/src/content/data-store.ts +++ b/packages/astro/src/content/data-store.ts @@ -3,6 +3,8 @@ import type { MarkdownHeading } from '@astrojs/markdown-remark'; import * as devalue from 'devalue'; import { imageSrcToImportId, importIdToSymbolName } from '../assets/utils/resolveImports.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; +import {CONTENT_MODULE_FLAG, DEFERRED_MODULE} from './consts.js'; + const SAVE_DEBOUNCE_MS = 500; export interface RenderedContent { @@ -33,6 +35,10 @@ export interface DataEntry = Record(); + #moduleImports = new Map(); constructor() { this.#collections = new Map(); } + get(collectionName: string, key: string): T | undefined { return this.#collections.get(collectionName)?.get(String(key)); } + entries(collectionName: string): Array<[id: string, T]> { const collection = this.#collections.get(collectionName) ?? new Map(); return [...collection.entries()]; } + values(collectionName: string): Array { const collection = this.#collections.get(collectionName) ?? new Map(); return [...collection.values()]; } + keys(collectionName: string): Array { const collection = this.#collections.get(collectionName) ?? new Map(); return [...collection.keys()]; } + set(collectionName: string, key: string, value: unknown) { const collection = this.#collections.get(collectionName) ?? new Map(); collection.set(String(key), value); this.#collections.set(collectionName, collection); this.#saveToDiskDebounced(); } + delete(collectionName: string, key: string) { const collection = this.#collections.get(collectionName); if (collection) { @@ -81,6 +97,7 @@ export class DataStore { this.#saveToDiskDebounced(); } } + clear(collectionName: string) { this.#collections.delete(collectionName); this.#saveToDiskDebounced(); @@ -122,6 +139,17 @@ export class DataStore { assets.forEach((asset) => this.addAssetImport(asset, filePath)); } + addModuleImport(fileName: string) { + const id = contentModuleToId(fileName); + if (id) { + this.#moduleImports.set(fileName, id); + // We debounce the writes to disk because addAssetImport is called for every image in every file, + // and can be called many times in quick succession by a filesystem watcher. We only want to write + // the file once, after all the imports have been added. + this.#writeModulesImportsDebounced(); + } + } + async writeAssetImports(filePath: PathLike) { this.#assetsFile = filePath; @@ -164,6 +192,45 @@ export default new Map([${exports.join(', ')}]); this.#assetsDirty = false; } + async writeModuleImports(filePath: PathLike) { + this.#modulesFile = filePath; + + if (this.#moduleImports.size === 0) { + try { + await fs.writeFile(filePath, 'export default new Map();'); + } catch (err) { + throw new AstroError({ + message: (err as Error).message, + ...AstroErrorData.ContentLayerWriteError, + }); + } + } + + if (!this.#modulesDirty && existsSync(filePath)) { + return; + } + + // Import the assets, with a symbol name that is unique to the import id. The import + // for each asset is an object with path, format and dimensions. + // We then export them all, mapped by the import id, so we can find them again in the build. + const lines: Array = []; + for (const [fileName, specifier] of this.#moduleImports) { + lines.push(`['${fileName}', () => import('${specifier}')]`); + } + const code = /* js */ ` +export default new Map([\n${lines.join(',\n')}]); + `; + try { + await fs.writeFile(filePath, code); + } catch (err) { + throw new AstroError({ + message: (err as Error).message, + ...AstroErrorData.ContentLayerWriteError, + }); + } + this.#modulesDirty = false; + } + #writeAssetsImportsDebounced() { this.#assetsDirty = true; if (this.#assetsFile) { @@ -177,6 +244,19 @@ export default new Map([${exports.join(', ')}]); } } + #writeModulesImportsDebounced() { + this.#modulesDirty = true; + if (this.#modulesFile) { + if (this.#modulesSaveTimeout) { + clearTimeout(this.#modulesSaveTimeout); + } + this.#modulesSaveTimeout = setTimeout(() => { + this.#modulesSaveTimeout = undefined; + this.writeModuleImports(this.#modulesFile!); + }, SAVE_DEBOUNCE_MS); + } + } + #saveToDiskDebounced() { this.#dirty = true; // Only save to disk if it has already been saved once @@ -198,7 +278,7 @@ export default new Map([${exports.join(', ')}]); entries: () => this.entries(collectionName), values: () => this.values(collectionName), keys: () => this.keys(collectionName), - set: ({ id: key, data, body, filePath, digest, rendered }) => { + set: ({ id: key, data, body, filePath, deferredRender, digest, rendered }) => { if (!key) { throw new Error(`ID must be a non-empty string`); } @@ -230,7 +310,12 @@ export default new Map([${exports.join(', ')}]); if (rendered) { entry.rendered = rendered; } - + if (deferredRender) { + entry.deferredRender = deferredRender; + if (filePath) { + this.addModuleImport(filePath) + } + } this.set(collectionName, id, entry); return true; }, @@ -241,6 +326,8 @@ export default new Map([${exports.join(', ')}]); this.addAssetImport(assetImport, fileName), addAssetImports: (assets: Array, fileName: string) => this.addAssetImports(assets, fileName), + addModuleImport: (fileName: string) => + this.addModuleImport(fileName), }; } /** @@ -275,6 +362,7 @@ export default new Map([${exports.join(', ')}]); }); } } + /** * Attempts to load a DataStore from the virtual module. * This only works in Vite. @@ -329,6 +417,10 @@ export interface ScopedDataStore { digest?: number | string; /** The rendered content, if applicable. */ rendered?: RenderedContent; + /** + * If an entry is a deferred, its rendering phase is delegated to a virtual module during the runtime phase. + */ + deferredRender?: boolean; }) => boolean; values: () => Array; keys: () => Array; @@ -343,6 +435,14 @@ export interface ScopedDataStore { * @internal Adds an asset import to the store. This is used to track image imports for the build. This API is subject to change. */ addAssetImport: (assetImport: string, fileName: string) => void; + /** + * Adds a single asset to the store. This asset will be transformed + * by Vite, and the URL will be available in the final build. + * @param fileName + * @param specifier + * @returns + */ + addModuleImport: (fileName: string) => void; } /** @@ -371,5 +471,13 @@ function dataStoreSingleton() { }; } +// TODO: find a better place to put this image +export function contentModuleToId(fileName: string) { + const params = new URLSearchParams(DEFERRED_MODULE); + params.set('fileName', fileName); + params.set(CONTENT_MODULE_FLAG, 'true'); + return `${DEFERRED_MODULE}?${params.toString()}`; +} + /** @internal */ export const globalDataStore = dataStoreSingleton(); diff --git a/packages/astro/src/content/loaders/glob.ts b/packages/astro/src/content/loaders/glob.ts index b3eeba005410..a6522a56d917 100644 --- a/packages/astro/src/content/loaders/glob.ts +++ b/packages/astro/src/content/loaders/glob.ts @@ -18,6 +18,7 @@ export interface GenerateIdOptions { /** The parsed, unvalidated data of the entry. */ data: Record; } + export interface GlobOptions { /** The glob pattern to match files, relative to the base directory */ pattern: string; @@ -102,6 +103,10 @@ export function glob(globOptions: GlobOptions): Loader { const digest = generateDigest(contents); if (existingEntry && existingEntry.digest === digest && existingEntry.filePath) { + if (existingEntry.deferredRender) { + store.addModuleImport(existingEntry.filePath); + } + if (existingEntry.rendered?.metadata?.imagePaths?.length) { // Add asset imports for existing entries store.addAssetImports( @@ -124,7 +129,9 @@ export function glob(globOptions: GlobOptions): Loader { filePath, }); - if (entryType.getRenderFunction) { + if (entryType.extensions.includes('.mdx')) { + store.set({ id, data: parsedData, body, filePath: relativePath, digest, deferredRender: true }); + } else if (entryType.getRenderFunction) { let render = renderFunctionByContentType.get(entryType); if (!render) { render = await entryType.getRenderFunction(settings); @@ -261,6 +268,7 @@ export function glob(globOptions: GlobOptions): Loader { await syncData(entry, baseUrl, entryType); logger.info(`Reloaded data from ${green(entry)}`); } + watcher.on('change', onChange); watcher.on('add', onChange); diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index f1934d09a63f..df22e6f9d065 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -20,6 +20,7 @@ import { import { CONTENT_LAYER_TYPE, IMAGE_IMPORT_PREFIX } from './consts.js'; import { type DataEntry, globalDataStore } from './data-store.js'; import type { ContentLookupMap } from './utils.js'; + type LazyImport = () => Promise; type GlobResult = Record; type CollectionToEntryMap = Record; @@ -87,6 +88,7 @@ export function createGetCollection({ const data = rawEntry.filePath ? updateImageReferencesInData(rawEntry.data, rawEntry.filePath, imageAssetMap) : rawEntry.data; + const entry = { ...rawEntry, data, @@ -107,6 +109,7 @@ export function createGetCollection({ ); return []; } + const lazyImports = Object.values( type === 'content' ? contentCollectionToEntryMap[collection] @@ -368,7 +371,7 @@ const CONTENT_LAYER_IMAGE_REGEX = /__ASTRO_IMAGE_="([^"]+)"/g; async function updateImageReferencesInBody(html: string, fileName: string) { // @ts-expect-error Virtual module const { default: imageAssetMap } = await import('astro:asset-imports'); - + const imageObjects = new Map(); // @ts-expect-error Virtual module resolved at runtime @@ -442,6 +445,20 @@ export async function renderEntry( return entry.render(); } + if (entry.deferredRender) { + try { + // @ts-expect-error virtual module + const { default: contentModules } = await import('astro:content-module-imports'); + const module = contentModules.get(entry.filePath); + return await module(); + + } catch (e) { + // eslint-disable-next-line + console.error(e); + } + + } + const html = entry?.rendered?.metadata?.imagePaths?.length && entry.filePath ? await updateImageReferencesInBody(entry.rendered.html, entry.filePath) diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 1aef0eb73b41..bc06bbd0b698 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -18,7 +18,7 @@ import { isYAMLException } from '../core/errors/utils.js'; import type { Logger } from '../core/logger/core.js'; import { CONTENT_FLAGS, - CONTENT_LAYER_TYPE, + CONTENT_LAYER_TYPE, CONTENT_MODULE_FLAG, IMAGE_IMPORT_PREFIX, PROPAGATED_ASSET_FLAG, } from './consts.js'; @@ -474,6 +474,11 @@ export function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[numb return flags.has(flag); } +export function isDeferredModule(viteId: string): boolean { + const flags = new URLSearchParams(viteId.split('?')[1] ?? ''); + return flags.has(CONTENT_MODULE_FLAG); +} + async function loadContentConfig({ fs, settings, diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts index 14f459575d72..cb80f9a52638 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -22,6 +22,9 @@ import { DATA_FLAG, DATA_STORE_FILE, DATA_STORE_VIRTUAL_ID, + MODULES_IMPORTS_FILE, + MODULES_MJS_ID, + MODULES_MJS_VIRTUAL_ID, RESOLVED_DATA_STORE_VIRTUAL_ID, RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID, @@ -37,6 +40,7 @@ import { getEntrySlug, getEntryType, getExtGlob, + isDeferredModule, } from './utils.js'; interface AstroContentVirtualModPluginParams { @@ -57,7 +61,7 @@ export function astroContentVirtualModPlugin({ configResolved(config) { IS_DEV = config.mode === 'development'; }, - resolveId(id) { + async resolveId(id) { if (id === VIRTUAL_MODULE_ID) { if (!settings.config.experimental.contentCollectionCache) { return RESOLVED_VIRTUAL_MODULE_ID; @@ -72,6 +76,28 @@ export function astroContentVirtualModPlugin({ if (id === DATA_STORE_VIRTUAL_ID) { return RESOLVED_DATA_STORE_VIRTUAL_ID; } + + if (isDeferredModule(id)) { + const [, query] = id.split('?'); + const params = new URLSearchParams(query); + const fileName = params.get('fileName'); + let importerPath = undefined; + if (fileName && URL.canParse(fileName, settings.config.root.toString())) { + importerPath = fileURLToPath(new URL(fileName, settings.config.root)); + } + if (importerPath) { + return await this.resolve(importerPath); + } + } + + if (id === MODULES_MJS_ID) { + const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir); + if (fs.existsSync(modules)) { + return fileURLToPath(modules); + } + return MODULES_MJS_VIRTUAL_ID; + } + if (id === ASSET_IMPORTS_VIRTUAL_ID) { const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); if (fs.existsSync(assetImportsFile)) { @@ -126,7 +152,20 @@ export function astroContentVirtualModPlugin({ } if (id === ASSET_IMPORTS_RESOLVED_STUB_ID) { - return 'export default new Map()'; + const assetImportsFile = new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir); + if (!fs.existsSync(assetImportsFile)) { + return 'export default new Map()'; + } + return fs.readFileSync(assetImportsFile, 'utf-8'); + + } + + if (id === MODULES_MJS_VIRTUAL_ID) { + const modules = new URL(MODULES_IMPORTS_FILE, settings.dotAstroDir); + if (!fs.existsSync(modules)) { + return 'export default new Map()'; + } + return fs.readFileSync(modules, 'utf-8'); } }, renderChunk(code, chunk) { diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index 263a8a16aabc..abc06592c466 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1490,7 +1490,8 @@ export const ContentLayerWriteError = { export const GetEntryDeprecationError = { name: 'GetEntryDeprecationError', title: 'Invalid use of `getDataEntryById` or `getEntryBySlug` function.', - message: (collection: string, method: string) => `The \`${method}\` function is deprecated and cannot be used to query the "${collection}" collection. Use \`getEntry\` instead.`, + message: (collection: string, method: string) => + `The \`${method}\` function is deprecated and cannot be used to query the "${collection}" collection. Use \`getEntry\` instead.`, hint: 'Use the `getEntry` or `getCollection` functions to query content layer collections.', } satisfies ErrorData; diff --git a/packages/astro/test/content-layer-render.test.js b/packages/astro/test/content-layer-render.test.js new file mode 100644 index 000000000000..3a44794045e9 --- /dev/null +++ b/packages/astro/test/content-layer-render.test.js @@ -0,0 +1,41 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { loadFixture } from './test-utils.js'; + +describe('Content Layer dev', () => { + /** @type {import("./test-utils.js").Fixture} */ + let fixture; + + let devServer; + before(async () => { + fixture = await loadFixture({ root: './fixtures/content-layer-rendering/', cacheDir: "./fixtures/content-layer-rendering/.cache" }); + devServer = await fixture.startDevServer(); + }); + + after(async () => { + devServer?.stop(); + }); + + it('Render an MDX file', async () => { + const html = await fixture.fetch('/reptiles/iguana').then((r) => r.text()); + + assert.match(html, /Iguana/); + assert.match(html, /This is a rendered entry/); + }); +}); + +describe('Content Layer build', () => { + /** @type {import("./test-utils.js").Fixture} */ + let fixture; + before(async () => { + fixture = await loadFixture({ root: './fixtures/content-layer-rendering/', cacheDir: "./fixtures/content-layer-rendering/.cache" }); + await fixture.build(); + }); + + it('Render an MDX file', async () => { + const html = await fixture.readFile('/reptiles/iguana/index.html'); + + assert.match(html, /Iguana/); + assert.match(html, /This is a rendered entry/); + }); +}); diff --git a/packages/astro/test/content-layer.test.js b/packages/astro/test/content-layer.test.js index a4a4f6dcb7cf..199267ce3f57 100644 --- a/packages/astro/test/content-layer.test.js +++ b/packages/astro/test/content-layer.test.js @@ -4,6 +4,7 @@ import { sep } from 'node:path'; import { sep as posixSep } from 'node:path/posix'; import { after, before, describe, it } from 'node:test'; import * as devalue from 'devalue'; + import { loadFixture } from './test-utils.js'; describe('Content Layer', () => { /** @type {import("./test-utils.js").Fixture} */ diff --git a/packages/astro/test/fixtures/content-layer-rendering/.gitignore b/packages/astro/test/fixtures/content-layer-rendering/.gitignore new file mode 100644 index 000000000000..16d3c4dbbfec --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/.gitignore @@ -0,0 +1 @@ +.cache diff --git a/packages/astro/test/fixtures/content-layer-rendering/astro.config.mjs b/packages/astro/test/fixtures/content-layer-rendering/astro.config.mjs new file mode 100644 index 000000000000..d90448042321 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/astro.config.mjs @@ -0,0 +1,14 @@ +import mdx from '@astrojs/mdx'; +import { defineConfig } from 'astro/config'; +import { fileURLToPath } from 'node:url'; + +export default defineConfig({ + integrations: [mdx()], + vite: { + resolve: { + alias: { + '@images': fileURLToPath(new URL('./images', import.meta.url)) + } + }, + } +}); diff --git a/packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/iguana.mdx b/packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/iguana.mdx new file mode 100644 index 000000000000..b34e732f5bb6 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/iguana.mdx @@ -0,0 +1,14 @@ +--- +title: Iguana +description: 'Introduction to Iguana.' +publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' +tags: [cats, felines] +--- + +import H2 from "../src/components/H2.astro"; + +

Iguana

+ +This is a rendered entry + +![file](./shuttle.jpg) diff --git a/packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/shuttle.jpg b/packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/shuttle.jpg new file mode 100644 index 000000000000..80b8ea67b8e4 Binary files /dev/null and b/packages/astro/test/fixtures/content-layer-rendering/content-outside-src-mdx/shuttle.jpg differ diff --git a/packages/astro/test/fixtures/content-layer-rendering/images/atlantis.avif b/packages/astro/test/fixtures/content-layer-rendering/images/atlantis.avif new file mode 100644 index 000000000000..6b9f6d5a5913 Binary files /dev/null and b/packages/astro/test/fixtures/content-layer-rendering/images/atlantis.avif differ diff --git a/packages/astro/test/fixtures/content-layer-rendering/images/launch.webp b/packages/astro/test/fixtures/content-layer-rendering/images/launch.webp new file mode 100644 index 000000000000..144ded978ad2 Binary files /dev/null and b/packages/astro/test/fixtures/content-layer-rendering/images/launch.webp differ diff --git a/packages/astro/test/fixtures/content-layer-rendering/images/shuttle.jpg b/packages/astro/test/fixtures/content-layer-rendering/images/shuttle.jpg new file mode 100644 index 000000000000..80b8ea67b8e4 Binary files /dev/null and b/packages/astro/test/fixtures/content-layer-rendering/images/shuttle.jpg differ diff --git a/packages/astro/test/fixtures/content-layer-rendering/package.json b/packages/astro/test/fixtures/content-layer-rendering/package.json new file mode 100644 index 000000000000..6679b8d6ecfb --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/content-layer-rendering", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*", + "@astrojs/mdx": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/content-layer-rendering/src/components/H2.astro b/packages/astro/test/fixtures/content-layer-rendering/src/components/H2.astro new file mode 100644 index 000000000000..d1ad359c2ee1 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/src/components/H2.astro @@ -0,0 +1,4 @@ +--- +--- + +

diff --git a/packages/astro/test/fixtures/content-layer-rendering/src/components/H3.astro b/packages/astro/test/fixtures/content-layer-rendering/src/components/H3.astro new file mode 100644 index 000000000000..fa476e929eae --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/src/components/H3.astro @@ -0,0 +1,2 @@ + +

\ No newline at end of file diff --git a/packages/astro/test/fixtures/content-layer-rendering/src/components/LayoutProp.astro b/packages/astro/test/fixtures/content-layer-rendering/src/components/LayoutProp.astro new file mode 100644 index 000000000000..a2954162a396 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/src/components/LayoutProp.astro @@ -0,0 +1,39 @@ +--- +import { CollectionEntry, getCollection } from 'astro:content'; +import H3 from './H3.astro' +// Test for recursive `getCollection()` calls +const blog = await getCollection('blog'); + +type Props = { + content: CollectionEntry<'blog'>['data']; +}; + +const { + content: { title }, +} = Astro.props; +--- + + + + + + + + With Layout Prop + + +

{title}

+

H3 inserted in the layout

+ + + + + diff --git a/packages/astro/test/fixtures/content-layer-rendering/src/components/WithScripts.astro b/packages/astro/test/fixtures/content-layer-rendering/src/components/WithScripts.astro new file mode 100644 index 000000000000..e37694eaf1f0 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/src/components/WithScripts.astro @@ -0,0 +1,11 @@ +

Hoisted script didn't update me :(

+ +

Inline script didn't update me :(

+ + + + diff --git a/packages/astro/test/fixtures/content-layer-rendering/src/content/config.ts b/packages/astro/test/fixtures/content-layer-rendering/src/content/config.ts new file mode 100644 index 000000000000..54c0fb5085bd --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/src/content/config.ts @@ -0,0 +1,19 @@ +import { defineCollection, z, reference } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const reptiles = defineCollection({ + type: 'experimental_content', + loader: glob({ + pattern: '*.mdx', + base: new URL('../../content-outside-src-mdx', import.meta.url), + }), + schema: () => + z.object({ + title: z.string(), + description: z.string(), + publishedDate: z.coerce.date(), + tags: z.array(z.string()), + }), +}); + +export const collections = { reptiles }; diff --git a/packages/astro/test/fixtures/content-layer-rendering/src/pages/reptiles/[slug].astro b/packages/astro/test/fixtures/content-layer-rendering/src/pages/reptiles/[slug].astro new file mode 100644 index 000000000000..526805e09947 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-rendering/src/pages/reptiles/[slug].astro @@ -0,0 +1,25 @@ +--- +import type { GetStaticPaths } from "astro"; +import { getCollection, render } from "astro:content" +import { Image } from "astro:assets" + +export const getStaticPaths = (async () => { + const collection = await getCollection("reptiles"); + if(!collection) return [] + return collection.map((reptile) => ({ + params: { + slug: `${reptile.id}` + }, + props: { + reptile + } + })); +}) satisfies GetStaticPaths; + +const { reptile } = Astro.props as any +const { Content } = await render(reptile) + +--- + +

{reptile.data.title}

+ diff --git a/packages/astro/test/fixtures/content-layer/src/content/config.ts b/packages/astro/test/fixtures/content-layer/src/content/config.ts index 116e5d995bf3..6fd28487c21a 100644 --- a/packages/astro/test/fixtures/content-layer/src/content/config.ts +++ b/packages/astro/test/fixtures/content-layer/src/content/config.ts @@ -1,7 +1,6 @@ import { defineCollection, z, reference } from 'astro:content'; import { file, glob } from 'astro/loaders'; import { loader } from '../loaders/post-loader.js'; -import { fileURLToPath } from 'node:url'; const blog = defineCollection({ type: 'experimental_content', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f1ac1b7e1c5..7aa98cccef85 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2724,6 +2724,15 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/content-layer-rendering: + dependencies: + '@astrojs/mdx': + specifier: workspace:* + version: link:../../../../integrations/mdx + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/content-mixed-errors: dependencies: astro: