From be0c81efccf85f112de50aaf4e4a267d5f973b52 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 10:14:14 -0400 Subject: [PATCH 001/183] feat: port content plugin as-is --- packages/astro/content-reference/content.d.ts | 13 + packages/astro/content-reference/content.mjs | 18 ++ packages/astro/src/core/create-vite.ts | 2 + .../astro/src/vite-plugin-content/index.ts | 245 ++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 packages/astro/content-reference/content.d.ts create mode 100644 packages/astro/content-reference/content.mjs create mode 100644 packages/astro/src/vite-plugin-content/index.ts diff --git a/packages/astro/content-reference/content.d.ts b/packages/astro/content-reference/content.d.ts new file mode 100644 index 000000000000..ef4734d5d28c --- /dev/null +++ b/packages/astro/content-reference/content.d.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +declare const defaultSchemaFileResolved: { + schema: { + parse: (mod) => any; + }; +}; +export declare const contentMap: { + // GENERATED_CONTENT_MAP_ENTRIES +}; +export declare const schemaMap: { + // GENERATED_SCHEMA_MAP_ENTRIES +}; diff --git a/packages/astro/content-reference/content.mjs b/packages/astro/content-reference/content.mjs new file mode 100644 index 000000000000..4598a5681fc3 --- /dev/null +++ b/packages/astro/content-reference/content.mjs @@ -0,0 +1,18 @@ +const NO_SCHEMA_MSG = (/** @type {string} */ collection) => + `${collection} does not have a ~schema file. We suggest adding one for type safety!`; + +const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; +/** Used to stub out `schemaMap` entries that don't have a `~schema.ts` file */ +const defaultSchemaFile = (/** @type {string} */ collection) => + new Promise((/** @type {(value: typeof defaultSchemaFileResolved) => void} */ resolve) => { + console.warn(NO_SCHEMA_MSG(collection)); + resolve(defaultSchemaFileResolved); + }); + +export const contentMap = { + // GENERATED_CONTENT_MAP_ENTRIES +}; + +export const schemaMap = { + // GENERATED_SCHEMA_MAP_ENTRIES +}; diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index a54e3ea7e3b5..16a6210ae117 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -22,6 +22,7 @@ import astroScriptsPlugin from '../vite-plugin-scripts/index.js'; import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js'; import { createCustomViteLogger } from './errors/dev/index.js'; import { resolveDependency } from './util.js'; +import { astroContentPlugin } from '../vite-plugin-content/index.js'; interface CreateViteOptions { settings: AstroSettings; @@ -116,6 +117,7 @@ export async function createVite( astroScriptsPageSSRPlugin({ settings }), astroHeadPropagationPlugin({ settings }), settings.config.experimental.prerender && astroScannerPlugin({ settings, logging }), + astroContentPlugin({ settings, logging }), ], publicDir: fileURLToPath(settings.config.publicDir), root: fileURLToPath(settings.config.root), diff --git a/packages/astro/src/vite-plugin-content/index.ts b/packages/astro/src/vite-plugin-content/index.ts new file mode 100644 index 000000000000..fc86f83c6a38 --- /dev/null +++ b/packages/astro/src/vite-plugin-content/index.ts @@ -0,0 +1,245 @@ +import type { Plugin, ErrorPayload as ViteErrorPayload } from 'vite'; +import glob from 'fast-glob'; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { cyan } from 'kleur/colors'; +import matter from 'gray-matter'; +import { info, LogOptions } from '../core/logger/core.js'; +import type { AstroSettings } from '../@types/astro.js'; + +type TypedMapEntry = { key: string; value: string; type: string }; +type Dirs = { + contentDir: URL; + cacheDir: URL; + generatedInputDir: URL; +}; + +const CONTENT_BASE = 'content'; +const CONTENT_FILE = CONTENT_BASE + '.mjs'; +const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; + +export function astroContentPlugin({ + settings, + logging, +}: { + logging: LogOptions; + settings: AstroSettings; +}): Plugin { + const { root, srcDir } = settings.config; + const dirs: Dirs = { + cacheDir: new URL('./.astro/', root), + contentDir: new URL('./content/', srcDir), + generatedInputDir: new URL('../../content-reference/', import.meta.url), + }; + let contentDirExists = false; + + return { + name: 'astro-fetch-content-plugin', + async config() { + try { + await fs.stat(dirs.contentDir); + contentDirExists = true; + } catch { + console.log('No content directory found!', dirs.contentDir); + } + + if (!contentDirExists) return; + + info(logging, 'content', 'Generating entries...'); + await generateContent(logging, dirs); + }, + async configureServer(viteServer) { + if (contentDirExists) { + info( + logging, + 'content', + `Watching ${cyan(dirs.contentDir.href.replace(root.href, ''))} for changes` + ); + attachListeners(); + } else { + viteServer.watcher.on('addDir', (dir) => { + console.log({ dir, path: dirs.contentDir.pathname }); + if (dir === dirs.contentDir.pathname) { + info(logging, 'content', `Content dir found. Watching for changes`); + contentDirExists = true; + attachListeners(); + } + }); + } + + function attachListeners() { + viteServer.watcher.on('add', (file) => generateContent(logging, dirs, 'add', file)); + viteServer.watcher.on('addDir', (file) => generateContent(logging, dirs, 'addDir', file)); + viteServer.watcher.on('change', (file) => generateContent(logging, dirs, 'change', file)); + viteServer.watcher.on('unlink', (file) => generateContent(logging, dirs, 'unlink', file)); + viteServer.watcher.on('unlinkDir', (file) => + generateContent(logging, dirs, 'unlinkDir', file) + ); + } + }, + }; +} + +type Entry = [ + entryKey: string, + value: { id: string; slug: string; data: any; body: string; rawData: string } +]; +type CollectionEntry = [collectionName: string, entries: Entry[]]; + +async function generateContent( + logging: LogOptions, + dirs: Dirs, + chokidarEvent?: string, + entryChanged?: string +) { + if (chokidarEvent && !entryChanged?.startsWith(dirs.contentDir.pathname)) return; + + if (chokidarEvent === 'addDir') { + info( + logging, + 'content', + `${cyan(getCollectionName(entryChanged ?? '', dirs))} collection added` + ); + } + + let [generatedMaps, generatedMapTypes] = await Promise.all([ + fs.readFile(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8'), + fs.readFile(new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), 'utf-8'), + ]); + + const collections = await glob(new URL('*', dirs.contentDir).pathname, { onlyDirectories: true }); + + const [contentEntries, schemaEntries] = await Promise.all([ + getContentMapEntries(collections, dirs), + getSchemaMapEntries(collections, dirs), + ]); + let contentMapStr = ''; + let contentMapTypesStr = ''; + for (const [collectionName, entries] of contentEntries) { + contentMapStr += `${JSON.stringify(collectionName)}: ${JSON.stringify( + Object.fromEntries(entries), + null, + 2 + )},`; + const types = entries.map(([key, { id, slug }]) => { + return [ + key, + `{\n id: ${JSON.stringify(id)},\n slug: ${JSON.stringify( + slug + )},\n body: string,\n data: z.infer['schema']>\n}`, + ]; + }); + contentMapTypesStr += `${JSON.stringify(collectionName)}: {\n${types.map( + ([key, stringifiedValue]) => `${JSON.stringify(key)}: ${stringifiedValue}` + )}\n},`; + } + + let schemaMapStr = ''; + let schemaMapTypesStr = ''; + for (const { key: collectionName, ...entry } of schemaEntries) { + schemaMapStr += `${JSON.stringify(collectionName)}: ${entry.value},\n`; + schemaMapTypesStr += `${JSON.stringify(collectionName)}: ${entry.type},\n`; + } + + generatedMaps = generatedMaps.replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr); + generatedMapTypes = generatedMapTypes.replace( + '// GENERATED_CONTENT_MAP_ENTRIES', + contentMapTypesStr + ); + generatedMaps = generatedMaps.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); + generatedMapTypes = generatedMapTypes.replace( + '// GENERATED_SCHEMA_MAP_ENTRIES', + schemaMapTypesStr + ); + + try { + await fs.stat(dirs.cacheDir); + } catch { + await fs.mkdir(dirs.cacheDir); + } + + await Promise.all([ + fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), + fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), + ]); +} + +async function getContentMapEntries( + collections: string[], + { contentDir }: Pick +): Promise { + return Promise.all( + collections.map(async (collectionPath) => { + const collectionName = getCollectionName(collectionPath, { contentDir }); + const entries = await getEntriesByCollection(collectionPath, { contentDir }); + return [collectionName, entries]; + }) + ); +} + +async function getEntriesByCollection( + collectionPath: string, + { contentDir }: Pick +): Promise { + const files = await glob(`${collectionPath}/*.{md,mdx}`); + return Promise.all( + files.map(async (filePath) => { + const entryKey = path.relative(collectionPath, filePath); + const id = path.relative(contentDir.pathname, filePath); + const slug = entryKey.replace(/\.mdx?$/, ''); + const body = await fs.readFile(filePath, 'utf-8'); + const { data, matter: rawData } = parseFrontmatter(body, filePath); + return [entryKey, { id, slug, body, data, rawData }]; + }) + ); +} + +async function getSchemaMapEntries( + collections: string[], + { contentDir }: Pick +): Promise { + return Promise.all( + collections.map(async (collectionPath) => { + const collectionName = getCollectionName(collectionPath, { contentDir }); + const schemaFilePath = await getSchemaFilePath(collectionPath); + const importStr = `import(${JSON.stringify(schemaFilePath)})`; + return { + key: collectionName, + value: schemaFilePath ? importStr : `defaultSchemaFile(${JSON.stringify(collectionName)})`, + type: schemaFilePath ? `typeof ${importStr}` : 'Promise', + }; + }) + ); +} + +async function getSchemaFilePath(collectionPath: string) { + const schemaFilePath = `${collectionPath}/~schema`; + const maybeSchemaFiles = await glob(`${schemaFilePath}.{js,mjs,ts}`); + return maybeSchemaFiles.length ? schemaFilePath : undefined; +} + +function getCollectionName(collectionPath: string, { contentDir }: Pick) { + return path.relative(contentDir.pathname, collectionPath); +} + +/** + * Match YAML exception handling from Astro core errors + * @see 'astro/src/core/errors.ts' + */ +export function parseFrontmatter(fileContents: string, filePath: string) { + try { + return matter(fileContents); + } catch (e: any) { + if (e.name === 'YAMLException') { + const err: Error & ViteErrorPayload['err'] = e; + err.id = filePath; + err.loc = { file: e.id, line: e.mark.line + 1, column: e.mark.column }; + err.message = e.reason; + throw err; + } else { + throw e; + } + } +} From 53889c2298a3fc596578b2a04f108fb48b2ba488 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 11:02:28 -0400 Subject: [PATCH 002/183] feat: add `fetchContent` to `.astro` --- packages/astro/content-reference/content.d.ts | 21 +++-- packages/astro/content-reference/content.mjs | 85 +++++++++++++++++-- packages/astro/package.json | 1 + packages/astro/src/content-internals/index.ts | 26 ++++++ .../astro/src/vite-plugin-content/index.ts | 4 +- 5 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 packages/astro/src/content-internals/index.ts diff --git a/packages/astro/content-reference/content.d.ts b/packages/astro/content-reference/content.d.ts index ef4734d5d28c..a43e69eafbd0 100644 --- a/packages/astro/content-reference/content.d.ts +++ b/packages/astro/content-reference/content.d.ts @@ -1,13 +1,24 @@ import { z } from 'zod'; declare const defaultSchemaFileResolved: { - schema: { - parse: (mod) => any; - }; + schema: { + parse: (mod) => any; + }; }; export declare const contentMap: { - // GENERATED_CONTENT_MAP_ENTRIES + // GENERATED_CONTENT_MAP_ENTRIES }; export declare const schemaMap: { - // GENERATED_SCHEMA_MAP_ENTRIES + // GENERATED_SCHEMA_MAP_ENTRIES }; +export declare function fetchContentByEntry< + C extends keyof typeof contentMap, + E extends keyof typeof contentMap[C] +>(collection: C, entryKey: E): Promise; +export declare function fetchContent< + C extends keyof typeof contentMap, + E extends keyof typeof contentMap[C] +>( + collection: C, + filter?: (data: typeof contentMap[C][E]['data']) => boolean +): Promise; diff --git a/packages/astro/content-reference/content.mjs b/packages/astro/content-reference/content.mjs index 4598a5681fc3..8d912f559dc9 100644 --- a/packages/astro/content-reference/content.mjs +++ b/packages/astro/content-reference/content.mjs @@ -1,18 +1,89 @@ +import { z } from 'zod'; +import { getFrontmatterErrorLine, errorMap } from 'astro/content-internals'; + const NO_SCHEMA_MSG = (/** @type {string} */ collection) => - `${collection} does not have a ~schema file. We suggest adding one for type safety!`; + `${collection} does not have a ~schema file. We suggest adding one for type safety!`; const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; /** Used to stub out `schemaMap` entries that don't have a `~schema.ts` file */ const defaultSchemaFile = (/** @type {string} */ collection) => - new Promise((/** @type {(value: typeof defaultSchemaFileResolved) => void} */ resolve) => { - console.warn(NO_SCHEMA_MSG(collection)); - resolve(defaultSchemaFileResolved); - }); + new Promise((/** @type {(value: typeof defaultSchemaFileResolved) => void} */ resolve) => { + console.warn(NO_SCHEMA_MSG(collection)); + resolve(defaultSchemaFileResolved); + }); + +const getSchemaError = (collection) => + new Error(`${collection}/~schema needs a named \`schema\` export.`); + +async function parseEntryData( + /** @type {string} */ collection, + /** @type {string} */ entryKey, + /** @type {{ data: any; rawData: string; }} */ unparsedEntry, + /** @type {{ schemaMap: any }} */ { schemaMap } +) { + const defineSchemaResult = await schemaMap[collection]; + if (!defineSchemaResult) throw getSchemaError(collection); + const { schema } = defineSchemaResult; + + try { + return schema.parse(unparsedEntry.data, { errorMap }); + } catch (e) { + if (e instanceof z.ZodError) { + const formattedError = new Error( + [ + `Could not parse frontmatter in ${String(collection)} → ${String(entryKey)}`, + ...e.errors.map((e) => e.message), + ].join('\n') + ); + formattedError.loc = { + file: 'TODO', + line: getFrontmatterErrorLine(unparsedEntry.rawData, String(e.errors[0].path[0])), + column: 1, + }; + throw formattedError; + } + } +} export const contentMap = { - // GENERATED_CONTENT_MAP_ENTRIES + // GENERATED_CONTENT_MAP_ENTRIES }; export const schemaMap = { - // GENERATED_SCHEMA_MAP_ENTRIES + // GENERATED_SCHEMA_MAP_ENTRIES }; + +export async function fetchContentByEntry( + /** @type {string} */ collection, + /** @type {string} */ entryKey +) { + const entry = contentMap[collection][entryKey]; + + return { + id: entry.id, + slug: entry.slug, + body: entry.body, + data: await parseEntryData(collection, entryKey, entry, { schemaMap }), + }; +} + +export async function fetchContent( + /** @type {string} */ collection, + /** @type {undefined | (() => boolean)} */ filter +) { + const entries = Promise.all( + Object.entries(contentMap[collection]).map(async ([key, entry]) => { + return { + id: entry.id, + slug: entry.slug, + body: entry.body, + data: await parseEntryData(collection, key, entry, { schemaMap }), + }; + }) + ); + if (typeof filter === 'function') { + return (await entries).filter(filter); + } else { + return entries; + } +} diff --git a/packages/astro/package.json b/packages/astro/package.json index 4e9e9dc91282..d9f91f3e456d 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -42,6 +42,7 @@ "types": "./config.d.ts", "default": "./config.mjs" }, + "./content-internals": "./dist/content-internals/index.js", "./app": "./dist/core/app/index.js", "./app/node": "./dist/core/app/node.js", "./client/*": "./dist/runtime/client/*", diff --git a/packages/astro/src/content-internals/index.ts b/packages/astro/src/content-internals/index.ts new file mode 100644 index 000000000000..2c4a2b72614d --- /dev/null +++ b/packages/astro/src/content-internals/index.ts @@ -0,0 +1,26 @@ +import { z } from 'zod'; + +const flattenPath = (path: (string | number)[]) => path.join('.'); + +export const errorMap: z.ZodErrorMap = (error, ctx) => { + if (error.code === 'invalid_type') { + const badKeyPath = JSON.stringify(flattenPath(error.path)); + if (error.received === 'undefined') { + return { message: `${badKeyPath} is required.` }; + } else { + return { message: `${badKeyPath} should be ${error.expected}, not ${error.received}.` }; + } + } + return { message: ctx.defaultError }; +}; + +// WARNING: MAXIMUM JANK AHEAD +export function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) { + console.log({ rawFrontmatter, frontmatterKey }); + const indexOfFrontmatterKey = rawFrontmatter.indexOf(`\n${frontmatterKey}`); + if (indexOfFrontmatterKey === -1) return 0; + + const frontmatterBeforeKey = rawFrontmatter.substring(0, indexOfFrontmatterKey + 1); + const numNewlinesBeforeKey = frontmatterBeforeKey.split('\n').length; + return numNewlinesBeforeKey; +} diff --git a/packages/astro/src/vite-plugin-content/index.ts b/packages/astro/src/vite-plugin-content/index.ts index fc86f83c6a38..8475cb04fa4e 100644 --- a/packages/astro/src/vite-plugin-content/index.ts +++ b/packages/astro/src/vite-plugin-content/index.ts @@ -190,8 +190,8 @@ async function getEntriesByCollection( const id = path.relative(contentDir.pathname, filePath); const slug = entryKey.replace(/\.mdx?$/, ''); const body = await fs.readFile(filePath, 'utf-8'); - const { data, matter: rawData } = parseFrontmatter(body, filePath); - return [entryKey, { id, slug, body, data, rawData }]; + const { data, matter } = parseFrontmatter(body, filePath); + return [entryKey, { id, slug, body, data, rawData: matter ?? '' }]; }) ); } From c83fb2211f786d4c7d0fc5011ea57d4557d2237b Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 11:13:30 -0400 Subject: [PATCH 003/183] refactor: move more logic to content-internals --- packages/astro/content-reference/content.d.ts | 2 +- packages/astro/content-reference/content.mjs | 40 +----------------- packages/astro/src/content-internals/index.ts | 41 ++++++++++++++++++- 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/packages/astro/content-reference/content.d.ts b/packages/astro/content-reference/content.d.ts index a43e69eafbd0..7d805358b02c 100644 --- a/packages/astro/content-reference/content.d.ts +++ b/packages/astro/content-reference/content.d.ts @@ -20,5 +20,5 @@ export declare function fetchContent< E extends keyof typeof contentMap[C] >( collection: C, - filter?: (data: typeof contentMap[C][E]['data']) => boolean + filter?: (data: typeof contentMap[C][E]) => boolean ): Promise; diff --git a/packages/astro/content-reference/content.mjs b/packages/astro/content-reference/content.mjs index 8d912f559dc9..470608634f40 100644 --- a/packages/astro/content-reference/content.mjs +++ b/packages/astro/content-reference/content.mjs @@ -1,50 +1,14 @@ import { z } from 'zod'; -import { getFrontmatterErrorLine, errorMap } from 'astro/content-internals'; - -const NO_SCHEMA_MSG = (/** @type {string} */ collection) => - `${collection} does not have a ~schema file. We suggest adding one for type safety!`; +import { getErrorMsg, parseEntryData } from 'astro/content-internals'; const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; /** Used to stub out `schemaMap` entries that don't have a `~schema.ts` file */ const defaultSchemaFile = (/** @type {string} */ collection) => new Promise((/** @type {(value: typeof defaultSchemaFileResolved) => void} */ resolve) => { - console.warn(NO_SCHEMA_MSG(collection)); + console.warn(getErrorMsg.schemaMissing(collection)); resolve(defaultSchemaFileResolved); }); -const getSchemaError = (collection) => - new Error(`${collection}/~schema needs a named \`schema\` export.`); - -async function parseEntryData( - /** @type {string} */ collection, - /** @type {string} */ entryKey, - /** @type {{ data: any; rawData: string; }} */ unparsedEntry, - /** @type {{ schemaMap: any }} */ { schemaMap } -) { - const defineSchemaResult = await schemaMap[collection]; - if (!defineSchemaResult) throw getSchemaError(collection); - const { schema } = defineSchemaResult; - - try { - return schema.parse(unparsedEntry.data, { errorMap }); - } catch (e) { - if (e instanceof z.ZodError) { - const formattedError = new Error( - [ - `Could not parse frontmatter in ${String(collection)} → ${String(entryKey)}`, - ...e.errors.map((e) => e.message), - ].join('\n') - ); - formattedError.loc = { - file: 'TODO', - line: getFrontmatterErrorLine(unparsedEntry.rawData, String(e.errors[0].path[0])), - column: 1, - }; - throw formattedError; - } - } -} - export const contentMap = { // GENERATED_CONTENT_MAP_ENTRIES }; diff --git a/packages/astro/src/content-internals/index.ts b/packages/astro/src/content-internals/index.ts index 2c4a2b72614d..1234c77e9090 100644 --- a/packages/astro/src/content-internals/index.ts +++ b/packages/astro/src/content-internals/index.ts @@ -1,8 +1,38 @@ import { z } from 'zod'; +export async function parseEntryData( + collection: string, + entryKey: string, + unparsedEntry: { data: any; rawData: string }, + { schemaMap }: { schemaMap: Record } +) { + const schemaImport = (await schemaMap[collection]) ?? {}; + if (!('schema' in schemaImport)) throw getErrorMsg.schemaNamedExp(collection); + const { schema } = schemaImport; + + try { + return schema.parse(unparsedEntry.data, { errorMap }); + } catch (e) { + if (e instanceof z.ZodError) { + const formattedError = new Error( + [ + `Could not parse frontmatter in ${String(collection)} → ${String(entryKey)}`, + ...e.errors.map((e) => e.message), + ].join('\n') + ); + (formattedError as any).loc = { + file: 'TODO', + line: getFrontmatterErrorLine(unparsedEntry.rawData, String(e.errors[0].path[0])), + column: 1, + }; + throw formattedError; + } + } +} + const flattenPath = (path: (string | number)[]) => path.join('.'); -export const errorMap: z.ZodErrorMap = (error, ctx) => { +const errorMap: z.ZodErrorMap = (error, ctx) => { if (error.code === 'invalid_type') { const badKeyPath = JSON.stringify(flattenPath(error.path)); if (error.received === 'undefined') { @@ -15,7 +45,7 @@ export const errorMap: z.ZodErrorMap = (error, ctx) => { }; // WARNING: MAXIMUM JANK AHEAD -export function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) { +function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) { console.log({ rawFrontmatter, frontmatterKey }); const indexOfFrontmatterKey = rawFrontmatter.indexOf(`\n${frontmatterKey}`); if (indexOfFrontmatterKey === -1) return 0; @@ -24,3 +54,10 @@ export function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: const numNewlinesBeforeKey = frontmatterBeforeKey.split('\n').length; return numNewlinesBeforeKey; } + +export const getErrorMsg = { + schemaMissing: (collection: string) => + `${collection} does not have a ~schema file. We suggest adding one for type safety!`, + schemaNamedExp: (collection: string) => + new Error(`${collection}/~schema needs a named \`schema\` export.`), +}; From 9b5124d7c5c6c22d553100abcd3b465a19b79e1b Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 11:29:50 -0400 Subject: [PATCH 004/183] refactor: move internals -> content dir --- .../content.d.ts => content-generated.d.ts} | 0 .../{content-reference/content.mjs => content-generated.mjs} | 2 +- packages/astro/package.json | 2 +- .../src/{content-internals/index.ts => content/internals.ts} | 0 .../{vite-plugin-content/index.ts => content/vite-plugin.ts} | 4 ++-- packages/astro/src/core/create-vite.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename packages/astro/{content-reference/content.d.ts => content-generated.d.ts} (100%) rename packages/astro/{content-reference/content.mjs => content-generated.mjs} (95%) rename packages/astro/src/{content-internals/index.ts => content/internals.ts} (100%) rename packages/astro/src/{vite-plugin-content/index.ts => content/vite-plugin.ts} (98%) diff --git a/packages/astro/content-reference/content.d.ts b/packages/astro/content-generated.d.ts similarity index 100% rename from packages/astro/content-reference/content.d.ts rename to packages/astro/content-generated.d.ts diff --git a/packages/astro/content-reference/content.mjs b/packages/astro/content-generated.mjs similarity index 95% rename from packages/astro/content-reference/content.mjs rename to packages/astro/content-generated.mjs index 470608634f40..a255377bff26 100644 --- a/packages/astro/content-reference/content.mjs +++ b/packages/astro/content-generated.mjs @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getErrorMsg, parseEntryData } from 'astro/content-internals'; +import { getErrorMsg, parseEntryData } from 'astro/content/internals'; const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; /** Used to stub out `schemaMap` entries that don't have a `~schema.ts` file */ diff --git a/packages/astro/package.json b/packages/astro/package.json index d9f91f3e456d..546e5f8a5c4e 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -42,7 +42,7 @@ "types": "./config.d.ts", "default": "./config.mjs" }, - "./content-internals": "./dist/content-internals/index.js", + "./content/internals": "./dist/content/internals.js", "./app": "./dist/core/app/index.js", "./app/node": "./dist/core/app/node.js", "./client/*": "./dist/runtime/client/*", diff --git a/packages/astro/src/content-internals/index.ts b/packages/astro/src/content/internals.ts similarity index 100% rename from packages/astro/src/content-internals/index.ts rename to packages/astro/src/content/internals.ts diff --git a/packages/astro/src/vite-plugin-content/index.ts b/packages/astro/src/content/vite-plugin.ts similarity index 98% rename from packages/astro/src/vite-plugin-content/index.ts rename to packages/astro/src/content/vite-plugin.ts index 8475cb04fa4e..99e774341515 100644 --- a/packages/astro/src/vite-plugin-content/index.ts +++ b/packages/astro/src/content/vite-plugin.ts @@ -14,7 +14,7 @@ type Dirs = { generatedInputDir: URL; }; -const CONTENT_BASE = 'content'; +const CONTENT_BASE = 'content-generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; @@ -29,7 +29,7 @@ export function astroContentPlugin({ const dirs: Dirs = { cacheDir: new URL('./.astro/', root), contentDir: new URL('./content/', srcDir), - generatedInputDir: new URL('../../content-reference/', import.meta.url), + generatedInputDir: new URL('../../', import.meta.url), }; let contentDirExists = false; diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 16a6210ae117..bb5585fc10ae 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -22,7 +22,7 @@ import astroScriptsPlugin from '../vite-plugin-scripts/index.js'; import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js'; import { createCustomViteLogger } from './errors/dev/index.js'; import { resolveDependency } from './util.js'; -import { astroContentPlugin } from '../vite-plugin-content/index.js'; +import { astroContentPlugin } from '../content/vite-plugin.js'; interface CreateViteOptions { settings: AstroSettings; From 55aeeea7effc5c24b7225fcce26e980895ed17b0 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 11:34:43 -0400 Subject: [PATCH 005/183] feat: nested dir support --- packages/astro/src/content/vite-plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/content/vite-plugin.ts b/packages/astro/src/content/vite-plugin.ts index 99e774341515..ea0289dce3f5 100644 --- a/packages/astro/src/content/vite-plugin.ts +++ b/packages/astro/src/content/vite-plugin.ts @@ -183,7 +183,7 @@ async function getEntriesByCollection( collectionPath: string, { contentDir }: Pick ): Promise { - const files = await glob(`${collectionPath}/*.{md,mdx}`); + const files = await glob(`${collectionPath}/**/*.{md,mdx}`); return Promise.all( files.map(async (filePath) => { const entryKey = path.relative(collectionPath, filePath); From 269d82ac887cfb937e016f476c5aca6bd2f52b10 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 11:40:36 -0400 Subject: [PATCH 006/183] feat: add with-content playground --- examples/with-content/.gitignore | 20 +++++++++ examples/with-content/.vscode/extensions.json | 4 ++ examples/with-content/.vscode/launch.json | 11 +++++ examples/with-content/README.md | 45 +++++++++++++++++++ examples/with-content/astro.config.mjs | 4 ++ examples/with-content/package.json | 19 ++++++++ examples/with-content/public/favicon.svg | 13 ++++++ examples/with-content/sandbox.config.json | 11 +++++ .../with-content/src/content/blog/first.md | 3 ++ .../src/content/blog/nested/entry.md | 3 ++ .../with-content/src/content/blog/second.md | 3 ++ .../with-content/src/content/blog/~schema.ts | 5 +++ examples/with-content/src/env.d.ts | 1 + examples/with-content/src/pages/index.astro | 25 +++++++++++ examples/with-content/tsconfig.json | 10 +++++ pnpm-lock.yaml | 10 ++++- 16 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 examples/with-content/.gitignore create mode 100644 examples/with-content/.vscode/extensions.json create mode 100644 examples/with-content/.vscode/launch.json create mode 100644 examples/with-content/README.md create mode 100644 examples/with-content/astro.config.mjs create mode 100644 examples/with-content/package.json create mode 100644 examples/with-content/public/favicon.svg create mode 100644 examples/with-content/sandbox.config.json create mode 100644 examples/with-content/src/content/blog/first.md create mode 100644 examples/with-content/src/content/blog/nested/entry.md create mode 100644 examples/with-content/src/content/blog/second.md create mode 100644 examples/with-content/src/content/blog/~schema.ts create mode 100644 examples/with-content/src/env.d.ts create mode 100644 examples/with-content/src/pages/index.astro create mode 100644 examples/with-content/tsconfig.json diff --git a/examples/with-content/.gitignore b/examples/with-content/.gitignore new file mode 100644 index 000000000000..10611f5841d0 --- /dev/null +++ b/examples/with-content/.gitignore @@ -0,0 +1,20 @@ +# build output +dist/ +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/examples/with-content/.vscode/extensions.json b/examples/with-content/.vscode/extensions.json new file mode 100644 index 000000000000..22a15055d638 --- /dev/null +++ b/examples/with-content/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/examples/with-content/.vscode/launch.json b/examples/with-content/.vscode/launch.json new file mode 100644 index 000000000000..d6422097621f --- /dev/null +++ b/examples/with-content/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/examples/with-content/README.md b/examples/with-content/README.md new file mode 100644 index 000000000000..da4f712f6bc4 --- /dev/null +++ b/examples/with-content/README.md @@ -0,0 +1,45 @@ +# Astro Starter Kit: Minimal + +``` +npm create astro@latest -- --template minimal +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal) + +> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! + +## 🚀 Project Structure + +Inside of your Astro project, you'll see the following folders and files: + +``` +/ +├── public/ +├── src/ +│ └── pages/ +│ └── index.astro +└── package.json +``` + +Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. + +There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. + +Any static assets, like images, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :--------------------- | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:3000` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). diff --git a/examples/with-content/astro.config.mjs b/examples/with-content/astro.config.mjs new file mode 100644 index 000000000000..882e6515a67e --- /dev/null +++ b/examples/with-content/astro.config.mjs @@ -0,0 +1,4 @@ +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({}); diff --git a/examples/with-content/package.json b/examples/with-content/package.json new file mode 100644 index 000000000000..97705290e786 --- /dev/null +++ b/examples/with-content/package.json @@ -0,0 +1,19 @@ +{ + "name": "@example/with-content", + "type": "module", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "astro": "^1.5.2" + }, + "devDependencies": { + "zod": "^3.17.3" + } +} diff --git a/examples/with-content/public/favicon.svg b/examples/with-content/public/favicon.svg new file mode 100644 index 000000000000..0f3906297879 --- /dev/null +++ b/examples/with-content/public/favicon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/examples/with-content/sandbox.config.json b/examples/with-content/sandbox.config.json new file mode 100644 index 000000000000..9178af77d7de --- /dev/null +++ b/examples/with-content/sandbox.config.json @@ -0,0 +1,11 @@ +{ + "infiniteLoopProtection": true, + "hardReloadOnChange": false, + "view": "browser", + "template": "node", + "container": { + "port": 3000, + "startScript": "start", + "node": "14" + } +} diff --git a/examples/with-content/src/content/blog/first.md b/examples/with-content/src/content/blog/first.md new file mode 100644 index 000000000000..5743a05d1b66 --- /dev/null +++ b/examples/with-content/src/content/blog/first.md @@ -0,0 +1,3 @@ +--- +title: 'Howdy' +--- diff --git a/examples/with-content/src/content/blog/nested/entry.md b/examples/with-content/src/content/blog/nested/entry.md new file mode 100644 index 000000000000..eef4b3a76dfd --- /dev/null +++ b/examples/with-content/src/content/blog/nested/entry.md @@ -0,0 +1,3 @@ +--- +title: Nested! +--- diff --git a/examples/with-content/src/content/blog/second.md b/examples/with-content/src/content/blog/second.md new file mode 100644 index 000000000000..c569441f455c --- /dev/null +++ b/examples/with-content/src/content/blog/second.md @@ -0,0 +1,3 @@ +--- +title: 'Hey!' +--- diff --git a/examples/with-content/src/content/blog/~schema.ts b/examples/with-content/src/content/blog/~schema.ts new file mode 100644 index 000000000000..2930ea941280 --- /dev/null +++ b/examples/with-content/src/content/blog/~schema.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const schema = z.object({ + title: z.string(), +}); diff --git a/examples/with-content/src/env.d.ts b/examples/with-content/src/env.d.ts new file mode 100644 index 000000000000..f964fe0cffd8 --- /dev/null +++ b/examples/with-content/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro new file mode 100644 index 000000000000..b477a02999f7 --- /dev/null +++ b/examples/with-content/src/pages/index.astro @@ -0,0 +1,25 @@ +--- +import { fetchContent, fetchContentByEntry } from '.astro'; + +const blog = await fetchContent('blog'); + +const nestedEntry = await fetchContentByEntry('blog', 'nested/entry.md'); +--- + + + + + + + + Astro + + +

{nestedEntry.id} {nestedEntry.data.title}

+ {blog.map((entry) => ( +

+ {entry.slug} - {entry.data.title} +

+ ))} + + diff --git a/examples/with-content/tsconfig.json b/examples/with-content/tsconfig.json new file mode 100644 index 000000000000..21281e77a029 --- /dev/null +++ b/examples/with-content/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "astro/tsconfigs/base", + "compilerOptions": { + "strictNullChecks": true, + "baseUrl": ".", + "paths": { + ".astro": [".astro/content-generated"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e3446dc0aa6..85b11b5d622e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,6 +283,15 @@ importers: unocss: 0.15.6 vite-imagetools: 4.0.11 + examples/with-content: + specifiers: + astro: ^1.5.2 + zod: ^3.17.3 + dependencies: + astro: link:../../packages/astro + devDependencies: + zod: 3.19.1 + examples/with-markdown-plugins: specifiers: '@astrojs/markdown-remark': ^1.1.3 @@ -19136,7 +19145,6 @@ packages: /zod/3.19.1: resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==} - dev: false /zwitch/2.0.3: resolution: {integrity: sha512-dn/sDAIuRCsXGnBD4P+SA6nv7Y54HQZjC4SPL8PToU3714zu7wSEc1129D/i0+vvjRfOlFo4Zqrpwj+Zhcykhw==} From f267b5836e890060c3221bf522a7df7f4bce4462 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 11:45:03 -0400 Subject: [PATCH 007/183] edit: update README --- examples/with-content/README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/with-content/README.md b/examples/with-content/README.md index da4f712f6bc4..b9bcb87977fa 100644 --- a/examples/with-content/README.md +++ b/examples/with-content/README.md @@ -1,26 +1,30 @@ -# Astro Starter Kit: Minimal - -``` -npm create astro@latest -- --template minimal -``` - -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal) +# Astro Starter Kit: Content > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! +This project is based on **[the Content Schema RFC](https://www.notion.so/astroinc/Content-Schemas-35f1952fb0a24b30b681b0509ac4d7c2)**. We suggest reading the intro and "detailed usage" sections to understand how content works. + ## 🚀 Project Structure Inside of your Astro project, you'll see the following folders and files: -``` +```sh / +├── .astro/ # Generated on build ├── public/ ├── src/ +│ └── content/ +│ └── blog/ +│ ├── ~schema.ts +│ ├── first.md +│ └── second.md │ └── pages/ │ └── index.astro └── package.json ``` +`src/content/` contains "collections" of Markdown or MDX documents you'll use in your website. Astro will generate a `fetchContent` function to grab posts from `src/content/` (see the generated `.astro` directory), with type-checked frontmatter based on a schema. + Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. From 1e167b7306f4b0d6c9a3e6dac667f6106d859756 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 12 Sep 2022 11:17:17 -0400 Subject: [PATCH 008/183] fix: serialize route pattern for Netlify edge Co-authored-by: Jackie Macharia --- .../integrations/netlify/src/integration-edge-functions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/integrations/netlify/src/integration-edge-functions.ts b/packages/integrations/netlify/src/integration-edge-functions.ts index 8e16e059570f..da62c3dab7f6 100644 --- a/packages/integrations/netlify/src/integration-edge-functions.ts +++ b/packages/integrations/netlify/src/integration-edge-functions.ts @@ -57,6 +57,10 @@ async function createEdgeManifest(routes: RouteData[], entryFile: string, dir: U path: route.pathname, }); } else { + console.log({ + og: route.pattern.source.toString(), + new: route.pattern.source.replace(/\\\//g, '/').toString(), + }); functions.push({ function: entryFile, // Make route pattern serializable to match expected From 3704dbba8d0a6d99aa556eeca428e2f0d332bb5a Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 19 Oct 2022 11:39:26 -0400 Subject: [PATCH 009/183] wip: add ?astro-asset-ssr flag --- packages/astro/src/jsx/babel.ts | 3 ++- packages/astro/src/vite-plugin-asset-ssr/index.ts | 1 + packages/astro/src/vite-plugin-jsx/index.ts | 5 ++++- packages/integrations/mdx/src/index.ts | 12 ++++++++++-- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 packages/astro/src/vite-plugin-asset-ssr/index.ts diff --git a/packages/astro/src/jsx/babel.ts b/packages/astro/src/jsx/babel.ts index 88b01ad8de8a..0369ec33c781 100644 --- a/packages/astro/src/jsx/babel.ts +++ b/packages/astro/src/jsx/babel.ts @@ -4,6 +4,7 @@ import { AstroErrorData } from '../core/errors/errors-data.js'; import { AstroError } from '../core/errors/errors.js'; import { resolvePath } from '../core/util.js'; import { HydrationDirectiveProps } from '../runtime/server/hydration.js'; +import { FLAG } from '../vite-plugin-asset-ssr/index.js'; import type { PluginMetadata } from '../vite-plugin-astro/types'; const ClientOnlyPlaceholder = 'astro-client-only'; @@ -186,7 +187,7 @@ export default function astroJSX(): PluginObj { const node = path.node; // Skip automatic `_components` in MDX files if ( - state.filename?.endsWith('.mdx') && + (state.filename?.endsWith('.mdx') || state.filename?.endsWith('.mdx' + FLAG)) && t.isJSXIdentifier(node.object) && node.object.name === '_components' ) { diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts new file mode 100644 index 000000000000..4cbc8d8ce31a --- /dev/null +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -0,0 +1 @@ +export const FLAG = '?astro-asset-ssr'; diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index b4d90c79d1e3..eac0741a3b4a 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -11,6 +11,7 @@ import { error } from '../core/logger/core.js'; import { removeQueryString } from '../core/path.js'; import { detectImportSource } from './import-source.js'; import tagExportsPlugin from './tag.js'; +import { FLAG } from '../vite-plugin-asset-ssr/index.js'; const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.mdx']); const IMPORT_STATEMENTS: Record = { @@ -126,7 +127,9 @@ export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugi } defaultJSXRendererEntry = [...jsxRenderersIntegrationOnly.entries()][0]; }, - async transform(code, id, opts) { + async transform(code, unresolvedId, opts) { + let id = unresolvedId.endsWith(`.mdx${FLAG}`) ? unresolvedId.replace(FLAG, '') : unresolvedId; + const ssr = Boolean(opts?.ssr); id = removeQueryString(id); if (!JSX_EXTENSIONS.has(path.extname(id))) { diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index 20a6bee820d3..ac223021b202 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -16,6 +16,8 @@ import { } from './plugins.js'; import { getFileInfo, handleExtendsNotSupported, parseFrontmatter } from './utils.js'; +const FLAG = '?astro-asset-ssr'; + const RAW_CONTENT_ERROR = 'MDX does not support rawContent()! If you need to read the Markdown contents to calculate values (ex. reading time), we suggest injecting frontmatter via remark plugins. Learn more on our docs: https://docs.astro.build/en/guides/integrations-guide/mdx/#inject-frontmatter-via-remark-or-rehype-plugins'; @@ -100,7 +102,10 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration { }, // Override transform to alter code before MDX compilation // ex. inject layouts - async transform(_, id) { + async transform(_, unresolvedId) { + let id = unresolvedId.endsWith(`.mdx${FLAG}`) + ? unresolvedId.replace(FLAG, '') + : unresolvedId; if (!id.endsWith('mdx')) return; // Read code from file manually to prevent Vite from parsing `import.meta.env` expressions @@ -129,7 +134,10 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration { { name: '@astrojs/mdx-postprocess', // These transforms must happen *after* JSX runtime transformations - transform(code, id) { + transform(code, unresolvedId) { + let id = unresolvedId.endsWith(`.mdx${FLAG}`) + ? unresolvedId.replace(FLAG, '') + : unresolvedId; if (!id.endsWith('.mdx')) return; const [moduleImports, moduleExports] = parseESM(code); From 56197476723cc0561f23793bacc465b4d1d3be2a Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 19 Oct 2022 15:00:39 -0400 Subject: [PATCH 010/183] wip: exclude astro-ssr-asset styles from build --- packages/astro/src/core/build/vite-plugin-css.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index 8183ff1ddeef..97715cb05a05 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -14,6 +14,7 @@ import { getPageDatasByHoistedScriptId, isHoistedScript, } from './internal.js'; +import { FLAG } from '../../vite-plugin-asset-ssr/index.js'; interface PluginOptions { internals: BuildInternals; @@ -102,14 +103,25 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] } }; + let exclude: Set = new Set(); + for (const [_, chunk] of Object.entries(bundle)) { + if (chunk.type === 'chunk' && chunk.facadeModuleId?.includes(FLAG)) { + exclude.add(chunk.fileName); + for (const imp of chunk.imports) { + exclude.add(imp); + } + } + } + for (const [_, chunk] of Object.entries(bundle)) { if (chunk.type === 'chunk') { const c = chunk; - if ('viteMetadata' in chunk) { + + if ('viteMetadata' in chunk && !exclude.has((chunk as OutputChunk).fileName)) { const meta = chunk['viteMetadata'] as ViteMetadata; // Chunks that have the viteMetadata.importedCss are CSS chunks - if (meta.importedCss.size) { + if (meta.importedCss.size && !(chunk as OutputChunk).facadeModuleId?.endsWith(FLAG)) { // In the SSR build, keep track of all CSS chunks' modules as the client build may // duplicate them, e.g. for `client:load` components that render in SSR and client // for hydation. From 6f6c39680a792bd27d91166009780f5fbcd00818 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 20 Oct 2022 17:46:07 -0400 Subject: [PATCH 011/183] revert: SSR asset flag babel changes --- packages/astro/src/jsx/babel.ts | 3 +-- packages/astro/src/vite-plugin-jsx/index.ts | 3 +-- packages/integrations/mdx/src/index.ts | 10 ++-------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/astro/src/jsx/babel.ts b/packages/astro/src/jsx/babel.ts index 0369ec33c781..88b01ad8de8a 100644 --- a/packages/astro/src/jsx/babel.ts +++ b/packages/astro/src/jsx/babel.ts @@ -4,7 +4,6 @@ import { AstroErrorData } from '../core/errors/errors-data.js'; import { AstroError } from '../core/errors/errors.js'; import { resolvePath } from '../core/util.js'; import { HydrationDirectiveProps } from '../runtime/server/hydration.js'; -import { FLAG } from '../vite-plugin-asset-ssr/index.js'; import type { PluginMetadata } from '../vite-plugin-astro/types'; const ClientOnlyPlaceholder = 'astro-client-only'; @@ -187,7 +186,7 @@ export default function astroJSX(): PluginObj { const node = path.node; // Skip automatic `_components` in MDX files if ( - (state.filename?.endsWith('.mdx') || state.filename?.endsWith('.mdx' + FLAG)) && + state.filename?.endsWith('.mdx') && t.isJSXIdentifier(node.object) && node.object.name === '_components' ) { diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index eac0741a3b4a..33cbbdda6352 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -11,7 +11,6 @@ import { error } from '../core/logger/core.js'; import { removeQueryString } from '../core/path.js'; import { detectImportSource } from './import-source.js'; import tagExportsPlugin from './tag.js'; -import { FLAG } from '../vite-plugin-asset-ssr/index.js'; const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.mdx']); const IMPORT_STATEMENTS: Record = { @@ -128,7 +127,7 @@ export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugi defaultJSXRendererEntry = [...jsxRenderersIntegrationOnly.entries()][0]; }, async transform(code, unresolvedId, opts) { - let id = unresolvedId.endsWith(`.mdx${FLAG}`) ? unresolvedId.replace(FLAG, '') : unresolvedId; + let id = unresolvedId; const ssr = Boolean(opts?.ssr); id = removeQueryString(id); diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index ac223021b202..107dd6a6e1ab 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -16,8 +16,6 @@ import { } from './plugins.js'; import { getFileInfo, handleExtendsNotSupported, parseFrontmatter } from './utils.js'; -const FLAG = '?astro-asset-ssr'; - const RAW_CONTENT_ERROR = 'MDX does not support rawContent()! If you need to read the Markdown contents to calculate values (ex. reading time), we suggest injecting frontmatter via remark plugins. Learn more on our docs: https://docs.astro.build/en/guides/integrations-guide/mdx/#inject-frontmatter-via-remark-or-rehype-plugins'; @@ -103,9 +101,7 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration { // Override transform to alter code before MDX compilation // ex. inject layouts async transform(_, unresolvedId) { - let id = unresolvedId.endsWith(`.mdx${FLAG}`) - ? unresolvedId.replace(FLAG, '') - : unresolvedId; + let id = unresolvedId; if (!id.endsWith('mdx')) return; // Read code from file manually to prevent Vite from parsing `import.meta.env` expressions @@ -135,9 +131,7 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration { name: '@astrojs/mdx-postprocess', // These transforms must happen *after* JSX runtime transformations transform(code, unresolvedId) { - let id = unresolvedId.endsWith(`.mdx${FLAG}`) - ? unresolvedId.replace(FLAG, '') - : unresolvedId; + let id = unresolvedId; if (!id.endsWith('.mdx')) return; const [moduleImports, moduleExports] = parseESM(code); From 8e6895766d972176902f2e4fe60dfa9c26cea597 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 20 Oct 2022 17:47:43 -0400 Subject: [PATCH 012/183] feat: only load CSS when mdx is rendered! --- packages/astro/src/core/build/graph.ts | 4 +- packages/astro/src/core/build/static-build.ts | 2 + packages/astro/src/core/build/types.ts | 1 + .../astro/src/core/build/vite-plugin-css.ts | 43 ++++++++++------ packages/astro/src/core/create-vite.ts | 2 + .../astro/src/vite-plugin-asset-ssr/index.ts | 50 ++++++++++++++++++- 6 files changed, 85 insertions(+), 17 deletions(-) diff --git a/packages/astro/src/core/build/graph.ts b/packages/astro/src/core/build/graph.ts index 2f7deac7fe09..5edb07a710e2 100644 --- a/packages/astro/src/core/build/graph.ts +++ b/packages/astro/src/core/build/graph.ts @@ -6,6 +6,7 @@ import { resolvedPagesVirtualModuleId } from '../app/index.js'; export function* walkParentInfos( id: string, ctx: { getModuleInfo: GetModuleInfo }, + until?: (importer: string) => boolean, depth = 0, order = 0, seen = new Set(), @@ -19,12 +20,13 @@ export function* walkParentInfos( } yield [info, depth, order]; } + if (until?.(id)) return; const importers = (info?.importers || []).concat(info?.dynamicImporters || []); for (const imp of importers) { if (seen.has(imp)) { continue; } - yield* walkParentInfos(imp, ctx, ++depth, order, seen, id); + yield* walkParentInfos(imp, ctx, until, ++depth, order, seen, id); } } diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index c184652bfe1d..723fbdea5af8 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -14,6 +14,7 @@ import { emptyDir, removeDir } from '../../core/fs/index.js'; import { prependForwardSlash } from '../../core/path.js'; import { isModeServerWithNoAdapter } from '../../core/util.js'; import { runHookBuildSetup } from '../../integrations/index.js'; +import { assetSsrPlugin } from '../../vite-plugin-asset-ssr/index.js'; import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { info } from '../logger/core.js'; @@ -168,6 +169,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp }), vitePluginPrerender(opts, internals), ...(viteConfig.plugins || []), + assetSsrPlugin({ internals }), // SSR needs to be last ssr && vitePluginSSR(internals, settings.adapter!), ], diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts index 57518fb42ff3..45432a371754 100644 --- a/packages/astro/src/core/build/types.ts +++ b/packages/astro/src/core/build/types.ts @@ -21,6 +21,7 @@ export interface PageBuildData { route: RouteData; moduleSpecifier: string; css: Map; + delayedCss?: Set; hoistedScript: { type: 'inline' | 'external'; value: string } | undefined; } export type AllPagesData = Record; diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index 97715cb05a05..8e6bea63e4d3 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -14,7 +14,7 @@ import { getPageDatasByHoistedScriptId, isHoistedScript, } from './internal.js'; -import { FLAG } from '../../vite-plugin-asset-ssr/index.js'; +import { DELAYED_ASSET_FLAG } from '../../vite-plugin-asset-ssr/index.js'; interface PluginOptions { internals: BuildInternals; @@ -103,25 +103,15 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] } }; - let exclude: Set = new Set(); - for (const [_, chunk] of Object.entries(bundle)) { - if (chunk.type === 'chunk' && chunk.facadeModuleId?.includes(FLAG)) { - exclude.add(chunk.fileName); - for (const imp of chunk.imports) { - exclude.add(imp); - } - } - } - for (const [_, chunk] of Object.entries(bundle)) { if (chunk.type === 'chunk') { const c = chunk; - if ('viteMetadata' in chunk && !exclude.has((chunk as OutputChunk).fileName)) { + if ('viteMetadata' in chunk) { const meta = chunk['viteMetadata'] as ViteMetadata; // Chunks that have the viteMetadata.importedCss are CSS chunks - if (meta.importedCss.size && !(chunk as OutputChunk).facadeModuleId?.endsWith(FLAG)) { + if (meta.importedCss.size) { // In the SSR build, keep track of all CSS chunks' modules as the client build may // duplicate them, e.g. for `client:load` components that render in SSR and client // for hydation. @@ -157,8 +147,31 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] // For this CSS chunk, walk parents until you find a page. Add the CSS to that page. for (const id of Object.keys(c.modules)) { - for (const [pageInfo, depth, order] of walkParentInfos(id, this)) { - if (moduleIsTopLevelPage(pageInfo)) { + for (const [pageInfo, depth, order] of walkParentInfos( + id, + this, + function until(importer) { + return importer.endsWith(DELAYED_ASSET_FLAG); + } + )) { + if (pageInfo.id.endsWith(DELAYED_ASSET_FLAG)) { + for (const parent of walkParentInfos(id, this)) { + console.log('walking parent...'); + const parentInfo = parent[0]; + if (moduleIsTopLevelPage(parentInfo)) { + const pageViteID = parentInfo.id; + const pageData = getPageDataByViteID(internals, pageViteID); + if (pageData) { + if (!pageData.delayedCss) { + pageData.delayedCss = new Set(); + } + for (const css of meta.importedCss) { + pageData.delayedCss.add(css); + } + } + } + } + } else if (moduleIsTopLevelPage(pageInfo)) { const pageViteID = pageInfo.id; const pageData = getPageDataByViteID(internals, pageViteID); if (pageData) { diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index bb5585fc10ae..19a65e8d1a6d 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -23,6 +23,7 @@ import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js'; import { createCustomViteLogger } from './errors/dev/index.js'; import { resolveDependency } from './util.js'; import { astroContentPlugin } from '../content/vite-plugin.js'; +import { injectDelayedAssetPlugin } from '../vite-plugin-asset-ssr/index.js'; interface CreateViteOptions { settings: AstroSettings; @@ -118,6 +119,7 @@ export async function createVite( astroHeadPropagationPlugin({ settings }), settings.config.experimental.prerender && astroScannerPlugin({ settings, logging }), astroContentPlugin({ settings, logging }), + injectDelayedAssetPlugin(), ], publicDir: fileURLToPath(settings.config.publicDir), root: fileURLToPath(settings.config.root), diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index 4cbc8d8ce31a..95e4824f0c6f 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -1 +1,49 @@ -export const FLAG = '?astro-asset-ssr'; +import { Plugin } from 'vite'; +import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; +import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; + +const assetPlaceholder = `'@@ASTRO-ASSET-PLACEHOLDER@@'`; + +export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; + +export function injectDelayedAssetPlugin(): Plugin { + return { + name: 'astro-inject-delayed-asset-plugin', + enforce: 'post', + load(id) { + if (id.endsWith(DELAYED_ASSET_FLAG)) { + const code = ` + export { Content } from ${JSON.stringify(id.replace(DELAYED_ASSET_FLAG, ''))}; + export const collectedCss = ${assetPlaceholder} + `; + return code; + } + }, + }; +} + +export function assetSsrPlugin({ internals }: { internals: BuildInternals }): Plugin { + return { + name: 'astro-asset-ssr-plugin', + async generateBundle(_options, bundle) { + for (const [_, chunk] of Object.entries(bundle)) { + if (chunk.type === 'chunk' && chunk.code.includes(assetPlaceholder)) { + for (const id of Object.keys(chunk.modules)) { + for (const [pageInfo, depth, order] of walkParentInfos(id, this)) { + if (moduleIsTopLevelPage(pageInfo)) { + const pageViteID = pageInfo.id; + const pageData = getPageDataByViteID(internals, pageViteID); + if (pageData) { + chunk.code = chunk.code.replace( + assetPlaceholder, + JSON.stringify([...(pageData.delayedCss ?? [])]) + ); + } + } + } + } + } + } + }, + }; +} From 73212adf6c3ab9e11cfb33daf3357eced600f63d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Seery?= Date: Mon, 24 Oct 2022 11:24:52 -0300 Subject: [PATCH 013/183] fix(vercel): Include all files inside `dist/` instead of only `entry.mjs` (#5175) --- .changeset/nine-roses-explain.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nine-roses-explain.md diff --git a/.changeset/nine-roses-explain.md b/.changeset/nine-roses-explain.md new file mode 100644 index 000000000000..0b4c8e0199d3 --- /dev/null +++ b/.changeset/nine-roses-explain.md @@ -0,0 +1,5 @@ +--- +'@astrojs/vercel': patch +--- + +Edge adapter includes all the generated files (all files inside `dist/`) instead of only `entry.mjs` From c11ebd025e4b8592de2b18148f261309082c6e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=B1=E6=9E=9C=E5=B1=B1=E5=A4=A7=E5=9C=A3?= <316783812@qq.com> Date: Tue, 25 Oct 2022 02:17:23 +0800 Subject: [PATCH 014/183] chore: only-allow pnpm (#5131) --- package.json | 1 + pnpm-lock.yaml | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index eccec29bbd34..fa438bd770d0 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "eslint-plugin-no-only-tests": "^2.6.0", "eslint-plugin-prettier": "^4.0.0", "execa": "^6.1.0", + "only-allow": "^1.1.1", "organize-imports-cli": "^0.10.0", "prettier": "^2.7.0", "prettier-plugin-astro": "^0.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85b11b5d622e..e563ad44a940 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,7 @@ importers: eslint-plugin-no-only-tests: ^2.6.0 eslint-plugin-prettier: ^4.0.0 execa: ^6.1.0 + only-allow: ^1.1.1 organize-imports-cli: ^0.10.0 prettier: ^2.7.0 prettier-plugin-astro: ^0.7.0 @@ -51,6 +52,7 @@ importers: eslint-plugin-no-only-tests: 2.6.0 eslint-plugin-prettier: 4.2.1_v7o5sx5x3wbs57ifz6wc4f76we execa: 6.1.0 + only-allow: 1.1.1 organize-imports-cli: 0.10.0 prettier: 2.7.1 prettier-plugin-astro: 0.7.0 @@ -10547,7 +10549,6 @@ packages: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} dependencies: string-width: 4.2.3 - dev: false /ansi-colors/4.1.1: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} @@ -10862,6 +10863,20 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} dev: false + /boxen/4.2.0: + resolution: {integrity: sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==} + engines: {node: '>=8'} + dependencies: + ansi-align: 3.0.1 + camelcase: 5.3.1 + chalk: 3.0.0 + cli-boxes: 2.2.1 + string-width: 4.2.3 + term-size: 2.2.1 + type-fest: 0.8.1 + widest-line: 3.1.0 + dev: true + /boxen/6.2.1: resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -11042,6 +11057,14 @@ packages: escape-string-regexp: 1.0.5 supports-color: 5.5.0 + /chalk/3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -11130,6 +11153,11 @@ packages: escape-string-regexp: 5.0.0 dev: true + /cli-boxes/2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + dev: true + /cli-boxes/3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -15511,6 +15539,14 @@ packages: dependencies: mimic-fn: 4.0.0 + /only-allow/1.1.1: + resolution: {integrity: sha512-WE01hpInLQUF3MKK7vhu4p1VZLKb4rL4d+CI3rwwwsToXELx6YawNFhZy3rVU3rpQpI9kF9zFMk4OjB3xwXdJA==} + hasBin: true + dependencies: + boxen: 4.2.0 + which-pm-runs: 1.1.0 + dev: true + /open/8.4.0: resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} engines: {node: '>=12'} @@ -17292,6 +17328,7 @@ packages: /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + requiresBuild: true /source-map/0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} @@ -18750,7 +18787,6 @@ packages: /which-pm-runs/1.1.0: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} engines: {node: '>=4'} - dev: false /which-pm/2.0.0: resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} @@ -18779,6 +18815,13 @@ packages: string-width: 4.2.3 dev: false + /widest-line/3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + dependencies: + string-width: 4.2.3 + dev: true + /widest-line/4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} From 2d6aeefa997a3a6579affd4e42b4fb86dbbc2428 Mon Sep 17 00:00:00 2001 From: Ben Holmes Date: Mon, 24 Oct 2022 14:27:37 -0400 Subject: [PATCH 015/183] [MDX] Support `recmaPlugins` config (#5146) * feat: support recma plugins * chore: add `test:match` to MDX * chore: changeset * docs: add recmaPlugins to README --- .changeset/clever-keys-hammer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clever-keys-hammer.md diff --git a/.changeset/clever-keys-hammer.md b/.changeset/clever-keys-hammer.md new file mode 100644 index 000000000000..5e91f47e470b --- /dev/null +++ b/.changeset/clever-keys-hammer.md @@ -0,0 +1,5 @@ +--- +'@astrojs/mdx': patch +--- + +Support recmaPlugins config option From 21240d327834d1f3c7649f8cb15266cf48ef428c Mon Sep 17 00:00:00 2001 From: Tony Sullivan Date: Mon, 24 Oct 2022 18:30:02 +0000 Subject: [PATCH 016/183] removes default content-visibility styles from image components (#5180) --- .changeset/chilly-experts-add.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/chilly-experts-add.md diff --git a/.changeset/chilly-experts-add.md b/.changeset/chilly-experts-add.md new file mode 100644 index 000000000000..46c228c7d176 --- /dev/null +++ b/.changeset/chilly-experts-add.md @@ -0,0 +1,9 @@ +--- +'@astrojs/image': minor +--- + +Removes the `content-visibility: auto` styling added by the `` and `` components. + +**Why:** The [content-visibility](https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility) style is rarely needed for an `` and can actually break certain layouts. + +**Migration:** Do images in your layout actually depend on `content-visibility`? No problem! You can add these styles where needed in your own component styles. From 334363c7abc1ce487cf6cfa915c96c8b21d0a772 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 14:44:28 -0400 Subject: [PATCH 017/183] wip: check renderContent works --- .../with-content/src/content/blog/nested/entry.md | 2 ++ examples/with-content/src/pages/index.astro | 3 +++ examples/with-content/src/render-content.ts | 13 +++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 examples/with-content/src/render-content.ts diff --git a/examples/with-content/src/content/blog/nested/entry.md b/examples/with-content/src/content/blog/nested/entry.md index eef4b3a76dfd..da3a1e88c0cb 100644 --- a/examples/with-content/src/content/blog/nested/entry.md +++ b/examples/with-content/src/content/blog/nested/entry.md @@ -1,3 +1,5 @@ --- title: Nested! --- + +Here's some **content.** diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index b477a02999f7..4b53517c4f30 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -1,9 +1,11 @@ --- import { fetchContent, fetchContentByEntry } from '.astro'; +import { renderContent } from '../render-content'; const blog = await fetchContent('blog'); const nestedEntry = await fetchContentByEntry('blog', 'nested/entry.md'); +const { Content } = await renderContent(nestedEntry); --- @@ -16,6 +18,7 @@ const nestedEntry = await fetchContentByEntry('blog', 'nested/entry.md');

{nestedEntry.id} {nestedEntry.data.title}

+ {blog.map((entry) => (

{entry.slug} - {entry.data.title} diff --git a/examples/with-content/src/render-content.ts b/examples/with-content/src/render-content.ts new file mode 100644 index 000000000000..b95ab4a80090 --- /dev/null +++ b/examples/with-content/src/render-content.ts @@ -0,0 +1,13 @@ +const content = import.meta.glob('./content/**/*.md'); + +export async function renderContent(entry: { id: string } | string) { + // TODO: respect srcDir + const unresolvedContentKey = typeof entry === 'object' ? entry.id : entry; + const contentKey = `./content/${unresolvedContentKey}`; + const modImport = content[contentKey]; + + if (!modImport) throw new Error(`${JSON.stringify(unresolvedContentKey)} does not exist!`); + + const mod = await modImport(); + return mod; +} From 9a068df160955d962761fa4df9cd71b4ee568231 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 15:24:09 -0400 Subject: [PATCH 018/183] nit: injectDelayedAssetPlugin doesn't need enforce --- packages/astro/src/vite-plugin-asset-ssr/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index 95e4824f0c6f..dfbd5c1c7871 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -9,7 +9,6 @@ export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; export function injectDelayedAssetPlugin(): Plugin { return { name: 'astro-inject-delayed-asset-plugin', - enforce: 'post', load(id) { if (id.endsWith(DELAYED_ASSET_FLAG)) { const code = ` From 7ea7270f743f4ebf36958df9414238f98245761b Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 15:43:40 -0400 Subject: [PATCH 019/183] feat: render content with generated entries --- examples/with-content/src/render-content.ts | 9 ++++----- packages/astro/src/content/vite-plugin.ts | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/with-content/src/render-content.ts b/examples/with-content/src/render-content.ts index b95ab4a80090..aaae430fd15a 100644 --- a/examples/with-content/src/render-content.ts +++ b/examples/with-content/src/render-content.ts @@ -1,12 +1,11 @@ -const content = import.meta.glob('./content/**/*.md'); +import { renderContentMap } from '../.astro/render-content-generated'; export async function renderContent(entry: { id: string } | string) { // TODO: respect srcDir - const unresolvedContentKey = typeof entry === 'object' ? entry.id : entry; - const contentKey = `./content/${unresolvedContentKey}`; - const modImport = content[contentKey]; + const contentKey = typeof entry === 'object' ? entry.id : entry; + const modImport = renderContentMap[contentKey]; - if (!modImport) throw new Error(`${JSON.stringify(unresolvedContentKey)} does not exist!`); + if (!modImport) throw new Error(`${JSON.stringify(contentKey)} does not exist!`); const mod = await modImport(); return mod; diff --git a/packages/astro/src/content/vite-plugin.ts b/packages/astro/src/content/vite-plugin.ts index ea0289dce3f5..4a64848967e8 100644 --- a/packages/astro/src/content/vite-plugin.ts +++ b/packages/astro/src/content/vite-plugin.ts @@ -18,6 +18,8 @@ const CONTENT_BASE = 'content-generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; +const EXPERIMENTAL_RENDER_CONTENT_OUTPUT = 'render-content-generated.ts'; + export function astroContentPlugin({ settings, logging, @@ -115,12 +117,21 @@ async function generateContent( ]); let contentMapStr = ''; let contentMapTypesStr = ''; + let renderContentStr = 'export const renderContentMap = {'; for (const [collectionName, entries] of contentEntries) { contentMapStr += `${JSON.stringify(collectionName)}: ${JSON.stringify( Object.fromEntries(entries), null, 2 )},`; + renderContentStr += entries.reduce((str, [, { id }]) => { + return ( + str + + `\n${JSON.stringify(id)}: () => import(${JSON.stringify( + new URL(id, dirs.contentDir).pathname + )}),` + ); + }, ''); const types = entries.map(([key, { id, slug }]) => { return [ key, @@ -136,6 +147,8 @@ async function generateContent( )}\n},`; } + renderContentStr += '}'; + let schemaMapStr = ''; let schemaMapTypesStr = ''; for (const { key: collectionName, ...entry } of schemaEntries) { @@ -163,6 +176,7 @@ async function generateContent( await Promise.all([ fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), + fs.writeFile(new URL(EXPERIMENTAL_RENDER_CONTENT_OUTPUT, dirs.cacheDir), renderContentStr), ]); } From 1284ed8e8acbd85f50a30773a26c71136bb983d7 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Mon, 24 Oct 2022 16:14:43 -0400 Subject: [PATCH 020/183] feat: renderContent style injection POC --- examples/with-content/astro.config.mjs | 6 ++++-- examples/with-content/package.json | 1 + examples/with-content/src/blog-styles.css | 3 +++ examples/with-content/src/content/blog/nested/entry.md | 5 ----- examples/with-content/src/content/blog/nested/entry.mdx | 7 +++++++ examples/with-content/src/pages/index.astro | 4 ++-- examples/with-content/src/render-content.ts | 8 +++++++- packages/astro/src/content/vite-plugin.ts | 3 ++- pnpm-lock.yaml | 2 ++ 9 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 examples/with-content/src/blog-styles.css delete mode 100644 examples/with-content/src/content/blog/nested/entry.md create mode 100644 examples/with-content/src/content/blog/nested/entry.mdx diff --git a/examples/with-content/astro.config.mjs b/examples/with-content/astro.config.mjs index 882e6515a67e..905a6c32318e 100644 --- a/examples/with-content/astro.config.mjs +++ b/examples/with-content/astro.config.mjs @@ -1,4 +1,6 @@ import { defineConfig } from 'astro/config'; +import mdx from '@astrojs/mdx'; -// https://astro.build/config -export default defineConfig({}); +export default defineConfig({ + integrations: [mdx()] +}); diff --git a/examples/with-content/package.json b/examples/with-content/package.json index 97705290e786..7a19fdb4d7ba 100644 --- a/examples/with-content/package.json +++ b/examples/with-content/package.json @@ -11,6 +11,7 @@ "astro": "astro" }, "dependencies": { + "@astrojs/mdx": "^0.11.4", "astro": "^1.5.2" }, "devDependencies": { diff --git a/examples/with-content/src/blog-styles.css b/examples/with-content/src/blog-styles.css new file mode 100644 index 000000000000..cce2effe20c1 --- /dev/null +++ b/examples/with-content/src/blog-styles.css @@ -0,0 +1,3 @@ +body { + font-family: 'Comic Sans MS', sans-serif; +} diff --git a/examples/with-content/src/content/blog/nested/entry.md b/examples/with-content/src/content/blog/nested/entry.md deleted file mode 100644 index da3a1e88c0cb..000000000000 --- a/examples/with-content/src/content/blog/nested/entry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Nested! ---- - -Here's some **content.** diff --git a/examples/with-content/src/content/blog/nested/entry.mdx b/examples/with-content/src/content/blog/nested/entry.mdx new file mode 100644 index 000000000000..354470fbded8 --- /dev/null +++ b/examples/with-content/src/content/blog/nested/entry.mdx @@ -0,0 +1,7 @@ +--- +title: Nested! +--- + +import '../../../blog-styles.css'; + +Here's some **content!** diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index 4b53517c4f30..f274789b6f0a 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -4,8 +4,8 @@ import { renderContent } from '../render-content'; const blog = await fetchContent('blog'); -const nestedEntry = await fetchContentByEntry('blog', 'nested/entry.md'); -const { Content } = await renderContent(nestedEntry); +const nestedEntry = await fetchContentByEntry('blog', 'nested/entry.mdx'); +const { Content } = await renderContent(nestedEntry, $$result); --- diff --git a/examples/with-content/src/render-content.ts b/examples/with-content/src/render-content.ts index aaae430fd15a..0f957053e7e8 100644 --- a/examples/with-content/src/render-content.ts +++ b/examples/with-content/src/render-content.ts @@ -1,6 +1,6 @@ import { renderContentMap } from '../.astro/render-content-generated'; -export async function renderContent(entry: { id: string } | string) { +export async function renderContent(entry: { id: string } | string, $$result?: any) { // TODO: respect srcDir const contentKey = typeof entry === 'object' ? entry.id : entry; const modImport = renderContentMap[contentKey]; @@ -8,5 +8,11 @@ export async function renderContent(entry: { id: string } | string) { if (!modImport) throw new Error(`${JSON.stringify(contentKey)} does not exist!`); const mod = await modImport(); + + if (import.meta.env.PROD && 'collectedCss' in mod && $$result) { + for (const cssAsset of mod.collectedCss) { + $$result.links.add({ props: { rel: 'stylesheet', href: cssAsset }, children: '' }); + } + } return mod; } diff --git a/packages/astro/src/content/vite-plugin.ts b/packages/astro/src/content/vite-plugin.ts index 4a64848967e8..77853d1c6e92 100644 --- a/packages/astro/src/content/vite-plugin.ts +++ b/packages/astro/src/content/vite-plugin.ts @@ -6,6 +6,7 @@ import { cyan } from 'kleur/colors'; import matter from 'gray-matter'; import { info, LogOptions } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; +import { DELAYED_ASSET_FLAG } from '../vite-plugin-asset-ssr/index.js'; type TypedMapEntry = { key: string; value: string; type: string }; type Dirs = { @@ -128,7 +129,7 @@ async function generateContent( return ( str + `\n${JSON.stringify(id)}: () => import(${JSON.stringify( - new URL(id, dirs.contentDir).pathname + new URL(id, dirs.contentDir).pathname + DELAYED_ASSET_FLAG )}),` ); }, ''); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e563ad44a940..26ee7119f95b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -287,9 +287,11 @@ importers: examples/with-content: specifiers: + '@astrojs/mdx': ^0.11.4 astro: ^1.5.2 zod: ^3.17.3 dependencies: + '@astrojs/mdx': link:../../packages/integrations/mdx astro: link:../../packages/astro devDependencies: zod: 3.19.1 From 4fbefa49b69d388f0d50cbfae596ac48ee4a871e Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 13:27:56 -0400 Subject: [PATCH 021/183] wip: scrappy renderContent binding --- packages/astro/src/core/create-vite.ts | 2 +- .../astro/src/vite-plugin-asset-ssr/index.ts | 42 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 19a65e8d1a6d..388e44c03634 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -119,7 +119,7 @@ export async function createVite( astroHeadPropagationPlugin({ settings }), settings.config.experimental.prerender && astroScannerPlugin({ settings, logging }), astroContentPlugin({ settings, logging }), - injectDelayedAssetPlugin(), + injectDelayedAssetPlugin({ settings }), ], publicDir: fileURLToPath(settings.config.publicDir), root: fileURLToPath(settings.config.root), diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index dfbd5c1c7871..1b59db17576d 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -1,14 +1,28 @@ import { Plugin } from 'vite'; import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; +import MagicString from 'magic-string'; +import ancestor from 'common-ancestor-path'; +import { AstroSettings } from '../@types/astro.js'; const assetPlaceholder = `'@@ASTRO-ASSET-PLACEHOLDER@@'`; export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; -export function injectDelayedAssetPlugin(): Plugin { +export function injectDelayedAssetPlugin({ settings }: { settings: AstroSettings }): Plugin { + // copied from page-ssr.ts + function normalizeFilename(filename: string) { + if (filename.startsWith('/@fs')) { + filename = filename.slice('/@fs'.length); + } else if (filename.startsWith('/') && !ancestor(filename, settings.config.root.pathname)) { + filename = new URL('.' + filename, settings.config.root).pathname; + } + return filename; + } + return { name: 'astro-inject-delayed-asset-plugin', + enforce: 'post', load(id) { if (id.endsWith(DELAYED_ASSET_FLAG)) { const code = ` @@ -18,6 +32,32 @@ export function injectDelayedAssetPlugin(): Plugin { return code; } }, + transform(code, id, options) { + if (options?.ssr && id.endsWith('.astro')) { + const filename = normalizeFilename(id); + + console.log('this', this.getModuleInfo(id)); + + // TODO: check if we should actually insert this + const s = new MagicString(code, { filename }); + s.prepend(`import { renderContent as $$renderContent } from '~renderContent';\n`); + // TODO: not this + const frontmatterPreamble = '$$createComponent(async ($$result, $$props, $$slots) => {'; + const indexOfFrontmatterPreamble = code.indexOf(frontmatterPreamble); + + if (indexOfFrontmatterPreamble < 0) return; + + s.appendLeft( + indexOfFrontmatterPreamble + frontmatterPreamble.length, + '\nlet renderContent = $$renderContent.bind($$result);\n' + ); + + return { + code: s.toString(), + map: s.generateMap(), + }; + } + }, }; } From 39af82fbd10ff3cb6bffc58722dbb08a1a4d15bb Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 13:42:27 -0400 Subject: [PATCH 022/183] deps: parse-imports --- packages/astro/package.json | 1 + pnpm-lock.yaml | 58 ++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 546e5f8a5c4e..166a659431ff 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -136,6 +136,7 @@ "magic-string": "^0.27.0", "mime": "^3.0.0", "ora": "^6.1.0", + "parse-imports": "^1.1.0", "path-browserify": "^1.0.1", "path-to-regexp": "^6.2.1", "postcss": "^8.4.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26ee7119f95b..763eec695179 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -291,7 +291,7 @@ importers: astro: ^1.5.2 zod: ^3.17.3 dependencies: - '@astrojs/mdx': link:../../packages/integrations/mdx + '@astrojs/mdx': 0.11.6 astro: link:../../packages/astro devDependencies: zod: 3.19.1 @@ -451,6 +451,7 @@ importers: node-fetch: ^3.2.5 node-mocks-http: ^1.11.0 ora: ^6.1.0 + parse-imports: ^1.1.0 path-browserify: ^1.0.1 path-to-regexp: ^6.2.1 postcss: ^8.4.14 @@ -519,6 +520,7 @@ importers: magic-string: 0.27.0 mime: 3.0.0 ora: 6.1.2 + parse-imports: 1.1.0 path-browserify: 1.0.1 path-to-regexp: 6.2.1 postcss: 8.4.19 @@ -1128,9 +1130,6 @@ importers: '@astrojs/node': link:../../../../integrations/node astro: link:../../.. - packages/astro/test/benchmark/simple/dist/server: - specifiers: {} - packages/astro/test/fixtures/0-css: specifiers: '@astrojs/react': workspace:* @@ -1671,9 +1670,6 @@ importers: dependencies: astro: link:../../.. - packages/astro/test/fixtures/config-vite/dist: - specifiers: {} - packages/astro/test/fixtures/css-assets: specifiers: '@astrojs/test-font-awesome-package': file:packages/font-awesome @@ -3966,6 +3962,31 @@ packages: vscode-uri: 3.0.6 dev: false + /@astrojs/mdx/0.11.6: + resolution: {integrity: sha512-MEi8CZuz4vXn74/jCb0CMOAKEqd6nsLrShYfE+ddb1//14CwuaVGbXN7V6/zGj8ZvBhFk/darBbNpJBmzfbI6A==} + engines: {node: ^14.18.0 || >=16.12.0} + dependencies: + '@astrojs/prism': 1.0.2 + '@mdx-js/mdx': 2.1.5 + '@mdx-js/rollup': 2.1.5 + acorn: 8.8.1 + es-module-lexer: 0.10.5 + estree-util-visit: 1.2.0 + github-slugger: 1.5.0 + gray-matter: 4.0.3 + kleur: 4.1.5 + rehype-raw: 6.1.1 + remark-frontmatter: 4.0.1 + remark-gfm: 3.0.1 + remark-smartypants: 2.0.0 + shiki: 0.11.1 + unist-util-visit: 4.1.1 + vfile: 5.3.5 + transitivePeerDependencies: + - rollup + - supports-color + dev: false + /@astrojs/micromark-extension-mdx-jsx/1.0.3: resolution: {integrity: sha512-O15+i2DGG0qb1R/1SYbFXgOKDGbYdV8iJMtuboVb1S9YFQfMOJxaCMco0bhXQI7PmZcQ4pZWIjT5oZ64dXUtRA==} dependencies: @@ -3986,6 +4007,13 @@ packages: '@astrojs/webapi': 1.1.1 dev: false + /@astrojs/prism/1.0.2: + resolution: {integrity: sha512-o3cUVoAuALDqdN5puNlsN2eO4Yi1kDh68YO8V7o6U4Ts+J/mMayzlJ7JsgYAmob0xrf/XnADVgu8khfMv/w3uA==} + engines: {node: ^14.18.0 || >=16.12.0} + dependencies: + prismjs: 1.29.0 + dev: false + /@astrojs/webapi/1.1.1: resolution: {integrity: sha512-yeUvP27PoiBK/WCxyQzC4HLYZo4Hg6dzRd/dTsL50WGlAQVCwWcqzVJrIZKvzNDNaW/fIXutZTmdj6nec0PIGg==} dependencies: @@ -11870,6 +11898,10 @@ packages: resolution: {integrity: sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==} dev: false + /es-module-lexer/0.4.1: + resolution: {integrity: sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==} + dev: false + /es-module-lexer/1.1.0: resolution: {integrity: sha512-fJg+1tiyEeS8figV+fPcPpm8WqJEflG3yPU0NOm5xMvrNkuiy7HzX/Ljng4Y0hAoiw4/3hQTCFYw+ub8+a2pRA==} dev: false @@ -15714,6 +15746,14 @@ packages: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + /parse-imports/1.1.0: + resolution: {integrity: sha512-ov3Rc9e3wX9+BLR7nFx08+ThdLJfzi8ZXQrqZXfmuxdL+JCCN24m2uuBFBoaa/yyiZ9s8HjcHGDKSXJbKAyDQA==} + engines: {node: '>= 12.17'} + dependencies: + es-module-lexer: 0.4.1 + slashes: 2.0.2 + dev: false + /parse-json/5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -17257,6 +17297,10 @@ packages: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} + /slashes/2.0.2: + resolution: {integrity: sha512-68p+QkFAQQRetIUzNXAdktNJr8AYLxJukjBegYQz8F7VATsBJG621UYtY/vS2j9jerxdJ1k6Tc25K4DXEw1d5w==} + dev: false + /slice-ansi/5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} From 24434f0fb1e462504d28a841c1525578169f1158 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 13:42:48 -0400 Subject: [PATCH 023/183] feat: only bind renderContent when imported --- .../astro/src/vite-plugin-asset-ssr/index.ts | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index 1b59db17576d..900b3bca35a7 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -3,12 +3,26 @@ import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; import MagicString from 'magic-string'; import ancestor from 'common-ancestor-path'; +import parseImports from 'parse-imports'; import { AstroSettings } from '../@types/astro.js'; const assetPlaceholder = `'@@ASTRO-ASSET-PLACEHOLDER@@'`; export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; +function getRenderContentImportName(parseImportRes: Awaited>) { + for (const imp of parseImportRes) { + if (imp.moduleSpecifier.value === '.astro/render') { + for (const namedImp of imp.importClause?.named ?? []) { + if (namedImp.specifier === 'renderContent') { + // Use `binding` to support `import { renderContent as somethingElse }... + return namedImp.binding; + } + } + } + } +} + export function injectDelayedAssetPlugin({ settings }: { settings: AstroSettings }): Plugin { // copied from page-ssr.ts function normalizeFilename(filename: string) { @@ -32,15 +46,15 @@ export function injectDelayedAssetPlugin({ settings }: { settings: AstroSettings return code; } }, - transform(code, id, options) { + async transform(code, id, options) { if (options?.ssr && id.endsWith('.astro')) { const filename = normalizeFilename(id); - console.log('this', this.getModuleInfo(id)); + let renderContentImportName = getRenderContentImportName(await parseImports(code)); + if (!renderContentImportName) return; - // TODO: check if we should actually insert this const s = new MagicString(code, { filename }); - s.prepend(`import { renderContent as $$renderContent } from '~renderContent';\n`); + s.prepend(`import { renderContent as $$renderContent } from '.astro/render';\n`); // TODO: not this const frontmatterPreamble = '$$createComponent(async ($$result, $$props, $$slots) => {'; const indexOfFrontmatterPreamble = code.indexOf(frontmatterPreamble); @@ -49,7 +63,7 @@ export function injectDelayedAssetPlugin({ settings }: { settings: AstroSettings s.appendLeft( indexOfFrontmatterPreamble + frontmatterPreamble.length, - '\nlet renderContent = $$renderContent.bind($$result);\n' + `\nlet ${renderContentImportName} = $$renderContent.bind($$result);\n` ); return { From fdc51a7479f9a59cb564665e2cfcde23f07ec56e Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 13:44:34 -0400 Subject: [PATCH 024/183] feat: new-and-improved render-content --- examples/with-content/src/render-content.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/with-content/src/render-content.ts b/examples/with-content/src/render-content.ts index 0f957053e7e8..1ef64ef86817 100644 --- a/examples/with-content/src/render-content.ts +++ b/examples/with-content/src/render-content.ts @@ -1,6 +1,6 @@ import { renderContentMap } from '../.astro/render-content-generated'; -export async function renderContent(entry: { id: string } | string, $$result?: any) { +export async function renderContent(this: any, entry: { id: string } | string) { // TODO: respect srcDir const contentKey = typeof entry === 'object' ? entry.id : entry; const modImport = renderContentMap[contentKey]; @@ -9,9 +9,9 @@ export async function renderContent(entry: { id: string } | string, $$result?: a const mod = await modImport(); - if (import.meta.env.PROD && 'collectedCss' in mod && $$result) { + if (import.meta.env.PROD && 'collectedCss' in mod && 'links' in (this ?? {})) { for (const cssAsset of mod.collectedCss) { - $$result.links.add({ props: { rel: 'stylesheet', href: cssAsset }, children: '' }); + this.links.add({ props: { rel: 'stylesheet', href: cssAsset }, children: '' }); } } return mod; From c7a8eca4a756877a4fab8059e521996b845519e3 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 13:45:00 -0400 Subject: [PATCH 025/183] fix: update example to use render-content --- examples/with-content/src/pages/index.astro | 4 ++-- examples/with-content/tsconfig.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index f274789b6f0a..abccf7c3ff65 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -1,11 +1,11 @@ --- import { fetchContent, fetchContentByEntry } from '.astro'; -import { renderContent } from '../render-content'; +import { renderContent as somethingElse } from '.astro/render'; const blog = await fetchContent('blog'); const nestedEntry = await fetchContentByEntry('blog', 'nested/entry.mdx'); -const { Content } = await renderContent(nestedEntry, $$result); +const { Content } = await somethingElse(nestedEntry); --- diff --git a/examples/with-content/tsconfig.json b/examples/with-content/tsconfig.json index 21281e77a029..0040bd150e28 100644 --- a/examples/with-content/tsconfig.json +++ b/examples/with-content/tsconfig.json @@ -4,7 +4,8 @@ "strictNullChecks": true, "baseUrl": ".", "paths": { - ".astro": [".astro/content-generated"] + ".astro": [".astro/content-generated"], + ".astro/render": ["./src/render-content"], } } } From c361de23c96c292cc09c894005825ae96b35ef8f Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 16:30:26 -0400 Subject: [PATCH 026/183] refactor: extract normalizeFilename helper --- .../astro/src/vite-plugin-asset-ssr/index.ts | 18 ++++------------- .../astro/src/vite-plugin-scripts/page-ssr.ts | 4 +--- packages/astro/src/vite-plugin-utils/index.ts | 20 ++++++++++++------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index 900b3bca35a7..a220c94ae3f8 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -2,9 +2,9 @@ import { Plugin } from 'vite'; import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; import MagicString from 'magic-string'; -import ancestor from 'common-ancestor-path'; import parseImports from 'parse-imports'; import { AstroSettings } from '../@types/astro.js'; +import { normalizeFilename } from '../vite-plugin-utils/index.js'; const assetPlaceholder = `'@@ASTRO-ASSET-PLACEHOLDER@@'`; @@ -24,16 +24,6 @@ function getRenderContentImportName(parseImportRes: Awaited s.stage === 'page-ssr'); if (!hasInjectedScript) return; - const filename = normalizeFilename(id, settings.config); + const filename = normalizeFilename({ fileName: id, projectRoot: settings.config.root }); let fileURL: URL; try { fileURL = new URL(`file://${filename}`); diff --git a/packages/astro/src/vite-plugin-utils/index.ts b/packages/astro/src/vite-plugin-utils/index.ts index 02f388eb0721..a5753d45035a 100644 --- a/packages/astro/src/vite-plugin-utils/index.ts +++ b/packages/astro/src/vite-plugin-utils/index.ts @@ -1,5 +1,5 @@ import ancestor from 'common-ancestor-path'; -import { Data } from 'vfile'; +import type { Data } from 'vfile'; import type { AstroConfig, MarkdownAstroData } from '../@types/astro'; import { appendExtension, @@ -62,11 +62,17 @@ export function safelyGetAstroData(vfileData: Data): MarkdownAstroData { * * as absolute file paths with forward slashes. */ -export function normalizeFilename(filename: string, config: AstroConfig) { - if (filename.startsWith('/@fs')) { - filename = filename.slice('/@fs'.length); - } else if (filename.startsWith('/') && !ancestor(filename, config.root.pathname)) { - filename = new URL('.' + filename, config.root).pathname; +export function normalizeFilename({ + fileName, + projectRoot, +}: { + fileName: string; + projectRoot: URL; +}) { + if (fileName.startsWith('/@fs')) { + fileName = fileName.slice('/@fs'.length); + } else if (fileName.startsWith('/') && !ancestor(fileName, projectRoot.pathname)) { + fileName = new URL('.' + fileName, projectRoot).pathname; } - return removeLeadingForwardSlashWindows(filename); + return removeLeadingForwardSlashWindows(fileName); } From 5f0289df5ac47d639da9ff10d67513fdcb5bdc23 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 17:47:06 -0400 Subject: [PATCH 027/183] feat: move renderContent to `.astro` --- examples/with-content/src/pages/index.astro | 21 +++++----- examples/with-content/src/render-content.ts | 18 --------- packages/astro/content-generated.d.ts | 6 +++ packages/astro/content-generated.mjs | 6 ++- packages/astro/src/content/internals.ts | 21 ++++++++++ packages/astro/src/content/vite-plugin.ts | 2 +- .../astro/src/vite-plugin-asset-ssr/index.ts | 38 +++++++++++-------- 7 files changed, 64 insertions(+), 48 deletions(-) delete mode 100644 examples/with-content/src/render-content.ts diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index abccf7c3ff65..1ae2b7403628 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -1,11 +1,7 @@ --- -import { fetchContent, fetchContentByEntry } from '.astro'; -import { renderContent as somethingElse } from '.astro/render'; +import { fetchContent } from '.astro'; const blog = await fetchContent('blog'); - -const nestedEntry = await fetchContentByEntry('blog', 'nested/entry.mdx'); -const { Content } = await somethingElse(nestedEntry); --- @@ -17,12 +13,13 @@ const { Content } = await somethingElse(nestedEntry); Astro -

{nestedEntry.id} {nestedEntry.data.title}

- - {blog.map((entry) => ( -

- {entry.slug} - {entry.data.title} -

- ))} +

Ma blog!

+ diff --git a/examples/with-content/src/render-content.ts b/examples/with-content/src/render-content.ts deleted file mode 100644 index 1ef64ef86817..000000000000 --- a/examples/with-content/src/render-content.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { renderContentMap } from '../.astro/render-content-generated'; - -export async function renderContent(this: any, entry: { id: string } | string) { - // TODO: respect srcDir - const contentKey = typeof entry === 'object' ? entry.id : entry; - const modImport = renderContentMap[contentKey]; - - if (!modImport) throw new Error(`${JSON.stringify(contentKey)} does not exist!`); - - const mod = await modImport(); - - if (import.meta.env.PROD && 'collectedCss' in mod && 'links' in (this ?? {})) { - for (const cssAsset of mod.collectedCss) { - this.links.add({ props: { rel: 'stylesheet', href: cssAsset }, children: '' }); - } - } - return mod; -} diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index 7d805358b02c..a83e82bd44be 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -22,3 +22,9 @@ export declare function fetchContent< collection: C, filter?: (data: typeof contentMap[C][E]) => boolean ): Promise; +export declare function renderContent< + C extends keyof typeof contentMap, + E extends keyof typeof contentMap[C] +>( + entryOrEntryId: typeof contentMap[C][E] | typeof contentMap[C][E]['id'] +): Promise<{ Content: any }>; diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index a255377bff26..2aed0a1e02b3 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -1,5 +1,5 @@ -import { z } from 'zod'; -import { getErrorMsg, parseEntryData } from 'astro/content/internals'; +import { getErrorMsg, parseEntryData, createRenderContent } from 'astro/content/internals'; +import { renderContentMap } from './render-content-generated.mjs'; const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; /** Used to stub out `schemaMap` entries that don't have a `~schema.ts` file */ @@ -51,3 +51,5 @@ export async function fetchContent( return entries; } } + +export const renderContent = createRenderContent(renderContentMap); diff --git a/packages/astro/src/content/internals.ts b/packages/astro/src/content/internals.ts index 1234c77e9090..8827e7315399 100644 --- a/packages/astro/src/content/internals.ts +++ b/packages/astro/src/content/internals.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { prependForwardSlash } from '../core/path.js'; export async function parseEntryData( collection: string, @@ -61,3 +62,23 @@ export const getErrorMsg = { schemaNamedExp: (collection: string) => new Error(`${collection}/~schema needs a named \`schema\` export.`), }; + +export function createRenderContent(renderContentMap: Record Promise>) { + return async function renderContent(this: any, entryOrEntryId: { id: string } | string) { + const contentKey = typeof entryOrEntryId === 'object' ? entryOrEntryId.id : entryOrEntryId; + const modImport = renderContentMap[contentKey]; + if (!modImport) throw new Error(`${JSON.stringify(contentKey)} does not exist!`); + + const mod = await modImport(); + + if (import.meta.env.PROD && 'collectedCss' in mod && 'links' in (this ?? {})) { + for (const cssAsset of mod.collectedCss) { + this.links.add({ + props: { rel: 'stylesheet', href: prependForwardSlash(cssAsset) }, + children: '', + }); + } + } + return mod; + }; +} diff --git a/packages/astro/src/content/vite-plugin.ts b/packages/astro/src/content/vite-plugin.ts index 77853d1c6e92..17ad4ff39708 100644 --- a/packages/astro/src/content/vite-plugin.ts +++ b/packages/astro/src/content/vite-plugin.ts @@ -19,7 +19,7 @@ const CONTENT_BASE = 'content-generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; -const EXPERIMENTAL_RENDER_CONTENT_OUTPUT = 'render-content-generated.ts'; +const EXPERIMENTAL_RENDER_CONTENT_OUTPUT = 'render-content-generated.mjs'; export function astroContentPlugin({ settings, diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index a220c94ae3f8..514887af2991 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -10,19 +10,6 @@ const assetPlaceholder = `'@@ASTRO-ASSET-PLACEHOLDER@@'`; export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; -function getRenderContentImportName(parseImportRes: Awaited>) { - for (const imp of parseImportRes) { - if (imp.moduleSpecifier.value === '.astro/render') { - for (const namedImp of imp.importClause?.named ?? []) { - if (namedImp.specifier === 'renderContent') { - // Use `binding` to support `import { renderContent as somethingElse }... - return namedImp.binding; - } - } - } - } -} - export function injectDelayedAssetPlugin({ settings }: { settings: AstroSettings }): Plugin { return { name: 'astro-inject-delayed-asset-plugin', @@ -38,13 +25,15 @@ export function injectDelayedAssetPlugin({ settings }: { settings: AstroSettings }, async transform(code, id, options) { if (options?.ssr && id.endsWith('.astro')) { - let renderContentImportName = getRenderContentImportName(await parseImports(code)); + let renderContentImportName = getRenderContentImportName( + await parseImports(escapeViteEnvReferences(code)) + ); if (!renderContentImportName) return; const s = new MagicString(code, { filename: normalizeFilename({ fileName: id, projectRoot: settings.config.root }), }); - s.prepend(`import { renderContent as $$renderContent } from '.astro/render';\n`); + s.prepend(`import { renderContent as $$renderContent } from '.astro';\n`); // TODO: not this const frontmatterPreamble = '$$createComponent(async ($$result, $$props, $$slots) => {'; const indexOfFrontmatterPreamble = code.indexOf(frontmatterPreamble); @@ -90,3 +79,22 @@ export function assetSsrPlugin({ internals }: { internals: BuildInternals }): Pl }, }; } + +function getRenderContentImportName(parseImportRes: Awaited>) { + for (const imp of parseImportRes) { + if (imp.moduleSpecifier.value === '.astro' && imp.importClause?.named) { + for (const namedImp of imp.importClause.named) { + if (namedImp.specifier === 'renderContent') { + // Use `binding` to support `import { renderContent as somethingElse }... + return namedImp.binding; + } + } + } + } + return undefined; +} + +// Necessary to avoid checking `import.meta` during import crawl +function escapeViteEnvReferences(code: string) { + return code.replace(/import\.meta/g, 'import\\u002Emeta'); +} From f1ed391fcb80e57087f7c86150cef085a858e688 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 25 Oct 2022 17:47:24 -0400 Subject: [PATCH 028/183] =?UTF-8?q?feat:=20add=20`getStaticPaths`=20demo?= =?UTF-8?q?=20=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../with-content/src/pages/[...slug].astro | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 examples/with-content/src/pages/[...slug].astro diff --git a/examples/with-content/src/pages/[...slug].astro b/examples/with-content/src/pages/[...slug].astro new file mode 100644 index 000000000000..df43e9db5901 --- /dev/null +++ b/examples/with-content/src/pages/[...slug].astro @@ -0,0 +1,28 @@ +--- +import { fetchContent, renderContent } from '.astro'; + +export async function getStaticPaths() { + const blog = await fetchContent('blog'); + return blog.map(({ slug, id, data }) => ({ + params: { slug }, + props: { id, title: data.title }, + })); +} + +const { id, title } = Astro.props; +const { Content } = await renderContent(id); +--- + + + + + + + + Astro + + +

{title}

+ + + From bd596f751a31bb881c5a1366c3839e30a5398401 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 26 Oct 2022 10:39:34 -0400 Subject: [PATCH 029/183] chore: remove console log --- packages/astro/src/core/build/vite-plugin-css.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index 8e6bea63e4d3..bba30e0a281e 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -156,7 +156,6 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] )) { if (pageInfo.id.endsWith(DELAYED_ASSET_FLAG)) { for (const parent of walkParentInfos(id, this)) { - console.log('walking parent...'); const parentInfo = parent[0]; if (moduleIsTopLevelPage(parentInfo)) { const pageViteID = parentInfo.id; From dbe1f2afdeda947d3155a544c308696be89fee7a Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 26 Oct 2022 10:39:44 -0400 Subject: [PATCH 030/183] refactor: internals -> internal --- packages/astro/content-generated.mjs | 2 +- packages/astro/package.json | 2 +- packages/astro/src/content/{internals.ts => internal.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/astro/src/content/{internals.ts => internal.ts} (100%) diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index 2aed0a1e02b3..cbf28c8086c0 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -1,4 +1,4 @@ -import { getErrorMsg, parseEntryData, createRenderContent } from 'astro/content/internals'; +import { getErrorMsg, parseEntryData, createRenderContent } from 'astro/content/internal'; import { renderContentMap } from './render-content-generated.mjs'; const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; diff --git a/packages/astro/package.json b/packages/astro/package.json index 166a659431ff..e87f4576babb 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -42,7 +42,7 @@ "types": "./config.d.ts", "default": "./config.mjs" }, - "./content/internals": "./dist/content/internals.js", + "./content/internal": "./dist/content/internal.js", "./app": "./dist/core/app/index.js", "./app/node": "./dist/core/app/node.js", "./client/*": "./dist/runtime/client/*", diff --git a/packages/astro/src/content/internals.ts b/packages/astro/src/content/internal.ts similarity index 100% rename from packages/astro/src/content/internals.ts rename to packages/astro/src/content/internal.ts From 019dc44b369b135f43b33099ac306b743f9ab242 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 26 Oct 2022 11:07:20 -0400 Subject: [PATCH 031/183] fix: style bleed on dynamic routes --- packages/astro/src/core/build/types.ts | 2 +- packages/astro/src/core/build/vite-plugin-css.ts | 12 +++++++++--- packages/astro/src/vite-plugin-asset-ssr/index.ts | 13 +++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts index 45432a371754..62d3cca62157 100644 --- a/packages/astro/src/core/build/types.ts +++ b/packages/astro/src/core/build/types.ts @@ -21,7 +21,7 @@ export interface PageBuildData { route: RouteData; moduleSpecifier: string; css: Map; - delayedCss?: Set; + contentDeferredCss?: Map>; hoistedScript: { type: 'inline' | 'external'; value: string } | undefined; } export type AllPagesData = Record; diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index bba30e0a281e..e64c83a922f7 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -161,11 +161,17 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] const pageViteID = parentInfo.id; const pageData = getPageDataByViteID(internals, pageViteID); if (pageData) { - if (!pageData.delayedCss) { - pageData.delayedCss = new Set(); + if (!pageData.contentDeferredCss) { + // TODO: make required to avoid `!` + pageData.contentDeferredCss = new Map(); } for (const css of meta.importedCss) { - pageData.delayedCss.add(css); + const existingCss = + pageData.contentDeferredCss!.get(pageInfo.id) ?? new Set(); + pageData.contentDeferredCss!.set( + pageInfo.id, + new Set([...existingCss, css]) + ); } } } diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index 514887af2991..8c576999c862 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -65,12 +65,13 @@ export function assetSsrPlugin({ internals }: { internals: BuildInternals }): Pl if (moduleIsTopLevelPage(pageInfo)) { const pageViteID = pageInfo.id; const pageData = getPageDataByViteID(internals, pageViteID); - if (pageData) { - chunk.code = chunk.code.replace( - assetPlaceholder, - JSON.stringify([...(pageData.delayedCss ?? [])]) - ); - } + if (!pageData) continue; + const entryDeferredCss = pageData.contentDeferredCss?.get(id); + if (!entryDeferredCss) continue; + chunk.code = chunk.code.replace( + assetPlaceholder, + JSON.stringify([...entryDeferredCss]) + ); } } } From bfcfba046b3fa6f83be2a1006a2e915e30ad7a4a Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 27 Oct 2022 10:54:23 -0400 Subject: [PATCH 032/183] chore: remove console log --- packages/astro/src/content/internal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 8827e7315399..b3afc3930393 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -47,7 +47,6 @@ const errorMap: z.ZodErrorMap = (error, ctx) => { // WARNING: MAXIMUM JANK AHEAD function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) { - console.log({ rawFrontmatter, frontmatterKey }); const indexOfFrontmatterKey = rawFrontmatter.indexOf(`\n${frontmatterKey}`); if (indexOfFrontmatterKey === -1) return 0; @@ -73,6 +72,7 @@ export function createRenderContent(renderContentMap: Record Promi if (import.meta.env.PROD && 'collectedCss' in mod && 'links' in (this ?? {})) { for (const cssAsset of mod.collectedCss) { + // console.log('that', this, mod, contentKey, renderContentMap); this.links.add({ props: { rel: 'stylesheet', href: prependForwardSlash(cssAsset) }, children: '', From ac5a28ffca7864f9c2619f76011dc185384eae22 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 27 Oct 2022 10:54:46 -0400 Subject: [PATCH 033/183] refactor: move rendercontentmap inside same file --- packages/astro/content-generated.mjs | 5 ++++- packages/astro/src/content/vite-plugin.ts | 11 ++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index cbf28c8086c0..453625d02387 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -1,5 +1,4 @@ import { getErrorMsg, parseEntryData, createRenderContent } from 'astro/content/internal'; -import { renderContentMap } from './render-content-generated.mjs'; const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; /** Used to stub out `schemaMap` entries that don't have a `~schema.ts` file */ @@ -17,6 +16,10 @@ export const schemaMap = { // GENERATED_SCHEMA_MAP_ENTRIES }; +export const renderContentMap = { + // GENERATED_RENDER_CONTENT_MAP_ENTRIES +}; + export async function fetchContentByEntry( /** @type {string} */ collection, /** @type {string} */ entryKey diff --git a/packages/astro/src/content/vite-plugin.ts b/packages/astro/src/content/vite-plugin.ts index 17ad4ff39708..39a79e7853d9 100644 --- a/packages/astro/src/content/vite-plugin.ts +++ b/packages/astro/src/content/vite-plugin.ts @@ -19,8 +19,6 @@ const CONTENT_BASE = 'content-generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; -const EXPERIMENTAL_RENDER_CONTENT_OUTPUT = 'render-content-generated.mjs'; - export function astroContentPlugin({ settings, logging, @@ -118,7 +116,7 @@ async function generateContent( ]); let contentMapStr = ''; let contentMapTypesStr = ''; - let renderContentStr = 'export const renderContentMap = {'; + let renderContentStr = ''; for (const [collectionName, entries] of contentEntries) { contentMapStr += `${JSON.stringify(collectionName)}: ${JSON.stringify( Object.fromEntries(entries), @@ -148,8 +146,6 @@ async function generateContent( )}\n},`; } - renderContentStr += '}'; - let schemaMapStr = ''; let schemaMapTypesStr = ''; for (const { key: collectionName, ...entry } of schemaEntries) { @@ -157,7 +153,9 @@ async function generateContent( schemaMapTypesStr += `${JSON.stringify(collectionName)}: ${entry.type},\n`; } - generatedMaps = generatedMaps.replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr); + generatedMaps = generatedMaps + .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) + .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); generatedMapTypes = generatedMapTypes.replace( '// GENERATED_CONTENT_MAP_ENTRIES', contentMapTypesStr @@ -177,7 +175,6 @@ async function generateContent( await Promise.all([ fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), - fs.writeFile(new URL(EXPERIMENTAL_RENDER_CONTENT_OUTPUT, dirs.cacheDir), renderContentStr), ]); } From f74c7cdfa527ffa445e3bd7982b31df6859e49c5 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 27 Oct 2022 13:56:25 -0400 Subject: [PATCH 034/183] fix: style bleed in dev! --- packages/astro/src/content/internal.ts | 15 ++++-- packages/astro/src/core/create-vite.ts | 2 +- packages/astro/src/core/render/dev/vite.ts | 3 ++ .../astro/src/vite-plugin-asset-ssr/index.ts | 46 ++++++++++++++++--- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index b3afc3930393..2205c1a0305b 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -70,15 +70,22 @@ export function createRenderContent(renderContentMap: Record Promi const mod = await modImport(); - if (import.meta.env.PROD && 'collectedCss' in mod && 'links' in (this ?? {})) { - for (const cssAsset of mod.collectedCss) { - // console.log('that', this, mod, contentKey, renderContentMap); + if ('collectedLinks' in mod && 'links' in (this ?? {})) { + for (const link of mod.collectedLinks) { this.links.add({ - props: { rel: 'stylesheet', href: prependForwardSlash(cssAsset) }, + props: { rel: 'stylesheet', href: prependForwardSlash(link) }, children: '', }); } } + if ('collectedStyles' in mod && 'styles' in (this ?? {})) { + for (const style of mod.collectedStyles) { + this.styles.add({ + props: {}, + children: style, + }); + } + } return mod; }; } diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 388e44c03634..573ee1d4dc93 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -119,7 +119,7 @@ export async function createVite( astroHeadPropagationPlugin({ settings }), settings.config.experimental.prerender && astroScannerPlugin({ settings, logging }), astroContentPlugin({ settings, logging }), - injectDelayedAssetPlugin({ settings }), + injectDelayedAssetPlugin({ settings, mode }), ], publicDir: fileURLToPath(settings.config.publicDir), root: fileURLToPath(settings.config.root), diff --git a/packages/astro/src/core/render/dev/vite.ts b/packages/astro/src/core/render/dev/vite.ts index ce864c6b4022..2addbc9763e7 100644 --- a/packages/astro/src/core/render/dev/vite.ts +++ b/packages/astro/src/core/render/dev/vite.ts @@ -2,6 +2,7 @@ import type { ModuleLoader, ModuleNode } from '../../module-loader/index'; import npath from 'path'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js'; +import { DELAYED_ASSET_FLAG } from '../../../vite-plugin-asset-ssr/index.js'; import { unwrapId } from '../../util.js'; import { STYLE_EXTENSIONS } from '../util.js'; @@ -22,6 +23,8 @@ export async function* crawlGraph( ): AsyncGenerator { const id = unwrapId(_id); const importedModules = new Set(); + if (id.endsWith(DELAYED_ASSET_FLAG)) return; + const moduleEntriesForId = isRootFile ? // "getModulesByFile" pulls from a delayed module cache (fun implementation detail), // So we can get up-to-date info on initial server load. diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/vite-plugin-asset-ssr/index.ts index 8c576999c862..0c34d163115b 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/vite-plugin-asset-ssr/index.ts @@ -1,29 +1,63 @@ -import { Plugin } from 'vite'; +import { Plugin, ViteDevServer } from 'vite'; import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; import MagicString from 'magic-string'; import parseImports from 'parse-imports'; import { AstroSettings } from '../@types/astro.js'; import { normalizeFilename } from '../vite-plugin-utils/index.js'; +import { getStylesForURL } from '../core/render/dev/css.js'; +import { pathToFileURL } from 'url'; -const assetPlaceholder = `'@@ASTRO-ASSET-PLACEHOLDER@@'`; +const LINKS_PLACEHOLDER = `[/* @@ASTRO-LINKS-PLACEHOLDER@@ */]`; +const STYLES_PLACEHOLDER = `[/* @@ASTRO-STYLES-PLACEHOLDER@@ */]`; export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; -export function injectDelayedAssetPlugin({ settings }: { settings: AstroSettings }): Plugin { +export function injectDelayedAssetPlugin({ + settings, + mode, +}: { + settings: AstroSettings; + mode: string; +}): Plugin { + let viteDevServer: ViteDevServer; return { name: 'astro-inject-delayed-asset-plugin', enforce: 'post', + configureServer(server) { + if (mode === 'dev') { + viteDevServer = server; + } + }, load(id) { if (id.endsWith(DELAYED_ASSET_FLAG)) { const code = ` export { Content } from ${JSON.stringify(id.replace(DELAYED_ASSET_FLAG, ''))}; - export const collectedCss = ${assetPlaceholder} + export const collectedLinks = ${LINKS_PLACEHOLDER}; + export const collectedStyles = ${STYLES_PLACEHOLDER}; `; return code; } }, async transform(code, id, options) { + if (id.endsWith(DELAYED_ASSET_FLAG) && viteDevServer) { + const baseId = id.replace(DELAYED_ASSET_FLAG, ''); + if (!viteDevServer.moduleGraph.getModuleById(baseId)?.ssrModule) { + await viteDevServer.ssrLoadModule(baseId); + } + const { stylesMap, urls } = await getStylesForURL( + pathToFileURL(baseId), + viteDevServer, + 'development' + ); + + return { + code: code + .replace(LINKS_PLACEHOLDER, JSON.stringify([...urls])) + .replace(STYLES_PLACEHOLDER, JSON.stringify([...stylesMap.values()])), + }; + } + if (options?.ssr && id.endsWith('.astro')) { let renderContentImportName = getRenderContentImportName( await parseImports(escapeViteEnvReferences(code)) @@ -59,7 +93,7 @@ export function assetSsrPlugin({ internals }: { internals: BuildInternals }): Pl name: 'astro-asset-ssr-plugin', async generateBundle(_options, bundle) { for (const [_, chunk] of Object.entries(bundle)) { - if (chunk.type === 'chunk' && chunk.code.includes(assetPlaceholder)) { + if (chunk.type === 'chunk' && chunk.code.includes(LINKS_PLACEHOLDER)) { for (const id of Object.keys(chunk.modules)) { for (const [pageInfo, depth, order] of walkParentInfos(id, this)) { if (moduleIsTopLevelPage(pageInfo)) { @@ -69,7 +103,7 @@ export function assetSsrPlugin({ internals }: { internals: BuildInternals }): Pl const entryDeferredCss = pageData.contentDeferredCss?.get(id); if (!entryDeferredCss) continue; chunk.code = chunk.code.replace( - assetPlaceholder, + LINKS_PLACEHOLDER, JSON.stringify([...entryDeferredCss]) ); } From cad294f5b64bea6a247f587abe212474793a769e Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 28 Oct 2022 09:44:50 -0400 Subject: [PATCH 035/183] feat: clean up example --- .../with-content/src/content/blog/columbia.md | 15 +++ .../src/content/blog/endeavour.md | 14 +++ .../src/content/blog/enterprise.md | 14 +++ .../with-content/src/content/blog/first.md | 3 - .../src/content/blog/nested/entry.mdx | 7 -- .../blog/promo/launch-week-styles.css} | 0 .../src/content/blog/promo/launch-week.mdx | 13 +++ .../with-content/src/content/blog/second.md | 3 - .../with-content/src/content/blog/~schema.ts | 7 ++ .../with-content/src/layouts/Layout.astro | 40 ++++++++ .../with-content/src/pages/[...slug].astro | 26 ++---- examples/with-content/src/pages/index.astro | 93 +++++++++++++++---- examples/with-content/tsconfig.json | 1 - 13 files changed, 185 insertions(+), 51 deletions(-) create mode 100644 examples/with-content/src/content/blog/columbia.md create mode 100644 examples/with-content/src/content/blog/endeavour.md create mode 100644 examples/with-content/src/content/blog/enterprise.md delete mode 100644 examples/with-content/src/content/blog/first.md delete mode 100644 examples/with-content/src/content/blog/nested/entry.mdx rename examples/with-content/src/{blog-styles.css => content/blog/promo/launch-week-styles.css} (100%) create mode 100644 examples/with-content/src/content/blog/promo/launch-week.mdx delete mode 100644 examples/with-content/src/content/blog/second.md create mode 100644 examples/with-content/src/layouts/Layout.astro diff --git a/examples/with-content/src/content/blog/columbia.md b/examples/with-content/src/content/blog/columbia.md new file mode 100644 index 000000000000..4971108e36ed --- /dev/null +++ b/examples/with-content/src/content/blog/columbia.md @@ -0,0 +1,15 @@ +--- +title: Columbia +description: 'Learn about the Columbia NASA space shuttle.' +publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' +tags: [space, 90s] +--- + +**Source:** [Wikipedia](https://en.wikipedia.org/wiki/Space_Shuttle_Endeavour) + +Space Shuttle Endeavour (Orbiter Vehicle Designation: OV-105) is a retired orbiter from NASA's Space Shuttle program and the fifth and final operational Shuttle built. It embarked on its first mission, STS-49, in May 1992 and its 25th and final mission, STS-134, in May 2011. STS-134 was expected to be the final mission of the Space Shuttle program, but with the authorization of STS-135, Atlantis became the last shuttle to fly. + +The United States Congress approved the construction of Endeavour in 1987 to replace the Space Shuttle Challenger, which was destroyed in 1986. + +NASA chose, on cost grounds, to build much of Endeavour from spare parts rather than refitting the Space Shuttle Enterprise, and used structural spares built during the construction of Discovery and Atlantis in its assembly. +Space Shuttle Endeavour (Orbiter Vehicle Designation: OV-105) is a retired orbiter from NASA's Space Shuttle program and the fifth and final operational Shuttle built. It embarked on its first mission, STS-49, in May 1992 and its 25th and final mission, STS-134, in May 2011. STS-134 was expected to be the final mission of the Space Shuttle program, but with the authorization of STS-135, Atlantis became the last shuttle to fly. diff --git a/examples/with-content/src/content/blog/endeavour.md b/examples/with-content/src/content/blog/endeavour.md new file mode 100644 index 000000000000..51d6e8c42178 --- /dev/null +++ b/examples/with-content/src/content/blog/endeavour.md @@ -0,0 +1,14 @@ +--- +title: Endeavour +description: 'Learn about the Endeavour NASA space shuttle.' +publishedDate: 'Sun Jul 11 2021 00:00:00 GMT-0400 (Eastern Daylight Time)' +tags: [space, 90s] +--- + +**Source:** [Wikipedia](https://en.wikipedia.org/wiki/Space_Shuttle_Endeavour) + +Space Shuttle Endeavour (Orbiter Vehicle Designation: OV-105) is a retired orbiter from NASA's Space Shuttle program and the fifth and final operational Shuttle built. It embarked on its first mission, STS-49, in May 1992 and its 25th and final mission, STS-134, in May 2011. STS-134 was expected to be the final mission of the Space Shuttle program, but with the authorization of STS-135, Atlantis became the last shuttle to fly. + +The United States Congress approved the construction of Endeavour in 1987 to replace the Space Shuttle Challenger, which was destroyed in 1986. + +NASA chose, on cost grounds, to build much of Endeavour from spare parts rather than refitting the Space Shuttle Enterprise, and used structural spares built during the construction of Discovery and Atlantis in its assembly. diff --git a/examples/with-content/src/content/blog/enterprise.md b/examples/with-content/src/content/blog/enterprise.md new file mode 100644 index 000000000000..3131e6d5df3a --- /dev/null +++ b/examples/with-content/src/content/blog/enterprise.md @@ -0,0 +1,14 @@ +--- +title: 'Enterprise' +description: 'Learn about the Enterprise NASA space shuttle.' +publishedDate: 'Tue Jun 08 2021 00:00:00 GMT-0400 (Eastern Daylight Time)' +tags: [space, 70s] +--- + +**Source:** [Wikipedia](https://en.wikipedia.org/wiki/Space_Shuttle_Enterprise) + +Space Shuttle Enterprise (Orbiter Vehicle Designation: OV-101) was the first orbiter of the Space Shuttle system. Rolled out on September 17, 1976, it was built for NASA as part of the Space Shuttle program to perform atmospheric test flights after being launched from a modified Boeing 747. It was constructed without engines or a functional heat shield. As a result, it was not capable of spaceflight. + +Originally, Enterprise had been intended to be refitted for orbital flight to become the second space-rated orbiter in service. However, during the construction of Space Shuttle Columbia, details of the final design changed, making it simpler and less costly to build Challenger around a body frame that had been built as a test article. Similarly, Enterprise was considered for refit to replace Challenger after the latter was destroyed, but Endeavour was built from structural spares instead. + +Enterprise was restored and placed on display in 2003 at the Smithsonian's new Steven F. Udvar-Hazy Center in Virginia. Following the retirement of the Space Shuttle fleet, Discovery replaced Enterprise at the Udvar-Hazy Center, and Enterprise was transferred to the Intrepid Sea, Air & Space Museum in New York City, where it has been on display since July 2012. diff --git a/examples/with-content/src/content/blog/first.md b/examples/with-content/src/content/blog/first.md deleted file mode 100644 index 5743a05d1b66..000000000000 --- a/examples/with-content/src/content/blog/first.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: 'Howdy' ---- diff --git a/examples/with-content/src/content/blog/nested/entry.mdx b/examples/with-content/src/content/blog/nested/entry.mdx deleted file mode 100644 index 354470fbded8..000000000000 --- a/examples/with-content/src/content/blog/nested/entry.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Nested! ---- - -import '../../../blog-styles.css'; - -Here's some **content!** diff --git a/examples/with-content/src/blog-styles.css b/examples/with-content/src/content/blog/promo/launch-week-styles.css similarity index 100% rename from examples/with-content/src/blog-styles.css rename to examples/with-content/src/content/blog/promo/launch-week-styles.css diff --git a/examples/with-content/src/content/blog/promo/launch-week.mdx b/examples/with-content/src/content/blog/promo/launch-week.mdx new file mode 100644 index 000000000000..4e126e08aba2 --- /dev/null +++ b/examples/with-content/src/content/blog/promo/launch-week.mdx @@ -0,0 +1,13 @@ +--- +title: 'Launch week!' +description: 'Join us for the exciting launch of SPACE BLOG' +publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' +--- + +import './launch-week-styles.css'; + +Join us for the space blog launch! + +- THIS THURSDAY +- Houston, TX +- Dress code: **interstellar casual** ✨ diff --git a/examples/with-content/src/content/blog/second.md b/examples/with-content/src/content/blog/second.md deleted file mode 100644 index c569441f455c..000000000000 --- a/examples/with-content/src/content/blog/second.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: 'Hey!' ---- diff --git a/examples/with-content/src/content/blog/~schema.ts b/examples/with-content/src/content/blog/~schema.ts index 2930ea941280..bf28557f04e3 100644 --- a/examples/with-content/src/content/blog/~schema.ts +++ b/examples/with-content/src/content/blog/~schema.ts @@ -2,4 +2,11 @@ import { z } from 'zod'; export const schema = z.object({ title: z.string(), + description: z.string().max(60, 'For SEO purposes, keep descriptions short!'), + // mark optional properties with `.optional()` + image: z.string().optional(), + tags: z.array(z.string()).default([]), + // transform to another data type with `transform` + // ex. convert date strings to Date objects + publishedDate: z.string().transform((str) => new Date(str)), }); diff --git a/examples/with-content/src/layouts/Layout.astro b/examples/with-content/src/layouts/Layout.astro new file mode 100644 index 000000000000..778a2caff39b --- /dev/null +++ b/examples/with-content/src/layouts/Layout.astro @@ -0,0 +1,40 @@ +--- +export interface Props { + title: string; +} + +const { title } = Astro.props; +--- + + + + + + + + + {title} + + + + + + diff --git a/examples/with-content/src/pages/[...slug].astro b/examples/with-content/src/pages/[...slug].astro index df43e9db5901..0d4efad00fc1 100644 --- a/examples/with-content/src/pages/[...slug].astro +++ b/examples/with-content/src/pages/[...slug].astro @@ -1,28 +1,20 @@ --- import { fetchContent, renderContent } from '.astro'; +import Layout from 'src/layouts/Layout.astro'; export async function getStaticPaths() { const blog = await fetchContent('blog'); - return blog.map(({ slug, id, data }) => ({ - params: { slug }, - props: { id, title: data.title }, - })); + return blog.map(entry => ({ + params: { slug: entry.slug }, + props: { id: entry.id, title: entry.data.title }, + })) } const { id, title } = Astro.props; const { Content } = await renderContent(id); --- - - - - - - - Astro - - -

{title}

- - - + +

{title}

+ +
diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index 1ae2b7403628..d90c44171139 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -1,25 +1,78 @@ --- import { fetchContent } from '.astro'; +import Layout from '../layouts/Layout.astro'; -const blog = await fetchContent('blog'); +const spaceBlog = await fetchContent('blog'); --- - - - - - - - Astro - - -

Ma blog!

- - - + +

My space blog 🚀

+ +
+ + diff --git a/examples/with-content/tsconfig.json b/examples/with-content/tsconfig.json index 0040bd150e28..f190bd7c4cb7 100644 --- a/examples/with-content/tsconfig.json +++ b/examples/with-content/tsconfig.json @@ -5,7 +5,6 @@ "baseUrl": ".", "paths": { ".astro": [".astro/content-generated"], - ".astro/render": ["./src/render-content"], } } } From bb301ca0c641c82235155da93a60dc92ba438f39 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 28 Oct 2022 09:45:17 -0400 Subject: [PATCH 036/183] refactor: curry generateContent --- packages/astro/src/content/vite-plugin.ts | 172 +++++++++++----------- 1 file changed, 84 insertions(+), 88 deletions(-) diff --git a/packages/astro/src/content/vite-plugin.ts b/packages/astro/src/content/vite-plugin.ts index 39a79e7853d9..83eb835e3905 100644 --- a/packages/astro/src/content/vite-plugin.ts +++ b/packages/astro/src/content/vite-plugin.ts @@ -33,6 +33,7 @@ export function astroContentPlugin({ generatedInputDir: new URL('../../', import.meta.url), }; let contentDirExists = false; + const generateContent = toGenerateContent({ logging, dirs }); return { name: 'astro-fetch-content-plugin', @@ -47,7 +48,7 @@ export function astroContentPlugin({ if (!contentDirExists) return; info(logging, 'content', 'Generating entries...'); - await generateContent(logging, dirs); + await generateContent(); }, async configureServer(viteServer) { if (contentDirExists) { @@ -69,12 +70,12 @@ export function astroContentPlugin({ } function attachListeners() { - viteServer.watcher.on('add', (file) => generateContent(logging, dirs, 'add', file)); - viteServer.watcher.on('addDir', (file) => generateContent(logging, dirs, 'addDir', file)); - viteServer.watcher.on('change', (file) => generateContent(logging, dirs, 'change', file)); - viteServer.watcher.on('unlink', (file) => generateContent(logging, dirs, 'unlink', file)); - viteServer.watcher.on('unlinkDir', (file) => - generateContent(logging, dirs, 'unlinkDir', file) + viteServer.watcher.on('add', (entryChanged) => generateContent('add', entryChanged)); + viteServer.watcher.on('addDir', (entryChanged) => generateContent('addDir', entryChanged)); + viteServer.watcher.on('change', (entryChanged) => generateContent('change', entryChanged)); + viteServer.watcher.on('unlink', (entryChanged) => generateContent('unlink', entryChanged)); + viteServer.watcher.on('unlinkDir', (entryChanged) => + generateContent('unlinkDir', entryChanged) ); } }, @@ -87,95 +88,90 @@ type Entry = [ ]; type CollectionEntry = [collectionName: string, entries: Entry[]]; -async function generateContent( - logging: LogOptions, - dirs: Dirs, - chokidarEvent?: string, - entryChanged?: string -) { - if (chokidarEvent && !entryChanged?.startsWith(dirs.contentDir.pathname)) return; +function toGenerateContent({ logging, dirs }: { logging: LogOptions; dirs: Dirs }) { + return async function generateContent(chokidarEvent?: string, entryChanged?: string) { + if (chokidarEvent && !entryChanged?.startsWith(dirs.contentDir.pathname)) return; - if (chokidarEvent === 'addDir') { - info( - logging, - 'content', - `${cyan(getCollectionName(entryChanged ?? '', dirs))} collection added` - ); - } - - let [generatedMaps, generatedMapTypes] = await Promise.all([ - fs.readFile(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8'), - fs.readFile(new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), 'utf-8'), - ]); + if (chokidarEvent === 'addDir') { + info( + logging, + 'content', + `${cyan(getCollectionName(entryChanged ?? '', dirs))} collection added` + ); + } - const collections = await glob(new URL('*', dirs.contentDir).pathname, { onlyDirectories: true }); + let [generatedMaps, generatedMapTypes] = await Promise.all([ + fs.readFile(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8'), + fs.readFile(new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), 'utf-8'), + ]); - const [contentEntries, schemaEntries] = await Promise.all([ - getContentMapEntries(collections, dirs), - getSchemaMapEntries(collections, dirs), - ]); - let contentMapStr = ''; - let contentMapTypesStr = ''; - let renderContentStr = ''; - for (const [collectionName, entries] of contentEntries) { - contentMapStr += `${JSON.stringify(collectionName)}: ${JSON.stringify( - Object.fromEntries(entries), - null, - 2 - )},`; - renderContentStr += entries.reduce((str, [, { id }]) => { - return ( - str + - `\n${JSON.stringify(id)}: () => import(${JSON.stringify( - new URL(id, dirs.contentDir).pathname + DELAYED_ASSET_FLAG - )}),` - ); - }, ''); - const types = entries.map(([key, { id, slug }]) => { - return [ - key, - `{\n id: ${JSON.stringify(id)},\n slug: ${JSON.stringify( - slug - )},\n body: string,\n data: z.infer['schema']>\n}`, - ]; + const collections = await glob(new URL('*', dirs.contentDir).pathname, { + onlyDirectories: true, }); - contentMapTypesStr += `${JSON.stringify(collectionName)}: {\n${types.map( - ([key, stringifiedValue]) => `${JSON.stringify(key)}: ${stringifiedValue}` - )}\n},`; - } - let schemaMapStr = ''; - let schemaMapTypesStr = ''; - for (const { key: collectionName, ...entry } of schemaEntries) { - schemaMapStr += `${JSON.stringify(collectionName)}: ${entry.value},\n`; - schemaMapTypesStr += `${JSON.stringify(collectionName)}: ${entry.type},\n`; - } + const [contentEntries, schemaEntries] = await Promise.all([ + getContentMapEntries(collections, dirs), + getSchemaMapEntries(collections, dirs), + ]); + let contentMapStr = ''; + let contentMapTypesStr = ''; + let renderContentStr = ''; + for (const [collectionName, entries] of contentEntries) { + contentMapStr += `${JSON.stringify(collectionName)}: ${JSON.stringify( + Object.fromEntries(entries), + null, + 2 + )},`; + renderContentStr += entries.reduce((str, [, { id }]) => { + const contentPath = new URL(id, dirs.contentDir).pathname + DELAYED_ASSET_FLAG; + return str + `\n${JSON.stringify(id)}: () => import(${JSON.stringify(contentPath)}),`; + }, ''); + const types = entries.map(([key, { id, slug }]) => { + return [ + key, + `{\n id: ${JSON.stringify(id)},\n slug: ${JSON.stringify( + slug + )},\n body: string,\n data: z.infer['schema']>\n}`, + ]; + }); + contentMapTypesStr += `${JSON.stringify(collectionName)}: {\n${types.map( + ([key, stringifiedValue]) => `${JSON.stringify(key)}: ${stringifiedValue}` + )}\n},`; + } - generatedMaps = generatedMaps - .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) - .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); - generatedMapTypes = generatedMapTypes.replace( - '// GENERATED_CONTENT_MAP_ENTRIES', - contentMapTypesStr - ); - generatedMaps = generatedMaps.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); - generatedMapTypes = generatedMapTypes.replace( - '// GENERATED_SCHEMA_MAP_ENTRIES', - schemaMapTypesStr - ); + let schemaMapStr = ''; + let schemaMapTypesStr = ''; + for (const { key: collectionName, ...entry } of schemaEntries) { + schemaMapStr += `${JSON.stringify(collectionName)}: ${entry.value},\n`; + schemaMapTypesStr += `${JSON.stringify(collectionName)}: ${entry.type},\n`; + } - try { - await fs.stat(dirs.cacheDir); - } catch { - await fs.mkdir(dirs.cacheDir); - } + generatedMaps = generatedMaps + .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) + .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); + generatedMapTypes = generatedMapTypes.replace( + '// GENERATED_CONTENT_MAP_ENTRIES', + contentMapTypesStr + ); + generatedMaps = generatedMaps.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); + generatedMapTypes = generatedMapTypes.replace( + '// GENERATED_SCHEMA_MAP_ENTRIES', + schemaMapTypesStr + ); + + try { + await fs.stat(dirs.cacheDir); + } catch { + await fs.mkdir(dirs.cacheDir); + } - await Promise.all([ - fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), - fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), - ]); + await Promise.all([ + fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), + fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), + ]); + }; } async function getContentMapEntries( From 7cdb60f0e1d8c909c4e3ed32e6766fa9b1297a13 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 28 Oct 2022 10:27:44 -0400 Subject: [PATCH 037/183] nit: add datetime demo --- examples/with-content/src/pages/index.astro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index d90c44171139..3b98d4831005 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -13,7 +13,8 @@ const spaceBlog = await fetchContent('blog');

{entry.data.title}

{entry.data.description}

-
+ + ))} From 34fd494903f1fba541f05a8d5e08fb2fdbc4541c Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 28 Oct 2022 10:40:08 -0400 Subject: [PATCH 038/183] refactor: rename and move content plugins --- .../{vite-plugin.ts => vite-plugin-content.ts} | 2 +- .../vite-plugin-delayed-assets.ts} | 12 ++++++++---- packages/astro/src/core/build/static-build.ts | 4 ++-- packages/astro/src/core/build/vite-plugin-css.ts | 2 +- packages/astro/src/core/create-vite.ts | 6 +++--- packages/astro/src/core/render/dev/vite.ts | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) rename packages/astro/src/content/{vite-plugin.ts => vite-plugin-content.ts} (99%) rename packages/astro/src/{vite-plugin-asset-ssr/index.ts => content/vite-plugin-delayed-assets.ts} (94%) diff --git a/packages/astro/src/content/vite-plugin.ts b/packages/astro/src/content/vite-plugin-content.ts similarity index 99% rename from packages/astro/src/content/vite-plugin.ts rename to packages/astro/src/content/vite-plugin-content.ts index 83eb835e3905..470b72f73eb7 100644 --- a/packages/astro/src/content/vite-plugin.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -6,7 +6,7 @@ import { cyan } from 'kleur/colors'; import matter from 'gray-matter'; import { info, LogOptions } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; -import { DELAYED_ASSET_FLAG } from '../vite-plugin-asset-ssr/index.js'; +import { DELAYED_ASSET_FLAG } from './vite-plugin-delayed-assets.js'; type TypedMapEntry = { key: string; value: string; type: string }; type Dirs = { diff --git a/packages/astro/src/vite-plugin-asset-ssr/index.ts b/packages/astro/src/content/vite-plugin-delayed-assets.ts similarity index 94% rename from packages/astro/src/vite-plugin-asset-ssr/index.ts rename to packages/astro/src/content/vite-plugin-delayed-assets.ts index 0c34d163115b..d509dfb823d2 100644 --- a/packages/astro/src/vite-plugin-asset-ssr/index.ts +++ b/packages/astro/src/content/vite-plugin-delayed-assets.ts @@ -13,7 +13,7 @@ const STYLES_PLACEHOLDER = `[/* @@ASTRO-STYLES-PLACEHOLDER@@ */]`; export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; -export function injectDelayedAssetPlugin({ +export function astroDelayedAssetPlugin({ settings, mode, }: { @@ -22,7 +22,7 @@ export function injectDelayedAssetPlugin({ }): Plugin { let viteDevServer: ViteDevServer; return { - name: 'astro-inject-delayed-asset-plugin', + name: 'astro-delayed-asset-plugin', enforce: 'post', configureServer(server) { if (mode === 'dev') { @@ -88,9 +88,13 @@ export function injectDelayedAssetPlugin({ }; } -export function assetSsrPlugin({ internals }: { internals: BuildInternals }): Plugin { +export function astroBundleDelayedAssetPlugin({ + internals, +}: { + internals: BuildInternals; +}): Plugin { return { - name: 'astro-asset-ssr-plugin', + name: 'astro-bundle-delayed-asset-plugin', async generateBundle(_options, bundle) { for (const [_, chunk] of Object.entries(bundle)) { if (chunk.type === 'chunk' && chunk.code.includes(LINKS_PLACEHOLDER)) { diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index 723fbdea5af8..f245fa955437 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -14,7 +14,7 @@ import { emptyDir, removeDir } from '../../core/fs/index.js'; import { prependForwardSlash } from '../../core/path.js'; import { isModeServerWithNoAdapter } from '../../core/util.js'; import { runHookBuildSetup } from '../../integrations/index.js'; -import { assetSsrPlugin } from '../../vite-plugin-asset-ssr/index.js'; +import { astroBundleDelayedAssetPlugin } from '../../content/vite-plugin-delayed-assets.js'; import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { info } from '../logger/core.js'; @@ -169,7 +169,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp }), vitePluginPrerender(opts, internals), ...(viteConfig.plugins || []), - assetSsrPlugin({ internals }), + astroBundleDelayedAssetPlugin({ internals }), // SSR needs to be last ssr && vitePluginSSR(internals, settings.adapter!), ], diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index e64c83a922f7..a0b9e50c7c71 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -14,7 +14,7 @@ import { getPageDatasByHoistedScriptId, isHoistedScript, } from './internal.js'; -import { DELAYED_ASSET_FLAG } from '../../vite-plugin-asset-ssr/index.js'; +import { DELAYED_ASSET_FLAG } from '../../content/vite-plugin-delayed-assets.js'; interface PluginOptions { internals: BuildInternals; diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 573ee1d4dc93..eded8995cbe4 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -22,8 +22,8 @@ import astroScriptsPlugin from '../vite-plugin-scripts/index.js'; import astroScriptsPageSSRPlugin from '../vite-plugin-scripts/page-ssr.js'; import { createCustomViteLogger } from './errors/dev/index.js'; import { resolveDependency } from './util.js'; -import { astroContentPlugin } from '../content/vite-plugin.js'; -import { injectDelayedAssetPlugin } from '../vite-plugin-asset-ssr/index.js'; +import { astroContentPlugin } from '../content/vite-plugin-content.js'; +import { astroDelayedAssetPlugin } from '../content/vite-plugin-delayed-assets.js'; interface CreateViteOptions { settings: AstroSettings; @@ -119,7 +119,7 @@ export async function createVite( astroHeadPropagationPlugin({ settings }), settings.config.experimental.prerender && astroScannerPlugin({ settings, logging }), astroContentPlugin({ settings, logging }), - injectDelayedAssetPlugin({ settings, mode }), + astroDelayedAssetPlugin({ settings, mode }), ], publicDir: fileURLToPath(settings.config.publicDir), root: fileURLToPath(settings.config.root), diff --git a/packages/astro/src/core/render/dev/vite.ts b/packages/astro/src/core/render/dev/vite.ts index 2addbc9763e7..836f50c4a368 100644 --- a/packages/astro/src/core/render/dev/vite.ts +++ b/packages/astro/src/core/render/dev/vite.ts @@ -2,7 +2,7 @@ import type { ModuleLoader, ModuleNode } from '../../module-loader/index'; import npath from 'path'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js'; -import { DELAYED_ASSET_FLAG } from '../../../vite-plugin-asset-ssr/index.js'; +import { DELAYED_ASSET_FLAG } from '../../../content/vite-plugin-delayed-assets.js'; import { unwrapId } from '../../util.js'; import { STYLE_EXTENSIONS } from '../util.js'; From c465b1cad55adb75f73d78a85d4471fc9f56d505 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 1 Nov 2022 17:20:43 -0400 Subject: [PATCH 039/183] feat: granular content updates in development --- .../astro/src/content/vite-plugin-content.ts | 412 ++++++++++++------ 1 file changed, 268 insertions(+), 144 deletions(-) diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index 470b72f73eb7..677bb37b0091 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -1,12 +1,11 @@ -import type { Plugin, ErrorPayload as ViteErrorPayload } from 'vite'; +import { Plugin, ErrorPayload as ViteErrorPayload, normalizePath } from 'vite'; import glob from 'fast-glob'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; -import { cyan } from 'kleur/colors'; +import { bold, cyan } from 'kleur/colors'; import matter from 'gray-matter'; -import { info, LogOptions } from '../core/logger/core.js'; +import { info, LogOptions, warn } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; -import { DELAYED_ASSET_FLAG } from './vite-plugin-delayed-assets.js'; type TypedMapEntry = { key: string; value: string; type: string }; type Dirs = { @@ -33,7 +32,7 @@ export function astroContentPlugin({ generatedInputDir: new URL('../../', import.meta.url), }; let contentDirExists = false; - const generateContent = toGenerateContent({ logging, dirs }); + let contentGenerator: GenerateContent; return { name: 'astro-fetch-content-plugin', @@ -42,13 +41,14 @@ export function astroContentPlugin({ await fs.stat(dirs.contentDir); contentDirExists = true; } catch { - console.log('No content directory found!', dirs.contentDir); + /* silently move on */ + return; } - if (!contentDirExists) return; - info(logging, 'content', 'Generating entries...'); - await generateContent(); + + contentGenerator = await toGenerateContent({ logging, dirs }); + await contentGenerator.init(); }, async configureServer(viteServer) { if (contentDirExists) { @@ -60,7 +60,6 @@ export function astroContentPlugin({ attachListeners(); } else { viteServer.watcher.on('addDir', (dir) => { - console.log({ dir, path: dirs.contentDir.pathname }); if (dir === dirs.contentDir.pathname) { info(logging, 'content', `Content dir found. Watching for changes`); contentDirExists = true; @@ -70,166 +69,291 @@ export function astroContentPlugin({ } function attachListeners() { - viteServer.watcher.on('add', (entryChanged) => generateContent('add', entryChanged)); - viteServer.watcher.on('addDir', (entryChanged) => generateContent('addDir', entryChanged)); - viteServer.watcher.on('change', (entryChanged) => generateContent('change', entryChanged)); - viteServer.watcher.on('unlink', (entryChanged) => generateContent('unlink', entryChanged)); - viteServer.watcher.on('unlinkDir', (entryChanged) => - generateContent('unlinkDir', entryChanged) + viteServer.watcher.on('add', (entry) => + contentGenerator.queueEvent({ name: 'add', entry }) + ); + viteServer.watcher.on('addDir', (entry) => + contentGenerator.queueEvent({ name: 'addDir', entry }) + ); + viteServer.watcher.on('change', (entry) => + contentGenerator.queueEvent({ name: 'change', entry }) + ); + viteServer.watcher.on('unlink', (entry) => + contentGenerator.queueEvent({ name: 'unlink', entry }) + ); + viteServer.watcher.on('unlinkDir', (entry) => + contentGenerator.queueEvent({ name: 'unlinkDir', entry }) ); } }, }; } -type Entry = [ - entryKey: string, - value: { id: string; slug: string; data: any; body: string; rawData: string } -]; -type CollectionEntry = [collectionName: string, entries: Entry[]]; - -function toGenerateContent({ logging, dirs }: { logging: LogOptions; dirs: Dirs }) { - return async function generateContent(chokidarEvent?: string, entryChanged?: string) { - if (chokidarEvent && !entryChanged?.startsWith(dirs.contentDir.pathname)) return; - - if (chokidarEvent === 'addDir') { - info( - logging, - 'content', - `${cyan(getCollectionName(entryChanged ?? '', dirs))} collection added` - ); - } +type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir'; +type ContentEvent = { name: ChokidarEvent; entry: string }; +type EntryInfo = { + id: string; + slug: string; + collection: string; +}; +type Entry = EntryInfo & { + data: Record; + rawData: string; + body: string; +}; + +type GenerateContent = { + init(): Promise; + queueEvent(event: ContentEvent): void; +}; + +type StringifiedInfo = { stringifiedJs: string; stringifiedType: string }; + +async function toGenerateContent({ + logging, + dirs, +}: { + logging: LogOptions; + dirs: Dirs; +}): Promise { + const contentMap: Record> = {}; + const schemaMap: Record = {}; + const renderContentMap: Record = {}; + + let events: Promise[] = []; + let debounceTimeout: NodeJS.Timeout | undefined; + let eventsSettled: Promise | undefined; + + let [generatedMaps, generatedMapTypes] = await Promise.all([ + fs.readFile(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8'), + fs.readFile(new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), 'utf-8'), + ]); + + function runEventsDebounced() { + eventsSettled = new Promise((resolve, reject) => { + try { + debounceTimeout && clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(async () => { + await Promise.all(events); + + let contentMapStr = ''; + let contentMapTypesStr = ''; + for (const collectionKey in contentMap) { + contentMapStr += `${collectionKey}: {\n`; + contentMapTypesStr += `${collectionKey}: {\n`; + for (const entryKey in contentMap[collectionKey]) { + const entry = contentMap[collectionKey][entryKey]; + contentMapStr += stringifyObjKeyValue(entryKey, entry.stringifiedJs); + contentMapTypesStr += stringifyObjKeyValue(entryKey, entry.stringifiedType); + } + contentMapStr += `},\n`; + contentMapTypesStr += `},\n`; + } + + let renderContentStr = ''; + for (const entryKey in renderContentMap) { + renderContentStr += stringifyObjKeyValue(entryKey, renderContentMap[entryKey]); + } - let [generatedMaps, generatedMapTypes] = await Promise.all([ - fs.readFile(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8'), - fs.readFile(new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), 'utf-8'), - ]); + let schemaMapStr = ''; + let schemaMapTypesStr = ''; + for (const collectionKey in schemaMap) { + const entry = schemaMap[collectionKey]; + schemaMapStr += stringifyObjKeyValue(collectionKey, entry.stringifiedJs); + schemaMapTypesStr += stringifyObjKeyValue(collectionKey, entry.stringifiedType); + } + + generatedMaps = generatedMaps + .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) + .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); + generatedMapTypes = generatedMapTypes.replace( + '// GENERATED_CONTENT_MAP_ENTRIES', + contentMapTypesStr + ); + generatedMaps = generatedMaps.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); + generatedMapTypes = generatedMapTypes.replace( + '// GENERATED_SCHEMA_MAP_ENTRIES', + schemaMapTypesStr + ); + + try { + await fs.stat(dirs.cacheDir); + } catch { + await fs.mkdir(dirs.cacheDir); + } - const collections = await glob(new URL('*', dirs.contentDir).pathname, { - onlyDirectories: true, + await Promise.all([ + fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), + fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), + ]); + + resolve(); + }, 50 /* debounce 50 ms to batch chokidar events */); + } catch (e) { + reject(e); + } }); + } - const [contentEntries, schemaEntries] = await Promise.all([ - getContentMapEntries(collections, dirs), - getSchemaMapEntries(collections, dirs), - ]); - let contentMapStr = ''; - let contentMapTypesStr = ''; - let renderContentStr = ''; - for (const [collectionName, entries] of contentEntries) { - contentMapStr += `${JSON.stringify(collectionName)}: ${JSON.stringify( - Object.fromEntries(entries), - null, - 2 - )},`; - renderContentStr += entries.reduce((str, [, { id }]) => { - const contentPath = new URL(id, dirs.contentDir).pathname + DELAYED_ASSET_FLAG; - return str + `\n${JSON.stringify(id)}: () => import(${JSON.stringify(contentPath)}),`; - }, ''); - const types = entries.map(([key, { id, slug }]) => { - return [ - key, - `{\n id: ${JSON.stringify(id)},\n slug: ${JSON.stringify( - slug - )},\n body: string,\n data: z.infer['schema']>\n}`, - ]; - }); - contentMapTypesStr += `${JSON.stringify(collectionName)}: {\n${types.map( - ([key, stringifiedValue]) => `${JSON.stringify(key)}: ${stringifiedValue}` - )}\n},`; - } + function stringifyObjKeyValue(key: string, value: string) { + return `${key}: ${value},\n`; + } + + function queueEvent(event: ContentEvent) { + events.push(onEvent(event)); + runEventsDebounced(); + } - let schemaMapStr = ''; - let schemaMapTypesStr = ''; - for (const { key: collectionName, ...entry } of schemaEntries) { - schemaMapStr += `${JSON.stringify(collectionName)}: ${entry.value},\n`; - schemaMapTypesStr += `${JSON.stringify(collectionName)}: ${entry.type},\n`; + async function init() { + const pattern = new URL('./**/', dirs.contentDir).pathname + '{*.{md,mdx},~schema.{js,mjs,ts}}'; + const entries = await glob(pattern); + for (const entry of entries) { + queueEvent({ name: 'add', entry }); } + await eventsSettled; + } + + async function onEvent(event: ContentEvent) { + if (!event.entry.startsWith(dirs.contentDir.pathname)) return; - generatedMaps = generatedMaps - .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) - .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); - generatedMapTypes = generatedMapTypes.replace( - '// GENERATED_CONTENT_MAP_ENTRIES', - contentMapTypesStr - ); - generatedMaps = generatedMaps.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); - generatedMapTypes = generatedMapTypes.replace( - '// GENERATED_SCHEMA_MAP_ENTRIES', - schemaMapTypesStr - ); - - try { - await fs.stat(dirs.cacheDir); - } catch { - await fs.mkdir(dirs.cacheDir); + if (event.name === 'addDir' || event.name === 'unlinkDir') { + const collection = path.relative(dirs.contentDir.pathname, event.entry); + const isCollectionEvent = collection.split(path.sep).length === 1; + if (!isCollectionEvent) return; + switch (event.name) { + case 'addDir': + addCollection(contentMap, collection, logging); + break; + case 'unlinkDir': + delete contentMap[collection]; + break; + } + } else { + const fileType = getEntryType(event.entry); + if (fileType === 'unknown') { + warn(logging, 'content', `${cyan(event.entry)} is not a supported file type. Skipping.`); + return; + } + const entryInfo = parseEntryInfo(event.entry, dirs); + // Not a valid `src/content/` entry. Silently return, but should be impossible? + if (entryInfo instanceof Error) return; + + const { id, slug, collection } = entryInfo; + const collectionKey = JSON.stringify(collection); + if (fileType === 'schema') { + if (event.name === 'add' && !(collectionKey in schemaMap)) { + addSchema(schemaMap, collection, event.entry, logging); + } else if (event.name === 'unlink' && collection in schemaMap) { + delete schemaMap[collection]; + } + return; + } + switch (event.name) { + case 'add': + if (!(collectionKey in contentMap)) { + addCollection(contentMap, collection, logging); + } + await addEntry(contentMap, event.entry, { id, slug, collection }, logging); + renderContentMap[JSON.stringify(id)] = `() => import(${JSON.stringify(event.entry)})`; + break; + case 'change': + await changeEntry(contentMap, event.entry, { id, slug, collection }); + break; + case 'unlink': + delete contentMap[collection][path.relative(collection, id)]; + delete renderContentMap[event.entry]; + break; + } } + } + return { init, queueEvent }; +} - await Promise.all([ - fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), - fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), - ]); +function addCollection(contentMap: Record, collection: string, logging: LogOptions) { + contentMap[JSON.stringify(collection)] = {}; + info(logging, 'content', `${cyan(collection)} collection added`); +} + +function addSchema( + schemaMap: Record, + collection: string, + entryPath: string, + logging: LogOptions +) { + const { dir, name } = path.parse(entryPath); + const pathWithExtStripped = path.join(dir, name); + const importStr = `import(${JSON.stringify(pathWithExtStripped)})`; + schemaMap[JSON.stringify(collection)] = { + stringifiedJs: importStr, + stringifiedType: `typeof ${importStr}`, }; + info(logging, 'content', `Schema added to ${collection}`); } -async function getContentMapEntries( - collections: string[], - { contentDir }: Pick -): Promise { - return Promise.all( - collections.map(async (collectionPath) => { - const collectionName = getCollectionName(collectionPath, { contentDir }); - const entries = await getEntriesByCollection(collectionPath, { contentDir }); - return [collectionName, entries]; - }) - ); +async function changeEntry( + contentMap: Record>, + entry: string, + { id, slug, collection }: EntryInfo +) { + const body = await fs.readFile(entry, 'utf-8'); + const { data, matter: rawData = '' } = parseFrontmatter(body, entry); + const collectionKey = JSON.stringify(collection); + const entryKey = JSON.stringify(path.relative(collection, id)); + contentMap[collectionKey][entryKey] = { + stringifiedJs: JSON.stringify({ + id, + slug, + data, + body, + rawData, + }), + stringifiedType: + contentMap[collectionKey][entryKey]?.stringifiedType ?? + // only do work of stringifying type for new entries + `{\n id: ${JSON.stringify(id)},\n slug: ${JSON.stringify( + slug + )},\n body: string,\n data: z.infer['schema']>\n}`, + }; } -async function getEntriesByCollection( - collectionPath: string, - { contentDir }: Pick -): Promise { - const files = await glob(`${collectionPath}/**/*.{md,mdx}`); - return Promise.all( - files.map(async (filePath) => { - const entryKey = path.relative(collectionPath, filePath); - const id = path.relative(contentDir.pathname, filePath); - const slug = entryKey.replace(/\.mdx?$/, ''); - const body = await fs.readFile(filePath, 'utf-8'); - const { data, matter } = parseFrontmatter(body, filePath); - return [entryKey, { id, slug, body, data, rawData: matter ?? '' }]; - }) - ); +async function addEntry( + contentMap: Record, + entry: string, + entryInfo: EntryInfo, + logging: LogOptions +) { + await changeEntry(contentMap, entry, entryInfo); + info(logging, 'content', `${cyan(entryInfo.slug)} added to ${bold(entryInfo.collection)}.`); } -async function getSchemaMapEntries( - collections: string[], +function parseEntryInfo( + entryPath: string, { contentDir }: Pick -): Promise { - return Promise.all( - collections.map(async (collectionPath) => { - const collectionName = getCollectionName(collectionPath, { contentDir }); - const schemaFilePath = await getSchemaFilePath(collectionPath); - const importStr = `import(${JSON.stringify(schemaFilePath)})`; - return { - key: collectionName, - value: schemaFilePath ? importStr : `defaultSchemaFile(${JSON.stringify(collectionName)})`, - type: schemaFilePath ? `typeof ${importStr}` : 'Promise', - }; - }) - ); -} +): EntryInfo | Error { + const id = normalizePath(path.relative(contentDir.pathname, entryPath)); + const collection = path.dirname(id).split(path.sep).shift(); + if (!collection) return new Error(); -async function getSchemaFilePath(collectionPath: string) { - const schemaFilePath = `${collectionPath}/~schema`; - const maybeSchemaFiles = await glob(`${schemaFilePath}.{js,mjs,ts}`); - return maybeSchemaFiles.length ? schemaFilePath : undefined; + const slug = path.relative(collection, id).replace(path.extname(id), ''); + return { + id, + slug, + collection, + }; } -function getCollectionName(collectionPath: string, { contentDir }: Pick) { - return path.relative(contentDir.pathname, collectionPath); +function getEntryType(entryPath: string): 'content' | 'schema' | 'unknown' { + const { base, ext } = path.parse(entryPath); + console.log({ entryPath, base, ext }); + if (['.md', '.mdx'].includes(ext)) { + return 'content'; + } else if (['~schema.js', '~schema.mjs', '~schema.ts'].includes(base)) { + return 'schema'; + } else { + return 'unknown'; + } } /** From 9688b50b535d3ccd7a4f50b51bf850a5a71d9099 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 1 Nov 2022 17:24:43 -0400 Subject: [PATCH 040/183] chore: console.log --- packages/astro/src/content/vite-plugin-content.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index 677bb37b0091..eaf7db6f3adf 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -346,7 +346,6 @@ function parseEntryInfo( function getEntryType(entryPath: string): 'content' | 'schema' | 'unknown' { const { base, ext } = path.parse(entryPath); - console.log({ entryPath, base, ext }); if (['.md', '.mdx'].includes(ext)) { return 'content'; } else if (['~schema.js', '~schema.mjs', '~schema.ts'].includes(base)) { From 5c5660cf4d0cdb5e8ee68f3ab452a585c85134a9 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 1 Nov 2022 18:42:36 -0400 Subject: [PATCH 041/183] refactor: move write logic, fix edge cases --- .../astro/src/content/vite-plugin-content.ts | 238 ++++++++++-------- 1 file changed, 136 insertions(+), 102 deletions(-) diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index eaf7db6f3adf..e213798a65d8 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -96,11 +96,6 @@ type EntryInfo = { slug: string; collection: string; }; -type Entry = EntryInfo & { - data: Record; - rawData: string; - body: string; -}; type GenerateContent = { init(): Promise; @@ -108,6 +103,9 @@ type GenerateContent = { }; type StringifiedInfo = { stringifiedJs: string; stringifiedType: string }; +type ContentMap = Record>; +type SchemaMap = Record; +type RenderContentMap = Record; async function toGenerateContent({ logging, @@ -116,113 +114,39 @@ async function toGenerateContent({ logging: LogOptions; dirs: Dirs; }): Promise { - const contentMap: Record> = {}; - const schemaMap: Record = {}; - const renderContentMap: Record = {}; + const contentMap: ContentMap = {}; + const schemaMap: SchemaMap = {}; + const renderContentMap: RenderContentMap = {}; let events: Promise[] = []; let debounceTimeout: NodeJS.Timeout | undefined; let eventsSettled: Promise | undefined; - let [generatedMaps, generatedMapTypes] = await Promise.all([ + let [contentJsBase, contentTypesBase] = await Promise.all([ fs.readFile(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8'), fs.readFile(new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), 'utf-8'), ]); - function runEventsDebounced() { - eventsSettled = new Promise((resolve, reject) => { - try { - debounceTimeout && clearTimeout(debounceTimeout); - debounceTimeout = setTimeout(async () => { - await Promise.all(events); - - let contentMapStr = ''; - let contentMapTypesStr = ''; - for (const collectionKey in contentMap) { - contentMapStr += `${collectionKey}: {\n`; - contentMapTypesStr += `${collectionKey}: {\n`; - for (const entryKey in contentMap[collectionKey]) { - const entry = contentMap[collectionKey][entryKey]; - contentMapStr += stringifyObjKeyValue(entryKey, entry.stringifiedJs); - contentMapTypesStr += stringifyObjKeyValue(entryKey, entry.stringifiedType); - } - contentMapStr += `},\n`; - contentMapTypesStr += `},\n`; - } - - let renderContentStr = ''; - for (const entryKey in renderContentMap) { - renderContentStr += stringifyObjKeyValue(entryKey, renderContentMap[entryKey]); - } - - let schemaMapStr = ''; - let schemaMapTypesStr = ''; - for (const collectionKey in schemaMap) { - const entry = schemaMap[collectionKey]; - schemaMapStr += stringifyObjKeyValue(collectionKey, entry.stringifiedJs); - schemaMapTypesStr += stringifyObjKeyValue(collectionKey, entry.stringifiedType); - } - - generatedMaps = generatedMaps - .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) - .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); - generatedMapTypes = generatedMapTypes.replace( - '// GENERATED_CONTENT_MAP_ENTRIES', - contentMapTypesStr - ); - generatedMaps = generatedMaps.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); - generatedMapTypes = generatedMapTypes.replace( - '// GENERATED_SCHEMA_MAP_ENTRIES', - schemaMapTypesStr - ); - - try { - await fs.stat(dirs.cacheDir); - } catch { - await fs.mkdir(dirs.cacheDir); - } - - await Promise.all([ - fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), generatedMaps), - fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), generatedMapTypes), - ]); - - resolve(); - }, 50 /* debounce 50 ms to batch chokidar events */); - } catch (e) { - reject(e); - } - }); - } - - function stringifyObjKeyValue(key: string, value: string) { - return `${key}: ${value},\n`; - } - - function queueEvent(event: ContentEvent) { - events.push(onEvent(event)); - runEventsDebounced(); - } - async function init() { const pattern = new URL('./**/', dirs.contentDir).pathname + '{*.{md,mdx},~schema.{js,mjs,ts}}'; const entries = await glob(pattern); for (const entry of entries) { - queueEvent({ name: 'add', entry }); + queueEvent({ name: 'add', entry }, { shouldLog: false }); } await eventsSettled; } - async function onEvent(event: ContentEvent) { - if (!event.entry.startsWith(dirs.contentDir.pathname)) return; + async function onEvent(event: ContentEvent, opts?: { shouldLog: boolean }) { + const shouldLog = opts?.shouldLog ?? true; if (event.name === 'addDir' || event.name === 'unlinkDir') { const collection = path.relative(dirs.contentDir.pathname, event.entry); + // If directory is multiple levels deep, it is not a collection! const isCollectionEvent = collection.split(path.sep).length === 1; if (!isCollectionEvent) return; switch (event.name) { case 'addDir': - addCollection(contentMap, collection, logging); + addCollection(contentMap, collection, shouldLog ? logging : undefined); break; case 'unlinkDir': delete contentMap[collection]; @@ -242,7 +166,7 @@ async function toGenerateContent({ const collectionKey = JSON.stringify(collection); if (fileType === 'schema') { if (event.name === 'add' && !(collectionKey in schemaMap)) { - addSchema(schemaMap, collection, event.entry, logging); + addSchema(schemaMap, collection, event.entry, shouldLog ? logging : undefined); } else if (event.name === 'unlink' && collection in schemaMap) { delete schemaMap[collection]; } @@ -251,43 +175,83 @@ async function toGenerateContent({ switch (event.name) { case 'add': if (!(collectionKey in contentMap)) { - addCollection(contentMap, collection, logging); + addCollection(contentMap, collection, shouldLog ? logging : undefined); } - await addEntry(contentMap, event.entry, { id, slug, collection }, logging); + await addEntry( + contentMap, + event.entry, + { id, slug, collection }, + shouldLog ? logging : undefined + ); renderContentMap[JSON.stringify(id)] = `() => import(${JSON.stringify(event.entry)})`; break; case 'change': await changeEntry(contentMap, event.entry, { id, slug, collection }); break; case 'unlink': - delete contentMap[collection][path.relative(collection, id)]; + delete contentMap[JSON.stringify(collection)][ + JSON.stringify(path.relative(collection, id)) + ]; delete renderContentMap[event.entry]; break; } } } + + function queueEvent(event: ContentEvent, eventOpts?: { shouldLog: boolean }) { + if (!event.entry.startsWith(dirs.contentDir.pathname)) return; + + events.push(onEvent(event, eventOpts)); + runEventsDebounced(); + } + + function runEventsDebounced() { + eventsSettled = new Promise((resolve, reject) => { + try { + debounceTimeout && clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(async () => { + await Promise.all(events); + await writeContentFiles({ + contentMap, + schemaMap, + renderContentMap, + dirs, + contentJsBase, + contentTypesBase, + }); + resolve(); + }, 50 /* debounce 50 ms to batch chokidar events */); + } catch (e) { + reject(e); + } + }); + } return { init, queueEvent }; } -function addCollection(contentMap: Record, collection: string, logging: LogOptions) { +function addCollection(contentMap: Record, collection: string, logging?: LogOptions) { contentMap[JSON.stringify(collection)] = {}; - info(logging, 'content', `${cyan(collection)} collection added`); + if (logging) { + info(logging, 'content', `${cyan(collection)} collection added`); + } } function addSchema( schemaMap: Record, collection: string, entryPath: string, - logging: LogOptions + logging?: LogOptions ) { const { dir, name } = path.parse(entryPath); const pathWithExtStripped = path.join(dir, name); const importStr = `import(${JSON.stringify(pathWithExtStripped)})`; schemaMap[JSON.stringify(collection)] = { - stringifiedJs: importStr, - stringifiedType: `typeof ${importStr}`, + stringifiedJs: `() => ${importStr}`, + stringifiedType: `() => typeof ${importStr}`, }; - info(logging, 'content', `Schema added to ${collection}`); + if (logging) { + info(logging, 'content', `${cyan(collection)} schema added`); + } } async function changeEntry( @@ -312,9 +276,9 @@ async function changeEntry( // only do work of stringifying type for new entries `{\n id: ${JSON.stringify(id)},\n slug: ${JSON.stringify( slug - )},\n body: string,\n data: z.infer['schema']>\n}`, + )}]>>['schema']>\n}`, }; } @@ -322,10 +286,12 @@ async function addEntry( contentMap: Record, entry: string, entryInfo: EntryInfo, - logging: LogOptions + logging?: LogOptions ) { await changeEntry(contentMap, entry, entryInfo); - info(logging, 'content', `${cyan(entryInfo.slug)} added to ${bold(entryInfo.collection)}.`); + if (logging) { + info(logging, 'content', `${cyan(entryInfo.slug)} added to ${bold(entryInfo.collection)}.`); + } } function parseEntryInfo( @@ -374,3 +340,71 @@ export function parseFrontmatter(fileContents: string, filePath: string) { } } } + +function stringifyObjKeyValue(key: string, value: string) { + return `${key}: ${value},\n`; +} + +async function writeContentFiles({ + dirs, + contentMap, + schemaMap, + renderContentMap, + contentJsBase, + contentTypesBase, +}: { + dirs: Dirs; + contentMap: ContentMap; + schemaMap: SchemaMap; + renderContentMap: RenderContentMap; + contentJsBase: string; + contentTypesBase: string; +}) { + let contentMapStr = ''; + let contentMapTypesStr = ''; + for (const collectionKey in contentMap) { + contentMapStr += `${collectionKey}: {\n`; + contentMapTypesStr += `${collectionKey}: {\n`; + for (const entryKey in contentMap[collectionKey]) { + const entry = contentMap[collectionKey][entryKey]; + contentMapStr += stringifyObjKeyValue(entryKey, entry.stringifiedJs); + contentMapTypesStr += stringifyObjKeyValue(entryKey, entry.stringifiedType); + } + contentMapStr += `},\n`; + contentMapTypesStr += `},\n`; + } + + let renderContentStr = ''; + for (const entryKey in renderContentMap) { + renderContentStr += stringifyObjKeyValue(entryKey, renderContentMap[entryKey]); + } + + let schemaMapStr = ''; + let schemaMapTypesStr = ''; + for (const collectionKey in schemaMap) { + const entry = schemaMap[collectionKey]; + schemaMapStr += stringifyObjKeyValue(collectionKey, entry.stringifiedJs); + schemaMapTypesStr += stringifyObjKeyValue(collectionKey, entry.stringifiedType); + } + + contentJsBase = contentJsBase + .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) + .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); + contentTypesBase = contentTypesBase.replace( + '// GENERATED_CONTENT_MAP_ENTRIES', + contentMapTypesStr + ); + contentJsBase = contentJsBase.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); + contentTypesBase = contentTypesBase.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapTypesStr); + + try { + await fs.stat(dirs.cacheDir); + } catch { + await fs.mkdir(dirs.cacheDir); + } + + await Promise.all([ + fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), contentJsBase), + fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), contentTypesBase), + ]); +} From bf7d3735e112e2039ef4ac6955e54c684331d669 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 1 Nov 2022 18:42:56 -0400 Subject: [PATCH 042/183] fix: use z.any() when ~schema is missing --- packages/astro/src/content/internal.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 2205c1a0305b..687396d30e35 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -7,8 +7,16 @@ export async function parseEntryData( unparsedEntry: { data: any; rawData: string }, { schemaMap }: { schemaMap: Record } ) { - const schemaImport = (await schemaMap[collection]) ?? {}; - if (!('schema' in schemaImport)) throw getErrorMsg.schemaNamedExp(collection); + let schemaImport; + try { + schemaImport = (await schemaMap[collection]()) ?? {}; + } catch { + schemaImport = { schema: z.any() }; + console.warn(getErrorMsg.schemaMissing(collection)); + } + if (!('schema' in (schemaImport ?? {}))) { + throw new Error(getErrorMsg.schemaNamedExp(collection)); + } const { schema } = schemaImport; try { @@ -58,8 +66,7 @@ function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) export const getErrorMsg = { schemaMissing: (collection: string) => `${collection} does not have a ~schema file. We suggest adding one for type safety!`, - schemaNamedExp: (collection: string) => - new Error(`${collection}/~schema needs a named \`schema\` export.`), + schemaNamedExp: (collection: string) => `${collection}/~schema needs a named \`schema\` export.`, }; export function createRenderContent(renderContentMap: Record Promise>) { From 1389adabea64b4cc89b998acf68393aafd0268e7 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 1 Nov 2022 18:56:50 -0400 Subject: [PATCH 043/183] refactor: clean up logging --- .../astro/src/content/vite-plugin-content.ts | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index e213798a65d8..a9036314aa37 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -107,6 +107,12 @@ type ContentMap = Record>; type SchemaMap = Record; type RenderContentMap = Record; +const msg = { + schemaAdded: (collection: string) => `${cyan(collection)} schema added`, + collectionAdded: (collection: string) => `${cyan(collection)} collection added`, + entryAdded: (entry: string, collection: string) => `${cyan(entry)} added to ${bold(collection)}.`, +}; + async function toGenerateContent({ logging, dirs, @@ -146,16 +152,25 @@ async function toGenerateContent({ if (!isCollectionEvent) return; switch (event.name) { case 'addDir': - addCollection(contentMap, collection, shouldLog ? logging : undefined); + addCollection(contentMap, collection); + if (shouldLog) { + info(logging, 'content', msg.collectionAdded(collection)); + } break; case 'unlinkDir': - delete contentMap[collection]; + removeCollection(contentMap, collection); break; } } else { const fileType = getEntryType(event.entry); if (fileType === 'unknown') { - warn(logging, 'content', `${cyan(event.entry)} is not a supported file type. Skipping.`); + warn( + logging, + 'content', + `${cyan( + path.relative(dirs.contentDir.pathname, event.entry) + )} is not a supported file type. Skipping.` + ); return; } const entryInfo = parseEntryInfo(event.entry, dirs); @@ -166,32 +181,31 @@ async function toGenerateContent({ const collectionKey = JSON.stringify(collection); if (fileType === 'schema') { if (event.name === 'add' && !(collectionKey in schemaMap)) { - addSchema(schemaMap, collection, event.entry, shouldLog ? logging : undefined); + addSchema(schemaMap, collection, event.entry); + if (shouldLog) { + info(logging, 'content', msg.schemaAdded(collection)); + } } else if (event.name === 'unlink' && collection in schemaMap) { - delete schemaMap[collection]; + removeSchema(schemaMap, collection); } return; } switch (event.name) { case 'add': if (!(collectionKey in contentMap)) { - addCollection(contentMap, collection, shouldLog ? logging : undefined); + addCollection(contentMap, collection); + } + await changeEntry(contentMap, event.entry, { id, slug, collection }); + if (shouldLog) { + info(logging, 'content', msg.entryAdded(entryInfo.slug, entryInfo.collection)); } - await addEntry( - contentMap, - event.entry, - { id, slug, collection }, - shouldLog ? logging : undefined - ); renderContentMap[JSON.stringify(id)] = `() => import(${JSON.stringify(event.entry)})`; break; case 'change': await changeEntry(contentMap, event.entry, { id, slug, collection }); break; case 'unlink': - delete contentMap[JSON.stringify(collection)][ - JSON.stringify(path.relative(collection, id)) - ]; + removeEntry(contentMap, entryInfo); delete renderContentMap[event.entry]; break; } @@ -229,19 +243,15 @@ async function toGenerateContent({ return { init, queueEvent }; } -function addCollection(contentMap: Record, collection: string, logging?: LogOptions) { +function addCollection(contentMap: ContentMap, collection: string) { contentMap[JSON.stringify(collection)] = {}; - if (logging) { - info(logging, 'content', `${cyan(collection)} collection added`); - } } -function addSchema( - schemaMap: Record, - collection: string, - entryPath: string, - logging?: LogOptions -) { +function removeCollection(contentMap: ContentMap, collection: string) { + delete contentMap[JSON.stringify(collection)]; +} + +function addSchema(schemaMap: SchemaMap, collection: string, entryPath: string) { const { dir, name } = path.parse(entryPath); const pathWithExtStripped = path.join(dir, name); const importStr = `import(${JSON.stringify(pathWithExtStripped)})`; @@ -249,9 +259,10 @@ function addSchema( stringifiedJs: `() => ${importStr}`, stringifiedType: `() => typeof ${importStr}`, }; - if (logging) { - info(logging, 'content', `${cyan(collection)} schema added`); - } +} + +function removeSchema(schemaMap: SchemaMap, collection: string) { + delete schemaMap[JSON.stringify(collection)]; } async function changeEntry( @@ -282,16 +293,11 @@ async function changeEntry( }; } -async function addEntry( - contentMap: Record, - entry: string, - entryInfo: EntryInfo, - logging?: LogOptions +function removeEntry( + contentMap: Record>, + { id, collection }: EntryInfo ) { - await changeEntry(contentMap, entry, entryInfo); - if (logging) { - info(logging, 'content', `${cyan(entryInfo.slug)} added to ${bold(entryInfo.collection)}.`); - } + delete contentMap[JSON.stringify(collection)][JSON.stringify(path.relative(collection, id))]; } function parseEntryInfo( From 84d62d04ef242d48a8070b8a8d042d4284ffbed0 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 2 Nov 2022 16:40:19 -0400 Subject: [PATCH 044/183] fix: add delayed asset flag to render map --- packages/astro/src/content/vite-plugin-content.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index a9036314aa37..cf0673a6bc63 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -6,8 +6,8 @@ import { bold, cyan } from 'kleur/colors'; import matter from 'gray-matter'; import { info, LogOptions, warn } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; +import { DELAYED_ASSET_FLAG } from './vite-plugin-delayed-assets.js'; -type TypedMapEntry = { key: string; value: string; type: string }; type Dirs = { contentDir: URL; cacheDir: URL; @@ -199,7 +199,9 @@ async function toGenerateContent({ if (shouldLog) { info(logging, 'content', msg.entryAdded(entryInfo.slug, entryInfo.collection)); } - renderContentMap[JSON.stringify(id)] = `() => import(${JSON.stringify(event.entry)})`; + renderContentMap[JSON.stringify(id)] = `() => import(${JSON.stringify( + event.entry + DELAYED_ASSET_FLAG + )})`; break; case 'change': await changeEntry(contentMap, event.entry, { id, slug, collection }); From 43b363cbd96b0905bff7962c9d035ed73b8ae5af Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 2 Nov 2022 16:43:08 -0400 Subject: [PATCH 045/183] fix: use new server loader --- .../src/content/vite-plugin-delayed-assets.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/astro/src/content/vite-plugin-delayed-assets.ts b/packages/astro/src/content/vite-plugin-delayed-assets.ts index d509dfb823d2..e5048b457e98 100644 --- a/packages/astro/src/content/vite-plugin-delayed-assets.ts +++ b/packages/astro/src/content/vite-plugin-delayed-assets.ts @@ -7,6 +7,8 @@ import { AstroSettings } from '../@types/astro.js'; import { normalizeFilename } from '../vite-plugin-utils/index.js'; import { getStylesForURL } from '../core/render/dev/css.js'; import { pathToFileURL } from 'url'; +import { createViteLoader } from '../core/module-loader/vite.js'; +import { ModuleLoader } from '../core/module-loader/loader.js'; const LINKS_PLACEHOLDER = `[/* @@ASTRO-LINKS-PLACEHOLDER@@ */]`; const STYLES_PLACEHOLDER = `[/* @@ASTRO-STYLES-PLACEHOLDER@@ */]`; @@ -20,13 +22,13 @@ export function astroDelayedAssetPlugin({ settings: AstroSettings; mode: string; }): Plugin { - let viteDevServer: ViteDevServer; + let devModuleLoader: ModuleLoader; return { name: 'astro-delayed-asset-plugin', enforce: 'post', configureServer(server) { if (mode === 'dev') { - viteDevServer = server; + devModuleLoader = createViteLoader(server); } }, load(id) { @@ -40,14 +42,15 @@ export function astroDelayedAssetPlugin({ } }, async transform(code, id, options) { - if (id.endsWith(DELAYED_ASSET_FLAG) && viteDevServer) { + if (!options?.ssr) return; + if (id.endsWith(DELAYED_ASSET_FLAG) && devModuleLoader) { const baseId = id.replace(DELAYED_ASSET_FLAG, ''); - if (!viteDevServer.moduleGraph.getModuleById(baseId)?.ssrModule) { - await viteDevServer.ssrLoadModule(baseId); + if (!devModuleLoader.getModuleById(baseId)?.ssrModule) { + await devModuleLoader.import(baseId); } const { stylesMap, urls } = await getStylesForURL( pathToFileURL(baseId), - viteDevServer, + devModuleLoader, 'development' ); @@ -58,7 +61,7 @@ export function astroDelayedAssetPlugin({ }; } - if (options?.ssr && id.endsWith('.astro')) { + if (id.endsWith('.astro')) { let renderContentImportName = getRenderContentImportName( await parseImports(escapeViteEnvReferences(code)) ); From 7aacd1021229115b918bf6257e6c7dfba1981255 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 2 Nov 2022 16:46:25 -0400 Subject: [PATCH 046/183] chore: import type --- packages/astro/src/content/vite-plugin-delayed-assets.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/astro/src/content/vite-plugin-delayed-assets.ts b/packages/astro/src/content/vite-plugin-delayed-assets.ts index e5048b457e98..0bc91f68ddf9 100644 --- a/packages/astro/src/content/vite-plugin-delayed-assets.ts +++ b/packages/astro/src/content/vite-plugin-delayed-assets.ts @@ -1,14 +1,14 @@ -import { Plugin, ViteDevServer } from 'vite'; -import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; -import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; +import type { Plugin } from 'vite'; +import type { ModuleLoader } from '../core/module-loader/loader.js'; import MagicString from 'magic-string'; import parseImports from 'parse-imports'; +import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js'; +import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js'; import { AstroSettings } from '../@types/astro.js'; import { normalizeFilename } from '../vite-plugin-utils/index.js'; import { getStylesForURL } from '../core/render/dev/css.js'; import { pathToFileURL } from 'url'; import { createViteLoader } from '../core/module-loader/vite.js'; -import { ModuleLoader } from '../core/module-loader/loader.js'; const LINKS_PLACEHOLDER = `[/* @@ASTRO-LINKS-PLACEHOLDER@@ */]`; const STYLES_PLACEHOLDER = `[/* @@ASTRO-STYLES-PLACEHOLDER@@ */]`; From b974c4f21a420a8d68f810eeacc29b619c1b4337 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 3 Nov 2022 12:27:46 -0400 Subject: [PATCH 047/183] fix: split delayed assets to separate chunks --- .../astro/src/core/build/vite-plugin-css.ts | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index a0b9e50c7c71..a96f13c7b385 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -1,12 +1,13 @@ import type { GetModuleInfo } from 'rollup'; import type { BuildInternals } from './internal'; import type { PageBuildData, StaticBuildOptions } from './types'; - +import * as crypto from 'node:crypto'; +import * as npath from 'node:path'; import { Plugin as VitePlugin, ResolvedConfig, transformWithEsbuild } from 'vite'; import { isCSSRequest } from '../render/util.js'; import * as assetName from './css-asset-name.js'; -import { moduleIsTopLevelPage, walkParentInfos } from './graph.js'; +import { getTopLevelPages, moduleIsTopLevelPage, walkParentInfos } from './graph.js'; import { eachPageData, getPageDataByViteID, @@ -28,6 +29,27 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] let resolvedConfig: ResolvedConfig; + function createNameForParentPages(id: string, ctx: { getModuleInfo: GetModuleInfo }): string { + const parents = Array.from(getTopLevelPages(id, ctx)); + const firstParentId = parents[0]?.[0].id; + const proposedName = createNameHash( + firstParentId, + parents.map(([page]) => page.id) + ); + return proposedName; + } + + function createNameHash(baseId: string, hashIds: string[]): string { + const baseName = baseId ? npath.parse(baseId).name : 'index'; + const hash = crypto.createHash('sha256'); + for (const id of hashIds) { + hash.update(id, 'utf-8'); + } + const h = hash.digest('hex').slice(0, 8); + const proposedName = baseName + '.' + h; + return proposedName; + } + function* getParentClientOnlys( id: string, ctx: { getModuleInfo: GetModuleInfo } @@ -64,6 +86,15 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] // For CSS, create a hash of all of the pages that use it. // This causes CSS to be built into shared chunks when used by multiple pages. if (isCSSRequest(id)) { + for (const [pageInfo] of walkParentInfos(id, { + getModuleInfo: args[0].getModuleInfo, + })) { + if (pageInfo.id.endsWith(DELAYED_ASSET_FLAG)) { + // Split delayed assets to separate modules + // so they can be injected where needed + return createNameHash(id, [id]); + } + } return createNameForParentPages(id, args[0]); } }; From 2049861031960571da4df6d19dd1d9b694de4602 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 3 Nov 2022 13:22:05 -0400 Subject: [PATCH 048/183] chore: bad merge changesets --- .changeset/chilly-experts-add.md | 9 --------- .changeset/clever-keys-hammer.md | 5 ----- .changeset/nine-roses-explain.md | 5 ----- 3 files changed, 19 deletions(-) delete mode 100644 .changeset/chilly-experts-add.md delete mode 100644 .changeset/clever-keys-hammer.md delete mode 100644 .changeset/nine-roses-explain.md diff --git a/.changeset/chilly-experts-add.md b/.changeset/chilly-experts-add.md deleted file mode 100644 index 46c228c7d176..000000000000 --- a/.changeset/chilly-experts-add.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@astrojs/image': minor ---- - -Removes the `content-visibility: auto` styling added by the `` and `` components. - -**Why:** The [content-visibility](https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility) style is rarely needed for an `` and can actually break certain layouts. - -**Migration:** Do images in your layout actually depend on `content-visibility`? No problem! You can add these styles where needed in your own component styles. diff --git a/.changeset/clever-keys-hammer.md b/.changeset/clever-keys-hammer.md deleted file mode 100644 index 5e91f47e470b..000000000000 --- a/.changeset/clever-keys-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/mdx': patch ---- - -Support recmaPlugins config option diff --git a/.changeset/nine-roses-explain.md b/.changeset/nine-roses-explain.md deleted file mode 100644 index 0b4c8e0199d3..000000000000 --- a/.changeset/nine-roses-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/vercel': patch ---- - -Edge adapter includes all the generated files (all files inside `dist/`) instead of only `entry.mjs` From d99d50de6a8a09a70fe7b10221088a16e9759cf1 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 11 Nov 2022 12:31:49 -0800 Subject: [PATCH 049/183] feat: rework content map to Vite globs --- packages/astro/content-generated.d.ts | 5 - packages/astro/content-generated.mjs | 93 ++--- packages/astro/src/content/internal.ts | 102 ++++- .../astro/src/content/vite-plugin-content.ts | 370 +++++++++--------- .../src/content/vite-plugin-delayed-assets.ts | 2 +- 5 files changed, 313 insertions(+), 259 deletions(-) diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index a83e82bd44be..ebbd4578b8eb 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -1,10 +1,5 @@ import { z } from 'zod'; -declare const defaultSchemaFileResolved: { - schema: { - parse: (mod) => any; - }; -}; export declare const contentMap: { // GENERATED_CONTENT_MAP_ENTRIES }; diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index 453625d02387..4f783fa888b6 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -1,58 +1,39 @@ -import { getErrorMsg, parseEntryData, createRenderContent } from 'astro/content/internal'; - -const defaultSchemaFileResolved = { schema: { parse: (mod) => mod } }; -/** Used to stub out `schemaMap` entries that don't have a `~schema.ts` file */ -const defaultSchemaFile = (/** @type {string} */ collection) => - new Promise((/** @type {(value: typeof defaultSchemaFileResolved) => void} */ resolve) => { - console.warn(getErrorMsg.schemaMissing(collection)); - resolve(defaultSchemaFileResolved); - }); - -export const contentMap = { - // GENERATED_CONTENT_MAP_ENTRIES -}; - -export const schemaMap = { - // GENERATED_SCHEMA_MAP_ENTRIES -}; - -export const renderContentMap = { - // GENERATED_RENDER_CONTENT_MAP_ENTRIES -}; - -export async function fetchContentByEntry( - /** @type {string} */ collection, - /** @type {string} */ entryKey -) { - const entry = contentMap[collection][entryKey]; - - return { - id: entry.id, - slug: entry.slug, - body: entry.body, - data: await parseEntryData(collection, entryKey, entry, { schemaMap }), - }; -} - -export async function fetchContent( - /** @type {string} */ collection, - /** @type {undefined | (() => boolean)} */ filter -) { - const entries = Promise.all( - Object.entries(contentMap[collection]).map(async ([key, entry]) => { - return { - id: entry.id, - slug: entry.slug, - body: entry.body, - data: await parseEntryData(collection, key, entry, { schemaMap }), - }; - }) - ); - if (typeof filter === 'function') { - return (await entries).filter(filter); - } else { - return entries; - } -} +import { + createFetchContent, + createFetchContentByEntry, + createRenderContent, + createCollectionToGlobResultMap, +} from 'astro/content/internal'; + +const contentDir = 'CONTENT_DIR'; + +const contentGlob = import.meta.glob('FETCH_CONTENT_GLOB_PATH', { + query: { content: true }, +}); +const collectionToContentMap = createCollectionToGlobResultMap({ + globResult: contentGlob, + contentDir, +}); + +const schemaGlob = import.meta.glob('SCHEMA_GLOB_PATH'); +const collectionToSchemaMap = createCollectionToGlobResultMap({ + globResult: schemaGlob, + contentDir, +}); + +const renderContentMap = import.meta.glob('RENDER_CONTENT_GLOB_PATH', { + query: { astroAssetSsr: true }, +}); + +export const fetchContent = createFetchContent({ + collectionToContentMap, + collectionToSchemaMap, +}); + +export const fetchContentByEntry = createFetchContentByEntry({ + collectionToContentMap, + collectionToSchemaMap, + contentDir, +}); export const renderContent = createRenderContent(renderContentMap); diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 687396d30e35..faa09842356f 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -1,23 +1,43 @@ import { z } from 'zod'; import { prependForwardSlash } from '../core/path.js'; +type GlobResult = Record Promise>; +type CollectionToEntryMap = Record; + +export function createCollectionToGlobResultMap({ + globResult, + contentDir, +}: { + globResult: GlobResult; + contentDir: string; +}) { + const collectionToGlobResultMap: CollectionToEntryMap = {}; + for (const key in globResult) { + const keyRelativeToContentDir = key.replace(new RegExp(`^${contentDir}`), ''); + const segments = keyRelativeToContentDir.split('/'); + if (segments.length <= 1) continue; + const collection = segments[0]; + const entryId = segments.slice(1).join('/'); + collectionToGlobResultMap[collection] ??= {}; + collectionToGlobResultMap[collection][entryId] = globResult[key]; + } + return collectionToGlobResultMap; +} + export async function parseEntryData( collection: string, - entryKey: string, - unparsedEntry: { data: any; rawData: string }, - { schemaMap }: { schemaMap: Record } + unparsedEntry: { id: string; data: any; rawData: string }, + collectionToSchemaMap: CollectionToEntryMap ) { - let schemaImport; - try { - schemaImport = (await schemaMap[collection]()) ?? {}; - } catch { - schemaImport = { schema: z.any() }; - console.warn(getErrorMsg.schemaMissing(collection)); + let schemaImport = Object.values(collectionToSchemaMap[collection] ?? {})[0]; + if (!schemaImport) { + console.warn(getErrorMsg.schemaFileMissing(collection)); } - if (!('schema' in (schemaImport ?? {}))) { - throw new Error(getErrorMsg.schemaNamedExp(collection)); + const schemaValue = await schemaImport(); + if (!('schema' in (schemaValue ?? {}))) { + throw new Error(getErrorMsg.schemaNamedExpMissing(collection)); } - const { schema } = schemaImport; + const { schema } = schemaValue; try { return schema.parse(unparsedEntry.data, { errorMap }); @@ -25,7 +45,7 @@ export async function parseEntryData( if (e instanceof z.ZodError) { const formattedError = new Error( [ - `Could not parse frontmatter in ${String(collection)} → ${String(entryKey)}`, + `Could not parse frontmatter in ${String(collection)} → ${String(unparsedEntry.id)}`, ...e.errors.map((e) => e.message), ].join('\n') ); @@ -64,11 +84,63 @@ function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) } export const getErrorMsg = { - schemaMissing: (collection: string) => + schemaFileMissing: (collection: string) => `${collection} does not have a ~schema file. We suggest adding one for type safety!`, - schemaNamedExp: (collection: string) => `${collection}/~schema needs a named \`schema\` export.`, + schemaNamedExpMissing: (collection: string) => + `${collection}/~schema needs a named \`schema\` export.`, }; +export function createFetchContent({ + collectionToContentMap, + collectionToSchemaMap, +}: { + collectionToContentMap: CollectionToEntryMap; + collectionToSchemaMap: CollectionToEntryMap; +}) { + return async function fetchContent(collection: string, filter?: () => boolean) { + const lazyImports = Object.values(collectionToContentMap[collection] ?? {}); + const entries = Promise.all( + lazyImports.map(async (lazyImport) => { + const entry = await lazyImport(); + const data = await parseEntryData(collection, entry, collectionToSchemaMap); + return { + id: entry.id, + slug: entry.slug, + body: entry.body, + data, + }; + }) + ); + if (typeof filter === 'function') { + return (await entries).filter(filter); + } else { + return entries; + } + }; +} + +export function createFetchContentByEntry({ + collectionToContentMap, + collectionToSchemaMap, +}: { + collectionToSchemaMap: CollectionToEntryMap; + collectionToContentMap: CollectionToEntryMap; +}) { + return async function fetchContentByEntry(collection: string, entryId: string) { + const lazyImport = collectionToContentMap[collection]?.[entryId]; + if (!lazyImport) throw new Error(`Ah! ${entryId}`); + + const entry = await lazyImport(); + const data = await parseEntryData(collection, entry, collectionToSchemaMap); + return { + id: entry.id, + slug: entry.slug, + body: entry.body, + data, + }; + }; +} + export function createRenderContent(renderContentMap: Record Promise>) { return async function renderContent(this: any, entryOrEntryId: { id: string } | string) { const contentKey = typeof entryOrEntryId === 'object' ? entryOrEntryId.id : entryOrEntryId; diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index cf0673a6bc63..fe02119642f0 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -6,7 +6,7 @@ import { bold, cyan } from 'kleur/colors'; import matter from 'gray-matter'; import { info, LogOptions, warn } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; -import { DELAYED_ASSET_FLAG } from './vite-plugin-delayed-assets.js'; +import { appendForwardSlash, prependForwardSlash } from '../core/path.js'; type Dirs = { contentDir: URL; @@ -18,13 +18,15 @@ const CONTENT_BASE = 'content-generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; +const contentFileExts = ['.md', '.mdx']; + export function astroContentPlugin({ settings, logging, }: { logging: LogOptions; settings: AstroSettings; -}): Plugin { +}): Plugin[] { const { root, srcDir } = settings.config; const dirs: Dirs = { cacheDir: new URL('./.astro/', root), @@ -34,59 +36,103 @@ export function astroContentPlugin({ let contentDirExists = false; let contentGenerator: GenerateContent; - return { - name: 'astro-fetch-content-plugin', - async config() { - try { - await fs.stat(dirs.contentDir); - contentDirExists = true; - } catch { - /* silently move on */ - return; - } - - info(logging, 'content', 'Generating entries...'); - - contentGenerator = await toGenerateContent({ logging, dirs }); - await contentGenerator.init(); + return [ + { + name: 'content-flag-plugin', + enforce: 'pre', + async load(id) { + const { pathname, searchParams } = new URL(id, 'file://'); + if (searchParams.has('content') && contentFileExts.some((ext) => pathname.endsWith(ext))) { + const rawContents = await fs.readFile(pathname, 'utf-8'); + const { content: body, data } = parseFrontmatter(rawContents, pathname); + const entryInfo = parseEntryInfo(pathname, { contentDir: dirs.contentDir }); + if (entryInfo instanceof Error) return; + return { + code: ` +export const id = ${JSON.stringify(entryInfo.id)}; +export const collection = ${JSON.stringify(entryInfo.collection)}; +export const slug = ${JSON.stringify(entryInfo.slug)}; +export const body = ${JSON.stringify(body)}; +export const data = ${JSON.stringify(data)}; +`, + }; + } + }, }, - async configureServer(viteServer) { - if (contentDirExists) { - info( - logging, - 'content', - `Watching ${cyan(dirs.contentDir.href.replace(root.href, ''))} for changes` - ); - attachListeners(); - } else { - viteServer.watcher.on('addDir', (dir) => { - if (dir === dirs.contentDir.pathname) { - info(logging, 'content', `Content dir found. Watching for changes`); - contentDirExists = true; - attachListeners(); - } - }); - } + { + name: 'astro-fetch-content-plugin', + async config() { + try { + await fs.stat(dirs.contentDir); + contentDirExists = true; + } catch { + /* silently move on */ + return; + } - function attachListeners() { - viteServer.watcher.on('add', (entry) => - contentGenerator.queueEvent({ name: 'add', entry }) - ); - viteServer.watcher.on('addDir', (entry) => - contentGenerator.queueEvent({ name: 'addDir', entry }) - ); - viteServer.watcher.on('change', (entry) => - contentGenerator.queueEvent({ name: 'change', entry }) + info(logging, 'content', 'Generating entries...'); + + contentGenerator = await toGenerateContent({ logging, dirs }); + const contentJsFile = await fs.readFile( + new URL(CONTENT_FILE, dirs.generatedInputDir), + 'utf-8' ); - viteServer.watcher.on('unlink', (entry) => - contentGenerator.queueEvent({ name: 'unlink', entry }) + const relContentDir = appendForwardSlash( + prependForwardSlash( + path.relative(settings.config.root.pathname, dirs.contentDir.pathname) + ) ); - viteServer.watcher.on('unlinkDir', (entry) => - contentGenerator.queueEvent({ name: 'unlinkDir', entry }) + const contentGlob = relContentDir + '**/*.{md,mdx}'; + const schemaGlob = relContentDir + '**/~schema.{ts,js,mjs}'; + await fs.writeFile( + new URL(CONTENT_FILE, dirs.cacheDir), + contentJsFile + .replace('CONTENT_DIR', relContentDir) + .replace('FETCH_CONTENT_GLOB_PATH', contentGlob) + .replace('RENDER_CONTENT_GLOB_PATH', contentGlob) + .replace('SCHEMA_GLOB_PATH', schemaGlob) ); - } + + await contentGenerator.init(); + }, + async configureServer(viteServer) { + if (contentDirExists) { + info( + logging, + 'content', + `Watching ${cyan(dirs.contentDir.href.replace(root.href, ''))} for changes` + ); + attachListeners(); + } else { + viteServer.watcher.on('addDir', (dir) => { + if (dir === dirs.contentDir.pathname) { + info(logging, 'content', `Content dir found. Watching for changes`); + contentDirExists = true; + attachListeners(); + } + }); + } + + function attachListeners() { + viteServer.watcher.on('add', (entry) => + contentGenerator.queueEvent({ name: 'add', entry }) + ); + viteServer.watcher.on('addDir', (entry) => + contentGenerator.queueEvent({ name: 'addDir', entry }) + ); + viteServer.watcher.on('change', (entry) => + contentGenerator.queueEvent({ name: 'change', entry }) + ); + viteServer.watcher.on('unlink', (entry) => + contentGenerator.queueEvent({ name: 'unlink', entry }) + ); + viteServer.watcher.on('unlinkDir', (entry) => + contentGenerator.queueEvent({ name: 'unlinkDir', entry }) + ); + } + }, }, - }; + ]; } type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir'; @@ -102,10 +148,8 @@ type GenerateContent = { queueEvent(event: ContentEvent): void; }; -type StringifiedInfo = { stringifiedJs: string; stringifiedType: string }; -type ContentMap = Record>; -type SchemaMap = Record; -type RenderContentMap = Record; +type ContentTypes = Record>; +type SchemaTypes = Record; const msg = { schemaAdded: (collection: string) => `${cyan(collection)} schema added`, @@ -120,18 +164,17 @@ async function toGenerateContent({ logging: LogOptions; dirs: Dirs; }): Promise { - const contentMap: ContentMap = {}; - const schemaMap: SchemaMap = {}; - const renderContentMap: RenderContentMap = {}; + const contentTypes: ContentTypes = {}; + const schemaTypes: SchemaTypes = {}; let events: Promise[] = []; let debounceTimeout: NodeJS.Timeout | undefined; let eventsSettled: Promise | undefined; - let [contentJsBase, contentTypesBase] = await Promise.all([ - fs.readFile(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8'), - fs.readFile(new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), 'utf-8'), - ]); + const contentTypesBase = await fs.readFile( + new URL(CONTENT_TYPES_FILE, dirs.generatedInputDir), + 'utf-8' + ); async function init() { const pattern = new URL('./**/', dirs.contentDir).pathname + '{*.{md,mdx},~schema.{js,mjs,ts}}'; @@ -152,13 +195,13 @@ async function toGenerateContent({ if (!isCollectionEvent) return; switch (event.name) { case 'addDir': - addCollection(contentMap, collection); + addCollection(contentTypes, JSON.stringify(collection)); if (shouldLog) { info(logging, 'content', msg.collectionAdded(collection)); } break; case 'unlinkDir': - removeCollection(contentMap, collection); + removeCollection(contentTypes, JSON.stringify(collection)); break; } } else { @@ -179,37 +222,49 @@ async function toGenerateContent({ const { id, slug, collection } = entryInfo; const collectionKey = JSON.stringify(collection); + const entryKey = JSON.stringify(id); if (fileType === 'schema') { - if (event.name === 'add' && !(collectionKey in schemaMap)) { - addSchema(schemaMap, collection, event.entry); - if (shouldLog) { - info(logging, 'content', msg.schemaAdded(collection)); - } - } else if (event.name === 'unlink' && collection in schemaMap) { - removeSchema(schemaMap, collection); + switch (event.name) { + case 'add': + if (!(collectionKey in schemaTypes)) { + addSchema(schemaTypes, collectionKey, event.entry); + if (shouldLog) { + info(logging, 'content', msg.schemaAdded(collection)); + } + } + break; + case 'unlink': + if (collectionKey in schemaTypes) { + removeSchema(schemaTypes, collectionKey); + } + break; + case 'change': + // noop. Types use `typeof import`, so they won't change! + break; } return; - } - switch (event.name) { - case 'add': - if (!(collectionKey in contentMap)) { - addCollection(contentMap, collection); - } - await changeEntry(contentMap, event.entry, { id, slug, collection }); - if (shouldLog) { - info(logging, 'content', msg.entryAdded(entryInfo.slug, entryInfo.collection)); - } - renderContentMap[JSON.stringify(id)] = `() => import(${JSON.stringify( - event.entry + DELAYED_ASSET_FLAG - )})`; - break; - case 'change': - await changeEntry(contentMap, event.entry, { id, slug, collection }); - break; - case 'unlink': - removeEntry(contentMap, entryInfo); - delete renderContentMap[event.entry]; - break; + } else { + switch (event.name) { + case 'add': + if (!(collectionKey in contentTypes)) { + addCollection(contentTypes, collectionKey); + } + if (!(entryKey in contentTypes[collectionKey])) { + addEntry(contentTypes, collectionKey, entryKey, slug); + } + if (shouldLog) { + info(logging, 'content', msg.entryAdded(entryInfo.slug, entryInfo.collection)); + } + break; + case 'unlink': + if (collectionKey in contentTypes && entryKey in contentTypes[collectionKey]) { + removeEntry(contentTypes, collectionKey, entryKey); + } + break; + case 'change': + // noop. Frontmatter types are inferred from schema, so they won't change! + break; + } } } } @@ -228,11 +283,9 @@ async function toGenerateContent({ debounceTimeout = setTimeout(async () => { await Promise.all(events); await writeContentFiles({ - contentMap, - schemaMap, - renderContentMap, + contentTypes, + schemaTypes, dirs, - contentJsBase, contentTypesBase, }); resolve(); @@ -245,72 +298,50 @@ async function toGenerateContent({ return { init, queueEvent }; } -function addCollection(contentMap: ContentMap, collection: string) { - contentMap[JSON.stringify(collection)] = {}; +function addCollection(contentMap: ContentTypes, collectionKey: string) { + contentMap[collectionKey] = {}; } -function removeCollection(contentMap: ContentMap, collection: string) { - delete contentMap[JSON.stringify(collection)]; +function removeCollection(contentMap: ContentTypes, collectionKey: string) { + delete contentMap[collectionKey]; } -function addSchema(schemaMap: SchemaMap, collection: string, entryPath: string) { +function addSchema(schemaTypes: SchemaTypes, collectionKey: string, entryPath: string) { const { dir, name } = path.parse(entryPath); const pathWithExtStripped = path.join(dir, name); const importStr = `import(${JSON.stringify(pathWithExtStripped)})`; - schemaMap[JSON.stringify(collection)] = { - stringifiedJs: `() => ${importStr}`, - stringifiedType: `() => typeof ${importStr}`, - }; + schemaTypes[collectionKey] = `typeof ${importStr}`; } -function removeSchema(schemaMap: SchemaMap, collection: string) { - delete schemaMap[JSON.stringify(collection)]; +function removeSchema(schemaTypes: SchemaTypes, collectionKey: string) { + delete schemaTypes[collectionKey]; } -async function changeEntry( - contentMap: Record>, - entry: string, - { id, slug, collection }: EntryInfo +function addEntry( + contentTypes: ContentTypes, + collectionKey: string, + entryKey: string, + slug: string ) { - const body = await fs.readFile(entry, 'utf-8'); - const { data, matter: rawData = '' } = parseFrontmatter(body, entry); - const collectionKey = JSON.stringify(collection); - const entryKey = JSON.stringify(path.relative(collection, id)); - contentMap[collectionKey][entryKey] = { - stringifiedJs: JSON.stringify({ - id, - slug, - data, - body, - rawData, - }), - stringifiedType: - contentMap[collectionKey][entryKey]?.stringifiedType ?? - // only do work of stringifying type for new entries - `{\n id: ${JSON.stringify(id)},\n slug: ${JSON.stringify( - slug - )},\n body: string,\n data: z.infer>['schema']>\n}`, - }; + contentTypes[collectionKey][entryKey] = `{\n id: ${entryKey},\n slug: ${JSON.stringify( + slug + )},\n body: string,\n data: z.infer\n}`; } -function removeEntry( - contentMap: Record>, - { id, collection }: EntryInfo -) { - delete contentMap[JSON.stringify(collection)][JSON.stringify(path.relative(collection, id))]; +function removeEntry(contentTypes: ContentTypes, collectionKey: string, entryKey: string) { + delete contentTypes[collectionKey][entryKey]; } function parseEntryInfo( entryPath: string, { contentDir }: Pick ): EntryInfo | Error { - const id = normalizePath(path.relative(contentDir.pathname, entryPath)); - const collection = path.dirname(id).split(path.sep).shift(); + const relativeEntryPath = normalizePath(path.relative(contentDir.pathname, entryPath)); + const collection = path.dirname(relativeEntryPath).split(path.sep).shift(); if (!collection) return new Error(); - const slug = path.relative(collection, id).replace(path.extname(id), ''); + const id = path.relative(collection, relativeEntryPath); + const slug = id.replace(path.extname(id), ''); return { id, slug, @@ -333,7 +364,7 @@ function getEntryType(entryPath: string): 'content' | 'schema' | 'unknown' { * Match YAML exception handling from Astro core errors * @see 'astro/src/core/errors.ts' */ -export function parseFrontmatter(fileContents: string, filePath: string) { +function parseFrontmatter(fileContents: string, filePath: string) { try { return matter(fileContents); } catch (e: any) { @@ -355,55 +386,33 @@ function stringifyObjKeyValue(key: string, value: string) { async function writeContentFiles({ dirs, - contentMap, - schemaMap, - renderContentMap, - contentJsBase, + contentTypes, + schemaTypes, contentTypesBase, }: { dirs: Dirs; - contentMap: ContentMap; - schemaMap: SchemaMap; - renderContentMap: RenderContentMap; - contentJsBase: string; + contentTypes: ContentTypes; + schemaTypes: SchemaTypes; contentTypesBase: string; }) { - let contentMapStr = ''; - let contentMapTypesStr = ''; - for (const collectionKey in contentMap) { - contentMapStr += `${collectionKey}: {\n`; - contentMapTypesStr += `${collectionKey}: {\n`; - for (const entryKey in contentMap[collectionKey]) { - const entry = contentMap[collectionKey][entryKey]; - contentMapStr += stringifyObjKeyValue(entryKey, entry.stringifiedJs); - contentMapTypesStr += stringifyObjKeyValue(entryKey, entry.stringifiedType); + let contentTypesStr = ''; + for (const collectionKey in contentTypes) { + contentTypesStr += `${collectionKey}: {\n`; + for (const entryKey in contentTypes[collectionKey]) { + const entry = contentTypes[collectionKey][entryKey]; + contentTypesStr += stringifyObjKeyValue(entryKey, entry); } - contentMapStr += `},\n`; - contentMapTypesStr += `},\n`; + contentTypesStr += `},\n`; } - let renderContentStr = ''; - for (const entryKey in renderContentMap) { - renderContentStr += stringifyObjKeyValue(entryKey, renderContentMap[entryKey]); + let schemaTypesStr = ''; + for (const collectionKey in schemaTypes) { + const entry = schemaTypes[collectionKey]; + schemaTypesStr += stringifyObjKeyValue(collectionKey, entry); } - let schemaMapStr = ''; - let schemaMapTypesStr = ''; - for (const collectionKey in schemaMap) { - const entry = schemaMap[collectionKey]; - schemaMapStr += stringifyObjKeyValue(collectionKey, entry.stringifiedJs); - schemaMapTypesStr += stringifyObjKeyValue(collectionKey, entry.stringifiedType); - } - - contentJsBase = contentJsBase - .replace('// GENERATED_CONTENT_MAP_ENTRIES', contentMapStr) - .replace('// GENERATED_RENDER_CONTENT_MAP_ENTRIES', renderContentStr); - contentTypesBase = contentTypesBase.replace( - '// GENERATED_CONTENT_MAP_ENTRIES', - contentMapTypesStr - ); - contentJsBase = contentJsBase.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapStr); - contentTypesBase = contentTypesBase.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaMapTypesStr); + contentTypesBase = contentTypesBase.replace('// GENERATED_CONTENT_MAP_ENTRIES', contentTypesStr); + contentTypesBase = contentTypesBase.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaTypesStr); try { await fs.stat(dirs.cacheDir); @@ -411,8 +420,5 @@ async function writeContentFiles({ await fs.mkdir(dirs.cacheDir); } - await Promise.all([ - fs.writeFile(new URL(CONTENT_FILE, dirs.cacheDir), contentJsBase), - fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), contentTypesBase), - ]); + await fs.writeFile(new URL(CONTENT_TYPES_FILE, dirs.cacheDir), contentTypesBase); } diff --git a/packages/astro/src/content/vite-plugin-delayed-assets.ts b/packages/astro/src/content/vite-plugin-delayed-assets.ts index 0bc91f68ddf9..658a42714c51 100644 --- a/packages/astro/src/content/vite-plugin-delayed-assets.ts +++ b/packages/astro/src/content/vite-plugin-delayed-assets.ts @@ -13,7 +13,7 @@ import { createViteLoader } from '../core/module-loader/vite.js'; const LINKS_PLACEHOLDER = `[/* @@ASTRO-LINKS-PLACEHOLDER@@ */]`; const STYLES_PLACEHOLDER = `[/* @@ASTRO-STYLES-PLACEHOLDER@@ */]`; -export const DELAYED_ASSET_FLAG = '?astro-asset-ssr'; +export const DELAYED_ASSET_FLAG = '?astroAssetSsr'; export function astroDelayedAssetPlugin({ settings, From e954a33bbbe0a7a9ae6eb9edf37e586eb32fbf1f Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 11 Nov 2022 14:55:52 -0800 Subject: [PATCH 050/183] fix: formatted errors --- packages/astro/src/content/internal.ts | 6 +++--- packages/astro/src/content/vite-plugin-content.ts | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index faa09842356f..55cd558b85e3 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -26,7 +26,7 @@ export function createCollectionToGlobResultMap({ export async function parseEntryData( collection: string, - unparsedEntry: { id: string; data: any; rawData: string }, + unparsedEntry: { id: string; data: any; _internal: { rawData: string; filePath: string } }, collectionToSchemaMap: CollectionToEntryMap ) { let schemaImport = Object.values(collectionToSchemaMap[collection] ?? {})[0]; @@ -50,8 +50,8 @@ export async function parseEntryData( ].join('\n') ); (formattedError as any).loc = { - file: 'TODO', - line: getFrontmatterErrorLine(unparsedEntry.rawData, String(e.errors[0].path[0])), + file: unparsedEntry._internal.filePath, + line: getFrontmatterErrorLine(unparsedEntry._internal.rawData, String(e.errors[0].path[0])), column: 1, }; throw formattedError; diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index fe02119642f0..cc33e83de46d 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -44,7 +44,7 @@ export function astroContentPlugin({ const { pathname, searchParams } = new URL(id, 'file://'); if (searchParams.has('content') && contentFileExts.some((ext) => pathname.endsWith(ext))) { const rawContents = await fs.readFile(pathname, 'utf-8'); - const { content: body, data } = parseFrontmatter(rawContents, pathname); + const { content: body, data, matter: rawData } = parseFrontmatter(rawContents, pathname); const entryInfo = parseEntryInfo(pathname, { contentDir: dirs.contentDir }); if (entryInfo instanceof Error) return; return { @@ -54,6 +54,10 @@ export const collection = ${JSON.stringify(entryInfo.collection)}; export const slug = ${JSON.stringify(entryInfo.slug)}; export const body = ${JSON.stringify(body)}; export const data = ${JSON.stringify(data)}; +export const _internal = { + filePath: ${JSON.stringify(pathname)}, + rawData: ${JSON.stringify(rawData)}, +}; `, }; } From d7811925b8f486b51d89eecd497e12d49be0c619 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 15 Nov 2022 13:02:22 -0500 Subject: [PATCH 051/183] fix: get `renderContent` working with new APIs --- packages/astro/content-generated.d.ts | 4 +-- packages/astro/content-generated.mjs | 12 ++++--- packages/astro/src/content/consts.ts | 3 ++ packages/astro/src/content/internal.ts | 35 +++++++++++++------ .../astro/src/content/vite-plugin-content.ts | 12 ++++--- .../src/content/vite-plugin-delayed-assets.ts | 24 ++++++++----- .../astro/src/core/build/vite-plugin-css.ts | 8 ++--- packages/astro/src/core/render/dev/vite.ts | 4 +-- 8 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 packages/astro/src/content/consts.ts diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index ebbd4578b8eb..df63e35426c1 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -20,6 +20,4 @@ export declare function fetchContent< export declare function renderContent< C extends keyof typeof contentMap, E extends keyof typeof contentMap[C] ->( - entryOrEntryId: typeof contentMap[C][E] | typeof contentMap[C][E]['id'] -): Promise<{ Content: any }>; +>(entry: { collection: C; id: E }): Promise<{ Content: any }>; diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index 4f783fa888b6..c8963faf9c21 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -1,14 +1,14 @@ import { createFetchContent, createFetchContentByEntry, - createRenderContent, + createRenderEntry, createCollectionToGlobResultMap, } from 'astro/content/internal'; const contentDir = 'CONTENT_DIR'; const contentGlob = import.meta.glob('FETCH_CONTENT_GLOB_PATH', { - query: { content: true }, + query: { astroContent: true }, }); const collectionToContentMap = createCollectionToGlobResultMap({ globResult: contentGlob, @@ -21,9 +21,13 @@ const collectionToSchemaMap = createCollectionToGlobResultMap({ contentDir, }); -const renderContentMap = import.meta.glob('RENDER_CONTENT_GLOB_PATH', { +const renderContentGlob = import.meta.glob('RENDER_CONTENT_GLOB_PATH', { query: { astroAssetSsr: true }, }); +const collectionToRenderContentMap = createCollectionToGlobResultMap({ + globResult: renderContentGlob, + contentDir, +}); export const fetchContent = createFetchContent({ collectionToContentMap, @@ -36,4 +40,4 @@ export const fetchContentByEntry = createFetchContentByEntry({ contentDir, }); -export const renderContent = createRenderContent(renderContentMap); +export const renderContent = createRenderEntry({ collectionToRenderContentMap }); diff --git a/packages/astro/src/content/consts.ts b/packages/astro/src/content/consts.ts new file mode 100644 index 000000000000..67297591ccba --- /dev/null +++ b/packages/astro/src/content/consts.ts @@ -0,0 +1,3 @@ +export const contentFileExts = ['.md', '.mdx']; +export const DELAYED_ASSET_FLAG = 'astroAssetSsr'; +export const CONTENT_FLAG = 'astroContent'; diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 55cd558b85e3..0e7292b73f97 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -26,7 +26,7 @@ export function createCollectionToGlobResultMap({ export async function parseEntryData( collection: string, - unparsedEntry: { id: string; data: any; _internal: { rawData: string; filePath: string } }, + entry: { id: string; data: any; _internal: { rawData: string; filePath: string } }, collectionToSchemaMap: CollectionToEntryMap ) { let schemaImport = Object.values(collectionToSchemaMap[collection] ?? {})[0]; @@ -40,22 +40,29 @@ export async function parseEntryData( const { schema } = schemaValue; try { - return schema.parse(unparsedEntry.data, { errorMap }); + return schema.parse(entry.data, { errorMap }); } catch (e) { if (e instanceof z.ZodError) { const formattedError = new Error( [ - `Could not parse frontmatter in ${String(collection)} → ${String(unparsedEntry.id)}`, + `Could not parse frontmatter in ${String(collection)} → ${String(entry.id)}`, ...e.errors.map((e) => e.message), ].join('\n') ); (formattedError as any).loc = { - file: unparsedEntry._internal.filePath, - line: getFrontmatterErrorLine(unparsedEntry._internal.rawData, String(e.errors[0].path[0])), + file: entry._internal.filePath, + line: getFrontmatterErrorLine(entry._internal.rawData, String(e.errors[0].path[0])), column: 1, }; throw formattedError; } + if (e instanceof Error) { + e.message = `There was a problem parsing frontmatter in ${String(collection)} → ${String( + entry.id + )}\n${e.message}`; + throw e; + } + throw e; } } @@ -107,6 +114,7 @@ export function createFetchContent({ id: entry.id, slug: entry.slug, body: entry.body, + collection: entry.collection, data, }; }) @@ -136,18 +144,23 @@ export function createFetchContentByEntry({ id: entry.id, slug: entry.slug, body: entry.body, + collection: entry.collection, data, }; }; } -export function createRenderContent(renderContentMap: Record Promise>) { - return async function renderContent(this: any, entryOrEntryId: { id: string } | string) { - const contentKey = typeof entryOrEntryId === 'object' ? entryOrEntryId.id : entryOrEntryId; - const modImport = renderContentMap[contentKey]; - if (!modImport) throw new Error(`${JSON.stringify(contentKey)} does not exist!`); +export function createRenderEntry({ + collectionToRenderContentMap, +}: { + collectionToRenderContentMap: CollectionToEntryMap; +}) { + return async function renderEntry(this: any, entry: { collection: string; id: string }) { + const lazyImport = collectionToRenderContentMap[entry.collection]?.[entry.id]; + if (!lazyImport) + throw new Error(`${String(entry.collection)} → ${String(entry.id)} does not exist.`); - const mod = await modImport(); + const mod = await lazyImport(); if ('collectedLinks' in mod && 'links' in (this ?? {})) { for (const link of mod.collectedLinks) { diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index cc33e83de46d..41c30d893017 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -7,6 +7,7 @@ import matter from 'gray-matter'; import { info, LogOptions, warn } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; import { appendForwardSlash, prependForwardSlash } from '../core/path.js'; +import { contentFileExts, CONTENT_FLAG } from './consts.js'; type Dirs = { contentDir: URL; @@ -18,8 +19,6 @@ const CONTENT_BASE = 'content-generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; -const contentFileExts = ['.md', '.mdx']; - export function astroContentPlugin({ settings, logging, @@ -42,7 +41,10 @@ export function astroContentPlugin({ enforce: 'pre', async load(id) { const { pathname, searchParams } = new URL(id, 'file://'); - if (searchParams.has('content') && contentFileExts.some((ext) => pathname.endsWith(ext))) { + if ( + searchParams.has(CONTENT_FLAG) && + contentFileExts.some((ext) => pathname.endsWith(ext)) + ) { const rawContents = await fs.readFile(pathname, 'utf-8'); const { content: body, data, matter: rawData } = parseFrontmatter(rawContents, pathname); const entryInfo = parseEntryInfo(pathname, { contentDir: dirs.contentDir }); @@ -327,9 +329,9 @@ function addEntry( entryKey: string, slug: string ) { - contentTypes[collectionKey][entryKey] = `{\n id: ${entryKey},\n slug: ${JSON.stringify( + contentTypes[collectionKey][entryKey] = `{\n id: ${entryKey},\n slug: ${JSON.stringify( slug - )},\n body: string,\n data: z.infer\n}`; + )},\n body: string,\n collection: ${collectionKey},\n data: z.infer\n}`; } function removeEntry(contentTypes: ContentTypes, collectionKey: string, entryKey: string) { diff --git a/packages/astro/src/content/vite-plugin-delayed-assets.ts b/packages/astro/src/content/vite-plugin-delayed-assets.ts index 658a42714c51..3e5a0eb4b929 100644 --- a/packages/astro/src/content/vite-plugin-delayed-assets.ts +++ b/packages/astro/src/content/vite-plugin-delayed-assets.ts @@ -9,11 +9,17 @@ import { normalizeFilename } from '../vite-plugin-utils/index.js'; import { getStylesForURL } from '../core/render/dev/css.js'; import { pathToFileURL } from 'url'; import { createViteLoader } from '../core/module-loader/vite.js'; +import { contentFileExts, DELAYED_ASSET_FLAG } from './consts.js'; const LINKS_PLACEHOLDER = `[/* @@ASTRO-LINKS-PLACEHOLDER@@ */]`; const STYLES_PLACEHOLDER = `[/* @@ASTRO-STYLES-PLACEHOLDER@@ */]`; -export const DELAYED_ASSET_FLAG = '?astroAssetSsr'; +function isDelayedAsset(url: URL): boolean { + return ( + url.searchParams.has(DELAYED_ASSET_FLAG) && + contentFileExts.some((ext) => url.pathname.endsWith(ext)) + ); +} export function astroDelayedAssetPlugin({ settings, @@ -32,9 +38,10 @@ export function astroDelayedAssetPlugin({ } }, load(id) { - if (id.endsWith(DELAYED_ASSET_FLAG)) { + const url = new URL(id, 'file://'); + if (isDelayedAsset(url)) { const code = ` - export { Content } from ${JSON.stringify(id.replace(DELAYED_ASSET_FLAG, ''))}; + export { Content } from ${JSON.stringify(url.pathname)}; export const collectedLinks = ${LINKS_PLACEHOLDER}; export const collectedStyles = ${STYLES_PLACEHOLDER}; `; @@ -43,13 +50,14 @@ export function astroDelayedAssetPlugin({ }, async transform(code, id, options) { if (!options?.ssr) return; - if (id.endsWith(DELAYED_ASSET_FLAG) && devModuleLoader) { - const baseId = id.replace(DELAYED_ASSET_FLAG, ''); - if (!devModuleLoader.getModuleById(baseId)?.ssrModule) { - await devModuleLoader.import(baseId); + const url = new URL(id, 'file://'); + if (devModuleLoader && isDelayedAsset(url)) { + const { pathname } = url; + if (!devModuleLoader.getModuleById(pathname)?.ssrModule) { + await devModuleLoader.import(pathname); } const { stylesMap, urls } = await getStylesForURL( - pathToFileURL(baseId), + pathToFileURL(pathname), devModuleLoader, 'development' ); diff --git a/packages/astro/src/core/build/vite-plugin-css.ts b/packages/astro/src/core/build/vite-plugin-css.ts index a96f13c7b385..cb44d8bddc73 100644 --- a/packages/astro/src/core/build/vite-plugin-css.ts +++ b/packages/astro/src/core/build/vite-plugin-css.ts @@ -15,7 +15,7 @@ import { getPageDatasByHoistedScriptId, isHoistedScript, } from './internal.js'; -import { DELAYED_ASSET_FLAG } from '../../content/vite-plugin-delayed-assets.js'; +import { DELAYED_ASSET_FLAG } from '../../content/consts.js'; interface PluginOptions { internals: BuildInternals; @@ -89,7 +89,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] for (const [pageInfo] of walkParentInfos(id, { getModuleInfo: args[0].getModuleInfo, })) { - if (pageInfo.id.endsWith(DELAYED_ASSET_FLAG)) { + if (new URL(pageInfo.id, 'file://').searchParams.has(DELAYED_ASSET_FLAG)) { // Split delayed assets to separate modules // so they can be injected where needed return createNameHash(id, [id]); @@ -182,10 +182,10 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] id, this, function until(importer) { - return importer.endsWith(DELAYED_ASSET_FLAG); + return new URL(importer, 'file://').searchParams.has(DELAYED_ASSET_FLAG); } )) { - if (pageInfo.id.endsWith(DELAYED_ASSET_FLAG)) { + if (new URL(pageInfo.id, 'file://').searchParams.has(DELAYED_ASSET_FLAG)) { for (const parent of walkParentInfos(id, this)) { const parentInfo = parent[0]; if (moduleIsTopLevelPage(parentInfo)) { diff --git a/packages/astro/src/core/render/dev/vite.ts b/packages/astro/src/core/render/dev/vite.ts index 836f50c4a368..86659f85aa58 100644 --- a/packages/astro/src/core/render/dev/vite.ts +++ b/packages/astro/src/core/render/dev/vite.ts @@ -2,7 +2,7 @@ import type { ModuleLoader, ModuleNode } from '../../module-loader/index'; import npath from 'path'; import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js'; -import { DELAYED_ASSET_FLAG } from '../../../content/vite-plugin-delayed-assets.js'; +import { DELAYED_ASSET_FLAG } from '../../../content/consts.js'; import { unwrapId } from '../../util.js'; import { STYLE_EXTENSIONS } from '../util.js'; @@ -23,7 +23,7 @@ export async function* crawlGraph( ): AsyncGenerator { const id = unwrapId(_id); const importedModules = new Set(); - if (id.endsWith(DELAYED_ASSET_FLAG)) return; + if (new URL(id, 'file://').searchParams.has(DELAYED_ASSET_FLAG)) return; const moduleEntriesForId = isRootFile ? // "getModulesByFile" pulls from a delayed module cache (fun implementation detail), From 0d0f8c047cc63d03e5f5c4bde7836d026ed5eacb Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Tue, 15 Nov 2022 13:26:38 -0500 Subject: [PATCH 052/183] refactor: rename to getCollection, getEntry, renderEntry --- examples/with-content/README.md | 2 +- .../src/content/blog/promo/launch-week.mdx | 1 + .../with-content/src/pages/[...slug].astro | 18 ++--- examples/with-content/src/pages/index.astro | 23 ++++--- examples/with-content/writePosts.mjs | 69 +++++++++++++++++++ packages/astro/content-generated.d.ts | 30 ++++---- packages/astro/content-generated.mjs | 30 ++++---- packages/astro/src/content/internal.ts | 26 +++---- .../astro/src/content/vite-plugin-content.ts | 14 ++-- .../src/content/vite-plugin-delayed-assets.ts | 14 ++-- 10 files changed, 149 insertions(+), 78 deletions(-) create mode 100644 examples/with-content/writePosts.mjs diff --git a/examples/with-content/README.md b/examples/with-content/README.md index b9bcb87977fa..6073eef157d2 100644 --- a/examples/with-content/README.md +++ b/examples/with-content/README.md @@ -23,7 +23,7 @@ Inside of your Astro project, you'll see the following folders and files: └── package.json ``` -`src/content/` contains "collections" of Markdown or MDX documents you'll use in your website. Astro will generate a `fetchContent` function to grab posts from `src/content/` (see the generated `.astro` directory), with type-checked frontmatter based on a schema. +`src/content/` contains "collections" of Markdown or MDX documents you'll use in your website. Astro will generate a `getCollection` function to grab posts from `src/content/` (see the generated `.astro` directory), with type-checked frontmatter based on a schema. Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. diff --git a/examples/with-content/src/content/blog/promo/launch-week.mdx b/examples/with-content/src/content/blog/promo/launch-week.mdx index 4e126e08aba2..f7c4bac161cd 100644 --- a/examples/with-content/src/content/blog/promo/launch-week.mdx +++ b/examples/with-content/src/content/blog/promo/launch-week.mdx @@ -2,6 +2,7 @@ title: 'Launch week!' description: 'Join us for the exciting launch of SPACE BLOG' publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' +tags: ['announcement'] --- import './launch-week-styles.css'; diff --git a/examples/with-content/src/pages/[...slug].astro b/examples/with-content/src/pages/[...slug].astro index 0d4efad00fc1..ce5b189f30af 100644 --- a/examples/with-content/src/pages/[...slug].astro +++ b/examples/with-content/src/pages/[...slug].astro @@ -1,20 +1,20 @@ --- -import { fetchContent, renderContent } from '.astro'; +import { getCollection, renderEntry } from '.astro'; import Layout from 'src/layouts/Layout.astro'; export async function getStaticPaths() { - const blog = await fetchContent('blog'); - return blog.map(entry => ({ - params: { slug: entry.slug }, - props: { id: entry.id, title: entry.data.title }, + const blog = await getCollection('blog'); + return blog.map(post => ({ + params: { slug: post.slug }, + props: { post }, })) } -const { id, title } = Astro.props; -const { Content } = await renderContent(id); +const { post } = Astro.props; +const { Content } = await renderEntry(post); --- - -

{title}

+ +

{post.data.title}

diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index 3b98d4831005..dd95cee7eb72 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -1,22 +1,23 @@ --- -import { fetchContent } from '.astro'; +import { getCollection, getEntry } from '.astro'; import Layout from '../layouts/Layout.astro'; -const spaceBlog = await fetchContent('blog'); +const blog = await getCollection('blog', ({ data }) => !data.tags.includes('announcement')); +const promo = await getEntry('blog', 'promo/launch-week.mdx'); ---

My space blog 🚀

+

+ It's launch week! Learn more here +

diff --git a/examples/with-content/writePosts.mjs b/examples/with-content/writePosts.mjs new file mode 100644 index 000000000000..8176c20c06a2 --- /dev/null +++ b/examples/with-content/writePosts.mjs @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; + +const POSTS_DIR = './src/generated.posts'; + +(async function writePosts() { + const numPosts = Number(process.argv[2]); + const ext = process.argv[3] ?? 'md'; + if (fs.existsSync(POSTS_DIR)) { + const files = await fs.promises.readdir(POSTS_DIR); + await Promise.all(files.map((file) => fs.promises.unlink(path.join(POSTS_DIR, file)))); + } else { + await fs.promises.mkdir(POSTS_DIR); + } + + await Promise.all( + Array.from(Array(numPosts).keys()).map((idx) => { + return fs.promises.writeFile(`${POSTS_DIR}/post-${idx}.${ext}`, toMdContents(idx)); + }) + ); + + console.log(`${numPosts} posts written 🚀`); +})(); + +const toMdContents = (idx) => `--- +title: Post ${idx} +publishedDate: 2021-10-03 +--- + +# Post ${idx} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur interdum quam vitae est dapibus auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus id purus vel ante interdum eleifend non sed magna. Nullam aliquet metus eget nunc pretium, ac malesuada elit ultricies. Quisque fermentum tellus sed risus tristique tincidunt. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas eleifend odio sed tortor rhoncus venenatis. Maecenas dignissim convallis sem et sagittis. Aliquam facilisis auctor consectetur. Morbi vulputate fermentum lobortis. Aenean luctus risus erat, sit amet imperdiet lectus tempor et. + +Aliquam erat volutpat. Vivamus sodales auctor hendrerit. Proin sollicitudin, neque id volutpat ultrices, urna tellus maximus quam, at placerat diam quam a nisl. In commodo, nibh quis rhoncus lacinia, felis nisi egestas tortor, dictum mollis magna massa at tortor. Cras tempus eleifend turpis, nec suscipit velit egestas a. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse nec nulla accumsan, sollicitudin dolor non, varius ipsum. Sed vel congue felis, sit amet bibendum neque. Pellentesque ut diam mollis augue auctor venenatis. Sed vitae aliquet lacus. Proin rutrum eget urna in vehicula. Vestibulum malesuada quis velit ac imperdiet. Donec interdum posuere nisl in auctor. Integer auctor pretium posuere. + +Aenean tincidunt vitae augue id lacinia. Fusce a lorem accumsan, luctus magna non, fermentum arcu. Quisque mattis nibh ut ultrices vehicula. Fusce at porta mauris, eu sollicitudin augue. Curabitur tempor ante vulputate posuere interdum. Nam volutpat odio in blandit dapibus. Aliquam sit amet rutrum tortor. + +Nulla eu odio nisl. Quisque malesuada arcu quis velit fermentum condimentum. Suspendisse potenti. Nullam non egestas sem. Sed et mi pharetra, ornare nunc ultricies, cursus est. Cras dignissim nisl eleifend nisl condimentum placerat. Vivamus tristique mattis vestibulum. + +Maecenas at convallis dui. Pellentesque ac convallis libero. Mauris elementum arcu in quam pulvinar, a tincidunt dolor volutpat. Donec posuere eros ac nunc aliquam, non iaculis purus faucibus. Maecenas non lacus eu elit hendrerit fringilla eget vitae justo. Donec non lorem eu libero placerat volutpat. Vivamus euismod tristique lacus quis tincidunt. + +Morbi a ligula eu odio dictum pharetra. Vestibulum a leo sit amet urna sodales facilisis posuere pretium lorem. Duis consectetur elementum sodales. Ut id libero quis dui laoreet faucibus eget ac felis. Suspendisse eu augue facilisis, consequat ex at, malesuada justo. Fusce tempor convallis orci a tristique. Pellentesque dapibus magna in sapien congue pharetra. Suspendisse potenti. Fusce in tortor justo. In hac habitasse platea dictumst. Pellentesque ligula odio, auctor vel consectetur quis, egestas a lectus. Sed arcu sapien, venenatis vitae nunc vitae, feugiat consequat elit. + +Fusce bibendum odio tellus, ac consequat magna fringilla nec. Donec sed purus at magna pulvinar iaculis ac at nulla. Cras placerat, velit quis suscipit malesuada, eros dui ultrices sapien, sodales imperdiet enim ipsum vitae nisi. Mauris malesuada pretium nibh et luctus. Suspendisse potenti. In ante nibh, euismod at diam in, dapibus facilisis nunc. Suspendisse eleifend mollis dolor sit amet tristique. Nulla mattis tempor urna, nec pellentesque ante feugiat ut. Curabitur eleifend purus sed justo facilisis lacinia. Etiam maximus magna rhoncus quam tincidunt sollicitudin. Proin rhoncus metus lacus, non euismod mi gravida ac. Nam ac ipsum nec ante ultrices tempus ac mollis erat. Quisque ac tortor dolor. Integer eros mi, porttitor at rutrum ut, cursus sit amet ex. Pellentesque sed tortor vitae lorem malesuada gravida. Pellentesque bibendum ex nunc, non cursus lorem viverra gravida. + +Integer lobortis erat quis dolor maximus porta. Sed ipsum est, maximus sit amet hendrerit ac, euismod quis nisi. Sed tincidunt, nisi sit amet varius tempus, turpis nisi sodales ante, sed ultricies urna neque vel purus. Maecenas sed laoreet tortor. Pellentesque enim massa, cursus in mauris vitae, facilisis egestas nisi. Mauris non ultrices purus, nec cursus diam. Proin eget ullamcorper augue. Pellentesque vehicula, sem vel dapibus tempus, neque tortor euismod libero, sed euismod diam enim id sapien. Sed mauris tellus, pretium eu ornare vitae, rhoncus at quam. Donec luctus mollis justo id rutrum. Aenean efficitur arcu nisi, non dignissim massa auctor et. In egestas lobortis nisi ac pharetra. Nam ultricies ipsum ut dui porta, sed commodo arcu vestibulum. Sed in felis molestie ante sodales auctor. + +Praesent ac augue dui. Sed venenatis sit amet quam non rutrum. Vestibulum vitae tempor mi. Cras id luctus sapien, consectetur euismod magna. Nunc quis pellentesque sem, ut suscipit justo. Aliquam dignissim risus ante, vitae luctus enim vestibulum id. In hac habitasse platea dictumst. Nam rhoncus ante sed commodo porta. Ut lectus eros, porta sit amet velit vitae, elementum dignissim nulla. Cras nec scelerisque nulla. Quisque in diam eleifend, congue nulla eu, vestibulum magna. Sed vel purus elementum, mattis nunc id, mollis arcu. Pellentesque in pellentesque ipsum, non condimentum augue. Aenean tincidunt dui ut purus aliquet pretium. Integer vitae velit aliquet, tincidunt urna sed, bibendum lorem. Vivamus sit amet sapien ut sapien rhoncus fringilla vel a mi. + +Praesent dignissim, arcu vel sollicitudin dictum, augue velit pretium ante, sit amet egestas velit lectus et tortor. In egestas ullamcorper risus, non vestibulum diam ultricies eu. Praesent a ex ac nisi consequat rhoncus. Fusce feugiat feugiat libero, vel lobortis mauris faucibus elementum. Mauris vitae luctus sapien. Etiam id pretium metus, in lacinia eros. Morbi et dictum risus. Morbi fringilla lorem ut elit fringilla blandit. + +Nullam eu nibh ipsum. Curabitur aliquet varius ante, a pretium mauris dictum in. Integer nibh arcu, tristique ac sagittis nec, maximus et ligula. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin blandit nec mi vel hendrerit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi consequat blandit orci, sed placerat sem fermentum sit amet. Quisque eu iaculis nisl. Suspendisse quam mauris, semper vel eleifend vitae, mollis in arcu. + +Aenean porttitor blandit orci id bibendum. Nunc sit amet ligula bibendum, congue urna fringilla, dictum purus. Pellentesque blandit, nibh id laoreet placerat, mauris dui semper mi, id tincidunt metus massa nec nisi. Suspendisse potenti. Phasellus ut risus velit. Curabitur porttitor metus nunc, in malesuada justo gravida sit amet. Cras auctor justo magna, ut eleifend turpis dictum id. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque ut augue ac velit imperdiet suscipit non at massa. + +Suspendisse euismod ornare quam id varius. Etiam ac velit id quam accumsan imperdiet sit amet eu nibh. Ut nec massa ultricies enim iaculis feugiat. Phasellus vehicula massa id ligula dapibus, sit amet viverra justo volutpat. Sed nunc est, efficitur et purus id, lacinia pellentesque metus. Pellentesque mi quam, maximus a blandit nec, mollis eget leo. Nulla sit amet elementum augue. Aenean id luctus nisl. Etiam non ante id augue dignissim suscipit in id quam. Quisque non consequat diam, eget condimentum turpis. Donec fringilla metus eget condimentum congue. Pellentesque aliquet blandit posuere. In bibendum ultrices ex a ornare. Donec quis efficitur metus. In commodo sollicitudin turpis et efficitur. Ut ac viverra nunc, sit amet varius sapien. + +In sit amet felis et diam vehicula placerat. Nullam finibus lorem libero, et pretium eros consectetur euismod. In fringilla semper diam et hendrerit. Phasellus id erat at justo imperdiet aliquet. Donec dignissim auctor nunc, et ultrices ex rutrum nec. Aliquam ut cursus leo. Suspendisse semper velit ac lorem aliquet fermentum. Suspendisse congue mi et ultrices bibendum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec ac neque et enim facilisis posuere volutpat a augue. Vivamus feugiat fermentum rhoncus. + +Proin ultricies turpis non mauris finibus elementum. Cras scelerisque pretium justo non efficitur. Curabitur at risus ut velit ullamcorper fringilla congue in nulla. Nunc laoreet lacinia purus at lobortis. Sed vulputate ex non cursus accumsan. Morbi risus elit, porttitor ac hendrerit sed, commodo suscipit nisi. Vivamus vestibulum ex sapien, sagittis blandit velit fermentum et. + +Suspendisse ut ullamcorper ex, a hendrerit elit. Vivamus gravida tempor efficitur. Ut lobortis neque a mollis efficitur. Donec sit amet arcu quis massa fringilla consequat. Duis vitae nisl laoreet, suscipit tellus nec, malesuada sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Phasellus in sollicitudin nisi. Fusce id sapien ac nunc sagittis lobortis tempor auctor eros. Nunc lacinia enim vitae massa lacinia, suscipit facilisis turpis tempus. Integer nec mollis ex. Pellentesque magna nisl, dignissim in mi quis, malesuada elementum nibh. Duis consectetur erat quis interdum ornare. Phasellus lorem felis, aliquam a nunc at, luctus faucibus odio. In hac habitasse platea dictumst. + +Vestibulum sit amet lorem arcu. Integer sed nisl ut turpis dapibus mollis sit amet sed turpis. Donec massa dolor, blandit at lacinia eu, ultricies eu turpis. Sed mollis non diam non consectetur. Morbi suscipit metus at orci sagittis ultricies. Mauris pulvinar maximus ex vitae convallis. Ut imperdiet vehicula mi ut imperdiet. Aliquam et dui at turpis volutpat condimentum. Morbi laoreet scelerisque leo, non tristique ante convallis vulputate. Nam et lorem enim. Cras cursus sodales nisi, nec facilisis felis feugiat sit amet. Aenean consequat pellentesque magna id venenatis. Nunc sed quam consequat, vestibulum diam nec, dignissim justo. Duis vulputate nibh sit amet tortor lobortis iaculis. Curabitur pellentesque dui sapien, nec varius libero hendrerit vel. + +Curabitur quis mi ac massa hendrerit ornare id eget velit. Nulla dui lacus, hendrerit et fringilla sed, eleifend ut erat. Nunc ut fringilla ex, sit amet fringilla libero. Maecenas non ullamcorper orci. Duis posuere erat et urna rhoncus iaculis. Proin pellentesque porttitor nulla, non blandit ante semper vitae. Phasellus ut augue venenatis, tempus purus eu, efficitur massa. Etiam vel egestas tellus, ac pharetra lectus. Aliquam non commodo turpis. Quisque pharetra nunc et mauris bibendum, id vestibulum tellus fringilla. Nullam enim massa, porta id nisi at, accumsan sollicitudin elit. Morbi auctor lectus vitae orci cursus, et hendrerit odio accumsan. Pellentesque quis libero auctor, tempor dolor tempor, finibus arcu. Aliquam non cursus ex. Aliquam quis lacus ut purus pellentesque ultrices in a augue. + +Morbi nunc diam, egestas sed condimentum a, interdum suscipit ligula. Morbi interdum dignissim imperdiet. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris egestas, nulla nec feugiat porttitor, ex magna sodales nisl, ac volutpat tortor mauris vitae nibh. Cras cursus dignissim pretium. Nunc faucibus dui at lectus pellentesque vehicula. Maecenas tincidunt, libero quis hendrerit aliquet, tortor leo iaculis enim, sit amet ullamcorper tellus risus a orci. Donec dignissim metus in nulla eleifend molestie. Nunc at turpis et sem laoreet rutrum. Nulla facilisi. Sed luctus nisi sed egestas cursus. +`; diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index df63e35426c1..93365e8066b0 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -1,23 +1,23 @@ import { z } from 'zod'; -export declare const contentMap: { - // GENERATED_CONTENT_MAP_ENTRIES +export declare const entryMap: { + // @@ENTRY_MAP@@ }; export declare const schemaMap: { - // GENERATED_SCHEMA_MAP_ENTRIES + // @@SCHEMA_MAP@@ }; -export declare function fetchContentByEntry< - C extends keyof typeof contentMap, - E extends keyof typeof contentMap[C] ->(collection: C, entryKey: E): Promise; -export declare function fetchContent< - C extends keyof typeof contentMap, - E extends keyof typeof contentMap[C] +export declare function getEntry< + C extends keyof typeof entryMap, + E extends keyof typeof entryMap[C] +>(collection: C, entryKey: E): Promise; +export declare function getCollection< + C extends keyof typeof entryMap, + E extends keyof typeof entryMap[C] >( collection: C, - filter?: (data: typeof contentMap[C][E]) => boolean -): Promise; -export declare function renderContent< - C extends keyof typeof contentMap, - E extends keyof typeof contentMap[C] + filter?: (data: typeof entryMap[C][E]) => boolean +): Promise; +export declare function renderEntry< + C extends keyof typeof entryMap, + E extends keyof typeof entryMap[C] >(entry: { collection: C; id: E }): Promise<{ Content: any }>; diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index c8963faf9c21..4984a0a26707 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -1,43 +1,43 @@ import { - createFetchContent, - createFetchContentByEntry, + createGetCollection, + createGetEntry, createRenderEntry, createCollectionToGlobResultMap, } from 'astro/content/internal'; -const contentDir = 'CONTENT_DIR'; +const contentDir = '@@CONTENT_DIR@@'; -const contentGlob = import.meta.glob('FETCH_CONTENT_GLOB_PATH', { +const entryGlob = import.meta.glob('@@ENTRY_GLOB_PATH@@', { query: { astroContent: true }, }); -const collectionToContentMap = createCollectionToGlobResultMap({ - globResult: contentGlob, +const collectionToEntryMap = createCollectionToGlobResultMap({ + globResult: entryGlob, contentDir, }); -const schemaGlob = import.meta.glob('SCHEMA_GLOB_PATH'); +const schemaGlob = import.meta.glob('@@SCHEMA_GLOB_PATH@@'); const collectionToSchemaMap = createCollectionToGlobResultMap({ globResult: schemaGlob, contentDir, }); -const renderContentGlob = import.meta.glob('RENDER_CONTENT_GLOB_PATH', { +const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', { query: { astroAssetSsr: true }, }); -const collectionToRenderContentMap = createCollectionToGlobResultMap({ - globResult: renderContentGlob, +const collectionToRenderEntryMap = createCollectionToGlobResultMap({ + globResult: renderEntryGlob, contentDir, }); -export const fetchContent = createFetchContent({ - collectionToContentMap, +export const getCollection = createGetCollection({ + collectionToEntryMap, collectionToSchemaMap, }); -export const fetchContentByEntry = createFetchContentByEntry({ - collectionToContentMap, +export const getEntry = createGetEntry({ + collectionToEntryMap, collectionToSchemaMap, contentDir, }); -export const renderContent = createRenderEntry({ collectionToRenderContentMap }); +export const renderEntry = createRenderEntry({ collectionToRenderEntryMap }); diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 0e7292b73f97..da59c130e009 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -97,15 +97,15 @@ export const getErrorMsg = { `${collection}/~schema needs a named \`schema\` export.`, }; -export function createFetchContent({ - collectionToContentMap, +export function createGetCollection({ + collectionToEntryMap, collectionToSchemaMap, }: { - collectionToContentMap: CollectionToEntryMap; + collectionToEntryMap: CollectionToEntryMap; collectionToSchemaMap: CollectionToEntryMap; }) { - return async function fetchContent(collection: string, filter?: () => boolean) { - const lazyImports = Object.values(collectionToContentMap[collection] ?? {}); + return async function getCollection(collection: string, filter?: () => boolean) { + const lazyImports = Object.values(collectionToEntryMap[collection] ?? {}); const entries = Promise.all( lazyImports.map(async (lazyImport) => { const entry = await lazyImport(); @@ -127,15 +127,15 @@ export function createFetchContent({ }; } -export function createFetchContentByEntry({ - collectionToContentMap, +export function createGetEntry({ + collectionToEntryMap, collectionToSchemaMap, }: { collectionToSchemaMap: CollectionToEntryMap; - collectionToContentMap: CollectionToEntryMap; + collectionToEntryMap: CollectionToEntryMap; }) { - return async function fetchContentByEntry(collection: string, entryId: string) { - const lazyImport = collectionToContentMap[collection]?.[entryId]; + return async function getEntry(collection: string, entryId: string) { + const lazyImport = collectionToEntryMap[collection]?.[entryId]; if (!lazyImport) throw new Error(`Ah! ${entryId}`); const entry = await lazyImport(); @@ -151,12 +151,12 @@ export function createFetchContentByEntry({ } export function createRenderEntry({ - collectionToRenderContentMap, + collectionToRenderEntryMap, }: { - collectionToRenderContentMap: CollectionToEntryMap; + collectionToRenderEntryMap: CollectionToEntryMap; }) { return async function renderEntry(this: any, entry: { collection: string; id: string }) { - const lazyImport = collectionToRenderContentMap[entry.collection]?.[entry.id]; + const lazyImport = collectionToRenderEntryMap[entry.collection]?.[entry.id]; if (!lazyImport) throw new Error(`${String(entry.collection)} → ${String(entry.id)} does not exist.`); diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index 41c30d893017..51117bfcd55c 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -88,15 +88,15 @@ export const _internal = { path.relative(settings.config.root.pathname, dirs.contentDir.pathname) ) ); - const contentGlob = relContentDir + '**/*.{md,mdx}'; + const entryGlob = relContentDir + '**/*.{md,mdx}'; const schemaGlob = relContentDir + '**/~schema.{ts,js,mjs}'; await fs.writeFile( new URL(CONTENT_FILE, dirs.cacheDir), contentJsFile - .replace('CONTENT_DIR', relContentDir) - .replace('FETCH_CONTENT_GLOB_PATH', contentGlob) - .replace('RENDER_CONTENT_GLOB_PATH', contentGlob) - .replace('SCHEMA_GLOB_PATH', schemaGlob) + .replace('@@CONTENT_DIR@@', relContentDir) + .replace('@@ENTRY_GLOB_PATH@@', entryGlob) + .replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob) + .replace('@@SCHEMA_GLOB_PATH@@', schemaGlob) ); await contentGenerator.init(); @@ -417,8 +417,8 @@ async function writeContentFiles({ schemaTypesStr += stringifyObjKeyValue(collectionKey, entry); } - contentTypesBase = contentTypesBase.replace('// GENERATED_CONTENT_MAP_ENTRIES', contentTypesStr); - contentTypesBase = contentTypesBase.replace('// GENERATED_SCHEMA_MAP_ENTRIES', schemaTypesStr); + contentTypesBase = contentTypesBase.replace('// @@ENTRY_MAP@@', contentTypesStr); + contentTypesBase = contentTypesBase.replace('// @@SCHEMA_MAP@@', schemaTypesStr); try { await fs.stat(dirs.cacheDir); diff --git a/packages/astro/src/content/vite-plugin-delayed-assets.ts b/packages/astro/src/content/vite-plugin-delayed-assets.ts index 3e5a0eb4b929..3b2655f78fb9 100644 --- a/packages/astro/src/content/vite-plugin-delayed-assets.ts +++ b/packages/astro/src/content/vite-plugin-delayed-assets.ts @@ -70,15 +70,15 @@ export function astroDelayedAssetPlugin({ } if (id.endsWith('.astro')) { - let renderContentImportName = getRenderContentImportName( + let renderEntryImportName = getRenderEntryImportName( await parseImports(escapeViteEnvReferences(code)) ); - if (!renderContentImportName) return; + if (!renderEntryImportName) return; const s = new MagicString(code, { filename: normalizeFilename({ fileName: id, projectRoot: settings.config.root }), }); - s.prepend(`import { renderContent as $$renderContent } from '.astro';\n`); + s.prepend(`import { renderEntry as $$renderEntry } from '.astro';\n`); // TODO: not this const frontmatterPreamble = '$$createComponent(async ($$result, $$props, $$slots) => {'; const indexOfFrontmatterPreamble = code.indexOf(frontmatterPreamble); @@ -87,7 +87,7 @@ export function astroDelayedAssetPlugin({ s.appendLeft( indexOfFrontmatterPreamble + frontmatterPreamble.length, - `\nlet ${renderContentImportName} = $$renderContent.bind($$result);\n` + `\nlet ${renderEntryImportName} = $$renderEntry.bind($$result);\n` ); return { @@ -130,12 +130,12 @@ export function astroBundleDelayedAssetPlugin({ }; } -function getRenderContentImportName(parseImportRes: Awaited>) { +function getRenderEntryImportName(parseImportRes: Awaited>) { for (const imp of parseImportRes) { if (imp.moduleSpecifier.value === '.astro' && imp.importClause?.named) { for (const namedImp of imp.importClause.named) { - if (namedImp.specifier === 'renderContent') { - // Use `binding` to support `import { renderContent as somethingElse }... + if (namedImp.specifier === 'renderEntry') { + // Use `binding` to support `import { renderEntry as somethingElse }... return namedImp.binding; } } From 41df912c5cf00fee40003d3741acc7906aa1ceff Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 16 Nov 2022 10:01:15 -0500 Subject: [PATCH 053/183] feat: streamline type alias! --- examples/with-content/src/env.d.ts | 1 + .../with-content/src/pages/[...slug].astro | 4 +- examples/with-content/src/pages/index.astro | 2 +- examples/with-content/tsconfig.json | 4 -- packages/astro/content-generated.d.ts | 39 +++++++------- .../astro/src/content/vite-plugin-content.ts | 53 +++++++++++-------- 6 files changed, 56 insertions(+), 47 deletions(-) diff --git a/examples/with-content/src/env.d.ts b/examples/with-content/src/env.d.ts index f964fe0cffd8..9fd4c2bc1fa0 100644 --- a/examples/with-content/src/env.d.ts +++ b/examples/with-content/src/env.d.ts @@ -1 +1,2 @@ /// +/// diff --git a/examples/with-content/src/pages/[...slug].astro b/examples/with-content/src/pages/[...slug].astro index ce5b189f30af..74dfda4667a6 100644 --- a/examples/with-content/src/pages/[...slug].astro +++ b/examples/with-content/src/pages/[...slug].astro @@ -1,6 +1,6 @@ --- -import { getCollection, renderEntry } from '.astro'; -import Layout from 'src/layouts/Layout.astro'; +import { getCollection, renderEntry } from 'astro:content'; +import Layout from '../layouts/Layout.astro'; export async function getStaticPaths() { const blog = await getCollection('blog'); diff --git a/examples/with-content/src/pages/index.astro b/examples/with-content/src/pages/index.astro index dd95cee7eb72..4c206fb2dfc9 100644 --- a/examples/with-content/src/pages/index.astro +++ b/examples/with-content/src/pages/index.astro @@ -1,5 +1,5 @@ --- -import { getCollection, getEntry } from '.astro'; +import { getCollection, getEntry } from 'astro:content'; import Layout from '../layouts/Layout.astro'; const blog = await getCollection('blog', ({ data }) => !data.tags.includes('announcement')); diff --git a/examples/with-content/tsconfig.json b/examples/with-content/tsconfig.json index f190bd7c4cb7..b86574fa8cdb 100644 --- a/examples/with-content/tsconfig.json +++ b/examples/with-content/tsconfig.json @@ -2,9 +2,5 @@ "extends": "astro/tsconfigs/base", "compilerOptions": { "strictNullChecks": true, - "baseUrl": ".", - "paths": { - ".astro": [".astro/content-generated"], - } } } diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index 93365e8066b0..c5bf48c72a32 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -1,23 +1,24 @@ -import { z } from 'zod'; - -export declare const entryMap: { +declare const entryMap: { // @@ENTRY_MAP@@ }; -export declare const schemaMap: { +declare const schemaMap: { // @@SCHEMA_MAP@@ }; -export declare function getEntry< - C extends keyof typeof entryMap, - E extends keyof typeof entryMap[C] ->(collection: C, entryKey: E): Promise; -export declare function getCollection< - C extends keyof typeof entryMap, - E extends keyof typeof entryMap[C] ->( - collection: C, - filter?: (data: typeof entryMap[C][E]) => boolean -): Promise; -export declare function renderEntry< - C extends keyof typeof entryMap, - E extends keyof typeof entryMap[C] ->(entry: { collection: C; id: E }): Promise<{ Content: any }>; + +declare module 'astro:content' { + export function getEntry( + collection: C, + entryKey: E + ): Promise; + export function getCollection< + C extends keyof typeof entryMap, + E extends keyof typeof entryMap[C] + >( + collection: C, + filter?: (data: typeof entryMap[C][E]) => boolean + ): Promise; + export function renderEntry< + C extends keyof typeof entryMap, + E extends keyof typeof entryMap[C] + >(entry: { collection: C; id: E }): Promise<{ Content: any }>; +} diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index 51117bfcd55c..1f491af1cf02 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -1,6 +1,7 @@ import { Plugin, ErrorPayload as ViteErrorPayload, normalizePath } from 'vite'; import glob from 'fast-glob'; import * as fs from 'node:fs/promises'; +import * as fsSync from 'node:fs'; import * as path from 'node:path'; import { bold, cyan } from 'kleur/colors'; import matter from 'gray-matter'; @@ -35,7 +36,37 @@ export function astroContentPlugin({ let contentDirExists = false; let contentGenerator: GenerateContent; + const relContentDir = appendForwardSlash( + prependForwardSlash(path.relative(settings.config.root.pathname, dirs.contentDir.pathname)) + ); + const entryGlob = relContentDir + '**/*.{md,mdx}'; + const schemaGlob = relContentDir + '**/~schema.{ts,js,mjs}'; + const astroContentModContents = fsSync + .readFileSync(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8') + .replace('@@CONTENT_DIR@@', relContentDir) + .replace('@@ENTRY_GLOB_PATH@@', entryGlob) + .replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob) + .replace('@@SCHEMA_GLOB_PATH@@', schemaGlob); + + const virtualModuleId = 'astro:content'; + const resolvedVirtualModuleId = '\0' + virtualModuleId; + return [ + { + name: 'astro-content-virtual-module-plugin', + resolveId(id) { + if (id === virtualModuleId) { + return resolvedVirtualModuleId; + } + }, + load(id) { + if (id === resolvedVirtualModuleId) { + return { + code: astroContentModContents, + }; + } + }, + }, { name: 'content-flag-plugin', enforce: 'pre', @@ -79,26 +110,6 @@ export const _internal = { info(logging, 'content', 'Generating entries...'); contentGenerator = await toGenerateContent({ logging, dirs }); - const contentJsFile = await fs.readFile( - new URL(CONTENT_FILE, dirs.generatedInputDir), - 'utf-8' - ); - const relContentDir = appendForwardSlash( - prependForwardSlash( - path.relative(settings.config.root.pathname, dirs.contentDir.pathname) - ) - ); - const entryGlob = relContentDir + '**/*.{md,mdx}'; - const schemaGlob = relContentDir + '**/~schema.{ts,js,mjs}'; - await fs.writeFile( - new URL(CONTENT_FILE, dirs.cacheDir), - contentJsFile - .replace('@@CONTENT_DIR@@', relContentDir) - .replace('@@ENTRY_GLOB_PATH@@', entryGlob) - .replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob) - .replace('@@SCHEMA_GLOB_PATH@@', schemaGlob) - ); - await contentGenerator.init(); }, async configureServer(viteServer) { @@ -331,7 +342,7 @@ function addEntry( ) { contentTypes[collectionKey][entryKey] = `{\n id: ${entryKey},\n slug: ${JSON.stringify( slug - )},\n body: string,\n collection: ${collectionKey},\n data: z.infer\n}`; + )},\n body: string,\n collection: ${collectionKey},\n data: import('zod').infer\n}`; } function removeEntry(contentTypes: ContentTypes, collectionKey: string, entryKey: string) { From 1f731067245639f5129ab986d5e6d47240f9fd34 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 16 Nov 2022 15:10:02 -0500 Subject: [PATCH 054/183] fix: check schema import is an import --- packages/astro/src/content/internal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index da59c130e009..72036f96faca 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -30,7 +30,7 @@ export async function parseEntryData( collectionToSchemaMap: CollectionToEntryMap ) { let schemaImport = Object.values(collectionToSchemaMap[collection] ?? {})[0]; - if (!schemaImport) { + if (typeof schemaImport !== 'function') { console.warn(getErrorMsg.schemaFileMissing(collection)); } const schemaValue = await schemaImport(); From 4be299f2608a4d9882ce6e749212677d728e295c Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 16 Nov 2022 15:47:04 -0500 Subject: [PATCH 055/183] refactor: change ~schema -> index with defineCollection --- examples/with-content/src/content/blog/index.ts | 14 ++++++++++++++ examples/with-content/src/content/blog/~schema.ts | 12 ------------ packages/astro/content-generated.d.ts | 5 +++++ packages/astro/content-generated.mjs | 6 ++++++ packages/astro/src/content/internal.ts | 8 ++++---- packages/astro/src/content/vite-plugin-content.ts | 14 +++++++------- 6 files changed, 36 insertions(+), 23 deletions(-) create mode 100644 examples/with-content/src/content/blog/index.ts delete mode 100644 examples/with-content/src/content/blog/~schema.ts diff --git a/examples/with-content/src/content/blog/index.ts b/examples/with-content/src/content/blog/index.ts new file mode 100644 index 000000000000..ca07507392fe --- /dev/null +++ b/examples/with-content/src/content/blog/index.ts @@ -0,0 +1,14 @@ +import { z, defineCollection } from 'astro:content'; + +export default defineCollection({ + schema: z.object({ + title: z.string(), + description: z.string().max(60, 'For SEO purposes, keep descriptions short!'), + // mark optional properties with `.optional()` + image: z.string().optional(), + tags: z.array(z.string()).default([]), + // transform to another data type with `transform` + // ex. convert date strings to Date objects + publishedDate: z.string().transform((str) => new Date(str)), + }), +}); diff --git a/examples/with-content/src/content/blog/~schema.ts b/examples/with-content/src/content/blog/~schema.ts deleted file mode 100644 index bf28557f04e3..000000000000 --- a/examples/with-content/src/content/blog/~schema.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from 'zod'; - -export const schema = z.object({ - title: z.string(), - description: z.string().max(60, 'For SEO purposes, keep descriptions short!'), - // mark optional properties with `.optional()` - image: z.string().optional(), - tags: z.array(z.string()).default([]), - // transform to another data type with `transform` - // ex. convert date strings to Date objects - publishedDate: z.string().transform((str) => new Date(str)), -}); diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index c5bf48c72a32..fb8904e3114d 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -6,6 +6,11 @@ declare const schemaMap: { }; declare module 'astro:content' { + export { z } from 'zod'; + export function defineCollection, O>(input: { + schema: T; + external?: Record; + }): typeof input; export function getEntry( collection: C, entryKey: E diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index 4984a0a26707..6f10d2e616f7 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -5,6 +5,12 @@ import { createCollectionToGlobResultMap, } from 'astro/content/internal'; +export { z } from 'zod'; + +export function defineCollection(input) { + return input; +} + const contentDir = '@@CONTENT_DIR@@'; const entryGlob = import.meta.glob('@@ENTRY_GLOB_PATH@@', { diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 72036f96faca..aa755da1a0c9 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -34,10 +34,10 @@ export async function parseEntryData( console.warn(getErrorMsg.schemaFileMissing(collection)); } const schemaValue = await schemaImport(); - if (!('schema' in (schemaValue ?? {}))) { + if (!('schema' in (schemaValue?.default ?? {}))) { throw new Error(getErrorMsg.schemaNamedExpMissing(collection)); } - const { schema } = schemaValue; + const { schema } = schemaValue.default; try { return schema.parse(entry.data, { errorMap }); @@ -92,9 +92,9 @@ function getFrontmatterErrorLine(rawFrontmatter: string, frontmatterKey: string) export const getErrorMsg = { schemaFileMissing: (collection: string) => - `${collection} does not have a ~schema file. We suggest adding one for type safety!`, + `${collection} does not have a config. We suggest adding one for type safety!`, schemaNamedExpMissing: (collection: string) => - `${collection}/~schema needs a named \`schema\` export.`, + `${collection}/index needs a default export. Try using \`defineCollection\`.`, }; export function createGetCollection({ diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index 1f491af1cf02..c22d9105ad7d 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -40,7 +40,7 @@ export function astroContentPlugin({ prependForwardSlash(path.relative(settings.config.root.pathname, dirs.contentDir.pathname)) ); const entryGlob = relContentDir + '**/*.{md,mdx}'; - const schemaGlob = relContentDir + '**/~schema.{ts,js,mjs}'; + const schemaGlob = relContentDir + '**/index.{ts,js,mjs}'; const astroContentModContents = fsSync .readFileSync(new URL(CONTENT_FILE, dirs.generatedInputDir), 'utf-8') .replace('@@CONTENT_DIR@@', relContentDir) @@ -194,7 +194,7 @@ async function toGenerateContent({ ); async function init() { - const pattern = new URL('./**/', dirs.contentDir).pathname + '{*.{md,mdx},~schema.{js,mjs,ts}}'; + const pattern = new URL('./**/', dirs.contentDir).pathname + '{*.{md,mdx},index.{js,mjs,ts}}'; const entries = await glob(pattern); for (const entry of entries) { queueEvent({ name: 'add', entry }, { shouldLog: false }); @@ -240,7 +240,7 @@ async function toGenerateContent({ const { id, slug, collection } = entryInfo; const collectionKey = JSON.stringify(collection); const entryKey = JSON.stringify(id); - if (fileType === 'schema') { + if (fileType === 'collection-config') { switch (event.name) { case 'add': if (!(collectionKey in schemaTypes)) { @@ -342,7 +342,7 @@ function addEntry( ) { contentTypes[collectionKey][entryKey] = `{\n id: ${entryKey},\n slug: ${JSON.stringify( slug - )},\n body: string,\n collection: ${collectionKey},\n data: import('zod').infer\n}`; + )},\n body: string,\n collection: ${collectionKey},\n data: import('zod').infer\n}`; } function removeEntry(contentTypes: ContentTypes, collectionKey: string, entryKey: string) { @@ -366,12 +366,12 @@ function parseEntryInfo( }; } -function getEntryType(entryPath: string): 'content' | 'schema' | 'unknown' { +function getEntryType(entryPath: string): 'content' | 'collection-config' | 'unknown' { const { base, ext } = path.parse(entryPath); if (['.md', '.mdx'].includes(ext)) { return 'content'; - } else if (['~schema.js', '~schema.mjs', '~schema.ts'].includes(base)) { - return 'schema'; + } else if (['index.js', 'index.mjs', 'index.ts'].includes(base)) { + return 'collection-config'; } else { return 'unknown'; } From 6e6c95c6adf5ee7abf657330d440d92b3a959164 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Wed, 16 Nov 2022 15:57:20 -0500 Subject: [PATCH 056/183] feat: auto apply env.d.ts reference --- examples/with-content/src/env.d.ts | 2 +- .../astro/src/content/vite-plugin-content.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/with-content/src/env.d.ts b/examples/with-content/src/env.d.ts index 9fd4c2bc1fa0..5f06dd158d3c 100644 --- a/examples/with-content/src/env.d.ts +++ b/examples/with-content/src/env.d.ts @@ -1,2 +1,2 @@ -/// /// +/// diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index c22d9105ad7d..87bf201364fa 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -9,6 +9,7 @@ import { info, LogOptions, warn } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; import { appendForwardSlash, prependForwardSlash } from '../core/path.js'; import { contentFileExts, CONTENT_FLAG } from './consts.js'; +import { fileURLToPath } from 'node:url'; type Dirs = { contentDir: URL; @@ -20,6 +21,11 @@ const CONTENT_BASE = 'content-generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; +function getEnvReference({ srcDir, contentTypesFile }: { srcDir: URL; contentTypesFile: URL }) { + const relToSrcDir = path.relative(fileURLToPath(srcDir), fileURLToPath(contentTypesFile)); + return `/// `; +} + export function astroContentPlugin({ settings, logging, @@ -111,6 +117,19 @@ export const _internal = { contentGenerator = await toGenerateContent({ logging, dirs }); await contentGenerator.init(); + + const typeEnvPath = new URL('./env.d.ts', settings.config.srcDir); + const typeEnvContent = await fs.readFile( + new URL('./env.d.ts', settings.config.srcDir), + 'utf-8' + ); + const envReference = getEnvReference({ + srcDir: settings.config.srcDir, + contentTypesFile: new URL(CONTENT_TYPES_FILE, dirs.cacheDir), + }); + if (!typeEnvContent.includes(envReference)) { + await fs.writeFile(typeEnvPath, `${envReference}\n${typeEnvContent}`); + } }, async configureServer(viteServer) { if (contentDirExists) { From a2d10ed21fd30dcd4a35747cf9213965588c33d8 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 17 Nov 2022 09:42:37 -0500 Subject: [PATCH 057/183] feat: expose `headings` on `renderEntry` --- packages/astro/content-generated.d.ts | 9 +++++++-- packages/astro/src/content/internal.ts | 5 ++++- packages/astro/src/content/vite-plugin-delayed-assets.ts | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index fb8904e3114d..f71570f13d81 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -9,7 +9,6 @@ declare module 'astro:content' { export { z } from 'zod'; export function defineCollection, O>(input: { schema: T; - external?: Record; }): typeof input; export function getEntry( collection: C, @@ -25,5 +24,11 @@ declare module 'astro:content' { export function renderEntry< C extends keyof typeof entryMap, E extends keyof typeof entryMap[C] - >(entry: { collection: C; id: E }): Promise<{ Content: any }>; + >(entry: { + collection: C; + id: E; + }): Promise<{ + Content: import('astro').MarkdownInstance<{}>['Content']; + headings: import('astro').MarkdownHeading[]; + }>; } diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index aa755da1a0c9..4159f9cddb4f 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -178,6 +178,9 @@ export function createRenderEntry({ }); } } - return mod; + return { + Content: mod.Content, + headings: mod.getHeadings(), + }; }; } diff --git a/packages/astro/src/content/vite-plugin-delayed-assets.ts b/packages/astro/src/content/vite-plugin-delayed-assets.ts index 3b2655f78fb9..e5b44d8d292d 100644 --- a/packages/astro/src/content/vite-plugin-delayed-assets.ts +++ b/packages/astro/src/content/vite-plugin-delayed-assets.ts @@ -41,7 +41,7 @@ export function astroDelayedAssetPlugin({ const url = new URL(id, 'file://'); if (isDelayedAsset(url)) { const code = ` - export { Content } from ${JSON.stringify(url.pathname)}; + export { Content, getHeadings } from ${JSON.stringify(url.pathname)}; export const collectedLinks = ${LINKS_PLACEHOLDER}; export const collectedStyles = ${STYLES_PLACEHOLDER}; `; From 473b7f1416832a1bc29b2aaf2779a8c6117b7eb6 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 17 Nov 2022 10:13:25 -0500 Subject: [PATCH 058/183] refactor: expose zod from astro/zod --- packages/astro/content-generated.d.ts | 4 ++-- packages/astro/content-generated.mjs | 2 +- packages/astro/package.json | 8 ++++++-- packages/astro/src/content/vite-plugin-content.ts | 2 +- packages/astro/zod.d.ts | 5 +++++ packages/astro/zod.mjs | 3 +++ 6 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 packages/astro/zod.d.ts create mode 100644 packages/astro/zod.mjs diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index f71570f13d81..952700d592fe 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -6,8 +6,8 @@ declare const schemaMap: { }; declare module 'astro:content' { - export { z } from 'zod'; - export function defineCollection, O>(input: { + export { z } from 'astro/zod'; + export function defineCollection, O>(input: { schema: T; }): typeof input; export function getEntry( diff --git a/packages/astro/content-generated.mjs b/packages/astro/content-generated.mjs index 6f10d2e616f7..047ca3945b66 100644 --- a/packages/astro/content-generated.mjs +++ b/packages/astro/content-generated.mjs @@ -5,7 +5,7 @@ import { createCollectionToGlobResultMap, } from 'astro/content/internal'; -export { z } from 'zod'; +export { z } from 'astro/zod'; export function defineCollection(input) { return input; diff --git a/packages/astro/package.json b/packages/astro/package.json index e87f4576babb..ec7e716d38c0 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -42,12 +42,12 @@ "types": "./config.d.ts", "default": "./config.mjs" }, - "./content/internal": "./dist/content/internal.js", "./app": "./dist/core/app/index.js", "./app/node": "./dist/core/app/node.js", "./client/*": "./dist/runtime/client/*", "./components": "./components/index.ts", "./components/*": "./components/*", + "./content/internal": "./dist/content/internal.js", "./debug": "./components/Debug.astro", "./internal/*": "./dist/runtime/server/*", "./package.json": "./package.json", @@ -63,7 +63,11 @@ "./vite-plugin-markdown-legacy/*": "./dist/vite-plugin-markdown-legacy/*", "./vite-plugin-markdown": "./dist/vite-plugin-markdown/index.js", "./vite-plugin-markdown/*": "./dist/vite-plugin-markdown/*", - "./dist/jsx/*": "./dist/jsx/*" + "./dist/jsx/*": "./dist/jsx/*", + "./zod": { + "types": "./zod.d.ts", + "default": "./zod.mjs" + } }, "imports": { "#astro/*": "./dist/*.js" diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index 87bf201364fa..6f6739d9fb36 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -361,7 +361,7 @@ function addEntry( ) { contentTypes[collectionKey][entryKey] = `{\n id: ${entryKey},\n slug: ${JSON.stringify( slug - )},\n body: string,\n collection: ${collectionKey},\n data: import('zod').infer\n}`; + )},\n body: string,\n collection: ${collectionKey},\n data: import('astro/zod').infer\n}`; } function removeEntry(contentTypes: ContentTypes, collectionKey: string, entryKey: string) { diff --git a/packages/astro/zod.d.ts b/packages/astro/zod.d.ts new file mode 100644 index 000000000000..72b66bf6761c --- /dev/null +++ b/packages/astro/zod.d.ts @@ -0,0 +1,5 @@ +import * as mod from 'zod'; +export * from 'zod'; +export { mod as z }; +export default mod; +export as namespace Zod; diff --git a/packages/astro/zod.mjs b/packages/astro/zod.mjs new file mode 100644 index 000000000000..0469594e42f6 --- /dev/null +++ b/packages/astro/zod.mjs @@ -0,0 +1,3 @@ +import * as mod from 'zod'; +export { mod as z }; +export default mod; From e98316bb5b351a00cd451c5c1b4bb1755d305b43 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 17 Nov 2022 10:21:07 -0500 Subject: [PATCH 059/183] feat: add Collection util type --- examples/with-content/src/pages/[...slug].astro | 8 ++++++-- packages/astro/content-generated.d.ts | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/with-content/src/pages/[...slug].astro b/examples/with-content/src/pages/[...slug].astro index 74dfda4667a6..642410b86cfe 100644 --- a/examples/with-content/src/pages/[...slug].astro +++ b/examples/with-content/src/pages/[...slug].astro @@ -1,5 +1,5 @@ --- -import { getCollection, renderEntry } from 'astro:content'; +import { getCollection, renderEntry, Collection } from 'astro:content'; import Layout from '../layouts/Layout.astro'; export async function getStaticPaths() { @@ -10,7 +10,11 @@ export async function getStaticPaths() { })) } -const { post } = Astro.props; +interface Props { + post: Collection<'blog'>; +} + +const { post } = Astro.props as Props; const { Content } = await renderEntry(post); --- diff --git a/packages/astro/content-generated.d.ts b/packages/astro/content-generated.d.ts index 952700d592fe..4d909ada66b2 100644 --- a/packages/astro/content-generated.d.ts +++ b/packages/astro/content-generated.d.ts @@ -7,6 +7,8 @@ declare const schemaMap: { declare module 'astro:content' { export { z } from 'astro/zod'; + export type Collection = + typeof entryMap[C][keyof typeof entryMap[C]]; export function defineCollection, O>(input: { schema: T; }): typeof input; @@ -17,10 +19,7 @@ declare module 'astro:content' { export function getCollection< C extends keyof typeof entryMap, E extends keyof typeof entryMap[C] - >( - collection: C, - filter?: (data: typeof entryMap[C][E]) => boolean - ): Promise; + >(collection: C, filter?: (data: typeof entryMap[C][E]) => boolean): Promise[]>; export function renderEntry< C extends keyof typeof entryMap, E extends keyof typeof entryMap[C] From aab0d02c04a4fc37abc29fb60e1d185a1afc1826 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 18 Nov 2022 11:24:42 -0500 Subject: [PATCH 060/183] refactor: generated types in src/content/ --- packages/astro/package.json | 3 ++- .../content/templates/types.generated.d.ts} | 0 .../content/templates/types.generated.mjs} | 0 .../astro/src/content/vite-plugin-content.ts | 27 ++++--------------- 4 files changed, 7 insertions(+), 23 deletions(-) rename packages/astro/{content-generated.d.ts => src/content/templates/types.generated.d.ts} (100%) rename packages/astro/{content-generated.mjs => src/content/templates/types.generated.mjs} (100%) diff --git a/packages/astro/package.json b/packages/astro/package.json index ec7e716d38c0..5364f78baea3 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -89,7 +89,8 @@ "astro-jsx.d.ts", "types.d.ts", "README.md", - "vendor" + "vendor", + "src/content/templates" ], "scripts": { "prebuild": "astro-scripts prebuild --to-string \"src/runtime/server/astro-island.ts\" \"src/runtime/client/{idle,load,media,only,visible}.ts\"", diff --git a/packages/astro/content-generated.d.ts b/packages/astro/src/content/templates/types.generated.d.ts similarity index 100% rename from packages/astro/content-generated.d.ts rename to packages/astro/src/content/templates/types.generated.d.ts diff --git a/packages/astro/content-generated.mjs b/packages/astro/src/content/templates/types.generated.mjs similarity index 100% rename from packages/astro/content-generated.mjs rename to packages/astro/src/content/templates/types.generated.mjs diff --git a/packages/astro/src/content/vite-plugin-content.ts b/packages/astro/src/content/vite-plugin-content.ts index 6f6739d9fb36..4de5e65b9c51 100644 --- a/packages/astro/src/content/vite-plugin-content.ts +++ b/packages/astro/src/content/vite-plugin-content.ts @@ -9,7 +9,6 @@ import { info, LogOptions, warn } from '../core/logger/core.js'; import type { AstroSettings } from '../@types/astro.js'; import { appendForwardSlash, prependForwardSlash } from '../core/path.js'; import { contentFileExts, CONTENT_FLAG } from './consts.js'; -import { fileURLToPath } from 'node:url'; type Dirs = { contentDir: URL; @@ -17,15 +16,10 @@ type Dirs = { generatedInputDir: URL; }; -const CONTENT_BASE = 'content-generated'; +const CONTENT_BASE = 'types.generated'; const CONTENT_FILE = CONTENT_BASE + '.mjs'; const CONTENT_TYPES_FILE = CONTENT_BASE + '.d.ts'; -function getEnvReference({ srcDir, contentTypesFile }: { srcDir: URL; contentTypesFile: URL }) { - const relToSrcDir = path.relative(fileURLToPath(srcDir), fileURLToPath(contentTypesFile)); - return `/// `; -} - export function astroContentPlugin({ settings, logging, @@ -35,9 +29,10 @@ export function astroContentPlugin({ }): Plugin[] { const { root, srcDir } = settings.config; const dirs: Dirs = { - cacheDir: new URL('./.astro/', root), + // Output generated types in content directory. May change in the future! + cacheDir: new URL('./content/', srcDir), contentDir: new URL('./content/', srcDir), - generatedInputDir: new URL('../../', import.meta.url), + generatedInputDir: new URL('../../src/content/templates/', import.meta.url), }; let contentDirExists = false; let contentGenerator: GenerateContent; @@ -117,19 +112,6 @@ export const _internal = { contentGenerator = await toGenerateContent({ logging, dirs }); await contentGenerator.init(); - - const typeEnvPath = new URL('./env.d.ts', settings.config.srcDir); - const typeEnvContent = await fs.readFile( - new URL('./env.d.ts', settings.config.srcDir), - 'utf-8' - ); - const envReference = getEnvReference({ - srcDir: settings.config.srcDir, - contentTypesFile: new URL(CONTENT_TYPES_FILE, dirs.cacheDir), - }); - if (!typeEnvContent.includes(envReference)) { - await fs.writeFile(typeEnvPath, `${envReference}\n${typeEnvContent}`); - } }, async configureServer(viteServer) { if (contentDirExists) { @@ -307,6 +289,7 @@ async function toGenerateContent({ function queueEvent(event: ContentEvent, eventOpts?: { shouldLog: boolean }) { if (!event.entry.startsWith(dirs.contentDir.pathname)) return; + if (event.entry.endsWith(CONTENT_TYPES_FILE)) return; events.push(onEvent(event, eventOpts)); runEventsDebounced(); From 6906230eab84cff11329d660163366acf1d46745 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 18 Nov 2022 11:25:19 -0500 Subject: [PATCH 061/183] edit: remove env.d.ts change from with-content --- examples/with-content/.gitignore | 2 +- examples/with-content/src/env.d.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/with-content/.gitignore b/examples/with-content/.gitignore index 10611f5841d0..2672f4ac9d3d 100644 --- a/examples/with-content/.gitignore +++ b/examples/with-content/.gitignore @@ -1,6 +1,6 @@ # build output dist/ -.astro/ +types.generated.d.ts # dependencies node_modules/ diff --git a/examples/with-content/src/env.d.ts b/examples/with-content/src/env.d.ts index 5f06dd158d3c..f964fe0cffd8 100644 --- a/examples/with-content/src/env.d.ts +++ b/examples/with-content/src/env.d.ts @@ -1,2 +1 @@ -/// /// From 5c46dc89f7356fd4cd400cd1336d0256b42519d4 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 18 Nov 2022 15:50:48 -0500 Subject: [PATCH 062/183] feat: collectionToPaths utility --- packages/astro/src/content/internal.ts | 14 ++++++++++++++ .../src/content/templates/types.generated.d.ts | 10 ++++++++-- .../src/content/templates/types.generated.mjs | 5 +++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/content/internal.ts b/packages/astro/src/content/internal.ts index 4159f9cddb4f..a8df10d35a79 100644 --- a/packages/astro/src/content/internal.ts +++ b/packages/astro/src/content/internal.ts @@ -150,6 +150,20 @@ export function createGetEntry({ }; } +export function createCollectionToPaths({ + getCollection, +}: { + getCollection: ReturnType; +}) { + return async function collectionToPaths(collection: string) { + const entries = await getCollection(collection); + return entries.map((entry) => ({ + params: { slug: entry.slug }, + props: entry, + })); + }; +} + export function createRenderEntry({ collectionToRenderEntryMap, }: { diff --git a/packages/astro/src/content/templates/types.generated.d.ts b/packages/astro/src/content/templates/types.generated.d.ts index 4d909ada66b2..0d18f427a88e 100644 --- a/packages/astro/src/content/templates/types.generated.d.ts +++ b/packages/astro/src/content/templates/types.generated.d.ts @@ -7,8 +7,11 @@ declare const schemaMap: { declare module 'astro:content' { export { z } from 'astro/zod'; - export type Collection = + export type CollectionEntry = typeof entryMap[C][keyof typeof entryMap[C]]; + export function collectionToPaths( + collection: C + ): Promise; export function defineCollection, O>(input: { schema: T; }): typeof input; @@ -19,7 +22,10 @@ declare module 'astro:content' { export function getCollection< C extends keyof typeof entryMap, E extends keyof typeof entryMap[C] - >(collection: C, filter?: (data: typeof entryMap[C][E]) => boolean): Promise[]>; + >( + collection: C, + filter?: (data: typeof entryMap[C][E]) => boolean + ): Promise; export function renderEntry< C extends keyof typeof entryMap, E extends keyof typeof entryMap[C] diff --git a/packages/astro/src/content/templates/types.generated.mjs b/packages/astro/src/content/templates/types.generated.mjs index 047ca3945b66..18378027242c 100644 --- a/packages/astro/src/content/templates/types.generated.mjs +++ b/packages/astro/src/content/templates/types.generated.mjs @@ -2,6 +2,7 @@ import { createGetCollection, createGetEntry, createRenderEntry, + createCollectionToPaths, createCollectionToGlobResultMap, } from 'astro/content/internal'; @@ -47,3 +48,7 @@ export const getEntry = createGetEntry({ }); export const renderEntry = createRenderEntry({ collectionToRenderEntryMap }); + +export const collectionToPaths = createCollectionToPaths({ + getCollection, +}); From 383e842a2492c0fadf917f0a14923ba2ee4eb8ef Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 18 Nov 2022 15:51:06 -0500 Subject: [PATCH 063/183] refactor: migrate `examples/blog` --- examples/blog/.gitignore | 1 + .../src/{pages => content}/blog/first-post.md | 3 +- examples/blog/src/content/blog/index.ts | 14 +++++ .../blog/markdown-style-guide.md | 1 - .../{pages => content}/blog/second-post.md | 3 +- .../src/{pages => content}/blog/third-post.md | 3 +- .../src/{pages => content}/blog/using-mdx.mdx | 1 - examples/blog/src/layouts/BlogPost.astro | 13 +--- examples/blog/src/pages/blog.astro | 59 ------------------- examples/blog/src/pages/blog/[...slug].astro | 17 ++++++ examples/blog/src/pages/blog/index.astro | 57 ++++++++++++++++++ examples/blog/tsconfig.json | 5 +- 12 files changed, 99 insertions(+), 78 deletions(-) rename examples/blog/src/{pages => content}/blog/first-post.md (98%) create mode 100644 examples/blog/src/content/blog/index.ts rename examples/blog/src/{pages => content}/blog/markdown-style-guide.md (98%) rename examples/blog/src/{pages => content}/blog/second-post.md (98%) rename examples/blog/src/{pages => content}/blog/third-post.md (98%) rename examples/blog/src/{pages => content}/blog/using-mdx.mdx (97%) delete mode 100644 examples/blog/src/pages/blog.astro create mode 100644 examples/blog/src/pages/blog/[...slug].astro create mode 100644 examples/blog/src/pages/blog/index.astro diff --git a/examples/blog/.gitignore b/examples/blog/.gitignore index 02f6e50b494c..2672f4ac9d3d 100644 --- a/examples/blog/.gitignore +++ b/examples/blog/.gitignore @@ -1,5 +1,6 @@ # build output dist/ +types.generated.d.ts # dependencies node_modules/ diff --git a/examples/blog/src/pages/blog/first-post.md b/examples/blog/src/content/blog/first-post.md similarity index 98% rename from examples/blog/src/pages/blog/first-post.md rename to examples/blog/src/content/blog/first-post.md index f98986a08a3f..33b844032f78 100644 --- a/examples/blog/src/pages/blog/first-post.md +++ b/examples/blog/src/content/blog/first-post.md @@ -1,5 +1,4 @@ --- -layout: "../../layouts/BlogPost.astro" title: "First post" description: "Lorem ipsum dolor sit amet" pubDate: "Jul 08 2022" @@ -14,4 +13,4 @@ Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam s Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi. -Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna. \ No newline at end of file +Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna. diff --git a/examples/blog/src/content/blog/index.ts b/examples/blog/src/content/blog/index.ts new file mode 100644 index 000000000000..33e1565c3146 --- /dev/null +++ b/examples/blog/src/content/blog/index.ts @@ -0,0 +1,14 @@ +import { defineCollection, z } from 'astro:content'; + +export default defineCollection({ + schema: z.object({ + title: z.string(), + description: z.string(), + pubDate: z.string().transform((str) => new Date(str)), + updatedDate: z + .string() + .optional() + .transform((str) => new Date(str)), + heroImage: z.string().optional(), + }), +}); diff --git a/examples/blog/src/pages/blog/markdown-style-guide.md b/examples/blog/src/content/blog/markdown-style-guide.md similarity index 98% rename from examples/blog/src/pages/blog/markdown-style-guide.md rename to examples/blog/src/content/blog/markdown-style-guide.md index e5e000de95f1..1f221fb91dba 100644 --- a/examples/blog/src/pages/blog/markdown-style-guide.md +++ b/examples/blog/src/content/blog/markdown-style-guide.md @@ -1,5 +1,4 @@ --- -layout: "../../layouts/BlogPost.astro" title: "Markdown Style Guide" description: "Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro." pubDate: "Jul 01 2022" diff --git a/examples/blog/src/pages/blog/second-post.md b/examples/blog/src/content/blog/second-post.md similarity index 98% rename from examples/blog/src/pages/blog/second-post.md rename to examples/blog/src/content/blog/second-post.md index 6b4f2d242de1..1bd5ee4658a4 100644 --- a/examples/blog/src/pages/blog/second-post.md +++ b/examples/blog/src/content/blog/second-post.md @@ -1,5 +1,4 @@ --- -layout: "../../layouts/BlogPost.astro" title: "Second post" description: "Lorem ipsum dolor sit amet" pubDate: "Jul 22 2022" @@ -14,4 +13,4 @@ Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam s Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi. -Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna. \ No newline at end of file +Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna. diff --git a/examples/blog/src/pages/blog/third-post.md b/examples/blog/src/content/blog/third-post.md similarity index 98% rename from examples/blog/src/pages/blog/third-post.md rename to examples/blog/src/content/blog/third-post.md index cba1991c546e..d7f1f24b0408 100644 --- a/examples/blog/src/pages/blog/third-post.md +++ b/examples/blog/src/content/blog/third-post.md @@ -1,5 +1,4 @@ --- -layout: "../../layouts/BlogPost.astro" title: "Third post" description: "Lorem ipsum dolor sit amet" pubDate: "Jul 15 2022" @@ -14,4 +13,4 @@ Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam s Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi. -Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna. \ No newline at end of file +Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna. diff --git a/examples/blog/src/pages/blog/using-mdx.mdx b/examples/blog/src/content/blog/using-mdx.mdx similarity index 97% rename from examples/blog/src/pages/blog/using-mdx.mdx rename to examples/blog/src/content/blog/using-mdx.mdx index 628cc7e024ab..036209d3bba0 100644 --- a/examples/blog/src/pages/blog/using-mdx.mdx +++ b/examples/blog/src/content/blog/using-mdx.mdx @@ -1,5 +1,4 @@ --- -layout: '../../layouts/BlogPost.astro' title: 'Using MDX' description: 'Lorem ipsum dolor sit amet' pubDate: 'Jul 02 2022' diff --git a/examples/blog/src/layouts/BlogPost.astro b/examples/blog/src/layouts/BlogPost.astro index 4baf8e9720f0..445c3b55cac7 100644 --- a/examples/blog/src/layouts/BlogPost.astro +++ b/examples/blog/src/layouts/BlogPost.astro @@ -2,19 +2,12 @@ import BaseHead from '../components/BaseHead.astro'; import Header from '../components/Header.astro'; import Footer from '../components/Footer.astro'; +import { CollectionEntry } from 'astro:content'; -export interface Props { - content: { - title: string; - description: string; - pubDate?: string; - updatedDate?: string; - heroImage?: string; - }; -} +type Props = CollectionEntry<'blog'>['data']; const { - content: { title, description, pubDate, updatedDate, heroImage }, + title, description, pubDate, updatedDate, heroImage, } = Astro.props; --- diff --git a/examples/blog/src/pages/blog.astro b/examples/blog/src/pages/blog.astro deleted file mode 100644 index cedf505b0d20..000000000000 --- a/examples/blog/src/pages/blog.astro +++ /dev/null @@ -1,59 +0,0 @@ ---- -import BaseHead from '../components/BaseHead.astro'; -import Header from '../components/Header.astro'; -import Footer from '../components/Footer.astro'; -import { SITE_TITLE, SITE_DESCRIPTION } from '../config'; - -// Use Astro.glob() to fetch all posts, and then sort them by date. -const posts = (await Astro.glob('./blog/*.{md,mdx}')).sort( - (a, b) => new Date(b.frontmatter.pubDate).valueOf() - new Date(a.frontmatter.pubDate).valueOf() -); ---- - - - - - - - - -
-
-
-
    - { - posts.map((post) => ( -
  • - - {post.frontmatter.title} -
  • - )) - } -
-
-
-