mirror of
https://github.com/reonokiy/blog.nokiy.net.git
synced 2025-06-15 11:12:54 +02:00
🚀 perf: add global content cache to significantly improve build speed, fix slow build times on [...tags_tag] pages
This commit is contained in:
parent
5b9c2d562a
commit
625879b061
3 changed files with 130 additions and 46 deletions
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
import PostList from '@/components/PostList.astro'
|
||||
import { allLocales, defaultLocale, moreLocales } from '@/config'
|
||||
import { defaultLocale, moreLocales } from '@/config'
|
||||
import { getTagPath } from '@/i18n/path'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getAllTags, getPostsByTag } from '@/utils/content'
|
||||
import { getAllTags, getPostsByTag, getTagSupportedLangs } from '@/utils/content'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
type PathItem = {
|
||||
|
@ -39,17 +39,7 @@ export async function getStaticPaths() {
|
|||
const { tag, lang } = Astro.props
|
||||
const posts = await getPostsByTag(tag, lang)
|
||||
const allTags = await getAllTags(lang)
|
||||
|
||||
// Check if tag has posts in each language, return language code if exists, null if not
|
||||
const tagSupportedLangs = await Promise.all(
|
||||
allLocales.map(async (locale) => {
|
||||
const postsInLang = await getPostsByTag(tag, locale)
|
||||
return postsInLang.length > 0 ? locale : null
|
||||
}),
|
||||
)
|
||||
|
||||
// Filter to get supported languages
|
||||
const supportedLangs = tagSupportedLangs.filter(Boolean) as string[]
|
||||
const supportedLangs = await getTagSupportedLangs(tag)
|
||||
---
|
||||
|
||||
<Layout supportedLangs={supportedLangs}>
|
||||
|
|
17
src/utils/cache.ts
Normal file
17
src/utils/cache.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Memoization decorator - provides in-memory caching for async functions
|
||||
*
|
||||
* @param fn The original async function to be memoized
|
||||
* @returns A function wrapper with caching capability
|
||||
*/
|
||||
export function memoize<T>(fn: (...args: any[]) => Promise<T>) {
|
||||
const cache = new Map<string, Promise<T>>()
|
||||
|
||||
return async (...args: any[]): Promise<T> => {
|
||||
const key = JSON.stringify(args) || 'default'
|
||||
if (!cache.has(key)) {
|
||||
cache.set(key, fn(...args))
|
||||
}
|
||||
return cache.get(key)!
|
||||
}
|
||||
}
|
|
@ -1,7 +1,26 @@
|
|||
import type { CollectionEntry } from 'astro:content'
|
||||
import { defaultLocale } from '@/config'
|
||||
import { memoize } from '@/utils/cache'
|
||||
import { getCollection, render } from 'astro:content'
|
||||
|
||||
/**
|
||||
* Core Functions
|
||||
* - addMetaToPost
|
||||
* - getPosts
|
||||
* - checkPostSlugDuplication
|
||||
*
|
||||
* Post Filtering
|
||||
* - getRegularPosts
|
||||
* - getPinnedPosts
|
||||
* - getPostsByYear
|
||||
*
|
||||
* Tag Related
|
||||
* - getPostsGroupByTags
|
||||
* - getAllTags
|
||||
* - getPostsByTag
|
||||
* - getTagSupportedLangs
|
||||
*/
|
||||
|
||||
// Type definitions
|
||||
export type Post = CollectionEntry<'posts'> & {
|
||||
remarkPluginFrontmatter: {
|
||||
|
@ -9,18 +28,32 @@ export type Post = CollectionEntry<'posts'> & {
|
|||
}
|
||||
}
|
||||
|
||||
// Add metadata including reading time to post
|
||||
/**
|
||||
* Add metadata including reading time to a post
|
||||
* @param post The post to enhance with metadata
|
||||
* @returns Enhanced post with reading time information
|
||||
*/
|
||||
const metaCache = new Map<string, { minutes: number }>()
|
||||
async function addMetaToPost(post: CollectionEntry<'posts'>): Promise<Post> {
|
||||
const { remarkPluginFrontmatter } = await render(post)
|
||||
return { ...post, remarkPluginFrontmatter: remarkPluginFrontmatter as { minutes: number } }
|
||||
const cacheKey = `${post.id}-${post.data.lang || 'universal'}`
|
||||
|
||||
if (!metaCache.has(cacheKey)) {
|
||||
const { remarkPluginFrontmatter } = await render(post)
|
||||
metaCache.set(cacheKey, remarkPluginFrontmatter as { minutes: number })
|
||||
}
|
||||
|
||||
return {
|
||||
...post,
|
||||
remarkPluginFrontmatter: metaCache.get(cacheKey)!,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find duplicate post slugs within the same language
|
||||
* @param posts Array of blog posts
|
||||
* @param posts Array of blog posts to check
|
||||
* @returns Array of descriptive error messages for duplicate slugs
|
||||
*/
|
||||
export async function checkPostSlugDuplication(posts: CollectionEntry<'posts'>[]): Promise<string[]> {
|
||||
async function _checkPostSlugDuplication(posts: CollectionEntry<'posts'>[]): Promise<string[]> {
|
||||
const slugMap = new Map<string, Set<string>>()
|
||||
const duplicates: string[] = []
|
||||
|
||||
|
@ -48,13 +81,15 @@ export async function checkPostSlugDuplication(posts: CollectionEntry<'posts'>[]
|
|||
|
||||
return duplicates
|
||||
}
|
||||
// Export memoized version
|
||||
export const checkPostSlugDuplication = memoize(_checkPostSlugDuplication)
|
||||
|
||||
/**
|
||||
* Get all posts (including pinned ones, excluding drafts in production)
|
||||
* @param lang Language code, optional, defaults to site's default language
|
||||
* @param lang The language code to filter by, defaults to site's default language
|
||||
* @returns Posts filtered by language, enhanced with metadata, sorted by date
|
||||
*/
|
||||
export async function getPosts(lang?: string) {
|
||||
async function _getPosts(lang?: string) {
|
||||
const currentLang = lang || defaultLocale
|
||||
|
||||
const filteredPosts = await getCollection(
|
||||
|
@ -72,35 +107,41 @@ export async function getPosts(lang?: string) {
|
|||
b.data.published.valueOf() - a.data.published.valueOf(),
|
||||
)
|
||||
}
|
||||
// Export memoized version
|
||||
export const getPosts = memoize(_getPosts)
|
||||
|
||||
/**
|
||||
* Get all non-pinned posts
|
||||
* @param lang Language code, optional, defaults to site's default language
|
||||
* @returns Regular posts (not pinned), already filtered by language and drafts
|
||||
* @param lang The language code to filter by, defaults to site's default language
|
||||
* @returns Regular posts (non-pinned), filtered by language
|
||||
*/
|
||||
export async function getRegularPosts(lang?: string) {
|
||||
async function _getRegularPosts(lang?: string) {
|
||||
const posts = await getPosts(lang)
|
||||
return posts.filter(post => !post.data.pin)
|
||||
}
|
||||
// Export memoized version
|
||||
export const getRegularPosts = memoize(_getRegularPosts)
|
||||
|
||||
/**
|
||||
* Get pinned posts and sort by pin priority
|
||||
* @param lang Language code, optional, defaults to site's default language
|
||||
* Get pinned posts sorted by pin priority
|
||||
* @param lang The language code to filter by, defaults to site's default language
|
||||
* @returns Pinned posts sorted by pin value in descending order
|
||||
*/
|
||||
export async function getPinnedPosts(lang?: string) {
|
||||
async function _getPinnedPosts(lang?: string) {
|
||||
const posts = await getPosts(lang)
|
||||
return posts
|
||||
.filter(post => post.data.pin && post.data.pin > 0)
|
||||
.sort((a, b) => (b.data.pin || 0) - (a.data.pin || 0))
|
||||
}
|
||||
// Export memoized version
|
||||
export const getPinnedPosts = memoize(_getPinnedPosts)
|
||||
|
||||
/**
|
||||
* Group posts by year and sort within each year
|
||||
* @param lang Language code, optional, defaults to site's default language
|
||||
* @returns Map of posts grouped by year (descending), with posts in each year sorted by date (descending)
|
||||
* @param lang The language code to filter by, defaults to site's default language
|
||||
* @returns Map of posts grouped by year (descending), sorted by date within each year
|
||||
*/
|
||||
export async function getPostsByYear(lang?: string): Promise<Map<number, Post[]>> {
|
||||
async function _getPostsByYear(lang?: string): Promise<Map<number, Post[]>> {
|
||||
const posts = await getRegularPosts(lang)
|
||||
const yearMap = new Map<number, Post[]>()
|
||||
|
||||
|
@ -122,26 +163,15 @@ export async function getPostsByYear(lang?: string): Promise<Map<number, Post[]>
|
|||
|
||||
return new Map([...yearMap.entries()].sort((a, b) => b[0] - a[0]))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tags sorted by post count
|
||||
* @param lang Language code, optional, defaults to site's default language
|
||||
* @returns Array of tags sorted by popularity (most posts first)
|
||||
*/
|
||||
export async function getAllTags(lang?: string) {
|
||||
const tagMap = await getPostsGroupByTags(lang)
|
||||
const tagsWithCount = Array.from(tagMap.entries())
|
||||
|
||||
tagsWithCount.sort((a, b) => b[1].length - a[1].length)
|
||||
return tagsWithCount.map(([tag]) => tag)
|
||||
}
|
||||
// Export memoized version
|
||||
export const getPostsByYear = memoize(_getPostsByYear)
|
||||
|
||||
/**
|
||||
* Group posts by their tags
|
||||
* @param lang Language code, optional, defaults to site's default language
|
||||
* @param lang The language code to filter by, defaults to site's default language
|
||||
* @returns Map where keys are tag names and values are arrays of posts with that tag
|
||||
*/
|
||||
export async function getPostsGroupByTags(lang?: string) {
|
||||
async function _getPostsGroupByTags(lang?: string) {
|
||||
const posts = await getPosts(lang)
|
||||
const tagMap = new Map<string, Post[]>()
|
||||
|
||||
|
@ -156,14 +186,61 @@ export async function getPostsGroupByTags(lang?: string) {
|
|||
|
||||
return tagMap
|
||||
}
|
||||
// Export memoized version
|
||||
export const getPostsGroupByTags = memoize(_getPostsGroupByTags)
|
||||
|
||||
/**
|
||||
* Get all tags sorted by post count
|
||||
* @param lang The language code to filter by, defaults to site's default language
|
||||
* @returns Array of tags sorted by popularity (most posts first)
|
||||
*/
|
||||
async function _getAllTags(lang?: string) {
|
||||
const tagMap = await getPostsGroupByTags(lang)
|
||||
const tagsWithCount = Array.from(tagMap.entries())
|
||||
|
||||
tagsWithCount.sort((a, b) => b[1].length - a[1].length)
|
||||
return tagsWithCount.map(([tag]) => tag)
|
||||
}
|
||||
// Export memoized version
|
||||
export const getAllTags = memoize(_getAllTags)
|
||||
|
||||
/**
|
||||
* Get all posts that contain a specific tag
|
||||
* @param tag The tag name to filter posts by
|
||||
* @param lang Language code, optional, defaults to site's default language
|
||||
* @param lang The language code to filter by, defaults to site's default language
|
||||
* @returns Array of posts that contain the specified tag
|
||||
*/
|
||||
export async function getPostsByTag(tag: string, lang?: string) {
|
||||
async function _getPostsByTag(tag: string, lang?: string) {
|
||||
const tagMap = await getPostsGroupByTags(lang)
|
||||
return tagMap.get(tag) || []
|
||||
}
|
||||
// Export memoized version
|
||||
export const getPostsByTag = memoize(_getPostsByTag)
|
||||
|
||||
/**
|
||||
* Check which languages support a specific tag
|
||||
* @param tag The tag name to check language support for
|
||||
* @returns Array of language codes that support the specified tag
|
||||
*/
|
||||
async function _getTagSupportedLangs(tag: string) {
|
||||
const posts = await getCollection(
|
||||
'posts',
|
||||
({ data }) => !data.draft,
|
||||
)
|
||||
const supportedLangs = []
|
||||
const { allLocales } = await import('@/config')
|
||||
|
||||
for (const locale of allLocales) {
|
||||
const hasPostsWithTag = posts.some(post =>
|
||||
post.data.tags?.includes(tag)
|
||||
&& (post.data.lang === locale || post.data.lang === ''),
|
||||
)
|
||||
if (hasPostsWithTag) {
|
||||
supportedLangs.push(locale)
|
||||
}
|
||||
}
|
||||
|
||||
return supportedLangs
|
||||
}
|
||||
// Export memoized version
|
||||
export const getTagSupportedLangs = memoize(_getTagSupportedLangs)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue