feat: add generated lookup-map

This commit is contained in:
bholmesdev 2023-04-26 13:55:31 -04:00
parent 4ea716e569
commit ddc9afe36b
6 changed files with 100 additions and 37 deletions

View file

@ -14,6 +14,7 @@ import {
type GlobResult = Record<string, () => Promise<any>>; type GlobResult = Record<string, () => Promise<any>>;
type CollectionToEntryMap = Record<string, GlobResult>; type CollectionToEntryMap = Record<string, GlobResult>;
type GetEntryImport = (collection: string, lookupId: string) => () => Promise<any>;
export function createCollectionToGlobResultMap({ export function createCollectionToGlobResultMap({
globResult, globResult,
@ -28,9 +29,8 @@ export function createCollectionToGlobResultMap({
const segments = keyRelativeToContentDir.split('/'); const segments = keyRelativeToContentDir.split('/');
if (segments.length <= 1) continue; if (segments.length <= 1) continue;
const collection = segments[0]; const collection = segments[0];
const entryId = segments.slice(1).join('/');
collectionToGlobResultMap[collection] ??= {}; collectionToGlobResultMap[collection] ??= {};
collectionToGlobResultMap[collection][entryId] = globResult[key]; collectionToGlobResultMap[collection][key] = globResult[key];
} }
return collectionToGlobResultMap; return collectionToGlobResultMap;
} }
@ -38,10 +38,10 @@ export function createCollectionToGlobResultMap({
const cacheEntriesByCollection = new Map<string, any[]>(); const cacheEntriesByCollection = new Map<string, any[]>();
export function createGetCollection({ export function createGetCollection({
collectionToEntryMap, collectionToEntryMap,
collectionToRenderEntryMap, getRenderEntryImport,
}: { }: {
collectionToEntryMap: CollectionToEntryMap; collectionToEntryMap: CollectionToEntryMap;
collectionToRenderEntryMap: CollectionToEntryMap; getRenderEntryImport: GetEntryImport;
}) { }) {
return async function getCollection(collection: string, filter?: (entry: any) => unknown) { return async function getCollection(collection: string, filter?: (entry: any) => unknown) {
const lazyImports = Object.values(collectionToEntryMap[collection] ?? {}); const lazyImports = Object.values(collectionToEntryMap[collection] ?? {});
@ -64,7 +64,7 @@ export function createGetCollection({
return render({ return render({
collection: entry.collection, collection: entry.collection,
id: entry.id, id: entry.id,
collectionToRenderEntryMap, renderEntryImport: await getRenderEntryImport(collection, entry.slug),
}); });
}, },
}; };
@ -82,10 +82,10 @@ export function createGetCollection({
export function createGetEntryBySlug({ export function createGetEntryBySlug({
getCollection, getCollection,
collectionToRenderEntryMap, getRenderEntryImport,
}: { }: {
getCollection: ReturnType<typeof createGetCollection>; getCollection: ReturnType<typeof createGetCollection>;
collectionToRenderEntryMap: CollectionToEntryMap; getRenderEntryImport: GetEntryImport;
}) { }) {
return async function getEntryBySlug(collection: string, slug: string) { return async function getEntryBySlug(collection: string, slug: string) {
// This is not an optimized lookup. Should look into an O(1) implementation // This is not an optimized lookup. Should look into an O(1) implementation
@ -114,7 +114,7 @@ export function createGetEntryBySlug({
return render({ return render({
collection: entry.collection, collection: entry.collection,
id: entry.id, id: entry.id,
collectionToRenderEntryMap, renderEntryImport: await getRenderEntryImport(collection, entry.slug),
}); });
}, },
}; };
@ -124,21 +124,20 @@ export function createGetEntryBySlug({
async function render({ async function render({
collection, collection,
id, id,
collectionToRenderEntryMap, renderEntryImport,
}: { }: {
collection: string; collection: string;
id: string; id: string;
collectionToRenderEntryMap: CollectionToEntryMap; renderEntryImport?: ReturnType<GetEntryImport>;
}) { }) {
const UnexpectedRenderError = new AstroError({ const UnexpectedRenderError = new AstroError({
...AstroErrorData.UnknownContentCollectionError, ...AstroErrorData.UnknownContentCollectionError,
message: `Unexpected error while rendering ${String(collection)}${String(id)}.`, message: `Unexpected error while rendering ${String(collection)}${String(id)}.`,
}); });
const lazyImport = collectionToRenderEntryMap[collection]?.[id]; if (typeof renderEntryImport !== 'function') throw UnexpectedRenderError;
if (typeof lazyImport !== 'function') throw UnexpectedRenderError;
const baseMod = await lazyImport(); const baseMod = await renderEntryImport();
if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError; if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError;
const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod; const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod;

View file

@ -28,6 +28,16 @@ const collectionToEntryMap = createCollectionToGlobResultMap({
contentDir, contentDir,
}); });
function createGlobLookup(entryGlob) {
return async (collection, lookupId) => {
const { default: lookupMap } = await import('@@LOOKUP_MAP_PATH@@');
const filePath = lookupMap[collection]?.[lookupId];
if (!filePath) return undefined;
return entryGlob[collection][filePath];
};
}
const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', { const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', {
query: { astroPropagatedAssets: true }, query: { astroPropagatedAssets: true },
}); });
@ -38,10 +48,10 @@ const collectionToRenderEntryMap = createCollectionToGlobResultMap({
export const getCollection = createGetCollection({ export const getCollection = createGetCollection({
collectionToEntryMap, collectionToEntryMap,
collectionToRenderEntryMap, getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap),
}); });
export const getEntryBySlug = createGetEntryBySlug({ export const getEntryBySlug = createGetEntryBySlug({
getCollection, getCollection,
collectionToRenderEntryMap, getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap),
}); });

View file

@ -22,6 +22,7 @@ import {
type ContentObservable, type ContentObservable,
type ContentPaths, type ContentPaths,
type EntryInfo, type EntryInfo,
updateLookupMaps,
} from './utils.js'; } from './utils.js';
type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir'; type ChokidarEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir';
@ -278,6 +279,12 @@ export async function createContentTypesGenerator({
contentConfig: observable.status === 'loaded' ? observable.config : undefined, contentConfig: observable.status === 'loaded' ? observable.config : undefined,
contentEntryTypes: settings.contentEntryTypes, contentEntryTypes: settings.contentEntryTypes,
}); });
await updateLookupMaps({
contentEntryExts,
contentPaths,
root: settings.config.root,
fs,
});
if (observable.status === 'loaded' && ['info', 'warn'].includes(logLevel)) { if (observable.status === 'loaded' && ['info', 'warn'].includes(logLevel)) {
warnNonexistentCollections({ warnNonexistentCollections({
logging, logging,

View file

@ -1,3 +1,4 @@
import glob, { type Options as FastGlobOptions } from 'fast-glob';
import { slug as githubSlug } from 'github-slugger'; import { slug as githubSlug } from 'github-slugger';
import matter from 'gray-matter'; import matter from 'gray-matter';
import fsMod from 'node:fs'; import fsMod from 'node:fs';
@ -12,6 +13,7 @@ import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { CONTENT_TYPES_FILE } from './consts.js'; import { CONTENT_TYPES_FILE } from './consts.js';
import { errorMap } from './error-map.js'; import { errorMap } from './error-map.js';
import { createImage } from './runtime-assets.js'; import { createImage } from './runtime-assets.js';
import { rootRelativePath } from '../core/util.js';
export const collectionConfigParser = z.object({ export const collectionConfigParser = z.object({
schema: z.any().optional(), schema: z.any().optional(),
@ -129,16 +131,23 @@ export function getContentEntryExts(settings: Pick<AstroSettings, 'contentEntryT
export class NoCollectionError extends Error {} export class NoCollectionError extends Error {}
export function getEntryInfo( export function getEntryInfo(
params: Pick<ContentPaths, 'contentDir'> & { entry: URL; allowFilesOutsideCollection?: true } params: Pick<ContentPaths, 'contentDir'> & {
entry: string | URL;
allowFilesOutsideCollection?: true;
}
): EntryInfo; ): EntryInfo;
export function getEntryInfo({ export function getEntryInfo({
entry, entry,
contentDir, contentDir,
allowFilesOutsideCollection = false, allowFilesOutsideCollection = false,
}: Pick<ContentPaths, 'contentDir'> & { entry: URL; allowFilesOutsideCollection?: boolean }): }: Pick<ContentPaths, 'contentDir'> & {
| EntryInfo entry: string | URL;
| NoCollectionError { allowFilesOutsideCollection?: boolean;
const rawRelativePath = path.relative(fileURLToPath(contentDir), fileURLToPath(entry)); }): EntryInfo | NoCollectionError {
const rawRelativePath = path.relative(
fileURLToPath(contentDir),
typeof entry === 'string' ? entry : fileURLToPath(entry)
);
const rawCollection = path.dirname(rawRelativePath).split(path.sep).shift(); const rawCollection = path.dirname(rawRelativePath).split(path.sep).shift();
const isOutsideCollection = rawCollection === '..' || rawCollection === '.'; const isOutsideCollection = rawCollection === '..' || rawCollection === '.';
@ -358,3 +367,51 @@ function search(fs: typeof fsMod, srcDir: URL) {
} }
return { exists: false, url: paths[0] }; return { exists: false, url: paths[0] };
} }
export async function updateLookupMaps({
contentPaths,
contentEntryExts,
root,
fs,
}: {
contentEntryExts: string[];
contentPaths: Pick<ContentPaths, 'contentDir' | 'cacheDir'>;
root: URL;
fs: typeof fsMod;
}) {
const { contentDir } = contentPaths;
const globOpts: FastGlobOptions = {
absolute: false,
cwd: fileURLToPath(root),
fs: {
readdir: fs.readdir.bind(fs),
readdirSync: fs.readdirSync.bind(fs),
},
};
const relContentDir = rootRelativePath(root, contentDir, false);
const contentGlob = await glob(`${relContentDir}/**/*${getExtGlob(contentEntryExts)}`, globOpts);
let filePathByLookupId: {
[collection: string]: Record<string, string>;
} = {};
for (const filePath of contentGlob) {
const info = getEntryInfo({ contentDir, entry: filePath });
if (info instanceof NoCollectionError) continue;
filePathByLookupId[info.collection] ??= {};
// TODO: frontmatter slugs
filePathByLookupId[info.collection][info.slug] = '/' + filePath;
}
await fs.promises.writeFile(
new URL('lookup-map.json', contentPaths.cacheDir),
JSON.stringify(filePathByLookupId, null, 2)
);
}
export function getExtGlob(exts: string[]) {
return exts.length === 1
? // Wrapping {...} breaks when there is only one extension
exts[0]
: `{${exts.join(',')}}`;
}

View file

@ -1,11 +1,9 @@
import fsMod from 'node:fs'; import fsMod from 'node:fs';
import * as path from 'node:path';
import type { Plugin } from 'vite'; import type { Plugin } from 'vite';
import { normalizePath } from 'vite';
import type { AstroSettings } from '../@types/astro.js'; import type { AstroSettings } from '../@types/astro.js';
import { appendForwardSlash, prependForwardSlash } from '../core/path.js';
import { VIRTUAL_MODULE_ID } from './consts.js'; import { VIRTUAL_MODULE_ID } from './consts.js';
import { getContentEntryExts, getContentPaths } from './utils.js'; import { getContentEntryExts, getContentPaths, getExtGlob } from './utils.js';
import { rootRelativePath } from '../core/util.js';
interface AstroContentVirtualModPluginParams { interface AstroContentVirtualModPluginParams {
settings: AstroSettings; settings: AstroSettings;
@ -15,13 +13,7 @@ export function astroContentVirtualModPlugin({
settings, settings,
}: AstroContentVirtualModPluginParams): Plugin { }: AstroContentVirtualModPluginParams): Plugin {
const contentPaths = getContentPaths(settings.config); const contentPaths = getContentPaths(settings.config);
const relContentDir = normalizePath( const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
appendForwardSlash(
prependForwardSlash(
path.relative(settings.config.root.pathname, contentPaths.contentDir.pathname)
)
)
);
const contentEntryExts = getContentEntryExts(settings); const contentEntryExts = getContentEntryExts(settings);
const extGlob = const extGlob =
@ -32,6 +24,7 @@ export function astroContentVirtualModPlugin({
const entryGlob = `${relContentDir}**/*${extGlob}`; const entryGlob = `${relContentDir}**/*${extGlob}`;
const virtualModContents = fsMod const virtualModContents = fsMod
.readFileSync(contentPaths.virtualModTemplate, 'utf-8') .readFileSync(contentPaths.virtualModTemplate, 'utf-8')
.replace('@@LOOKUP_MAP_PATH@@', new URL('lookup-map.json', contentPaths.cacheDir).pathname)
.replace('@@CONTENT_DIR@@', relContentDir) .replace('@@CONTENT_DIR@@', relContentDir)
.replace('@@ENTRY_GLOB_PATH@@', entryGlob) .replace('@@ENTRY_GLOB_PATH@@', entryGlob)
.replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob); .replace('@@RENDER_ENTRY_GLOB_PATH@@', entryGlob);

View file

@ -151,18 +151,15 @@ export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) {
return id.slice(slash(fileURLToPath(config.srcDir)).length); return id.slice(slash(fileURLToPath(config.srcDir)).length);
} }
export function rootRelativePath(root: URL, idOrUrl: URL | string) { export function rootRelativePath(root: URL, idOrUrl: URL | string, prependSlash = true) {
let id: string; let id: string;
if (typeof idOrUrl !== 'string') { if (typeof idOrUrl !== 'string') {
id = unwrapId(viteID(idOrUrl)); id = unwrapId(viteID(idOrUrl));
} else { } else {
id = idOrUrl; id = idOrUrl;
} }
const normalizedRoot = normalizePath(fileURLToPath(root)); id = id.slice(normalizePath(fileURLToPath(root)).length);
if (id.startsWith(normalizedRoot)) { return prependSlash ? prependForwardSlash(id) : id;
id = id.slice(normalizedRoot.length);
}
return prependForwardSlash(id);
} }
export function emoji(char: string, fallback: string) { export function emoji(char: string, fallback: string) {