Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 11 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
205 changes: 205 additions & 0 deletions PERFORMANCE_IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 5 additions & 3 deletions e2e/dlog.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
12 changes: 6 additions & 6 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion src/lib/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
const title = $derived(m.title());
</script>

<header id="main-header" class="sticky top-0 flex h-12 border-b border-surface-200-800 bg-surface-50-950/70 backdrop-blur">
<header
id="main-header"
class="sticky top-0 flex h-12 border-b border-surface-200-800 bg-surface-50-950/70 backdrop-blur"
>
<h1 class="flex-1 text-center content-center-safe font-black text-2xl uppercase">
{title}
</h1>
Expand Down
24 changes: 12 additions & 12 deletions src/lib/components/HeroSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
</script>

<section class="markdown">
<p>{m.introduction()}</p>
<p>{m.introduction()}</p>

<figure>
<enhanced:img
src={terminalBrowsing}
alt={m.terminalBrowsing()}
data-title={m.terminal()}
loading="lazy"
decoding="async"
/>
<figcaption>{m.terminalCaption()}</figcaption>
</figure>
</section>
<figure>
<enhanced:img
src={terminalBrowsing}
alt={m.terminalBrowsing()}
data-title={m.terminal()}
loading="lazy"
decoding="async"
/>
<figcaption>{m.terminalCaption()}</figcaption>
</figure>
</section>
21 changes: 11 additions & 10 deletions src/lib/components/Nav.svelte
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
<!-- 언어별 국기 SVG 컴포넌트 선언 -->
<script module>
export const FlagKo = () =>
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60' width='18' height='18'><circle cx='30' cy='30' r='30' fill='#fff'/><circle cx='30' cy='30' r='13' fill='#c60c30'/><path d='M17 17l26 26M43 17L17 43' stroke='#003478' stroke-width='2'/></svg>`;
export const FlagJa = () =>
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60' width='18' height='18'><circle cx='30' cy='30' r='30' fill='#fff'/><circle cx='30' cy='30' r='13' fill='#bc002d'/></svg>`;
export const FlagEn = () =>
`<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60' width='18' height='18'><circle cx='30' cy='30' r='30' fill='#00247d'/><path d='M0 30h60M30 0v60' stroke='#fff' stroke-width='8'/><path d='M0 0l60 60M60 0L0 60' stroke='#fff' stroke-width='4'/><path d='M0 0l60 60M60 0L0 60' stroke='#cf142b' stroke-width='2'/></svg>`;
</script>

<script>
import { page } from '$app/state';
import { deLocalizeHref } from '$lib/paraglide/runtime.js';
Expand All @@ -22,13 +32,6 @@
}
</script>

<!-- 언어별 국기 SVG 컴포넌트 선언 -->
<script module>
export const FlagKo = () => `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60' width='18' height='18'><circle cx='30' cy='30' r='30' fill='#fff'/><circle cx='30' cy='30' r='13' fill='#c60c30'/><path d='M17 17l26 26M43 17L17 43' stroke='#003478' stroke-width='2'/></svg>`;
export const FlagJa = () => `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60' width='18' height='18'><circle cx='30' cy='30' r='30' fill='#fff'/><circle cx='30' cy='30' r='13' fill='#bc002d'/></svg>`;
export const FlagEn = () => `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60' width='18' height='18'><circle cx='30' cy='30' r='30' fill='#00247d'/><path d='M0 30h60M30 0v60' stroke='#fff' stroke-width='8'/><path d='M0 0l60 60M60 0L0 60' stroke='#fff' stroke-width='4'/><path d='M0 0l60 60M60 0L0 60' stroke='#cf142b' stroke-width='2'/></svg>`;
</script>

<nav class="p-4 uppercase text-2xl font-black tracking-widest" {...rest}>
<ul class="grid">
{#each Object.entries(menuPaths) as [key, path] (key)}
Expand All @@ -47,9 +50,7 @@

<ul class="grid">
{#each locales as locale (locale)}
{#snippet localeItem(
/** @type {string} */ locale
)}
{#snippet localeItem(/** @type {string} */ locale)}
<li class="text-right flex items-center gap-2 justify-end">
{@html locale === 'ko-kr' ? FlagKo() : locale === 'ja-jp' ? FlagJa() : FlagEn()}
<a
Expand Down
Loading