Skip to content

Commit

Permalink
Separate render function and merge content layer types (#11579)
Browse files Browse the repository at this point in the history
* Separate render function and merge content layer types

* Changes from review
  • Loading branch information
ascorbic authored Aug 1, 2024
1 parent c366ae4 commit 2abc38b
Show file tree
Hide file tree
Showing 14 changed files with 63 additions and 45 deletions.
2 changes: 2 additions & 0 deletions packages/astro/src/content/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ 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 CONTENT_LAYER_TYPE = 'experimental_content';
4 changes: 2 additions & 2 deletions packages/astro/src/content/data-store.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { promises as fs, type PathLike, existsSync } from 'fs';
import { imageSrcToImportId, importIdToSymbolName } from '../assets/utils/resolveImports.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
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';
const SAVE_DEBOUNCE_MS = 500;

export interface RenderedContent {
Expand Down
33 changes: 25 additions & 8 deletions packages/astro/src/content/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import pLimit from 'p-limit';
import { ZodIssueCode, z } from 'zod';
import type { GetImageResult, ImageMetadata } from '../@types/astro.js';
import { imageSrcToImportId } from '../assets/utils/resolveImports.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { AstroError, AstroErrorData, AstroUserError } from '../core/errors/index.js';
import { prependForwardSlash } from '../core/path.js';
import {
type AstroComponentFactory,
Expand All @@ -17,7 +17,7 @@ import {
render as serverRender,
unescapeHTML,
} from '../runtime/server/index.js';
import { IMAGE_IMPORT_PREFIX } from './consts.js';
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<any>;
Expand All @@ -26,6 +26,16 @@ type CollectionToEntryMap = Record<string, GlobResult>;
type GetEntryImport = (collection: string, lookupId: string) => Promise<LazyImport>;

export function defineCollection(config: any) {
if (
('loader' in config && config.type !== CONTENT_LAYER_TYPE) ||
(config.type === CONTENT_LAYER_TYPE && !('loader' in config))
) {
// TODO: when this moves out of experimental, we will set the type automatically
throw new AstroUserError(
'Collections that use the content layer must have a `loader` defined and `type` set to `experimental_content`',
"Check your collection definitions in `src/content/config.*`.'"
);
}
if (!config.type) config.type = 'content';
return config;
}
Expand Down Expand Up @@ -62,7 +72,7 @@ export function createGetCollection({
}) {
return async function getCollection(collection: string, filter?: (entry: any) => unknown) {
const store = await globalDataStore.get();
let type: 'content' | 'data' | 'experimental_data' | 'experimental_content';
let type: 'content' | 'data';
if (collection in contentCollectionToEntryMap) {
type = 'content';
} else if (collection in dataCollectionToEntryMap) {
Expand All @@ -80,7 +90,6 @@ export function createGetCollection({
...entry,
data,
collection,
render: () => renderEntry(entry),
});
}
return result;
Expand Down Expand Up @@ -167,7 +176,6 @@ export function createGetEntryBySlug({
return {
...entry,
collection,
render: () => renderEntry(entry),
};
}
if (!collectionNames.has(collection)) {
Expand Down Expand Up @@ -201,7 +209,10 @@ export function createGetEntryBySlug({
export function createGetDataEntryById({
getEntryImport,
collectionNames,
}: { getEntryImport: GetEntryImport; collectionNames: Set<string> }) {
}: {
getEntryImport: GetEntryImport;
collectionNames: Set<string>;
}) {
return async function getDataEntryById(collection: string, id: string) {
const store = await globalDataStore.get();

Expand Down Expand Up @@ -307,7 +318,6 @@ export function createGetEntry({
return {
...entry,
collection,
render: () => renderEntry(entry),
} as DataEntryResult | ContentEntryResult;
}

Expand Down Expand Up @@ -433,7 +443,14 @@ function updateImageReferencesInData<T extends Record<string, unknown>>(
});
}

async function renderEntry(entry?: DataEntry) {
export async function renderEntry(
entry: DataEntry | { render: () => Promise<{ Content: AstroComponentFactory }> }
) {
if (entry && 'render' in entry) {
// This is an old content collection entry, so we use its render method
return entry.render();
}

const html =
entry?.rendered?.metadata?.imagePaths?.length && entry.filePath
? await updateImageReferencesInBody(entry.rendered.html, entry.filePath)
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/content/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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, DATA_STORE_FILE } from './consts.js';
import { ASSET_IMPORTS_FILE, CONTENT_LAYER_TYPE, DATA_STORE_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';
Expand Down Expand Up @@ -49,7 +49,7 @@ export async function syncContentLayer({

await Promise.all(
Object.entries(contentConfig.config.collections).map(async ([name, collection]) => {
if (collection.type !== 'experimental_data' && collection.type !== 'experimental_content') {
if (collection.type !== CONTENT_LAYER_TYPE) {
return;
}

Expand Down
17 changes: 7 additions & 10 deletions packages/astro/src/content/types-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AstroError } from '../core/errors/errors.js';
import { AstroErrorData } from '../core/errors/index.js';
import type { Logger } from '../core/logger/core.js';
import { isRelativePath } from '../core/path.js';
import { CONTENT_LAYER_TYPE } from './consts.js';
import { CONTENT_TYPES_FILE, VIRTUAL_MODULE_ID } from './consts.js';
import {
type ContentConfig,
Expand Down Expand Up @@ -45,7 +46,7 @@ type CollectionEntryMap = {
entries: Record<string, ContentEntryMetadata>;
}
| {
type: 'data' | 'experimental_content' | 'experimental_data';
type: 'data' | typeof CONTENT_LAYER_TYPE;
entries: Record<string, DataEntryMetadata>;
};
};
Expand Down Expand Up @@ -366,7 +367,7 @@ async function typeForCollection<T extends keyof ContentConfig['collections']>(
}

if (
(collection?.type === 'experimental_data' || collection?.type === 'experimental_content') &&
collection?.type === CONTENT_LAYER_TYPE &&
typeof collection.loader === 'object' &&
collection.loader.schema
) {
Expand Down Expand Up @@ -426,8 +427,7 @@ async function writeContentFiles({
if (
collectionConfig?.type &&
collection.type !== 'unknown' &&
collectionConfig.type !== 'experimental_data' &&
collectionConfig.type !== 'experimental_content' &&
collectionConfig.type !== CONTENT_LAYER_TYPE &&
collection.type !== collectionConfig.type
) {
viteServer.hot.send({
Expand All @@ -450,7 +450,7 @@ async function writeContentFiles({
});
return;
}
const resolvedType: 'content' | 'data' | 'experimental_data' | 'experimental_content' =
const resolvedType =
collection.type === 'unknown'
? // Add empty / unknown collections to the data type map by default
// This ensures `getCollection('empty-collection')` doesn't raise a type error
Expand All @@ -477,11 +477,8 @@ async function writeContentFiles({
}
contentTypesStr += `};\n`;
break;
case 'experimental_data':
dataTypesStr += `${collectionKey}: Record<string, {\n id: string;\n collection: ${collectionKey};\n data: ${dataType};\n}>;\n`;
break;
case 'experimental_content':
dataTypesStr += `${collectionKey}: Record<string, {\n id: string;\n collection: ${collectionKey};\n data: ${dataType};\n render(): Promise<ContentLayerRenderer>;\n rendered?: RenderedContent \n}>;\n`;
case CONTENT_LAYER_TYPE:
dataTypesStr += `${collectionKey}: Record<string, {\n id: string;\n collection: ${collectionKey};\n data: ${dataType};\n rendered?: RenderedContent \n}>;\n`;
break;
case 'data':
if (collectionEntryKeys.length === 0) {
Expand Down
20 changes: 9 additions & 11 deletions packages/astro/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import type {
import { AstroError, AstroErrorData, MarkdownError, errorMap } from '../core/errors/index.js';
import { isYAMLException } from '../core/errors/utils.js';
import type { Logger } from '../core/logger/core.js';
import { CONTENT_FLAGS, IMAGE_IMPORT_PREFIX, PROPAGATED_ASSET_FLAG } from './consts.js';
import {
CONTENT_FLAGS,
CONTENT_LAYER_TYPE,
IMAGE_IMPORT_PREFIX,
PROPAGATED_ASSET_FLAG,
} from './consts.js';
import { createImage } from './runtime-assets.js';
/**
* Amap from a collection + slug to the local file path.
Expand All @@ -36,7 +41,7 @@ export const collectionConfigParser = z.union([
schema: z.any().optional(),
}),
z.object({
type: z.union([z.literal('experimental_data'), z.literal('experimental_content')]),
type: z.literal(CONTENT_LAYER_TYPE),
schema: z.any().optional(),
loader: z.union([
z.function().returns(
Expand Down Expand Up @@ -135,11 +140,7 @@ export async function getEntryDataAndImages<
pluginContext?: PluginContext
): Promise<{ data: TOutputData; imageImports: Array<string> }> {
let data: TOutputData;
if (
collectionConfig.type === 'data' ||
collectionConfig.type === 'experimental_data' ||
collectionConfig.type === 'experimental_content'
) {
if (collectionConfig.type === 'data' || collectionConfig.type === CONTENT_LAYER_TYPE) {
data = entry.unvalidatedData as TOutputData;
} else {
const { slug, ...unvalidatedData } = entry.unvalidatedData;
Expand All @@ -155,10 +156,7 @@ export async function getEntryDataAndImages<
schema = schema({
image: createImage(pluginContext, shouldEmitFile, entry._internal.filePath),
});
} else if (
collectionConfig.type === 'experimental_data' ||
collectionConfig.type === 'experimental_content'
) {
} else if (collectionConfig.type === CONTENT_LAYER_TYPE) {
schema = schema({
image: () =>
z.string().transform((val) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { performance } from 'perf_hooks';
import { gt, major, minor, patch } from 'semver';
import type * as vite from 'vite';
import type { AstroInlineConfig } from '../../@types/astro.js';
import { DATA_STORE_FILE } from '../../content/consts.js';
import { DataStore, globalDataStore } from '../../content/data-store.js';
import { attachContentServerListeners } from '../../content/index.js';
import { syncContentLayer } from '../../content/sync.js';
Expand All @@ -19,7 +20,6 @@ import {
fetchLatestAstroVersion,
shouldCheckForUpdates,
} from './update-check.js';
import { DATA_STORE_FILE } from '../../content/consts.js';

export interface DevServer {
address: AddressInfo;
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { dim } from 'kleur/colors';
import { type HMRPayload, createServer } from 'vite';
import type { AstroConfig, AstroInlineConfig, AstroSettings } from '../../@types/astro.js';
import { getPackage } from '../../cli/install-package.js';
import { DATA_STORE_FILE } from '../../content/consts.js';
import { DataStore, globalDataStore } from '../../content/data-store.js';
import { createContentTypesGenerator } from '../../content/index.js';
import { syncContentLayer } from '../../content/sync.js';
Expand All @@ -30,7 +31,6 @@ import type { Logger } from '../logger/core.js';
import { formatErrorMessage } from '../messages.js';
import { ensureProcessNodeEnv } from '../util.js';
import { setUpEnvTs } from './setup-env-ts.js';
import { DATA_STORE_FILE } from '../../content/consts.js';

export type SyncOptions = {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/templates/content/module.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
createReference,
} from 'astro/content/runtime';

export { defineCollection } from 'astro/content/runtime';
export { defineCollection, renderEntry as render } from 'astro/content/runtime';
export { z } from 'astro/zod';

const contentDir = '@@CONTENT_DIR@@';
Expand Down
6 changes: 5 additions & 1 deletion packages/astro/templates/content/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ declare module 'astro:content' {
remarkPluginFrontmatter: Record<string, any>;
}>;
}
interface ContentLayerRenderer {
interface ContentLayerRenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
}

Expand Down Expand Up @@ -109,6 +109,10 @@ declare module 'astro:content' {
}[]
): Promise<CollectionEntry<C>[]>;

export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string]
): Promise<ContentLayerRenderResult>;

export function reference<C extends keyof AnyEntryMap>(
collection: C
): import('astro/zod').ZodEffects<
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/content-layer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { promises as fs } from 'node:fs';
import { sep } from 'node:path';
import { sep as posixSep } from 'node:path/posix';
import { after, before, describe, it } from 'node:test';
import { loadFixture } from './test-utils.js';
import * as devalue from 'devalue';
import { loadFixture } from './test-utils.js';
describe('Content Layer', () => {
/** @type {import("./test-utils.js").Fixture} */
let fixture;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { loader } from '../loaders/post-loader.js';
import { fileURLToPath } from 'node:url';

const blog = defineCollection({
type: 'experimental_data',
type: 'experimental_content',
loader: loader({ url: 'https://jsonplaceholder.typicode.com/posts' }),
});

const dogs = defineCollection({
type: 'experimental_data',
type: 'experimental_content',
loader: file('src/data/dogs.json'),
schema: z.object({
breed: z.string(),
Expand All @@ -22,7 +22,7 @@ const dogs = defineCollection({
});

const cats = defineCollection({
type: 'experimental_data',
type: 'experimental_content',
loader: async function () {
return [
{
Expand Down Expand Up @@ -92,7 +92,7 @@ const numbers = defineCollection({
});

const increment = defineCollection({
type: 'experimental_data',
type: 'experimental_content',
loader: {
name: 'increment-loader',
load: async ({ store }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
import type { GetStaticPaths } from "astro";
import { getCollection, getEntry } from "astro:content"
import { getCollection, getEntry, render } from "astro:content"
import { Image } from "astro:assets"
export const getStaticPaths = (async () => {
Expand All @@ -22,7 +22,7 @@ export const getStaticPaths = (async () => {
const { craft } = Astro.props as any
let cat = craft.data.cat ? await getEntry(craft.data.cat) : undefined
const { Content } = await craft.render()
const { Content } = await render(craft)
---
<meta charset="utf-8">
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/types/content.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ declare module 'astro:content' {
export type SchemaContext = { image: ImageFunction };

type ContentLayerConfig<S extends BaseSchema, TData extends { id: string } = { id: string }> = {
type: 'experimental_data' | 'experimental_content';
type: 'experimental_content';
schema?: S | ((context: SchemaContext) => S);
loader: import('astro/loaders').Loader | (() => Array<TData> | Promise<Array<TData>>);
};
Expand Down

0 comments on commit 2abc38b

Please sign in to comment.