redi 이슈 해결
This commit is contained in:
103
.cursor/rules/cursor-step-by-step-rule.mdc
Normal file
103
.cursor/rules/cursor-step-by-step-rule.mdc
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
## Core Directive
|
||||
You are a senior software engineer AI assistant. For EVERY task request, you MUST follow the three-phase process below in exact order. Each phase must be completed with expert-level precision and detail.
|
||||
|
||||
## Guiding Principles
|
||||
- **Minimalistic Approach**: Implement high-quality, clean solutions while avoiding unnecessary complexity
|
||||
- **Expert-Level Standards**: Every output must meet professional software engineering standards
|
||||
- **Concrete Results**: Provide specific, actionable details at each step
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Codebase Exploration & Analysis
|
||||
**REQUIRED ACTIONS:**
|
||||
1. **Systematic File Discovery**
|
||||
- List ALL potentially relevant files, directories, and modules
|
||||
- Search for related keywords, functions, classes, and patterns
|
||||
- Examine each identified file thoroughly
|
||||
|
||||
2. **Convention & Style Analysis**
|
||||
- Document coding conventions (naming, formatting, architecture patterns)
|
||||
- Identify existing code style guidelines
|
||||
- Note framework/library usage patterns
|
||||
- Catalog error handling approaches
|
||||
|
||||
**OUTPUT FORMAT:**
|
||||
```
|
||||
### Codebase Analysis Results
|
||||
**Relevant Files Found:**
|
||||
- [file_path]: [brief description of relevance]
|
||||
|
||||
**Code Conventions Identified:**
|
||||
- Naming: [convention details]
|
||||
- Architecture: [pattern details]
|
||||
- Styling: [format details]
|
||||
|
||||
**Key Dependencies & Patterns:**
|
||||
- [library/framework]: [usage pattern]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Implementation Planning
|
||||
**REQUIRED ACTIONS:**
|
||||
Based on Phase 1 findings, create a detailed implementation roadmap.
|
||||
|
||||
**OUTPUT FORMAT:**
|
||||
```markdown
|
||||
## Implementation Plan
|
||||
|
||||
### Module: [Module Name]
|
||||
**Summary:** [1-2 sentence description of what needs to be implemented]
|
||||
|
||||
**Tasks:**
|
||||
- [ ] [Specific implementation task]
|
||||
- [ ] [Specific implementation task]
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] [Measurable success criterion]
|
||||
- [ ] [Measurable success criterion]
|
||||
- [ ] [Performance/quality requirement]
|
||||
|
||||
### Module: [Next Module Name]
|
||||
[Repeat structure above]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Implementation Execution
|
||||
**REQUIRED ACTIONS:**
|
||||
1. Implement each module following the plan from Phase 2
|
||||
2. Verify ALL acceptance criteria are met before proceeding
|
||||
3. Ensure code adheres to conventions identified in Phase 1
|
||||
|
||||
**QUALITY GATES:**
|
||||
- [ ] All acceptance criteria validated
|
||||
- [ ] Code follows established conventions
|
||||
- [ ] Minimalistic approach maintained
|
||||
- [ ] Expert-level implementation standards met
|
||||
|
||||
---
|
||||
|
||||
## Success Validation
|
||||
Before completing any task, confirm:
|
||||
- ✅ All three phases completed sequentially
|
||||
- ✅ Each phase output meets specified format requirements
|
||||
- ✅ Implementation satisfies all acceptance criteria
|
||||
- ✅ Code quality meets professional standards
|
||||
|
||||
## Response Structure
|
||||
Always structure your response as:
|
||||
1. **Phase 1 Results**: [Codebase analysis findings]
|
||||
2. **Phase 2 Plan**: [Implementation roadmap]
|
||||
3. **Phase 3 Implementation**: [Actual code with validation]
|
||||
154
.cursor/rules/univer-redi-management.mdc
Normal file
154
.cursor/rules/univer-redi-management.mdc
Normal file
@@ -0,0 +1,154 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
## REDI Duplicate Identifier Prevention in Univer CE
|
||||
|
||||
### **Problem Analysis**
|
||||
- **Root Cause**: REDI dependency injection system retains global service identifiers in memory even after Univer instance disposal
|
||||
- **Trigger**: Component remounting causes duplicate service registration attempts
|
||||
- **Symptoms**: "Identifier [service-name] already exists" console errors
|
||||
|
||||
### **Core Prevention Patterns**
|
||||
|
||||
#### **1. Global Instance Management**
|
||||
```typescript
|
||||
// ✅ DO: Use singleton pattern with proper state tracking
|
||||
const UniverseManager = {
|
||||
async createInstance(container: HTMLElement): Promise<any> {
|
||||
const state = getGlobalState();
|
||||
|
||||
// Check existing instance with complete validation
|
||||
if (state.instance && state.univerAPI &&
|
||||
!state.isInitializing && !state.isDisposing) {
|
||||
return { univer: state.instance, univerAPI: state.univerAPI };
|
||||
}
|
||||
|
||||
// Wait for ongoing operations
|
||||
if (state.isInitializing && state.initializationPromise) {
|
||||
return state.initializationPromise;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ❌ DON'T: Create new instances without checking global state
|
||||
const univer = createUniver({ /* config */ }); // Direct creation
|
||||
```
|
||||
|
||||
#### **2. Component Mount Lifecycle**
|
||||
```typescript
|
||||
// ✅ DO: Smart initialization with state checks
|
||||
useEffect(() => {
|
||||
const existingUniver = UniverseManager.getInstance();
|
||||
const state = getGlobalState();
|
||||
|
||||
// Reuse existing instance if available and stable
|
||||
if (existingUniver && state.univerAPI &&
|
||||
!UniverseManager.isInitializing() && !UniverseManager.isDisposing()) {
|
||||
setIsInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for ongoing operations
|
||||
if (UniverseManager.isInitializing() || UniverseManager.isDisposing()) {
|
||||
const waitTimer = setInterval(() => {
|
||||
if (!UniverseManager.isInitializing() && !UniverseManager.isDisposing()) {
|
||||
clearInterval(waitTimer);
|
||||
// Check and initialize as needed
|
||||
}
|
||||
}, 100);
|
||||
return () => clearInterval(waitTimer);
|
||||
}
|
||||
}, []); // Empty dependency array to prevent re-execution
|
||||
|
||||
// ❌ DON'T: Include dependencies that cause re-initialization
|
||||
useEffect(() => {
|
||||
initializeUniver();
|
||||
}, [initializeUniver]); // This causes re-execution on every render
|
||||
```
|
||||
|
||||
#### **3. Complete Cleanup Strategy**
|
||||
```typescript
|
||||
// ✅ DO: Implement thorough REDI state cleanup
|
||||
forceReset(): void {
|
||||
// Standard disposal
|
||||
if (state.instance) {
|
||||
state.instance.dispose();
|
||||
}
|
||||
|
||||
// REDI global state cleanup attempt
|
||||
if (typeof window !== "undefined") {
|
||||
const globalKeys = Object.keys(window).filter(key =>
|
||||
key.includes('redi') || key.includes('REDI') ||
|
||||
key.includes('univerjs') || key.includes('univer')
|
||||
);
|
||||
|
||||
globalKeys.forEach(key => {
|
||||
try {
|
||||
delete (window as any)[key];
|
||||
} catch (e) {
|
||||
console.warn(`Global key ${key} cleanup failed:`, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reset all state flags
|
||||
Object.assign(state, {
|
||||
instance: null,
|
||||
univerAPI: null,
|
||||
isInitializing: false,
|
||||
isDisposing: false,
|
||||
initializationPromise: null,
|
||||
lastContainerId: null,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### **4. State Validation Patterns**
|
||||
```typescript
|
||||
// ✅ DO: Multi-level state validation
|
||||
const isInstanceReady = (state: GlobalUniverState): boolean => {
|
||||
return !!(
|
||||
state.instance &&
|
||||
state.univerAPI &&
|
||||
!state.isInitializing &&
|
||||
!state.isDisposing &&
|
||||
state.lastContainerId
|
||||
);
|
||||
};
|
||||
|
||||
// ❌ DON'T: Simple existence check
|
||||
if (state.instance) { /* insufficient validation */ }
|
||||
```
|
||||
|
||||
### **5. Debug Tools Integration**
|
||||
```typescript
|
||||
// ✅ DO: Provide comprehensive debugging tools
|
||||
window.__UNIVER_DEBUG__ = {
|
||||
getGlobalUniver: () => UniverseManager.getInstance(),
|
||||
getGlobalState: () => getGlobalState(),
|
||||
forceReset: () => UniverseManager.forceReset(),
|
||||
completeCleanup: () => UniverseManager.completeCleanup(),
|
||||
};
|
||||
```
|
||||
|
||||
### **Error Resolution Steps**
|
||||
1. **Immediate**: Call `window.__UNIVER_DEBUG__.completeCleanup()` in console
|
||||
2. **Prevention**: Use empty dependency arrays in useEffect for initialization
|
||||
3. **Validation**: Always check both `instance` and `univerAPI` existence
|
||||
4. **Cleanup**: Implement thorough REDI global state cleanup in disposal methods
|
||||
|
||||
### **Common Anti-Patterns to Avoid**
|
||||
- ❌ Re-initializing on every component render
|
||||
- ❌ Not waiting for ongoing initialization/disposal operations
|
||||
- ❌ Incomplete state validation before reuse
|
||||
- ❌ Missing REDI global state cleanup
|
||||
- ❌ Using complex dependency arrays in initialization useEffect
|
||||
|
||||
### **Best Practices**
|
||||
- ✅ Implement singleton pattern with proper state management
|
||||
- ✅ Use polling-based waiting for state transitions
|
||||
- ✅ Provide debug tools for runtime state inspection
|
||||
- ✅ Separate initialization concerns from component lifecycle
|
||||
- ✅ Implement both standard and emergency cleanup methods
|
||||
20
src/App.tsx
20
src/App.tsx
@@ -1,7 +1,12 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "./components/ui/button";
|
||||
import HomeButton from "./components/ui/homeButton";
|
||||
import EditSheetViewer from "./components/sheet/EditSheetViewer";
|
||||
import { lazy, Suspense } from "react";
|
||||
|
||||
// 동적 import로 EditSheetViewer 로드 (필요할 때만)
|
||||
const EditSheetViewer = lazy(
|
||||
() => import("./components/sheet/EditSheetViewer"),
|
||||
);
|
||||
|
||||
function App() {
|
||||
const [showTestViewer, setShowTestViewer] = useState(false);
|
||||
@@ -52,9 +57,20 @@ function App() {
|
||||
{/* 메인 콘텐츠 */}
|
||||
<main className="h-[calc(100vh-4rem)]">
|
||||
{showTestViewer ? (
|
||||
// 테스트 뷰어 표시
|
||||
// 동적 로딩된 테스트 뷰어 표시
|
||||
<div className="h-full">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">🚀 그리드 그리는 중...</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EditSheetViewer />
|
||||
</Suspense>
|
||||
</div>
|
||||
) : (
|
||||
// 메인 페이지
|
||||
|
||||
@@ -44,6 +44,7 @@ declare global {
|
||||
getGlobalState: () => GlobalUniverState;
|
||||
clearGlobalState: () => void;
|
||||
forceReset: () => void;
|
||||
completeCleanup: () => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -213,6 +214,33 @@ const UniverseManager = {
|
||||
}
|
||||
}
|
||||
|
||||
// REDI 전역 상태 완전 정리 시도
|
||||
try {
|
||||
// 브라우저의 전역 REDI 상태 정리 (가능한 경우)
|
||||
if (typeof window !== "undefined") {
|
||||
// REDI 관련 전역 객체들 정리
|
||||
const globalKeys = Object.keys(window).filter(
|
||||
(key) =>
|
||||
key.includes("redi") ||
|
||||
key.includes("REDI") ||
|
||||
key.includes("univerjs") ||
|
||||
key.includes("univer"),
|
||||
);
|
||||
|
||||
globalKeys.forEach((key) => {
|
||||
try {
|
||||
delete (window as any)[key];
|
||||
} catch (e) {
|
||||
console.warn(`전역 키 ${key} 정리 실패:`, e);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("🧹 REDI 전역 상태 정리 시도 완료");
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("⚠️ REDI 전역 상태 정리 중 오류:", error);
|
||||
}
|
||||
|
||||
state.instance = null;
|
||||
state.univerAPI = null;
|
||||
state.isInitializing = false;
|
||||
@@ -221,13 +249,46 @@ const UniverseManager = {
|
||||
state.lastContainerId = null;
|
||||
window[GLOBAL_UNIVER_KEY] = null;
|
||||
|
||||
console.log("🔄 전역 Univer 상태 강제 리셋 완료");
|
||||
console.log("🔄 전역 Univer 상태 강제 리셋 완료 (REDI 정리 포함)");
|
||||
},
|
||||
|
||||
// 완전한 정리 메서드 (REDI 포함)
|
||||
async completeCleanup(): Promise<void> {
|
||||
const state = getGlobalState();
|
||||
|
||||
if (state.isDisposing) {
|
||||
console.log("🔄 이미 정리 진행 중...");
|
||||
return;
|
||||
}
|
||||
|
||||
state.isDisposing = true;
|
||||
|
||||
try {
|
||||
console.log("🗑️ 완전한 정리 시작 (REDI 포함)");
|
||||
|
||||
if (state.instance) {
|
||||
await state.instance.dispose();
|
||||
}
|
||||
|
||||
// 약간의 대기 후 REDI 상태 정리
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// REDI 전역 상태 정리 시도
|
||||
this.forceReset();
|
||||
|
||||
console.log("✅ 완전한 정리 완료");
|
||||
} catch (error) {
|
||||
console.error("❌ 완전한 정리 실패:", error);
|
||||
} finally {
|
||||
state.isDisposing = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 전역 디버그 객체 설정
|
||||
if (typeof window !== "undefined") {
|
||||
// 전역 상태 초기화
|
||||
// 전역 디버그 객체 설정을 위한 헬퍼 함수 (모듈 레벨 실행 방지)
|
||||
const setupDebugTools = (): void => {
|
||||
if (typeof window !== "undefined" && !window.__UNIVER_DEBUG__) {
|
||||
// 전역 상태 초기화 (필요한 경우에만)
|
||||
initializeGlobalState();
|
||||
|
||||
// 디버그 객체 설정
|
||||
@@ -248,10 +309,12 @@ if (typeof window !== "undefined") {
|
||||
console.log("🧹 전역 상태 정리 완료");
|
||||
},
|
||||
forceReset: () => UniverseManager.forceReset(),
|
||||
completeCleanup: () => UniverseManager.completeCleanup(),
|
||||
};
|
||||
|
||||
console.log("🐛 디버그 객체 설정 완료: window.__UNIVER_DEBUG__");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Univer CE + 파일 업로드 오버레이
|
||||
@@ -520,28 +583,76 @@ const TestSheetViewer: React.FC = () => {
|
||||
mountedRef.current = true;
|
||||
console.log("🎯 컴포넌트 마운트됨");
|
||||
|
||||
// 기존 전역 인스턴스 확인 및 재사용
|
||||
// 디버그 도구 설정 (컴포넌트 마운트 시에만)
|
||||
setupDebugTools();
|
||||
|
||||
// 강화된 기존 인스턴스 확인 및 재사용
|
||||
const existingUniver = UniverseManager.getInstance();
|
||||
if (existingUniver && !UniverseManager.isInitializing()) {
|
||||
console.log("♻️ 기존 전역 Univer 인스턴스 재사용");
|
||||
const state = getGlobalState();
|
||||
|
||||
// 기존 인스턴스가 있고 정상 상태면 재사용
|
||||
if (
|
||||
existingUniver &&
|
||||
state.univerAPI &&
|
||||
!UniverseManager.isInitializing() &&
|
||||
!UniverseManager.isDisposing()
|
||||
) {
|
||||
console.log("♻️ 기존 전역 Univer 인스턴스와 univerAPI 재사용");
|
||||
setIsInitialized(true);
|
||||
|
||||
// 기존 인스턴스의 컨테이너가 현재 컨테이너와 다른 경우 갱신
|
||||
if (
|
||||
containerRef.current &&
|
||||
state.lastContainerId !== containerRef.current.id
|
||||
) {
|
||||
console.log("🔄 컨테이너 정보 갱신");
|
||||
state.lastContainerId = containerRef.current.id;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 컨테이너 준비 후 초기화
|
||||
const initTimer = setTimeout(() => {
|
||||
if (containerRef.current && !UniverseManager.isInitializing()) {
|
||||
console.log("🚀 컴포넌트 마운트 시 Univer 초기화");
|
||||
// 초기화 중이거나 정리 중인 경우 대기
|
||||
if (UniverseManager.isInitializing() || UniverseManager.isDisposing()) {
|
||||
console.log("⏳ 초기화/정리 중이므로 대기");
|
||||
const waitTimer = setInterval(() => {
|
||||
if (
|
||||
!UniverseManager.isInitializing() &&
|
||||
!UniverseManager.isDisposing()
|
||||
) {
|
||||
clearInterval(waitTimer);
|
||||
const currentUniver = UniverseManager.getInstance();
|
||||
if (currentUniver && getGlobalState().univerAPI) {
|
||||
console.log("✅ 대기 후 기존 인스턴스 재사용");
|
||||
setIsInitialized(true);
|
||||
} else if (containerRef.current && mountedRef.current) {
|
||||
console.log("🚀 대기 후 새 인스턴스 초기화");
|
||||
initializeUniver();
|
||||
}
|
||||
}, 100); // 짧은 지연으로 DOM 완전 준비 보장
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(waitTimer);
|
||||
}
|
||||
|
||||
// 완전히 새로운 초기화가 필요한 경우만 진행
|
||||
const initTimer = setTimeout(() => {
|
||||
// 마운트 상태와 컨테이너 재확인
|
||||
if (
|
||||
containerRef.current &&
|
||||
mountedRef.current &&
|
||||
!UniverseManager.isInitializing()
|
||||
) {
|
||||
console.log("🚀 컴포넌트 마운트 시 새 Univer 초기화");
|
||||
initializeUniver();
|
||||
}
|
||||
}, 100); // DOM 완전 준비 보장
|
||||
|
||||
return () => {
|
||||
clearTimeout(initTimer);
|
||||
mountedRef.current = false;
|
||||
console.log("👋 컴포넌트 언마운트됨");
|
||||
};
|
||||
}, [initializeUniver]);
|
||||
}, []); // 의존성 배열을 빈 배열로 변경하여 한 번만 실행
|
||||
|
||||
// 컴포넌트 언마운트 시 정리 (전역 인스턴스는 유지)
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user