UNPKG

motiontext-renderer

Version:

Web-based animated caption/subtitle renderer with plugin system

554 lines (440 loc) β€’ 15.4 kB
# MotionText Renderer 🎬 **μ›Ή 기반 μ• λ‹ˆλ©”μ΄μ…˜ μžλ§‰/μΊ‘μ…˜ λ Œλ”λŸ¬ 라이브러리** λ™μ˜μƒ μ½˜ν…μΈ μ— 동적인 μžλ§‰κ³Ό μ• λ‹ˆλ©”μ΄μ…˜ 효과λ₯Ό μ‰½κ²Œ μΆ”κ°€ν•  수 μžˆλŠ” TypeScript λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€. ν”ŒλŸ¬κ·ΈμΈ μ‹œμŠ€ν…œμ„ 톡해 ν™•μž₯ κ°€λŠ₯ν•˜λ©°, μ›Ή ν‘œμ€€μ„ μ€€μˆ˜ν•˜λŠ” μ•ˆμ „ν•œ μƒŒλ“œλ°•μŠ€ ν™˜κ²½μ—μ„œ λ™μž‘ν•©λ‹ˆλ‹€. ## ✨ μ£Όμš” κΈ°λŠ₯ - 🎯 **μ •κ·œν™” μ’Œν‘œκ³„**: μŠ€ν…Œμ΄μ§€ κΈ°μ€€ (0~1) μ’Œν‘œλ‘œ λͺ¨λ“  λ””λ°”μ΄μŠ€ 지원 - ⏰ **μ •λ°€ν•œ λ―Έλ””μ–΄ 싱크**: requestVideoFrameCallback 기반 ν”„λ ˆμž„ 동기화 - πŸ”Œ **동적 ν”ŒλŸ¬κ·ΈμΈ μ‹œμŠ€ν…œ**: ES Dynamic Import + 무결성 검증 - πŸ›‘οΈ **λ³΄μ•ˆ μƒŒλ“œλ°•μŠ€**: ν”ŒλŸ¬κ·ΈμΈ 격리 μ‹€ν–‰ ν™˜κ²½ - 🎭 **λ‹€μΈ΅ λ ˆμ΄μ–΄ μ‹œμŠ€ν…œ**: Track β†’ Cue β†’ Element 계측 ꡬ쑰 - πŸ“¦ **TypeScript μ™„μ „ 지원**: νƒ€μž… μ•ˆμ „μ„±κ³Ό IntelliSense ## πŸš€ μ„€μΉ˜ ```bash pnpm add motiontext-renderer ``` ```bash npm install motiontext-renderer ``` ```bash yarn add motiontext-renderer ``` > μ°Έκ³ : 이 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” GSAP을 ν”Όμ–΄ μ˜μ‘΄μ„±μœΌλ‘œ μš”κ΅¬ν•©λ‹ˆλ‹€. 호슀트 앱에 GSAP을 μ„€μΉ˜ν•˜μ„Έμš”. > > μ„€μΉ˜: `pnpm add gsap` (λ˜λŠ” npm/yarn) ## πŸ“– κΈ°λ³Έ μ‚¬μš©λ²• ```typescript import { MotionTextRenderer } from 'motiontext-renderer'; // μ»¨ν…Œμ΄λ„ˆ μš”μ†Œμ™€ λΉ„λ””μ˜€ μš”μ†Œ μ€€λΉ„ const container = document.getElementById('caption-container'); const video = document.getElementById('main-video'); // λ Œλ”λŸ¬ μ΄ˆκΈ°ν™” const renderer = new MotionTextRenderer(container); // μ„€μ • λ‘œλ“œ const config = { version: '1.3', timebase: { unit: 'seconds' }, stage: { baseAspect: '16:9' }, tracks: [ { id: 'subtitle', type: 'subtitle', layer: 1 } ], cues: [ { id: 'cue1', track: 'subtitle', hintTime: 0, root: { id: 'group1', type: 'group', children: [ { id: 'text1', type: 'text', absStart: 0, absEnd: 3, content: 'μ•ˆλ…•ν•˜μ„Έμš”!', layout: { position: [0.5, 0.8] } } ] } } ] }; await renderer.loadConfig(config); // λΉ„λ””μ˜€μ™€ 연동 renderer.attachMedia(video); // μž¬μƒ μ‹œμž‘ renderer.play(); ``` ### πŸ”Œ μ™ΈλΆ€(μ»€μŠ€ν…€) ν”ŒλŸ¬κ·ΈμΈ 등둝/원점 μ„€μ • ν”„λ‘œλ•μ…˜ μ‚¬μš©μ²˜μ—μ„œ μ»€μŠ€ν…€ ν”ŒλŸ¬κ·ΈμΈμ„ λ“±λ‘ν•˜κ±°λ‚˜, ν”ŒλŸ¬κ·ΈμΈ 원점(server/local/auto)을 μ„€μ •ν•  수 μžˆλŠ” 곡개 APIλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. ```ts import { configurePluginSource, // 원점 μ„€μ • (server/local/auto) registerExternalPlugin, // 단일 ν”ŒλŸ¬κ·ΈμΈ 등둝 registerExternalPluginsFromGlob // 닀건 등둝 (예: import.meta.glob) } from 'motiontext-renderer'; // 1) 원점 μ„€μ • (선택) configurePluginSource({ mode: 'auto', // 'server' | 'local' | 'auto' serverBase: 'https://plugins.example.com', localBase: '/plugins/' // λ²ˆλ“€/정적 경둜 }); // 2) ν”ŒλŸ¬κ·ΈμΈ 등둝 (단일) // - module: { default: { name, version, animate... }, evalChannels? } // - baseUrl: assets.getUrl()의 κΈ°μ€€ URL registerExternalPlugin({ name: 'myEffect', version: '1.0.0', module: await import('/plugins/myEffect@1.0.0/index.mjs'), baseUrl: '/plugins/myEffect@1.0.0/' }); // 3) ν”ŒλŸ¬κ·ΈμΈ 일괄 등둝 (Vite dev μ˜ˆμ‹œ) const PLUGINS = import.meta.glob('/plugins/*/index.mjs'); await registerExternalPluginsFromGlob(PLUGINS); ``` ### πŸ“¦ λ‚΄μž₯ CWI ν”ŒλŸ¬κ·ΈμΈ μ‹œλ¦¬μ¦ˆ Caption with Intention (CWI) ν”ŒλŸ¬κ·ΈμΈλ“€μ€ 단어별 λ°œν™” 강도에 λ”°λ₯Έ λ‹€μ–‘ν•œ μ• λ‹ˆλ©”μ΄μ…˜μ„ μ œκ³΅ν•©λ‹ˆλ‹€: - **cwi-color@1.0.0**: 색상 λ³€ν™” (흰색 β†’ ν™”μžλ³„ 색상) - **cwi-loud@1.0.0**: 큰 μ†Œλ¦¬ 효과 (2.4λ°° ν™•λŒ€ + 진동) - **cwi-whisper@1.0.0**: μ†μ‚­μž„ 효과 (0.6λ°° μΆ•μ†Œ) - **cwi-bouncing@1.0.0**: λ°”μš΄μ‹± 효과 (1.15λ°° ν™•λŒ€ + μƒν•˜ μ›€μ§μž„) #### μ‚¬μš© μ˜ˆμ‹œ ```json { "definitions": { "speakerPalette": { "SPEAKER_01": "#4AA3FF", "SPEAKER_02": "#FF4D4D", "SPEAKER_03": "#FFD400" } }, "cues": [{ "root": { "children": [{ "e_type": "text", "text": "Hello", "pluginChain": [ { "name": "cwi-loud@1.0.0", "params": { "speaker": "SPEAKER_01", "t0": 0.5, "t1": 0.8 } }, { "name": "cwi-color@1.0.0", "params": { "speaker": "SPEAKER_01", "t0": 0.5, "t1": 0.8 } } ] }] } }] } ``` #### Definitions μ„Ήμ…˜μ„ ν†΅ν•œ μ΅œμ ν™” `definitions` μ„Ήμ…˜μ„ μ‚¬μš©ν•˜λ©΄ 곡톡 데이터λ₯Ό μ€‘μ•™μ—μ„œ 관리할 수 μžˆμŠ΅λ‹ˆλ‹€: ```json { "definitions": { "speakerPalette": { "SPEAKER_01": "#4AA3FF", "SPEAKER_02": "#FF4D4D" } }, "cues": [{ "root": { "children": [{ "pluginChain": [{ "name": "cwi-color@1.0.0", "params": { "speaker": "SPEAKER_01", "palette": "definitions.speakerPalette" } }] }] } }] } ``` **μ£Όμš” 이점:** - **쀑볡 제거**: paletteλ₯Ό ν•œ 번만 μ •μ˜ν•˜κ³  참쑰둜 μž¬μ‚¬μš© - **파일 크기 κ°μ†Œ**: κΈ°μ‘΄ λŒ€λΉ„ μ•½ 75% 크기 κ°μ†Œ (예: 800KB β†’ 206KB) - **μœ μ§€λ³΄μˆ˜ κ°œμ„ **: palette 쀑앙 κ΄€λ¦¬λ‘œ 색상 λ³€κ²½ 용이 - **λŸ°νƒ€μž„ ν•΄κ²°**: λ Œλ”λŸ¬κ°€ `"definitions.speakerPalette"` λ¬Έμžμ—΄μ„ μ‹€μ œ 객체둜 μΉ˜ν™˜ λͺ¨λ“œ κ°œμš” - server: `serverBase`μ—μ„œ `plugins/<name@version>/manifest.json`을 λ°›μ•„ entry(index.mjs)λ₯Ό λ‘œλ“œν•©λ‹ˆλ‹€. CDN/별도 ν”ŒλŸ¬κ·ΈμΈ μ„œλ²„λ₯Ό μ“°λŠ” 배포 ν™˜κ²½μ— μ ν•©ν•©λ‹ˆλ‹€. - local: λ²ˆλ“€ λ˜λŠ” 정적 κ²½λ‘œμ— ν¬ν•¨λœ ν”ŒλŸ¬κ·ΈμΈμ„ 직접 importν•©λ‹ˆλ‹€. μ„œλ²„ 없이도 λ™μž‘ν•˜λ©°, 앱이 μ œκ³΅ν•˜λŠ” 정적 μžμ‚°μ—μ„œ μ¦‰μ‹œ λ‘œλ”©ν•  λ•Œ μ ν•©ν•©λ‹ˆλ‹€. - auto: μ„œλ²„ μš°μ„  μ‹œλ„ ν›„ μ‹€νŒ¨ν•˜λ©΄ 둜컬둜 ν΄λ°±ν•©λ‹ˆλ‹€. 개발/μ‹œμ—° ν™˜κ²½μ—μ„œ νŽΈλ¦¬ν•©λ‹ˆλ‹€. μ–Έμ œ μ–΄λ–€ λͺ¨λ“œλ₯Ό μ“ΈκΉŒ - 배포용 CDN/μ „μš© μ„œλ²„κ°€ 있고, ν”ŒλŸ¬κ·ΈμΈ κ΅μ²΄Β·λ¬΄νš¨ν™”Β·λ²„μ „ 고정이 ν•„μš”: server - μ•± λ²ˆλ“€μ— ν”ŒλŸ¬κ·ΈμΈμ„ ν¬ν•¨ν•˜κ±°λ‚˜, ν”„λ‘μ‹œ/μ˜€ν”„λΌμΈ ν™˜κ²½: local - 개발 쀑 μ„œλ²„κ°€ μžˆμ„ λ•Œ/없을 λ•Œλ₯Ό λͺ¨λ‘ κ³ λ €: auto ν”ŒλŸ¬κ·ΈμΈ λͺ¨λ“ˆ κ·œμ•½(μš”μ•½, v2.1) ```js // index.mjs (μ˜ˆμ‹œ) export default { name: 'myEffect', version: '1.0.0', init(el, opts, ctx) { // effectsRoot(el) ν•˜μœ„λ§Œ μ‘°μž‘ (μƒŒλ“œλ°•μŠ€) }, animate(el, opts, ctx, duration) { // 0..1 진행을 λ°›λŠ” seek ν•¨μˆ˜ν˜• λ˜λŠ” GSAP Timeline λ°˜ν™˜ return (p) => { el.style.opacity = String(Math.min(1, Math.max(0, p))); }; }, cleanup(el) {} }; ``` μžμ‚° URLκ³Ό baseUrl - `registerExternalPlugin`의 `baseUrl`은 ν”ŒλŸ¬κ·ΈμΈ λ‚΄λΆ€ `ctx.assets.getUrl('path')` 해석 기쀀이 λ©λ‹ˆλ‹€. - server λͺ¨λ“œμ—μ„œλŠ” manifest의 entry/μžμ‚° 경둜λ₯Ό κΈ°μ€€μœΌλ‘œ μžλ™ κ³„μ‚°λ©λ‹ˆλ‹€. - `registerExternalPluginsFromGlob`λŠ” κΈ°λ³Έ νŒŒμ„œλ‘œ `.../<name>@<version>/index.mjs`λ₯Ό 인식해 `baseUrl=.../<name>@<version>/`둜 μ„€μ •ν•©λ‹ˆλ‹€. λ‹€λ₯Έ 디렉터리 ꡬ쑰라면 `parse` μ½œλ°±μ„ 전달해 직접 μ§€μ •ν•˜μ„Έμš”. μ„œλ²„ λͺ¨λ“œμš© μ΅œμ†Œ manifest μ˜ˆμ‹œ ```json { "name": "myEffect", "version": "1.0.0", "entry": "index.mjs" } ``` μ„œλ²„λŠ” `plugins/<name>@<version>/manifest.json`와 `index.mjs`(및 ν•„μš” μžμ‚°)λ₯Ό μ •μ μœΌλ‘œ μ„œλΉ™ν•˜λ©΄ λ©λ‹ˆλ‹€. 닀건 등둝(λ²ˆλ“€λŸ¬λ³„ 팁) - Vite: `import.meta.glob('/plugins/*/index.mjs')`λ₯Ό ꢌμž₯ (비동기 λ‘œλ” λ§΅ 생성) - Webpack/기타: 정적 import ν›„ `registerExternalPlugin`을 반볡 ν˜ΈμΆœν•˜κ±°λ‚˜, 동적 import κ°€λŠ₯ν•œ 경둜 κ·œμΉ™μ„ μ‚¬μš©ν•˜μ—¬ λ‘œλ” 맡을 κ΅¬μ„±ν•˜μ„Έμš”. SSR/Next.js 주의 - ν΄λΌμ΄μ–ΈνŠΈμ—μ„œλ§Œ λ“±λ‘ν•˜μ„Έμš”. 예: `if (typeof window !== 'undefined') await registerExternalPluginsFromGlob(...)`. νŠΈλŸ¬λΈ”μŠˆνŒ… - β€œFailed to fetch dynamically imported module”: 경둜/도메인(μ„œλ²„ λͺ¨λ“œ), 정적 파일 μœ„μΉ˜(local λͺ¨λ“œ) 확인. μ„œλ²„ λͺ¨λ“œλΌλ©΄ CORS/경둜(`plugins/<name@version>/...`)λ₯Ό μ κ²€ν•˜μ„Έμš”. - β€œnot found @ version”: μ‹œλ‚˜λ¦¬μ˜€ JSON의 `plugin.name`이 `myEffect@1.0.0`처럼 λ²„μ „κΉŒμ§€ ν¬ν•¨λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€(ν˜Ήμ€ 동일 name ν‚€λ‘œ 등둝). - 둜컬 경둜 404 (Vite dev): dev root에 λ§žλŠ” κ²½λ‘œμΈμ§€ ν™•μΈν•˜κ³ , κ°€λŠ₯ν•˜λ©΄ κΈ€λ‘­(registrar)을 μ‚¬μš©ν•˜μ„Έμš”. --- ## πŸ”§ 개발 κ°€μ΄λ“œ ### 개발 ν™˜κ²½ μ„€μ • 1. **μ €μž₯μ†Œ 클둠** ```bash git clone https://github.com/teamKimtaerin/motiontext-renderer.git cd motiontext-renderer ``` 2. **μ˜μ‘΄μ„± μ„€μΉ˜** ```bash pnpm install ``` 3. **AI νŽΈμ§‘κΈ° ν™˜κ²½ μ„€μ • (선택사항)** AI 기반 μžλ§‰ νŽΈμ§‘ κΈ°λŠ₯을 μ‚¬μš©ν•˜λ €λ©΄ ν™˜κ²½λ³€μˆ˜λ₯Ό μ„€μ •ν•˜μ„Έμš”: ```bash # .env 파일 생성 cp .env.example .env # .env νŒŒμΌμ— API ν‚€ μ„€μ • ANTHROPIC_API_KEY=sk-ant-your-api-key-here ``` 4. **개발 μ„œλ²„ μ‹€ν–‰** ```bash pnpm dev # AI νŽΈμ§‘κΈ° μ‚¬μš© μ‹œ μΆ”κ°€λ‘œ ν”„λ‘μ‹œ μ„œλ²„ μ‹€ν–‰ pnpm proxy:server ``` ### 개발 λͺ…λ Ήμ–΄ ```bash # 개발 λͺ¨λ“œ (Vite 개발 μ„œλ²„) pnpm dev # λΉŒλ“œ pnpm build # μ½”λ“œ ν’ˆμ§ˆ 검사 pnpm lint # ESLint μ‹€ν–‰ pnpm lint:fix # ESLint μžλ™ μˆ˜μ • pnpm format # Prettier ν¬λ§·νŒ… pnpm format:check # ν¬λ§·νŒ… 검사 pnpm typecheck # TypeScript νƒ€μž… 체크 # 정리 pnpm clean # dist 폴더 μ‚­μ œ ``` ### Dev ν”ŒλŸ¬κ·ΈμΈ 원점 μ„€μ • (M6.8) 데λͺ¨/개발 ν™˜κ²½μ—μ„œ ν”ŒλŸ¬κ·ΈμΈ μ†ŒμŠ€(μ„œλ²„/둜컬)λ₯Ό init으둜 μ„€μ •ν•©λ‹ˆλ‹€. - ν™˜κ²½λ³€μˆ˜λ‘œ μ„€μ •(ꢌμž₯): ```bash # μ„œλ²„ μš°μ„ , μ‹€νŒ¨ μ‹œ 둜컬 폴백(auto) pnpm dev # μ„œλ²„λ§Œ μ‚¬μš© VITE_PLUGIN_MODE=server VITE_PLUGIN_ORIGIN=http://localhost:3300 pnpm dev # 둜컬 ν΄λ”λ§Œ μ‚¬μš© VITE_PLUGIN_MODE=local VITE_PLUGIN_LOCAL_BASE=./demo/plugin-server/plugins/ pnpm dev ``` - μ½”λ“œμ—μ„œ μ„€μ •(`demo/devPlugins.ts`): ```ts import { configureDevPlugins } from '../src/loader/dev/DevPluginConfig'; configureDevPlugins({ mode: 'auto', serverBase: 'http://localhost:3300', localBase: './demo/plugin-server/plugins/', }); ``` --- ## πŸ“¦ 버전 관리 및 배포 κ°€μ΄λ“œ 이 ν”„λ‘œμ νŠΈλŠ” **Changesets**λ₯Ό μ‚¬μš©ν•˜μ—¬ Semantic Versioning을 μžλ™ν™”ν•©λ‹ˆλ‹€. ### πŸ› οΈ κΈ°λŠ₯ 개발 μ‹œ μ›Œν¬ν”Œλ‘œμš° 1. **μƒˆ 브랜치 생성 및 μž‘μ—…** ```bash git checkout -b feature/μƒˆκΈ°λŠ₯ # μ½”λ“œ μž‘μ—…... ``` 2. **변경사항 기둝 (μ€‘μš”!)** ```bash pnpm changeset ``` μ‹€ν–‰ν•˜λ©΄ λŒ€ν™”ν˜• ν”„λ‘¬ν”„νŠΈκ°€ λ‚˜νƒ€λ‚©λ‹ˆλ‹€: - **패치(patch)**: 버그 μˆ˜μ • (1.0.0 β†’ 1.0.1) - **λ§ˆμ΄λ„ˆ(minor)**: μƒˆ κΈ°λŠ₯ (1.0.0 β†’ 1.1.0) - **메이저(major)**: λΈŒλ ˆμ΄ν‚Ή 체인지 (1.0.0 β†’ 2.0.0) 3. **컀밋 및 PR 생성** ```bash git add .changeset/ git commit -m "feat: μƒˆλ‘œμš΄ κΈ°λŠ₯ μΆ”κ°€" git push origin feature/μƒˆκΈ°λŠ₯ # GitHubμ—μ„œ PR 생성 ``` 4. **PR λ¨Έμ§€** - CI 톡과 확인 - μ½”λ“œ 리뷰 μ™„λ£Œ - `main` 브랜치둜 λ¨Έμ§€ ### πŸ€– μžλ™ 배포 ν”„λ‘œμ„ΈμŠ€ #### 1단계: μžλ™ 버전 PR 생성 - `main` λΈŒλžœμΉ˜μ— push되면 **Changesets Bot**이 λ™μž‘ - "Version Packages" PR이 μžλ™ μƒμ„±λ©λ‹ˆλ‹€ - 이 PRμ—λŠ” λ‹€μŒμ΄ ν¬ν•¨λ©λ‹ˆλ‹€: - `package.json` 버전 μžλ™ 증가 - `CHANGELOG.md` μžλ™ μ—…λ°μ΄νŠΈ - λˆ„μ λœ λͺ¨λ“  변경사항 정리 #### 2단계: NPM μžλ™ 배포 - **Version Packages** PR을 λ¨Έμ§€ν•˜λ©΄: - GitHub Actionsκ°€ μžλ™ μ‹€ν–‰ - ν’ˆμ§ˆ 검사 (lint, typecheck, build) μˆ˜ν–‰ - NPM Registry에 μžλ™ 배포 - Git νƒœκ·Έ μžλ™ 생성 (예: `v1.2.0`) ### πŸ” 배포 μƒνƒœ 확인 ```bash # ν˜„μž¬ 배포된 버전 확인 npm info motiontext-renderer # 둜컬 버전 확인 pnpm version ``` ### πŸ“Š 버전 νžˆμŠ€ν† λ¦¬ μ˜ˆμ‹œ ``` v0.1.0 β†’ feat: 초기 λ Œλ”λŸ¬ κ΅¬ν˜„ v0.1.1 β†’ fix: νƒ€μž… μ •μ˜ 였λ₯˜ μˆ˜μ • v0.2.0 β†’ feat: ν”ŒλŸ¬κ·ΈμΈ μ‹œμŠ€ν…œ μΆ”κ°€ v0.2.1 β†’ fix: λ©”λͺ¨λ¦¬ λˆ„μˆ˜ ν•΄κ²° v1.0.0 β†’ feat!: API μž¬μ„€κ³„ (Breaking Change) ``` --- ## πŸ—οΈ CI/CD νŒŒμ΄ν”„λΌμΈ ### PR 검증 (.github/workflows/ci.yml) λͺ¨λ“  Pull Request에 λŒ€ν•΄ λ‹€μŒμ„ μžλ™ 검사: - βœ… ESLint κ·œμΉ™ μ€€μˆ˜ - βœ… Prettier ν¬λ§·νŒ… - βœ… TypeScript νƒ€μž… 체크 - βœ… λΉŒλ“œ 성곡 μ—¬λΆ€ ### μžλ™ 배포 (.github/workflows/release.yml) `main` 브랜치 push μ‹œ μžλ™ μ‹€ν–‰: 1. ν’ˆμ§ˆ 검사 톡과 2. ν”„λ‘œλ•μ…˜ λΉŒλ“œ 생성 3. Changesets둜 버전 관리 4. NPM 배포 (NPM_TOKEN ν•„μš”) 5. GitHub Release 생성 ### πŸ” ν•„μˆ˜ GitHub Secrets 리포지토리 Settings β†’ Secretsμ—μ„œ μ„€μ •: ``` NPM_TOKEN=npm_xxxxxxxxxxxxxxx ``` NPM 토큰 생성 방법: 1. [npmjs.com](https://npmjs.com) 둜그인 2. Profile β†’ Access Tokens 3. "Generate New Token" β†’ "Automation" 선택 4. μƒμ„±λœ 토큰을 GitHub Secrets에 μΆ”κ°€ --- ## 🎯 배포 μ‹œλ‚˜λ¦¬μ˜€ 예제 ### μ‹œλ‚˜λ¦¬μ˜€ 1: 버그 μˆ˜μ • ```bash # 1. 브랜치 생성 및 μˆ˜μ • git checkout -b fix/memory-leak # μ½”λ“œ μˆ˜μ •... # 2. 변경사항 기둝 pnpm changeset # β†’ patch 선택 # β†’ "λ©”λͺ¨λ¦¬ λˆ„μˆ˜ ν•΄κ²°" μ„€λͺ… μž…λ ₯ # 3. 컀밋 및 PR git add . git commit -m "fix: λ©”λͺ¨λ¦¬ λˆ„μˆ˜ ν•΄κ²°" git push # 4. PR λ¨Έμ§€ ν›„ μžλ™μœΌλ‘œ v1.0.1둜 배포 ``` ### μ‹œλ‚˜λ¦¬μ˜€ 2: μƒˆ κΈ°λŠ₯ μΆ”κ°€ ```bash # 1. κΈ°λŠ₯ 개발 git checkout -b feature/plugin-system # μ½”λ“œ μž‘μ„±... # 2. 변경사항 기둝 pnpm changeset # β†’ minor 선택 # β†’ "ν”ŒλŸ¬κ·ΈμΈ μ‹œμŠ€ν…œ μΆ”κ°€" μ„€λͺ… # 3. PR λ¨Έμ§€ ν›„ μžλ™μœΌλ‘œ v1.1.0으둜 배포 ``` ### μ‹œλ‚˜λ¦¬μ˜€ 3: κΈ΄κΈ‰ μˆ˜μ • ```bash # hotfix λΈŒλžœμΉ˜μ—μ„œ μž‘μ—… git checkout -b hotfix/critical-bug pnpm changeset # patch 선택 # PR λ¨Έμ§€ μ¦‰μ‹œ 패치 버전 배포 ``` --- ## πŸ“ ν”„λ‘œμ νŠΈ ꡬ쑰 ``` motiontext-renderer/ β”œβ”€β”€ src/ # μ†ŒμŠ€ μ½”λ“œ β”‚ β”œβ”€β”€ index.ts # 메인 μ§„μž…μ  β”‚ β”œβ”€β”€ core/ # 핡심 λ Œλ”λ§ μ—”μ§„ β”‚ β”‚ └── renderer.ts # λ Œλ”λŸ¬ 클래슀 β”‚ └── types/ # TypeScript νƒ€μž… μ •μ˜ β”‚ └── index.ts # 곡용 νƒ€μž… λͺ¨μŒ β”œβ”€β”€ dist/ # λΉŒλ“œ κ²°κ³Όλ¬Ό (μžλ™ 생성) β”œβ”€β”€ .changeset/ # 버전 관리 μ„€μ • β”œβ”€β”€ .github/workflows/ # CI/CD νŒŒμ΄ν”„λΌμΈ β”œβ”€β”€ package.json # ν”„λ‘œμ νŠΈ μ„€μ • β”œβ”€β”€ tsconfig.json # TypeScript μ„€μ • β”œβ”€β”€ vite.config.ts # Vite λΉŒλ“œ μ„€μ • └── README.md # 이 파일 ``` --- ## 🀝 κΈ°μ—¬ν•˜κΈ° 1. Fork the Project 2. Create Feature Branch (`git checkout -b feature/AmazingFeature`) 3. Make your changes 4. Record changeset (`pnpm changeset`) 5. Commit Changes (`git commit -m 'Add some AmazingFeature'`) 6. Push to Branch (`git push origin feature/AmazingFeature`) 7. Open a Pull Request --- ## πŸ“„ λΌμ΄μ„ μŠ€ MIT License - μžμ„Έν•œ λ‚΄μš©μ€ [LICENSE](LICENSE) νŒŒμΌμ„ μ°Έμ‘°ν•˜μ„Έμš”. --- ## πŸ”— 링크 - **GitHub**: https://github.com/teamKimtaerin/motiontext-renderer - **NPM**: https://npmjs.com/package/motiontext-renderer - **Issues**: https://github.com/teamKimtaerin/motiontext-renderer/issues --- ## πŸ“ž 지원 λ¬Έμ˜μ‚¬ν•­μ΄λ‚˜ 버그 λ¦¬ν¬νŠΈλŠ” [GitHub Issues](https://github.com/teamKimtaerin/motiontext-renderer/issues)λ₯Ό μ΄μš©ν•΄ μ£Όμ„Έμš”. --- Made with ❀️ by Team Kimtaerin