feat: add generated lookup-map
This commit is contained in:
parent
4ea716e569
commit
ddc9afe36b
6 changed files with 100 additions and 37 deletions
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(',')}}`;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue