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

View file

@ -28,6 +28,16 @@ const collectionToEntryMap = createCollectionToGlobResultMap({
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@@', {
query: { astroPropagatedAssets: true },
});
@ -38,10 +48,10 @@ const collectionToRenderEntryMap = createCollectionToGlobResultMap({
export const getCollection = createGetCollection({
collectionToEntryMap,
collectionToRenderEntryMap,
getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap),
});
export const getEntryBySlug = createGetEntryBySlug({
getCollection,
collectionToRenderEntryMap,
getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap),
});

View file

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

View file

@ -1,3 +1,4 @@
import glob, { type Options as FastGlobOptions } from 'fast-glob';
import { slug as githubSlug } from 'github-slugger';
import matter from 'gray-matter';
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 { errorMap } from './error-map.js';
import { createImage } from './runtime-assets.js';
import { rootRelativePath } from '../core/util.js';
export const collectionConfigParser = z.object({
schema: z.any().optional(),
@ -129,16 +131,23 @@ export function getContentEntryExts(settings: Pick<AstroSettings, 'contentEntryT
export class NoCollectionError extends Error {}
export function getEntryInfo(
params: Pick<ContentPaths, 'contentDir'> & { entry: URL; allowFilesOutsideCollection?: true }
params: Pick<ContentPaths, 'contentDir'> & {
entry: string | URL;
allowFilesOutsideCollection?: true;
}
): EntryInfo;
export function getEntryInfo({
entry,
contentDir,
allowFilesOutsideCollection = false,
}: Pick<ContentPaths, 'contentDir'> & { entry: URL; allowFilesOutsideCollection?: boolean }):
| EntryInfo
| NoCollectionError {
const rawRelativePath = path.relative(fileURLToPath(contentDir), fileURLToPath(entry));
}: Pick<ContentPaths, 'contentDir'> & {
entry: string | URL;
allowFilesOutsideCollection?: boolean;
}): EntryInfo | NoCollectionError {
const rawRelativePath = path.relative(
fileURLToPath(contentDir),
typeof entry === 'string' ? entry : fileURLToPath(entry)
);
const rawCollection = path.dirname(rawRelativePath).split(path.sep).shift();
const isOutsideCollection = rawCollection === '..' || rawCollection === '.';
@ -358,3 +367,51 @@ function search(fs: typeof fsMod, srcDir: URL) {
}
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 * as path from 'node:path';
import type { Plugin } from 'vite';
import { normalizePath } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { appendForwardSlash, prependForwardSlash } from '../core/path.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 {
settings: AstroSettings;
@ -15,13 +13,7 @@ export function astroContentVirtualModPlugin({
settings,
}: AstroContentVirtualModPluginParams): Plugin {
const contentPaths = getContentPaths(settings.config);
const relContentDir = normalizePath(
appendForwardSlash(
prependForwardSlash(
path.relative(settings.config.root.pathname, contentPaths.contentDir.pathname)
)
)
);
const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir);
const contentEntryExts = getContentEntryExts(settings);
const extGlob =
@ -32,6 +24,7 @@ export function astroContentVirtualModPlugin({
const entryGlob = `${relContentDir}**/*${extGlob}`;
const virtualModContents = fsMod
.readFileSync(contentPaths.virtualModTemplate, 'utf-8')
.replace('@@LOOKUP_MAP_PATH@@', new URL('lookup-map.json', contentPaths.cacheDir).pathname)
.replace('@@CONTENT_DIR@@', relContentDir)
.replace('@@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);
}
export function rootRelativePath(root: URL, idOrUrl: URL | string) {
export function rootRelativePath(root: URL, idOrUrl: URL | string, prependSlash = true) {
let id: string;
if (typeof idOrUrl !== 'string') {
id = unwrapId(viteID(idOrUrl));
} else {
id = idOrUrl;
}
const normalizedRoot = normalizePath(fileURLToPath(root));
if (id.startsWith(normalizedRoot)) {
id = id.slice(normalizedRoot.length);
}
return prependForwardSlash(id);
id = id.slice(normalizePath(fileURLToPath(root)).length);
return prependSlash ? prependForwardSlash(id) : id;
}
export function emoji(char: string, fallback: string) {