Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.5 KiB
5.5 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Colocate Template, Script, and Style in SFCs for Maintainability | MEDIUM | Separating template, logic, and styles into different files reduces component cohesion and maintainability | best-practice |
|
Colocate Template, Script, and Style in SFCs for Maintainability
Impact: MEDIUM - Vue SFCs are designed to colocate template, logic, and styles because these concerns are inherently coupled within a component. Separating them into different files based on file type (not concern) makes components harder to understand and maintain.
Task Checklist
- Keep template, script, and style in the same
.vuefile by default - Only use
srcimports when files become extremely large (500+ lines) - Group related components in folders rather than separating by file type
- Use component composition to manage complexity instead of file splitting
Not Recommended:
components/
├── UserCard.vue # Just template
├── UserCard.js # Logic
├── UserCard.css # Styles
Recommended:
<!-- components/UserCard.vue - Everything in one file -->
<script setup>
import { computed } from 'vue'
import { useUserStatus } from '@/composables/useUserStatus'
const props = defineProps({
user: { type: Object, required: true }
})
const { isOnline } = useUserStatus(props.user.id)
const displayName = computed(() =>
`${props.user.firstName} ${props.user.lastName}`
)
</script>
<template>
<div class="user-card">
<img :src="user.avatar" :alt="displayName" class="avatar" />
<div class="info">
<h3 class="name">{{ displayName }}</h3>
<span :class="['status', { online: isOnline }]">
{{ isOnline ? 'Online' : 'Offline' }}
</span>
</div>
</div>
</template>
<style scoped>
.user-card {
display: flex;
padding: 1rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
.info {
margin-left: 1rem;
}
.name {
margin: 0 0 0.25rem;
}
.status {
font-size: 0.875rem;
color: #666;
}
.status.online {
color: #22c55e;
}
</style>
Why Colocation is Preferred
1. Coupled Concerns Should Stay Together
Template, logic, and styles within a component are inherently coupled:
- Template references reactive data from the script
- Styles target classes used in the template
- Changes in one often require changes in others
<!-- Everything references each other -->
<script setup>
const isExpanded = ref(false) // Used by template and affects styling
</script>
<template>
<!-- Uses isExpanded from script, applies class for styling -->
<div :class="{ expanded: isExpanded }">...</div>
</template>
<style scoped>
/* Targets class from template, shows visual state from script */
.expanded { max-height: 500px; }
</style>
2. Easier Navigation and Understanding
Single file = single place to look:
<!-- Open one file, understand the whole component -->
<script setup>
// All logic here
</script>
<template>
<!-- All markup here -->
</template>
<style scoped>
/* All styles here */
</style>
3. Better Refactoring Experience
Renaming a class? Everything is in one file:
<!-- Before: .card-title used in both template and style -->
<!-- After: Rename to .user-title in one file, one commit -->
When Src Imports Are Acceptable
For genuinely large components (rare), use src imports:
<!-- LargeDataTable.vue -->
<script src="./LargeDataTable.ts" lang="ts"></script>
<template src="./LargeDataTable.html"></template>
<style src="./LargeDataTable.scss" scoped lang="scss"></style>
But first, consider if the component should be split into smaller components.
Managing Complexity Without File Splitting
Extract Logic to Composables
// composables/useDataTable.ts
export function useDataTable(initialData: Ref<Item[]>) {
const sortColumn = ref<string | null>(null)
const sortDirection = ref<'asc' | 'desc'>('asc')
const currentPage = ref(1)
const sortedData = computed(() => { ... })
const paginatedData = computed(() => { ... })
function sort(column: string) { ... }
function goToPage(page: number) { ... }
return {
sortColumn,
sortDirection,
currentPage,
sortedData,
paginatedData,
sort,
goToPage
}
}
<!-- DataTable.vue - Component stays focused -->
<script setup>
import { useDataTable } from '@/composables/useDataTable'
const props = defineProps(['items'])
const { sortedData, sort, goToPage } = useDataTable(toRef(props, 'items'))
</script>
Break Into Smaller Components
<!-- Instead of one 500-line component -->
<template>
<div class="data-table">
<DataTableHeader @sort="handleSort" />
<DataTableBody :rows="visibleRows" />
<DataTablePagination
:total="total"
:current="currentPage"
@change="handlePageChange"
/>
</div>
</template>
The Real Separation of Concerns
True separation of concerns in Vue means:
- Components handle their own template/logic/style (coupled by nature)
- Composables handle reusable stateful logic
- Utilities handle pure functions
- Stores handle global state
This is more maintainable than separating HTML/CSS/JS into different files.