| name | vue-giant-file-refactoring |
| description | Refactor Vue 3 giant files (>500 lines) using Blueprint-First Mode. Extract composables and UI sub-components while preserving 100% business logic. |
| domain | devops |
Vue Giant File Refactoring Workflow
Trigger
When encountering Vue files >500 lines, especially complex page components or massive single-file components.
Phase 1: Blueprint-First Mode
DO NOT MODIFY CODE. Output a detailed architectural blueprint containing:
- Composables Extraction Matrix: List each proposed composable, its responsibilities, input/output props, and key functions.
- UI Sub-Components: List each proposed dumb component with props/emits signatures.
- File Structure: Show the target directory layout.
- State Flow Diagram: ASCII diagram showing data flow between composables and components.
- Risk & Constraints: Explicitly state what MUST be preserved (provide/inject, specific API params, business logic).
- Wait for [APPROVE] from the user before writing any code.
Phase 2: Step-by-Step Execution
After approval, execute in strict order:
- Create composables (data → state → interaction → layout)
- Create UI sub-components
- Refactor main file (replace script with composable imports, keep template & styles intact initially)
- Output completion report with line-count comparison.
Critical Rules
- Zero Business Logic Changes: API parameters, role checks, and data processing must be identical.
- Console Cleanup: Remove debug
console.log statements, keep console.warn/error.
- Preserve External Contracts:
defineExpose, provide, event listeners (uni.$on/$off) must remain functional.
- Style Preservation: Keep CSS in main file initially; extract only if component becomes fully self-contained.
- Verification Note: Always end with simulator verification checklist.
Composable Patterns
use*Data.js: Data fetching, filtering, grid generation
use*Interaction.js: Click routing, API calls, modal state
use*Layout.js: DOM measurements, drag/drop, responsive calculations
use*Status.js: Complex computed properties, state machines, visual status calculation
Feature-Scoped Composable Placement
For uni-app / feature-based projects, place composables under the feature directory:
pages/course/
├── composables/ ← Feature-scoped (useCourseTableData.js, useTeacherCellInteraction.js)
├── components/ ← Feature-scoped sub-components
└── course.vue ← Main page
Do NOT place feature-specific composables in the global composables/ directory at project root. Only truly shared composables (like useI18n, useAuth) belong there.
Reuse Evaluation Matrix
Before creating a new composable, check if an existing one can be reused:
- Same semantic meaning → Reuse (e.g.,
useCourseTable utility functions shared between student/teacher)
- Same interface, different logic → Do NOT reuse; create feature-specific version (e.g.,
useTeacherTableData vs useCourseTableData — isMyTimeSlot logic is fundamentally different)
- Forced reuse creates spaghetti code → Separate composables, share utility functions instead
Uni-app / WeChat Mini Program Specific Patterns
- Cross-component data flow: Use
provide/inject for deeply nested table/grid data instead of prop-drilling. Define keys in constants/inject-keys.js (e.g., SCHEDULE_DATA_KEY).
- Lifecycle safety:
onShow fires on every page revisit; onMounted only once. Never duplicate heavy computations in both. Prefer onShow for state sync, onMounted for one-time setup.
- Event bus cleanup: Every
uni.$on('event', handler) MUST have a matching uni.$off('event', handler) in onUnmounted. Memory leaks are silent but fatal in long mini-program sessions.
- Template-first refactoring: When extracting from 3000+ line files, keep the
<template> and <style> blocks intact initially. Only replace <script> with composable imports. This prevents DOM breaking during the transition.
- Dual-Role Strategy: When a feature has fundamentally different logic for different user roles (e.g., Student vs Teacher course tables), never use boolean flags (
isTeacher ? ... : ...). Create parallel composables (useStudentTableData.js, useTeacherTableData.js). Share only the pure utility layer.
Inline Style → CSS Class Migration Pattern
When converting inline style="..." to SCSS classes:
- Check if a class already exists with similar rules
- If not, add the CSS rule to the component's
<style> block
- Critical: Verify the class name isn't already used elsewhere in the template before repurposing (e.g.,
grid-canvas-viewport vs grid-x-container — both exist and serve different purposes)
Emergency Recovery Patterns (when refactoring breaks compilation)
1. The "Double Script Block" Trap
When migrating from Options API to <script setup>, patch might leave the old export default { ... } block intact after the new </script>, creating two script blocks.
Symptom: ViteError: Invalid end tag or Multiple default exports.
Fix:
lines = open("file.vue").readlines()
first_script_end = next(i for i, l in enumerate(lines) if "</script>" in l)
style_start = next(i for i, l in enumerate(lines) if "<style" in l)
new_lines = lines[:first_script_end+1] + lines[style_start:]
open("file.vue", "w").writelines(new_lines)
2. Truncated <template> Block
Vite's SFC parser requires a strictly closed <template>. If the template is cut off or missing </template>, compilation fails instantly.
Symptom: [plugin:vite:vue] Element is missing end tag.
Fix: Manually reconstruct the closing tags (</template>, </view>, </ScheduleTable>) and ensure the template block ends exactly before <script setup>. Never use patch to rewrite an entire 3000+ line file; use write_file with fully assembled content, or surgical line-range deletions as above.
3. Composable Destructuring Alias Mismatch
When extracting logic to composables, the exported function names must exactly match the destructured names in the main file.
Symptom: TypeError: handleX is not a function or ReferenceError.
Fix:
return { loadMoreLessonHistory }
const { loadMoreLessonHistory: handleLoadMoreLessonHistory } = useComposable()
Ensure the alias matches exactly what the <template> expects.
UI Component Resolution Check
Before writing templates for extracted sub-components, verify the exact component names exist in uni_modules/.
Symptom: Could not resolve "uni_modules/uview-pro/components/u-loading-icon/..."
Context: UI libraries (like uview-pro) often change component names across versions (e.g., u-loading-icon might be just u-loading). Blindly using names from old code or AI hallucinations breaks Vite.
Fix:
ls uni_modules/uview-pro/components/ | grep loading
Post-Refactor Verification (Critical)
After modifying the main file, always verify no orphaned code remains between </script> and <style>:
grep -n "</script>" file.vue
grep -n "<style" file.vue
If </script> appears twice or there's code between </script> and <style>, the old script block wasn't fully replaced. Use a surgical line-range deletion to remove it:
lines = open("file.vue").readlines()
This is especially critical when using patch on files >1000 lines — partial view edits can leave orphaned Options API code after the new <script setup>.
Production Hygiene Checklist (auto-apply during refactoring)