diff --git a/CHANGELOG.md b/CHANGELOG.md index b4bbcb2..93fb1c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,49 +1,42 @@ # [3.1.0](https://github.com/XIYO/xiyo.github.io/compare/v3.0.0...v3.1.0) (2025-10-20) - ### Features -* 최근 변경 이력 dlog 작성 및 footer 스타일 간소화 ([c7dc1eb](https://github.com/XIYO/xiyo.github.io/commit/c7dc1ebf1fbe178de6b9f03b10fcf3c853f1343c)) +- 최근 변경 이력 dlog 작성 및 footer 스타일 간소화 ([c7dc1eb](https://github.com/XIYO/xiyo.github.io/commit/c7dc1ebf1fbe178de6b9f03b10fcf3c853f1343c)) # [3.0.0](https://github.com/XIYO/xiyo.github.io/compare/v2.8.1...v3.0.0) (2025-10-18) - ### Bug Fixes -* increase test timeout and fix authors test assertion ([41ff9a0](https://github.com/XIYO/xiyo.github.io/commit/41ff9a06ec2252b0679c0db779bbcc6548744499)) -* increase test timeout to 20s for CI environment ([a54ad45](https://github.com/XIYO/xiyo.github.io/commit/a54ad45a6c7a2bd83e5dd067f8517bd5cf29dcfe)) -* update semantic-release config for pnpm ([702790e](https://github.com/XIYO/xiyo.github.io/commit/702790eb02883e32fba0568c3499212793acda1d)) -* use createdAt consistently across all dlog components and tests ([d4d8da8](https://github.com/XIYO/xiyo.github.io/commit/d4d8da8c4171f5052ac179830b85ca322ca6075d)) - +- increase test timeout and fix authors test assertion ([41ff9a0](https://github.com/XIYO/xiyo.github.io/commit/41ff9a06ec2252b0679c0db779bbcc6548744499)) +- increase test timeout to 20s for CI environment ([a54ad45](https://github.com/XIYO/xiyo.github.io/commit/a54ad45a6c7a2bd83e5dd067f8517bd5cf29dcfe)) +- update semantic-release config for pnpm ([702790e](https://github.com/XIYO/xiyo.github.io/commit/702790eb02883e32fba0568c3499212793acda1d)) +- use createdAt consistently across all dlog components and tests ([d4d8da8](https://github.com/XIYO/xiyo.github.io/commit/d4d8da8c4171f5052ac179830b85ca322ca6075d)) ### Code Refactoring -* standardize time field names to createdAt/modifiedAt ([71e1336](https://github.com/XIYO/xiyo.github.io/commit/71e1336cd03114e76f4a66cdada2341cf824b6d7)) - +- standardize time field names to createdAt/modifiedAt ([71e1336](https://github.com/XIYO/xiyo.github.io/commit/71e1336cd03114e76f4a66cdada2341cf824b6d7)) ### Features -* add first dlog entry and fix date display ([b8bc27c](https://github.com/XIYO/xiyo.github.io/commit/b8bc27cd67d9087208d82e023be7dcd17686d1cc)) - +- add first dlog entry and fix date display ([b8bc27c](https://github.com/XIYO/xiyo.github.io/commit/b8bc27cd67d9087208d82e023be7dcd17686d1cc)) ### BREAKING CHANGES -* Field names in frontmatter have changed from published/lastModified to createdAt/modifiedAt +- Field names in frontmatter have changed from published/lastModified to createdAt/modifiedAt ## [2.8.1](https://github.com/XIYO/xiyo.github.io/compare/v2.8.0...v2.8.1) (2025-10-18) - ### Bug Fixes -* add PUBLIC_SITE_URL environment variable to GitHub Actions build steps ([aa5f269](https://github.com/XIYO/xiyo.github.io/commit/aa5f269308538972304538f72841a1d8c88669e2)) -* add UTF-8 encoding support for markdown files and remove submodule references ([6f94802](https://github.com/XIYO/xiyo.github.io/commit/6f948029e7621925e4d15ca6391d00549f30a611)) +- add PUBLIC_SITE_URL environment variable to GitHub Actions build steps ([aa5f269](https://github.com/XIYO/xiyo.github.io/commit/aa5f269308538972304538f72841a1d8c88669e2)) +- add UTF-8 encoding support for markdown files and remove submodule references ([6f94802](https://github.com/XIYO/xiyo.github.io/commit/6f948029e7621925e4d15ca6391d00549f30a611)) # [2.8.0](https://github.com/XIYO/xiyo.github.io/compare/v2.7.0...v2.8.0) (2025-10-08) - ### Features -* hoverable sidebar locale switcher ([bf79cfc](https://github.com/XIYO/xiyo.github.io/commit/bf79cfca1b53530e742194e85a1a511eeec3cbab)) +- hoverable sidebar locale switcher ([bf79cfc](https://github.com/XIYO/xiyo.github.io/commit/bf79cfca1b53530e742194e85a1a511eeec3cbab)) # [2.7.0](https://github.com/XIYO/xiyo.github.io/compare/v2.6.0...v2.7.0) (2025-08-16) diff --git a/PERFORMANCE_IMPROVEMENTS.md b/PERFORMANCE_IMPROVEMENTS.md new file mode 100644 index 0000000..7d2dcb9 --- /dev/null +++ b/PERFORMANCE_IMPROVEMENTS.md @@ -0,0 +1,205 @@ +# Performance Improvements + +This document summarizes the performance optimizations made to the xiyo.dev blog codebase. + +## Summary of Changes + +### 1. Parallel Processing in Category Tree Traversal + +**File**: `src/lib/post/Category.js` +**Impact**: High + +Changed the `getAllPosts()` method to process child categories in parallel instead of sequentially: + +```javascript +// Before: Sequential processing +for (const childCategory of this.#childCategories.values()) { + const posts = await childCategory.getAllPosts(); // Blocking + childPosts.push(...posts); +} + +// After: Parallel processing +const childPostsArrays = await Promise.all( + [...this.#childCategories.values()].map((childCategory) => childCategory.getAllPosts()) +); +const childPosts = childPostsArrays.flat(); +``` + +**Benefits**: + +- Significantly faster traversal of deep category trees +- Better utilization of async operations +- Scales well with number of child categories + +### 2. Eliminated Filesystem Scanning in Sitemap Generation + +**File**: `src/routes/sitemap.xml/+server.js` +**Impact**: High + +Replaced manual filesystem scanning with the existing Category/Post infrastructure: + +```javascript +// Before: Manual FS scanning +async function scanDirectory(dirPath, locale, relativePath, urls) { + const entries = await readdir(dirPath); + // ... recursive scanning with fs.stat +} + +// After: Use Category system +const root = Category.getCategory(''); +const [allPosts, allCategories] = await Promise.all([ + root.getAllPosts(), + Promise.resolve(root.allChildCategories) +]); +``` + +**Benefits**: + +- No redundant file system operations +- Consistent with rest of application +- Batch metadata loading +- Easier to maintain + +### 3. Extracted Common Sorting Logic + +**File**: `src/lib/post/Category.js` +**Impact**: Medium + +Created a reusable sorting method to eliminate code duplication: + +```javascript +// New private static method +static #sortPostsByDate(posts) { + return posts.sort((a, b) => b.sortDate.getTime() - a.sortDate.getTime()); +} +``` + +Used in both `getPosts()` and `getAllPosts()` methods. + +**Benefits**: + +- DRY principle (Don't Repeat Yourself) +- Consistent sorting behavior +- Easier to modify sorting logic in the future + +### 4. Optimized getLatestPostDate + +**File**: `src/lib/post/Category.js` +**Impact**: Low + +Changed from O(n) operation to O(1) by utilizing already-sorted posts: + +```javascript +// Before: Iterating all posts +const dates = allPosts.map((post) => post.sortDate); +return new Date(Math.max(...dates.map((d) => d.getTime()))); + +// After: Using first element of sorted array +return allPosts[0].sortDate; +``` + +**Benefits**: + +- Constant time lookup instead of linear +- No unnecessary array iterations + +### 5. Added Limits to toSerialize Method + +**File**: `src/lib/post/Category.js`, `src/routes/(main)/[...slug]/+page.server.js` +**Impact**: Medium + +Added `maxPosts` parameter to prevent loading excessive data: + +```javascript +async toSerialize(maxDepth = 10, maxPosts = undefined) { + // ... + const postsToSerialize = maxPosts ? allPosts.slice(0, maxPosts) : allPosts; + // ... +} + +// Usage in routes +categoryInstance?.toSerialize(10, 100); // Limit to 100 posts +``` + +**Benefits**: + +- Reduced memory usage for large categories +- Faster serialization +- Prevents overwhelming the client with data + +### 6. Added Documentation Comments + +**Files**: Various +**Impact**: Low + +Added clarifying comments about metadata caching and performance considerations: + +```javascript +// Extract first 50 posts - metadata already cached from getAllPosts() +const recent = await Promise.all(...); +``` + +**Benefits**: + +- Better code understanding +- Helps future optimization efforts +- Documents intentional design decisions + +### 7. Code Quality Improvements + +**Files**: Various +**Impact**: Low + +- Removed unused `__noop` function from `markdown.js` +- Removed unused `baseLocale` import from `feed.xml/+server.js` +- Added comment to empty catch block in `+layout.svelte` + +**Benefits**: + +- Cleaner codebase +- Passes linting checks +- Better maintainability + +## Performance Considerations + +### Current Caching Strategy + +The codebase uses several levels of caching: + +1. **Post Metadata Caching**: Each Post instance caches its metadata after first load +2. **Processed Markdown Caching**: Markdown processing results are cached per Post +3. **Unified Processor Caching**: The markdown processor itself is cached and reused + +### Known Performance Characteristics + +- **getAllPosts()**: Loads and caches metadata for all posts, then sorts. Expensive on first call, fast on subsequent calls due to caching. +- **toSerialize()**: Now respects depth and post limits to prevent excessive data loading. +- **Sitemap generation**: Uses Category system instead of FS, leveraging existing metadata cache. + +### Potential Future Optimizations + +1. **Lazy metadata loading**: Only load metadata when actually needed (complex change) +2. **Incremental sorting**: For very large post sets, consider pagination at the data layer +3. **Mermaid diagram caching**: Cache rendered SVGs to avoid browser launches during build +4. **Memory management**: Consider more aggressive cache clearing for long-running processes + +## Testing + +All changes maintain existing functionality. The build completes successfully, and pre-existing tests pass. + +## Measurements + +Before making these changes, the main performance bottlenecks were: + +1. Sequential category traversal in `getAllPosts()` +2. Redundant filesystem operations in sitemap generation +3. Unnecessary data loading in `toSerialize()` + +After these optimizations: + +- Category tree traversal is parallelized +- Sitemap generation eliminates FS operations +- Data serialization respects reasonable limits +- Code is more maintainable with less duplication + +For specific benchmarks, consider adding timing logs in development mode. diff --git a/e2e/dlog.spec.js b/e2e/dlog.spec.js index dfa223f..9297223 100644 --- a/e2e/dlog.spec.js +++ b/e2e/dlog.spec.js @@ -44,7 +44,7 @@ test.describe('Dlog Feature - E2E Tests', () => { await page.goto('/'); const timeElements = page.locator('.dlog-list time'); - if (await timeElements.count() > 0) { + if ((await timeElements.count()) > 0) { const datetimeAttr = await timeElements.first().getAttribute('datetime'); expect(datetimeAttr).toBeTruthy(); } @@ -133,7 +133,9 @@ test.describe('Dlog Feature - E2E Tests', () => { test('should have hr separator after dlog section', async ({ page }) => { await page.goto('/'); - const dlogSection = page.locator('section.markdown', { has: page.locator('h2:has-text("최근 작업")') }); + const dlogSection = page.locator('section.markdown', { + has: page.locator('h2:has-text("최근 작업")') + }); const hrInSection = dlogSection.locator('hr'); expect(await hrInSection.count()).toBeGreaterThanOrEqual(0); @@ -143,7 +145,7 @@ test.describe('Dlog Feature - E2E Tests', () => { await page.goto('/'); const dlogItems = page.locator('.dlog-list li'); - if (await dlogItems.count() > 0) { + if ((await dlogItems.count()) > 0) { const spans = dlogItems.first().locator('span'); const spanCount = await spans.count(); expect(spanCount).toBeGreaterThanOrEqual(0); diff --git a/src/app.css b/src/app.css index a5792ee..bb0bb20 100644 --- a/src/app.css +++ b/src/app.css @@ -14,13 +14,13 @@ } @layer base { - html { - @apply overscroll-none; - } + html { + @apply overscroll-none; + } - body > div:first-child { - @apply contents; - } + body > div:first-child { + @apply contents; + } } @theme { diff --git a/src/lib/Header.svelte b/src/lib/Header.svelte index 7d3a49f..ed164f0 100644 --- a/src/lib/Header.svelte +++ b/src/lib/Header.svelte @@ -5,7 +5,10 @@ const title = $derived(m.title()); -
+

{title}

diff --git a/src/lib/components/HeroSection.svelte b/src/lib/components/HeroSection.svelte index eb80292..742afb3 100644 --- a/src/lib/components/HeroSection.svelte +++ b/src/lib/components/HeroSection.svelte @@ -4,16 +4,16 @@
-

{m.introduction()}

+

{m.introduction()}

-
- -
{m.terminalCaption()}
-
-
\ No newline at end of file +
+ +
{m.terminalCaption()}
+
+ diff --git a/src/lib/components/Nav.svelte b/src/lib/components/Nav.svelte index caf8a78..c812568 100644 --- a/src/lib/components/Nav.svelte +++ b/src/lib/components/Nav.svelte @@ -1,3 +1,13 @@ + + + - - -