본문으로 건너뛰기

[Open WebUI] Shiki 지연 로딩으로 초기 번들 5~10MB 감소

PR 링크: open-webui/open-webui#22304 상태: Merged | 변경: +99 / -5

들어가며

Shiki는 VS Code와 동일한 TextMate 문법을 사용하는 강력한 코드 하이라이터입니다. 하지만 모든 언어 문법을 번들에 포함하면 5~10MB에 달하는 JavaScript가 초기 로드에 포함됩니다. Open WebUI에서는 isCodeFile() 같은 단순한 확장자 체크 함수도 이 모듈을 import하고 있었기 때문에, 코드 하이라이팅이 전혀 필요 없는 페이지에서도 이 무거운 번들이 로드되고 있었습니다.

핵심 코드 분석

정적 import 제거 및 정적 언어 목록으로 교체

Before:

import { codeToHtml, bundledLanguages } from 'shiki';

const _langSet = new Set(Object.keys(bundledLanguages));

export function extToLang(ext: string): string | null {
    const lower = ext.toLowerCase();
    if (EXT_OVERRIDE[lower]) return EXT_OVERRIDE[lower];
    if (_langSet.has(lower)) return lower;
    return null;
}

After:

const KNOWN_LANG_IDS = new Set([
    'ada', 'c', 'cpp', 'css', 'dart', 'go', 'html',
    'java', 'javascript', 'json', 'kotlin', 'lua',
    'python', 'ruby', 'rust', 'sql', 'swift',
    'typescript', 'yaml', 'zig',
    // ... 총 85개 언어
]);

export function extToLang(ext: string): string | null {
    const lower = ext.toLowerCase();
    if (EXT_OVERRIDE[lower]) return EXT_OVERRIDE[lower];
    if (KNOWN_LANG_IDS.has(lower)) return lower;
    return null;
}

하이라이팅 함수에서 동적 import 사용

Before:

export async function highlightCode(code: string, filePath: string): Promise<string> {
    const lang = extToLang(ext) ?? 'text';
    return await codeToHtml(code, { lang, themes: { ... } });
}

After:

export async function highlightCode(code: string, filePath: string): Promise<string> {
    const lang = extToLang(ext) ?? 'text';
    const { codeToHtml } = await import('shiki');
    return await codeToHtml(code, { lang, themes: { ... } });
}

highlightCode는 이미 async 함수이므로 호출자 측에서는 아무런 변경이 필요 없습니다.

왜 이게 좋은가

  • 초기 번들에서 약 5~10MB의 JavaScript가 제거되어 첫 페이지 로드 시간이 크게 단축됩니다
  • 코드 하이라이팅이 실제로 필요한 시점에만 shiki가 로드됩니다
  • isCodeFile() 같은 동기 유틸리티 함수가 더 이상 무거운 의존성을 끌어오지 않습니다
  • 드문 확장자가 KNOWN_LANG_IDS에 없더라도 파일 자체는 정상적으로 열리며, 하이라이팅 시에는 shiki의 전체 번들이 동적으로 로드되므로 문제가 없습니다

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글