diff --git a/.changeset/clever-garlics-doubt.md b/.changeset/clever-garlics-doubt.md deleted file mode 100644 index a09ca541e..000000000 --- a/.changeset/clever-garlics-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/sitemap': patch ---- - -exported enum type to support typescript > 5.0 diff --git a/.changeset/curvy-hotels-study.md b/.changeset/curvy-hotels-study.md deleted file mode 100644 index 7ac2e8033..000000000 --- a/.changeset/curvy-hotels-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/partytown': patch ---- - -fix typescript type for partytown options diff --git a/.changeset/dirty-singers-enjoy.md b/.changeset/dirty-singers-enjoy.md deleted file mode 100644 index b02f166fe..000000000 --- a/.changeset/dirty-singers-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/mdx': patch ---- - -Add `optimize` option for faster builds and rendering diff --git a/.changeset/dry-taxis-suffer.md b/.changeset/dry-taxis-suffer.md deleted file mode 100644 index b0cb68b24..000000000 --- a/.changeset/dry-taxis-suffer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'astro': patch ---- - -Refactor how pages are emitted during the internal bundling. Now each -page is emitted as a separate entry point. diff --git a/.changeset/eighty-gifts-cheer.md b/.changeset/eighty-gifts-cheer.md deleted file mode 100644 index 41ca0d7f6..000000000 --- a/.changeset/eighty-gifts-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fix CSS deduping and missing chunks diff --git a/.changeset/eleven-tables-speak.md b/.changeset/eleven-tables-speak.md deleted file mode 100644 index 3eac90a36..000000000 --- a/.changeset/eleven-tables-speak.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@astrojs/markdoc': patch ---- - -Add support for syntax highlighting with Shiki. Apply to your Markdoc config using the `extends` property: - -```js -// markdoc.config.mjs -import { defineMarkdocConfig } from '@astrojs/markdoc/config'; -import shiki from '@astrojs/markdoc/shiki'; - -export default defineMarkdocConfig({ - extends: [ - shiki({ /** Shiki config options */ }), - ], -}) -``` - -Learn more in the [`@astrojs/markdoc` README.](https://docs.astro.build/en/guides/integrations-guide/markdoc/#syntax-highlighting) diff --git a/.changeset/eleven-walls-explain.md b/.changeset/eleven-walls-explain.md deleted file mode 100644 index fd2772185..000000000 --- a/.changeset/eleven-walls-explain.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@astrojs/preact': patch -'@astrojs/react': patch -'@astrojs/vue': patch ---- - -Fix `astro-static-slot` hydration mismatch error diff --git a/.changeset/fifty-months-mix.md b/.changeset/fifty-months-mix.md deleted file mode 100644 index 78a009950..000000000 --- a/.changeset/fifty-months-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/webapi': minor ---- - -Add polyfill for `crypto` diff --git a/.changeset/hungry-peaches-speak.md b/.changeset/hungry-peaches-speak.md deleted file mode 100644 index 4467b521d..000000000 --- a/.changeset/hungry-peaches-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Prioritize dynamic prerendered routes over dynamic server routes diff --git a/.changeset/lazy-zebras-invent.md b/.changeset/lazy-zebras-invent.md deleted file mode 100644 index 1c2abb981..000000000 --- a/.changeset/lazy-zebras-invent.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@astrojs/mdx': patch -'astro': patch ---- - -Detect `mdx` files using their full extension diff --git a/.changeset/moody-coats-develop.md b/.changeset/moody-coats-develop.md deleted file mode 100644 index 20c12708c..000000000 --- a/.changeset/moody-coats-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Add error message if `Astro.glob` is called outside of an Astro file diff --git a/.changeset/plenty-geese-fold.md b/.changeset/plenty-geese-fold.md deleted file mode 100644 index aa7783e92..000000000 --- a/.changeset/plenty-geese-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/vercel': patch ---- - -Fix `imagesConfig` being wrongly spelt as `imageConfig` in the README diff --git a/.changeset/popular-berries-travel.md b/.changeset/popular-berries-travel.md deleted file mode 100644 index a3755d267..000000000 --- a/.changeset/popular-berries-travel.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@astrojs/markdoc': patch ---- - -Add a built-in extension for syntax highlighting with Prism. Apply to your Markdoc config using the `extends` property: - -```js -// markdoc.config.mjs -import { defineMarkdocConfig } from '@astrojs/markdoc/config'; -import prism from '@astrojs/markdoc/prism'; - -export default defineMarkdocConfig({ - extends: [prism()], -}) -``` - -Learn more in the [`@astrojs/markdoc` README.](https://docs.astro.build/en/guides/integrations-guide/markdoc/#syntax-highlighting) diff --git a/.changeset/small-wombats-know.md b/.changeset/small-wombats-know.md deleted file mode 100644 index ca826a1aa..000000000 --- a/.changeset/small-wombats-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -fix miss a head when the templaterender has a promise diff --git a/.changeset/spotty-glasses-return.md b/.changeset/spotty-glasses-return.md deleted file mode 100644 index 6786f77eb..000000000 --- a/.changeset/spotty-glasses-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/mdx': patch ---- - -Remove `@mdx-js/rollup` dependency diff --git a/.changeset/static-slot-css.md b/.changeset/static-slot-css.md deleted file mode 100644 index a3f1c2475..000000000 --- a/.changeset/static-slot-css.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -fix: add astro-static-slot to the list of inert tags in astro css diff --git a/.changeset/strange-socks-give.md b/.changeset/strange-socks-give.md deleted file mode 100644 index f1e9492bd..000000000 --- a/.changeset/strange-socks-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Use `AstroError` for `Astro.glob` errors diff --git a/.changeset/wise-cars-hear.md b/.changeset/wise-cars-hear.md deleted file mode 100644 index 21019c26f..000000000 --- a/.changeset/wise-cars-hear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -The `src` property returned by ESM importing images with `astro:assets` is now an absolute path, unlocking support for importing images outside the project. diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index 9710862db..5eefd0415 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,46 @@ # astro +## 2.5.6 + +### Patch Changes + +- [#7193](https://github.com/withastro/astro/pull/7193) [`8b041bf57`](https://github.com/withastro/astro/commit/8b041bf57c76830c4070330270521e05d8e58474) Thanks [@ematipico](https://github.com/ematipico)! - Refactor how pages are emitted during the internal bundling. Now each + page is emitted as a separate entry point. + +- [#7218](https://github.com/withastro/astro/pull/7218) [`6c7df28ab`](https://github.com/withastro/astro/commit/6c7df28ab34b756b8426443bf6976e24d4611a62) Thanks [@bluwy](https://github.com/bluwy)! - Fix CSS deduping and missing chunks + +- [#7235](https://github.com/withastro/astro/pull/7235) [`ee2aca80a`](https://github.com/withastro/astro/commit/ee2aca80a71afe843af943b11966fcf77f556cfb) Thanks [@MoustaphaDev](https://github.com/MoustaphaDev)! - Prioritize dynamic prerendered routes over dynamic server routes + +- [#7192](https://github.com/withastro/astro/pull/7192) [`7851f9258`](https://github.com/withastro/astro/commit/7851f9258fae2f54795470253df9ce4bcd5f9cb0) Thanks [@ematipico](https://github.com/ematipico)! - Detect `mdx` files using their full extension + +- [#7244](https://github.com/withastro/astro/pull/7244) [`bef3a75db`](https://github.com/withastro/astro/commit/bef3a75dbc48d584daff9f7f3d5a8937b0356170) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Remove the auto-generated `$entry` variable for Markdoc entries. To access frontmatter as a variable, you can pass `entry.data` as a prop where you render your content: + + ```astro + --- + import { getEntry } from 'astro:content'; + + const entry = await getEntry('docs', 'why-markdoc'); + const { Content } = await entry.render(); + --- + + + ``` + +- [#7204](https://github.com/withastro/astro/pull/7204) [`52af9ad18`](https://github.com/withastro/astro/commit/52af9ad18840ffa4e2996386c82cbe34d9fd076a) Thanks [@bluwy](https://github.com/bluwy)! - Add error message if `Astro.glob` is called outside of an Astro file + +- [#7246](https://github.com/withastro/astro/pull/7246) [`f5063d0a0`](https://github.com/withastro/astro/commit/f5063d0a01e3179da902fdc0a2b22f88cb3c95c7) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix content collection build errors for empty collections or underscore files of type `.json`. + +- [#7062](https://github.com/withastro/astro/pull/7062) [`cf621340b`](https://github.com/withastro/astro/commit/cf621340b00fda441f4ef43196c0363d09eae70c) Thanks [@wulinsheng123](https://github.com/wulinsheng123)! - fix miss a head when the templaterender has a promise + +- [#7189](https://github.com/withastro/astro/pull/7189) [`2bda7fb0b`](https://github.com/withastro/astro/commit/2bda7fb0bce346f7725086980e1648e2636bbefb) Thanks [@elevatebart](https://github.com/elevatebart)! - fix: add astro-static-slot to the list of inert tags in astro css + +- [#7219](https://github.com/withastro/astro/pull/7219) [`af3c5a2e2`](https://github.com/withastro/astro/commit/af3c5a2e25bd3e7b2a3f7f08e41ee457093c8cb1) Thanks [@bluwy](https://github.com/bluwy)! - Use `AstroError` for `Astro.glob` errors + +- [#7139](https://github.com/withastro/astro/pull/7139) [`f2f18b440`](https://github.com/withastro/astro/commit/f2f18b44055c6334a39d6379de88fe41e518aa1e) Thanks [@Princesseuh](https://github.com/Princesseuh)! - The `src` property returned by ESM importing images with `astro:assets` is now an absolute path, unlocking support for importing images outside the project. + +- Updated dependencies [[`bf63f615f`](https://github.com/withastro/astro/commit/bf63f615fc1b97d6fb84db55f7639084e3ada5af)]: + - @astrojs/webapi@2.2.0 + ## 2.5.5 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index 8fa7bb7f1..abd3f8fa8 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "astro", - "version": "2.5.5", + "version": "2.5.6", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "type": "module", "author": "withastro", @@ -117,7 +117,7 @@ "@astrojs/language-server": "^1.0.0", "@astrojs/markdown-remark": "^2.2.1", "@astrojs/telemetry": "^2.1.1", - "@astrojs/webapi": "^2.1.1", + "@astrojs/webapi": "^2.2.0", "@babel/core": "^7.21.8", "@babel/generator": "^7.18.2", "@babel/parser": "^7.18.4", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index c37d9a3f7..bb37c4989 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1275,8 +1275,9 @@ export interface ContentEntryType { getRenderModule?( this: rollup.PluginContext, params: { + contents: string; + fileUrl: URL; viteId: string; - entry: ContentEntryModule; } ): rollup.LoadResult | Promise; contentModuleTypes?: string; diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts index ee4c99077..7958c330f 100644 --- a/packages/astro/src/content/types-generator.ts +++ b/packages/astro/src/content/types-generator.ts @@ -11,12 +11,12 @@ import { info, warn, type LogOptions } from '../core/logger/core.js'; import { isRelativePath } from '../core/path.js'; import { CONTENT_TYPES_FILE, VIRTUAL_MODULE_ID } from './consts.js'; import { - getContentEntryConfigByExtMap, getContentEntryIdAndSlug, getContentPaths, getDataEntryExts, getDataEntryId, getEntryCollectionName, + getEntryConfigByExtMap, getEntrySlug, getEntryType, reloadContentConfigObserver, @@ -74,7 +74,7 @@ export async function createContentTypesGenerator({ }: CreateContentGeneratorParams) { const collectionEntryMap: CollectionEntryMap = {}; const contentPaths = getContentPaths(settings.config, fs); - const contentEntryConfigByExt = getContentEntryConfigByExtMap(settings); + const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes); const contentEntryExts = [...contentEntryConfigByExt.keys()]; const dataEntryExts = getDataEntryExts(settings); @@ -414,7 +414,11 @@ async function writeContentFiles({ for (const collectionKey of Object.keys(collectionEntryMap).sort()) { const collectionConfig = contentConfig?.collections[JSON.parse(collectionKey)]; const collection = collectionEntryMap[collectionKey]; - if (collectionConfig?.type && collection.type !== collectionConfig.type) { + if ( + collectionConfig?.type && + collection.type !== 'unknown' && + collection.type !== collectionConfig.type + ) { viteServer.ws.send({ type: 'error', err: new AstroError({ @@ -433,7 +437,14 @@ async function writeContentFiles({ }); return; } - switch (collection.type) { + const resolvedType: 'content' | 'data' = + 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 + collectionConfig?.type ?? 'data' + : collection.type; + + switch (resolvedType) { case 'content': contentTypesStr += `${collectionKey}: {\n`; for (const entryKey of Object.keys(collection.entries).sort()) { @@ -449,9 +460,6 @@ async function writeContentFiles({ contentTypesStr += `};\n`; break; case 'data': - // Add empty / unknown collections to the data type map by default - // This ensures `getCollection('empty-collection')` doesn't raise a type error - case 'unknown': dataTypesStr += `${collectionKey}: {\n`; for (const entryKey of Object.keys(collection.entries).sort()) { const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : 'any'; diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 686176096..40b2ab9e7 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -10,6 +10,7 @@ import type { AstroConfig, AstroSettings, ContentEntryType, + DataEntryType, ImageInputFormat, } from '../@types/astro.js'; import { VALID_INPUT_FORMATS } from '../assets/consts.js'; @@ -172,9 +173,11 @@ export function getDataEntryExts(settings: Pick return settings.dataEntryTypes.map((t) => t.extensions).flat(); } -export function getContentEntryConfigByExtMap(settings: Pick) { - const map: Map = new Map(); - for (const entryType of settings.contentEntryTypes) { +export function getEntryConfigByExtMap( + entryTypes: TEntryType[] +): Map { + const map: Map = new Map(); + for (const entryType of entryTypes) { for (const ext of entryType.extensions) { map.set(ext, entryType); } diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index c0d0571e3..8e8ee0ba0 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -5,6 +5,7 @@ import type { PluginContext } from 'rollup'; import { pathToFileURL } from 'url'; import type { Plugin } from 'vite'; import type { + AstroConfig, AstroSettings, ContentEntryModule, ContentEntryType, @@ -13,16 +14,16 @@ import type { } from '../@types/astro.js'; import { AstroErrorData } from '../core/errors/errors-data.js'; import { AstroError } from '../core/errors/errors.js'; -import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js'; +import { escapeViteEnvReferences } from '../vite-plugin-utils/index.js'; import { CONTENT_FLAG, DATA_FLAG } from './consts.js'; import { - getContentEntryConfigByExtMap, getContentEntryExts, getContentEntryIdAndSlug, getContentPaths, getDataEntryExts, getDataEntryId, getEntryCollectionName, + getEntryConfigByExtMap, getEntryData, getEntryType, globalContentConfigObserver, @@ -30,7 +31,6 @@ import { parseEntrySlug, reloadContentConfigObserver, type ContentConfig, - type ContentPaths, } from './utils.js'; function getContentRendererByViteId( @@ -70,14 +70,9 @@ export function astroContentImportPlugin({ const contentEntryExts = getContentEntryExts(settings); const dataEntryExts = getDataEntryExts(settings); - const contentEntryConfigByExt = getContentEntryConfigByExtMap(settings); - - const dataEntryExtToParser: Map = new Map(); - for (const entryType of settings.dataEntryTypes) { - for (const ext of entryType.extensions) { - dataEntryExtToParser.set(ext, entryType.getEntryInfo); - } - } + const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes); + const dataEntryConfigByExt = getEntryConfigByExtMap(settings.dataEntryTypes); + const { contentDir } = contentPaths; const plugins: Plugin[] = [ { @@ -89,9 +84,9 @@ export function astroContentImportPlugin({ // This cache only exists for the `render()` function specific to content. const { id, data, collection, _internal } = await getDataEntryModule({ fileId, - dataEntryExtToParser, - contentPaths, - settings, + entryConfigByExt: dataEntryConfigByExt, + contentDir, + config: settings.config, fs, pluginContext: this, }); @@ -109,8 +104,12 @@ export const _internal = { return code; } else if (hasContentFlag(viteId, CONTENT_FLAG)) { const fileId = viteId.split('?')[0]; - const { id, slug, collection, body, data, _internal } = await setContentEntryModuleCache({ + const { id, slug, collection, body, data, _internal } = await getContentEntryModule({ fileId, + entryConfigByExt: contentEntryConfigByExt, + contentDir, + config: settings.config, + fs, pluginContext: this, }); @@ -170,152 +169,153 @@ export const _internal = { if (settings.contentEntryTypes.some((t) => t.getRenderModule)) { plugins.push({ name: 'astro:content-render-imports', - async transform(_, viteId) { + async transform(contents, viteId) { const contentRenderer = getContentRendererByViteId(viteId, settings); if (!contentRenderer) return; - const { fileId } = getFileInfo(viteId, settings.config); - const entry = await getContentEntryModuleFromCache(fileId); - if (!entry) { - // Cached entry must exist (or be in-flight) when importing the module via content collections. - // This is ensured by the `astro:content-imports` plugin. - throw new AstroError({ - ...AstroErrorData.UnknownContentCollectionError, - message: `Unable to render ${JSON.stringify( - fileId - )}. Did you import this module directly without using a content collection query?`, - }); - } - - return contentRenderer.bind(this)({ entry, viteId }); + const fileId = viteId.split('?')[0]; + return contentRenderer.bind(this)({ viteId, contents, fileUrl: pathToFileURL(fileId) }); }, }); } - /** - * There are two content collection plugins that depend on the same entry data: - * - `astro:content-imports` - creates module containing the `getCollection()` result. - * - `astro:content-render-imports` - creates module containing the `collectionEntry.render()` result. - * - * We could run the same transforms to generate the slug and parsed data in each plugin, - * though this would run the user's collection schema _twice_ for each entry. - * - * Instead, we've implemented a cache for all content entry data. To avoid race conditions, - * this may store either the module itself or a queue of promises awaiting this module. - * See the implementations of `getContentEntryModuleFromCache` and `setContentEntryModuleCache`. - */ - const contentEntryModuleByIdCache = new Map< - string, - ContentEntryModule | AwaitingCacheResultQueue - >(); - type AwaitingCacheResultQueue = { - awaitingQueue: ((val: ContentEntryModule) => void)[]; - }; - function isAwaitingQueue( - cacheEntry: ReturnType - ): cacheEntry is AwaitingCacheResultQueue { - return typeof cacheEntry === 'object' && cacheEntry != null && 'awaitingQueue' in cacheEntry; - } - - function getContentEntryModuleFromCache(id: string): Promise { - const cacheEntry = contentEntryModuleByIdCache.get(id); - // It's possible to request an entry while `setContentEntryModuleCache` is still - // setting that entry. In this case, queue a promise for `setContentEntryModuleCache` - // to resolve once it is complete. - if (isAwaitingQueue(cacheEntry)) { - return new Promise((resolve, reject) => { - cacheEntry.awaitingQueue.push(resolve); - }); - } else if (cacheEntry) { - return Promise.resolve(cacheEntry); - } - return Promise.resolve(undefined); - } - - async function setContentEntryModuleCache({ - fileId, - pluginContext, - }: { - fileId: string; - pluginContext: PluginContext; - }): Promise { - // Create a queue so, if `getContentEntryModuleFromCache` is called - // while this function is running, we can resolve all requests - // in the `awaitingQueue` with the result. - contentEntryModuleByIdCache.set(fileId, { awaitingQueue: [] }); - - const contentConfig = await getContentConfigFromGlobal(); - const rawContents = await fs.promises.readFile(fileId, 'utf-8'); - const fileExt = extname(fileId); - if (!contentEntryConfigByExt.has(fileExt)) { - throw new AstroError({ - ...AstroErrorData.UnknownContentCollectionError, - message: `No parser found for content entry ${JSON.stringify( - fileId - )}. Did you apply an integration for this file type?`, - }); - } - const contentEntryConfig = contentEntryConfigByExt.get(fileExt)!; - const { - rawData, - body, - slug: frontmatterSlug, - data: unvalidatedData, - } = await contentEntryConfig.getEntryInfo({ - fileUrl: pathToFileURL(fileId), - contents: rawContents, - }); - const entry = pathToFileURL(fileId); - const { contentDir } = contentPaths; - const collection = getEntryCollectionName({ entry, contentDir }); - if (collection === undefined) - throw new AstroError(AstroErrorData.UnknownContentCollectionError); - - const { id, slug: generatedSlug } = getContentEntryIdAndSlug({ entry, contentDir, collection }); - - const _internal = { filePath: fileId, rawData: rawData }; - // TODO: move slug calculation to the start of the build - // to generate a performant lookup map for `getEntryBySlug` - const slug = parseEntrySlug({ - id, - collection, - generatedSlug, - frontmatterSlug, - }); - - const collectionConfig = contentConfig?.collections[collection]; - let data = collectionConfig - ? await getEntryData( - { id, collection, _internal, unvalidatedData }, - collectionConfig, - pluginContext, - settings.config - ) - : unvalidatedData; - - const contentEntryModule: ContentEntryModule = { - id, - slug, - collection, - data, - body, - _internal, - }; - - const cacheEntry = contentEntryModuleByIdCache.get(fileId); - // Pass the entry to all promises awaiting this result - if (isAwaitingQueue(cacheEntry)) { - for (const resolve of cacheEntry.awaitingQueue) { - resolve(contentEntryModule); - } - } - contentEntryModuleByIdCache.set(fileId, contentEntryModule); - return contentEntryModule; - } - return plugins; } +type GetEntryModuleParams = { + fs: typeof fsMod; + fileId: string; + contentDir: URL; + pluginContext: PluginContext; + entryConfigByExt: Map; + config: AstroConfig; +}; + +async function getContentEntryModule( + params: GetEntryModuleParams +): Promise { + const { fileId, contentDir, pluginContext, config } = params; + const { collectionConfig, entryConfig, entry, rawContents, collection } = + await getEntryModuleBaseInfo(params); + + const { + rawData, + data: unvalidatedData, + body, + slug: frontmatterSlug, + } = await entryConfig.getEntryInfo({ + fileUrl: pathToFileURL(fileId), + contents: rawContents, + }); + const _internal = { filePath: fileId, rawData }; + const { id, slug: generatedSlug } = getContentEntryIdAndSlug({ entry, contentDir, collection }); + + const slug = parseEntrySlug({ + id, + collection, + generatedSlug, + frontmatterSlug, + }); + + const data = collectionConfig + ? await getEntryData( + { id, collection, _internal, unvalidatedData }, + collectionConfig, + pluginContext, + config + ) + : unvalidatedData; + + const contentEntryModule: ContentEntryModule = { + id, + slug, + collection, + data, + body, + _internal, + }; + + return contentEntryModule; +} + +async function getDataEntryModule( + params: GetEntryModuleParams +): Promise { + const { fileId, contentDir, pluginContext, config } = params; + const { collectionConfig, entryConfig, entry, rawContents, collection } = + await getEntryModuleBaseInfo(params); + + const { rawData = '', data: unvalidatedData } = await entryConfig.getEntryInfo({ + fileUrl: pathToFileURL(fileId), + contents: rawContents, + }); + const _internal = { filePath: fileId, rawData }; + const id = getDataEntryId({ entry, contentDir, collection }); + + const data = collectionConfig + ? await getEntryData( + { id, collection, _internal, unvalidatedData }, + collectionConfig, + pluginContext, + config + ) + : unvalidatedData; + + const dataEntryModule: DataEntryModule = { + id, + collection, + data, + _internal, + }; + + return dataEntryModule; +} + +// Shared logic for `getContentEntryModule` and `getDataEntryModule` +// Extracting to a helper was easier that conditionals and generics :) +async function getEntryModuleBaseInfo({ + fileId, + entryConfigByExt, + contentDir, + fs, +}: GetEntryModuleParams) { + const contentConfig = await getContentConfigFromGlobal(); + let rawContents; + try { + rawContents = await fs.promises.readFile(fileId, 'utf-8'); + } catch (e) { + throw new AstroError({ + ...AstroErrorData.UnknownContentCollectionError, + message: `Unexpected error reading entry ${JSON.stringify(fileId)}.`, + stack: e instanceof Error ? e.stack : undefined, + }); + } + const fileExt = extname(fileId); + const entryConfig = entryConfigByExt.get(fileExt); + + if (!entryConfig) { + throw new AstroError({ + ...AstroErrorData.UnknownContentCollectionError, + message: `No parser found for data entry ${JSON.stringify( + fileId + )}. Did you apply an integration for this file type?`, + }); + } + const entry = pathToFileURL(fileId); + const collection = getEntryCollectionName({ entry, contentDir }); + if (collection === undefined) throw new AstroError(AstroErrorData.UnknownContentCollectionError); + + const collectionConfig = contentConfig?.collections[collection]; + + return { + collectionConfig, + entry, + entryConfig, + collection, + rawContents, + }; +} + async function getContentConfigFromGlobal() { const observable = globalContentConfigObserver.get(); @@ -352,68 +352,3 @@ async function getContentConfigFromGlobal() { return contentConfig; } - -type GetDataEntryModuleParams = { - fs: typeof fsMod; - fileId: string; - contentPaths: Pick; - pluginContext: PluginContext; - dataEntryExtToParser: Map; - settings: Pick; -}; - -async function getDataEntryModule({ - fileId, - dataEntryExtToParser, - contentPaths, - fs, - pluginContext, - settings, -}: GetDataEntryModuleParams): Promise { - const contentConfig = await getContentConfigFromGlobal(); - let rawContents; - try { - rawContents = await fs.promises.readFile(fileId, 'utf-8'); - } catch (e) { - throw new AstroError({ - ...AstroErrorData.UnknownContentCollectionError, - message: `Unexpected error reading entry ${JSON.stringify(fileId)}.`, - stack: e instanceof Error ? e.stack : undefined, - }); - } - const fileExt = extname(fileId); - const dataEntryParser = dataEntryExtToParser.get(fileExt); - - if (!dataEntryParser) { - throw new AstroError({ - ...AstroErrorData.UnknownContentCollectionError, - message: `No parser found for data entry ${JSON.stringify( - fileId - )}. Did you apply an integration for this file type?`, - }); - } - const { data: unvalidatedData, rawData = '' } = await dataEntryParser({ - fileUrl: pathToFileURL(fileId), - contents: rawContents, - }); - const entry = pathToFileURL(fileId); - const { contentDir } = contentPaths; - const collection = getEntryCollectionName({ entry, contentDir }); - if (collection === undefined) throw new AstroError(AstroErrorData.UnknownContentCollectionError); - - const id = getDataEntryId({ entry, contentDir, collection }); - - const _internal = { filePath: fileId, rawData }; - - const collectionConfig = contentConfig?.collections[collection]; - const data = collectionConfig - ? await getEntryData( - { id, collection, _internal, unvalidatedData }, - collectionConfig, - pluginContext, - settings.config - ) - : unvalidatedData; - - return { id, collection, data, _internal }; -} 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 71d41751e..838d6b425 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -4,17 +4,17 @@ import { extname } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import pLimit from 'p-limit'; import type { Plugin } from 'vite'; -import type { AstroSettings } from '../@types/astro.js'; +import type { AstroSettings, ContentEntryType } from '../@types/astro.js'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { rootRelativePath } from '../core/util.js'; import { VIRTUAL_MODULE_ID } from './consts.js'; import { - getContentEntryConfigByExtMap, getContentEntryIdAndSlug, getContentPaths, getDataEntryExts, getDataEntryId, getEntryCollectionName, + getEntryConfigByExtMap, getEntrySlug, getEntryType, getExtGlob, @@ -32,7 +32,7 @@ export function astroContentVirtualModPlugin({ const contentPaths = getContentPaths(settings.config); const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir); - const contentEntryConfigByExt = getContentEntryConfigByExtMap(settings); + const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes); const contentEntryExts = [...contentEntryConfigByExt.keys()]; const dataEntryExts = getDataEntryExts(settings); @@ -43,8 +43,12 @@ export function astroContentVirtualModPlugin({ new URL('reference-map.json', contentPaths.cacheDir).pathname ) .replace('@@CONTENT_DIR@@', relContentDir) - .replace('@@CONTENT_ENTRY_GLOB_PATH@@', `${relContentDir}**/*${getExtGlob(contentEntryExts)}`) - .replace('@@DATA_ENTRY_GLOB_PATH@@', `${relContentDir}**/*${getExtGlob(dataEntryExts)}`) + .replace( + '@@CONTENT_ENTRY_GLOB_PATH@@', + // [!_] = ignore files starting with "_" + `${relContentDir}**/[!_]*${getExtGlob(contentEntryExts)}` + ) + .replace('@@DATA_ENTRY_GLOB_PATH@@', `${relContentDir}**/[!_]*${getExtGlob(dataEntryExts)}`) .replace( '@@RENDER_ENTRY_GLOB_PATH@@', `${relContentDir}**/*${getExtGlob(/** Note: data collections excluded */ contentEntryExts)}` @@ -92,7 +96,7 @@ export async function getStringifiedLookupMap({ root, fs, }: { - contentEntryConfigByExt: ReturnType; + contentEntryConfigByExt: Map; dataEntryExts: string[]; contentPaths: Pick; root: URL; diff --git a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js index 355f54a0f..d323946f8 100644 --- a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js +++ b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js @@ -119,4 +119,32 @@ title: Post expect(e.hint).to.include("Try adding `type: 'data'`"); } }); + + it('does not raise error for empty collection with config', async () => { + const fs = createFsWithFallback( + { + // Add placeholder to ensure directory exists + '/src/content/i18n/_placeholder.txt': 'Need content here', + '/src/content/config.ts': ` + import { z, defineCollection } from 'astro:content'; + + const i18n = defineCollection({ + type: 'data', + schema: z.object({ + greeting: z.string(), + }), + }); + + export const collections = { i18n };`, + }, + root + ); + + try { + const res = await sync({ fs }); + expect(res).to.equal(0); + } catch (e) { + expect.fail(0, 1, `Did not expect sync to throw: ${e.message}`); + } + }); }); diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json index cd8bd1dec..99059421b 100644 --- a/packages/integrations/cloudflare/package.json +++ b/packages/integrations/cloudflare/package.json @@ -43,7 +43,7 @@ "tiny-glob": "^0.2.9" }, "peerDependencies": { - "astro": "workspace:^2.5.5" + "astro": "workspace:^2.5.6" }, "devDependencies": { "astro": "workspace:*", diff --git a/packages/integrations/deno/package.json b/packages/integrations/deno/package.json index fc0402570..60a8bd0a1 100644 --- a/packages/integrations/deno/package.json +++ b/packages/integrations/deno/package.json @@ -36,7 +36,7 @@ "esbuild": "^0.15.18" }, "peerDependencies": { - "astro": "workspace:^2.5.5" + "astro": "workspace:^2.5.6" }, "devDependencies": { "astro": "workspace:*", diff --git a/packages/integrations/image/package.json b/packages/integrations/image/package.json index 76fd1b633..ed7b1caba 100644 --- a/packages/integrations/image/package.json +++ b/packages/integrations/image/package.json @@ -62,7 +62,7 @@ "vite": "^4.3.1" }, "peerDependencies": { - "astro": "workspace:^2.5.5", + "astro": "workspace:^2.5.6", "sharp": ">=0.31.0" }, "peerDependenciesMeta": { diff --git a/packages/integrations/markdoc/CHANGELOG.md b/packages/integrations/markdoc/CHANGELOG.md index d2233ff60..a737acc4c 100644 --- a/packages/integrations/markdoc/CHANGELOG.md +++ b/packages/integrations/markdoc/CHANGELOG.md @@ -1,5 +1,59 @@ # @astrojs/markdoc +## 0.3.0 + +### Minor Changes + +- [#7244](https://github.com/withastro/astro/pull/7244) [`bef3a75db`](https://github.com/withastro/astro/commit/bef3a75dbc48d584daff9f7f3d5a8937b0356170) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Remove the auto-generated `$entry` variable for Markdoc entries. To access frontmatter as a variable, you can pass `entry.data` as a prop where you render your content: + + ```astro + --- + import { getEntry } from 'astro:content'; + + const entry = await getEntry('docs', 'why-markdoc'); + const { Content } = await entry.render(); + --- + + + ``` + +### Patch Changes + +- [#7187](https://github.com/withastro/astro/pull/7187) [`1efaef6be`](https://github.com/withastro/astro/commit/1efaef6be0265c68eac706623778e8ad23b33247) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add support for syntax highlighting with Shiki. Apply to your Markdoc config using the `extends` property: + + ```js + // markdoc.config.mjs + import { defineMarkdocConfig } from '@astrojs/markdoc/config'; + import shiki from '@astrojs/markdoc/shiki'; + + export default defineMarkdocConfig({ + extends: [ + shiki({ + /** Shiki config options */ + }), + ], + }); + ``` + + Learn more in the [`@astrojs/markdoc` README.](https://docs.astro.build/en/guides/integrations-guide/markdoc/#syntax-highlighting) + +- [#7209](https://github.com/withastro/astro/pull/7209) [`16b836411`](https://github.com/withastro/astro/commit/16b836411980f18c58ca15712d92cec1b3c95670) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Add a built-in extension for syntax highlighting with Prism. Apply to your Markdoc config using the `extends` property: + + ```js + // markdoc.config.mjs + import { defineMarkdocConfig } from '@astrojs/markdoc/config'; + import prism from '@astrojs/markdoc/prism'; + + export default defineMarkdocConfig({ + extends: [prism()], + }); + ``` + + Learn more in the [`@astrojs/markdoc` README.](https://docs.astro.build/en/guides/integrations-guide/markdoc/#syntax-highlighting) + +- Updated dependencies [[`8b041bf57`](https://github.com/withastro/astro/commit/8b041bf57c76830c4070330270521e05d8e58474), [`6c7df28ab`](https://github.com/withastro/astro/commit/6c7df28ab34b756b8426443bf6976e24d4611a62), [`ee2aca80a`](https://github.com/withastro/astro/commit/ee2aca80a71afe843af943b11966fcf77f556cfb), [`7851f9258`](https://github.com/withastro/astro/commit/7851f9258fae2f54795470253df9ce4bcd5f9cb0), [`bef3a75db`](https://github.com/withastro/astro/commit/bef3a75dbc48d584daff9f7f3d5a8937b0356170), [`52af9ad18`](https://github.com/withastro/astro/commit/52af9ad18840ffa4e2996386c82cbe34d9fd076a), [`f5063d0a0`](https://github.com/withastro/astro/commit/f5063d0a01e3179da902fdc0a2b22f88cb3c95c7), [`cf621340b`](https://github.com/withastro/astro/commit/cf621340b00fda441f4ef43196c0363d09eae70c), [`2bda7fb0b`](https://github.com/withastro/astro/commit/2bda7fb0bce346f7725086980e1648e2636bbefb), [`af3c5a2e2`](https://github.com/withastro/astro/commit/af3c5a2e25bd3e7b2a3f7f08e41ee457093c8cb1), [`f2f18b440`](https://github.com/withastro/astro/commit/f2f18b44055c6334a39d6379de88fe41e518aa1e)]: + - astro@2.5.6 + ## 0.2.3 ### Patch Changes diff --git a/packages/integrations/markdoc/README.md b/packages/integrations/markdoc/README.md index dd2f2d4de..da5aeb46a 100644 --- a/packages/integrations/markdoc/README.md +++ b/packages/integrations/markdoc/README.md @@ -97,13 +97,9 @@ const { Content } = await entry.render(); `@astrojs/markdoc` offers configuration options to use all of Markdoc's features and connect UI components to your content. -### Using components +### Use Astro components as Markdoc tags -You can add Astro components to your Markdoc using both [Markdoc tags][markdoc-tags] and HTML element [nodes][markdoc-nodes]. - -#### Render Markdoc tags as Astro components - -You may configure [Markdoc tags][markdoc-tags] that map to components. You can configure a new tag by creating a `markdoc.config.mjs|ts` file at the root of your project and configuring the `tag` attribute. +You can configure [Markdoc tags][markdoc-tags] that map to `.astro` components. You can add a new tag by creating a `markdoc.config.mjs|ts` file at the root of your project and configuring the `tag` attribute. This example renders an `Aside` component, and allows a `type` prop to be passed as a string: @@ -141,9 +137,11 @@ Use tags like this fancy "aside" to add some *flair* to your docs. {% /aside %} ``` -#### Render Markdoc nodes / HTML elements as Astro components +### Custom headings -You may also want to map standard HTML elements like headings and paragraphs to components. For this, you can configure a custom [Markdoc node][markdoc-nodes]. This example overrides Markdoc's `heading` node to render a `Heading` component, and passes through Astro's default heading properties to define attributes and generate heading ids / slugs: +`@astrojs/markdoc` automatically adds anchor links to your headings, and [generates a list of `headings` via the content collections API](https://docs.astro.build/en/guides/content-collections/#rendering-content-to-html). To further customize how headings are rendered, you can apply an Astro component [as a Markdoc node][markdoc-nodes]. + +This example renders a `Heading.astro` component using the `render` property: ```js // markdoc.config.mjs @@ -154,55 +152,20 @@ export default defineMarkdocConfig({ nodes: { heading: { render: Heading, + // Preserve default anchor link generation ...nodes.heading, }, }, }) ``` -All Markdown headings will render the `Heading.astro` component and pass `attributes` as component props. For headings, Astro provides the following attributes by default: +All Markdown headings will render the `Heading.astro` component and pass the following `attributes` as component props: - `level: number` The heading level 1 - 6 - `id: string` An `id` generated from the heading's text contents. This corresponds to the `slug` generated by the [content `render()` function](https://docs.astro.build/en/guides/content-collections/#rendering-content-to-html). For example, the heading `### Level 3 heading!` will pass `level: 3` and `id: 'level-3-heading'` as component props. -📚 [Find all of Markdoc's built-in nodes and node attributes on their documentation.](https://markdoc.dev/docs/nodes#built-in-nodes) - -#### Use client-side UI components - -Today, the `components` prop does not support the `client:` directive for hydrating components. To embed client-side components, create a wrapper `.astro` file to import your component and apply a `client:` directive manually. - -This example wraps a `Aside.tsx` component with a `ClientAside.astro` wrapper: - -```astro ---- -// src/components/ClientAside.astro -import Aside from './Aside'; ---- - -