From acbb8b7e6db6823a176efd98f4b7fc6f2edc10e8 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 17 Mar 2023 17:56:32 -0400 Subject: [PATCH] feat: implement with cache --- packages/astro/src/@types/astro.ts | 4 +- .../content/vite-plugin-content-imports.ts | 171 ++++++++++-------- packages/integrations/markdoc/src/index.ts | 2 +- 3 files changed, 97 insertions(+), 80 deletions(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 54a409798..0af084217 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1053,7 +1053,9 @@ export interface ContentEntryType { fileUrl: URL; contents: string; }): GetEntryInfoReturnType | Promise; - getModule?(params: { entry: ContentEntryModule }): rollup.LoadResult | Promise; + getRenderModule?(params: { + entry: ContentEntryModule; + }): rollup.LoadResult | Promise; contentModuleTypes?: string; } diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index cf727f9e0..bc445c9d1 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -45,10 +45,98 @@ export function astroContentImportPlugin({ } } - async function getEntryDataById( - fileId: string, - pluginContext: PluginContext - ): Promise { + // Used by the `render-module` plugin to avoid double-parsing your schema + const contentEntryModuleByIdCache = new Map(); + + const plugins: Plugin[] = [ + { + name: 'astro:content-imports', + async load(viteId) { + if (isContentFlagImport(viteId, contentEntryExts)) { + const { fileId } = getFileInfo(viteId, settings.config); + const { id, slug, collection, body, data, _internal } = await getContentEntryModule({ + fileId, + pluginContext: this, + }); + + const code = escapeViteEnvReferences(` +export const id = ${JSON.stringify(id)}; +export const collection = ${JSON.stringify(collection)}; +export const slug = ${JSON.stringify(slug)}; +export const body = ${JSON.stringify(body)}; +export const data = ${devalue.uneval(data) /* TODO: reuse astro props serializer */}; +export const _internal = { + filePath: ${JSON.stringify(_internal.filePath)}, + rawData: ${JSON.stringify(_internal.rawData)}, +}; +`); + return { code }; + } + }, + configureServer(viteServer) { + viteServer.watcher.on('all', async (event, entry) => { + if ( + ['add', 'unlink', 'change'].includes(event) && + getEntryType(entry, contentPaths, contentEntryExts) === 'config' + ) { + // Content modules depend on config, so we need to invalidate them. + for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) { + if ( + isContentFlagImport(modUrl, contentEntryExts) || + // TODO: refine to content types with getModule + contentEntryExts.some((ext) => modUrl.endsWith(ext)) + ) { + const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl); + if (mod) { + viteServer.moduleGraph.invalidateModule(mod); + } + } + } + } + }); + }, + async transform(code, id) { + if (isContentFlagImport(id, contentEntryExts)) { + // Escape before Rollup internal transform. + // Base on MUCH trial-and-error, inspired by MDX integration 2-step transform. + return { code: escapeViteEnvReferences(code) }; + } + }, + }, + ]; + + if (settings.contentEntryTypes.some((t) => t.getRenderModule)) { + plugins.push({ + name: 'astro:content-render-imports', + async load(viteId) { + if (!contentEntryExts.some((ext) => viteId.endsWith(ext))) return; + + const { fileId } = getFileInfo(viteId, settings.config); + for (const contentEntryType of settings.contentEntryTypes) { + if (contentEntryType.getRenderModule) { + const entry = await contentEntryModuleByIdCache.get(fileId); + if (!entry) + throw new AstroError({ + ...AstroErrorData.UnknownContentCollectionError, + message: `Unable to render ${JSON.stringify( + fileId + )}. Did you import this module directly without using a content collection query?`, + }); + + return contentEntryType.getRenderModule({ entry }); + } + } + }, + }); + } + + async function getContentEntryModule({ + fileId, + pluginContext, + }: { + fileId: string; + pluginContext: PluginContext; + }): Promise { const observable = globalContentConfigObserver.get(); // Content config should be loaded before this plugin is used @@ -122,82 +210,9 @@ export function astroContentImportPlugin({ data, body: info.body, }; - + contentEntryModuleByIdCache.set(fileId, contentEntryModule); return contentEntryModule; } - const plugins: Plugin[] = [ - { - name: 'astro:content-imports', - async load(viteId) { - const { fileId } = getFileInfo(viteId, settings.config); - if (isContentFlagImport(viteId, contentEntryExts)) { - const { id, slug, collection, body, data, _internal } = await getEntryDataById( - fileId, - this - ); - - const code = escapeViteEnvReferences(` -export const id = ${JSON.stringify(id)}; -export const collection = ${JSON.stringify(collection)}; -export const slug = ${JSON.stringify(slug)}; -export const body = ${JSON.stringify(body)}; -export const data = ${devalue.uneval(data) /* TODO: reuse astro props serializer */}; -export const _internal = { - filePath: ${JSON.stringify(_internal.filePath)}, - rawData: ${JSON.stringify(_internal.rawData)}, -}; -`); - return { code }; - } - }, - configureServer(viteServer) { - viteServer.watcher.on('all', async (event, entry) => { - if ( - ['add', 'unlink', 'change'].includes(event) && - getEntryType(entry, contentPaths, contentEntryExts) === 'config' - ) { - // Content modules depend on config, so we need to invalidate them. - for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) { - if ( - isContentFlagImport(modUrl, contentEntryExts) || - // TODO: refine to content types with getModule - contentEntryExts.some((ext) => modUrl.endsWith(ext)) - ) { - const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl); - if (mod) { - viteServer.moduleGraph.invalidateModule(mod); - } - } - } - } - }); - }, - async transform(code, id) { - if (isContentFlagImport(id, contentEntryExts)) { - // Escape before Rollup internal transform. - // Base on MUCH trial-and-error, inspired by MDX integration 2-step transform. - return { code: escapeViteEnvReferences(code) }; - } - }, - }, - ]; - - if (settings.contentEntryTypes.some((t) => t.getModule)) { - plugins.push({ - name: 'astro:content-render-modules', - async load(viteId) { - if (!contentEntryExts.some((ext) => viteId.endsWith(ext))) return; - - const { fileId } = getFileInfo(viteId, settings.config); - for (const contentEntryType of settings.contentEntryTypes) { - if (contentEntryType.getModule) { - const entry = await getEntryDataById(fileId, this); - return contentEntryType.getModule({ entry }); - } - } - }, - }); - } return plugins; } diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index 57113cdc7..6e168da71 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -36,7 +36,7 @@ export default function markdoc(markdocConfig: Config = {}): IntegrationWithPriv addContentEntryType({ extensions: ['.mdoc'], getEntryInfo, - getModule({ entry }: any): Partial { + getRenderModule({ entry }: any): Partial { validateRenderProperties(markdocConfig, config); const ast = Markdoc.parse(entry.body); const content = Markdoc.transform(ast, {