Олег Марков
Организация и структура исходных файлов в проектах Vue
Введение
При работе над приложениями на Vue очень важно с самого начала продумывать структуру исходных файлов. Правильная организация каталогов и файлов ― это не только порядок в проекте, но и залог удобной поддержки, масштабирования и быстрой адаптации новых участников команды. Ниже мы разберемся, какие подходы используют разработчики на практике, как организовать свой проект для решения самых разных задач, какие файлы и папки будут в нем встречаться и почему имеет смысл разделять одни сущности, а другие объединять. Я покажу на примерах, как устроены реальные проекты, и объясню, для чего нужны разные уровни вложенности.
Стартовая структура Vue-проекта
Сначала давайте рассмотрим базовую структуру Vue-приложения, которая получается при инициализации через Vue CLI (vue create my-app
) или Vite (npm init vue@latest
). Посмотрим, что там появляется автоматически:
my-app/
├─ node_modules/
├─ public/
│ └─ index.html
├─ src/
│ ├─ assets/
│ ├─ components/
│ ├─ App.vue
│ └─ main.js
├─ .gitignore
├─ package.json
└─ README.md
Объясню коротко, зачем нужны эти папки и файлы:
- node_modules/ — сторонние пакеты, сюда обычно не лезут руками.
- public/ — статические файлы (иконки, favicon, HTML-шаблон).
- src/ — тут лежит весь исходный код приложения.
- .gitignore — список файлов и папок, игнорируемых git.
- package.json — зависимости, скрипты и базовые настройки проекта.
- README.md — описание проекта для разработчиков.
Дальнейшая организация всей логики и компонентов будет происходить именно внутри папки src
.
Разделение по слоям и каталоги в папке src
Базовые каталоги
В типовом проекте на Vue структура файлов немного напоминает анатомию: каждая группа файлов — это отдельная “система”, отвечающая за свои функции:
src/
├─ assets/ // Статические ресурсы — картинки, шрифты
├─ components/ // Мелкие переиспользуемые компоненты-инструменты
├─ views/ // Страницы/экраны, которые сопоставляются с роутами
├─ router/ // Настройки маршрутизации
├─ store/ // Центральное хранилище состояния (Vuex, Pinia)
├─ composables/ // Логика-композиции (composition API)
├─ utils/ // Утилиты и хелперы
├─ services/ // Работа с API/сервером
├─ App.vue // Главный корневой компонент
└─ main.js // Точка входа, инициализация Vue-приложения
Теперь коротко о каждом разделе.
assets
Тут хранят изображения, иконки, шрифты, иногда глобальные стили (например, normalize.css), которые нужны во всем проекте.
components
Сюда складываются переиспользуемые, независимо автономные “кусочки” интерфейса: кнопки, карточки, поля ввода, модальные окна и т.д.
views
Подходит для компонентов-страниц. Например, "Главная", "Профиль пользователя", "Панель администратора" — всё, что напрямую сопоставляется какому-либо маршруту в приложении. Обычно каждая view состоит из других компонентов.
router
Обычно содержит основной файл маршрутизации (router.js или index.js), где описаны маршруты, хуки, middleware и т.п.
store
Если вы используете Vuex или Pinia, здесь будут лежать модули состояния, actions, mutations, getters и т.д.
composables
Здесь складываются функции-композиции. Это отличный паттерн для выделения повторяющейся бизнес-логики. Подробнее расскажу чуть ниже.
utils
Мелкие утилитарные функции, которые не зависят от состояния приложения или Vue и могут использоваться где угодно.
services
Инкапсулирует работу с API: здесь лежат функции для HTTP-запросов (fetch/axios).
Пример более развернутой структуры
Если проект становится больше, появляются доп. уровни вложенности:
src/
├─ components/
│ ├─ ui/ // Общие элементы: Button, Modal, Icon
│ └─ layout/ // Header, Footer, Sidebar и др.
├─ views/
│ ├─ HomeView.vue
│ ├─ ProfileView.vue
│ └─ SettingsView.vue
├─ store/
│ ├─ modules/
│ │ ├─ user.js
│ │ └─ products.js
│ └─ index.js
├─ composables/
│ ├─ useAuth.js
│ └─ useFetch.js
├─ utils/
├─ services/
├─ router/
├─ App.vue
└─ main.js
Выделение папки ui
особенно удобно, если собирается “собственная библиотека” часто используемых элементов управления. Папка layout
содержит некие “шаблоны” с местами для вложения содержимого.
Организация компонентов
Что класть в components/
В папке components
размещаются, как правило, атомарные (Button, Input), молекулярные (Form, Card) и иногда ещё более сложные компоненты, из которых собираются страницы. Переиспользуемость—главный ориентир.
Давайте рассмотрим структуру примера:
src/
├─ components/
│ ├─ ui/
│ │ ├─ MyButton.vue
│ │ └─ MyInput.vue
│ ├─ layout/
│ │ ├─ AppHeader.vue
│ │ ├─ AppFooter.vue
│ │ └─ AppSidebar.vue
│ └─ MyCard.vue
Пример компонента MyButton.vue
<template>
<button :class="['my-button', type]" @click="$emit('click')">
<slot />
</button>
</template>
<script>
export default {
name: 'MyButton',
props: {
type: {
type: String,
default: 'primary' // primary, secondary
}
}
}
</script>
// Компонент — простая кнопка с предустановленным стилем, можно использовать везде.
Когда создавать отдельную папку для компонента
Если компонент сложный и требует собственных стилей, вспомогательных функций или тестов, стоит выделить под него отдельную папку. Например:
components/
└─ UserCard/
├─ UserCard.vue
├─ UserAvatar.vue
├─ UserCard.spec.js // тесты
├─ styles.css
└─ utils.js
Это удобно для изоляции логики и упрощает навигацию по коду.
Page-компоненты: Каталог views
Папка views
нужна для видимых пользователю страниц, часто связанных с роутами. Поддерживать единый шаблон легко:
views/
├─ HomeView.vue
├─ ProfileView.vue
└─ ArticleView.vue
Каждая страница обычно собирается из компонентов разного уровня вложенности, отображая одну бизнес-функцию.
Пример страницы:
<template>
<div>
<AppHeader />
<UserCard :user="user" />
<AppFooter />
</div>
</template>
<script>
import UserCard from '@/components/UserCard/UserCard.vue'
export default {
name: 'ProfileView',
components: { UserCard },
data() {
return {
user: {
name: "Иван",
avatar: "/img/ivan.png"
}
}
}
}
</script>
Как видите, страницы сами «складываются» из компонентов.
Работа с Vue Router: каталог router
В небольших проектах настройки роутера обычно помещают в одном файле:
src/router/index.js
Здесь указываются пути страниц, lazy load, guards и т. д.
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import ProfileView from '@/views/ProfileView.vue'
const routes = [
{ path: '/', name: 'home', component: HomeView },
{ path: '/profile', name: 'profile', component: ProfileView }
]
export default createRouter({
history: createWebHistory(),
routes
})
Если приложение большое, конфигурацию роутов делят по модулям (например, admin.js, user.js).
Организация глобального состояния: папка store
Vuex или Pinia помогают централизовать логику работы с состоянием приложения. Обычно структура такая:
store/
├─ modules/
│ ├─ user.js
│ └─ products.js
├─ index.js
index.js
подключает модули и экспортирует настроенный store для всего приложения.
Пример модуля user.js:
// store/modules/user.js
export default {
state: () => ({
loggedIn: false,
userInfo: null
}),
mutations: {
login(state, userInfo) {
state.loggedIn = true
state.userInfo = userInfo
},
logout(state) {
state.loggedIn = false
state.userInfo = null
}
}
}
Такой подход помогает держать логику “по темам”, не сливая всё в один файл.
composables: Как разбивать логику по use-функциям
Папка composables
появилась с приходом Composition API (Vue 3). Как её использовать — покажу на полезном примере.
Допустим, в разных частях приложения вам нужна функция для работы с локальным хранилищем:
// composables/useLocalStorage.js
import { ref, watch } from 'vue'
export function useLocalStorage(key, initialValue) {
const storedValue = ref(localStorage.getItem(key) || initialValue)
// обновлять localStorage при изменении значения
watch(storedValue, (newValue) => {
localStorage.setItem(key, newValue)
})
return storedValue
}
Использование в компоненте:
import { useLocalStorage } from '@/composables/useLocalStorage'
export default {
setup() {
const name = useLocalStorage('name', 'Гость')
return { name }
}
}
В composables/
удобно выносить любую бизнес-логику, которую можно, как “крючок”, подтягивать в разные части приложения: работа с API, подписка на события, анимации, склонение слов и так далее.
utils: папка для хелперов
В utils/
удобно держать любые js-функции, не связанные с состоянием, компонентами и Vue:
// utils/pluralize.js
export function pluralize(value, one, two, five) {
// 1 элемент, 2 элемента, 5 элементов — функция склонения числительных
// Пример: pluralize(2, 'товар', 'товара', 'товаров') => 'товара'
}
services: как организовать работу с внешними API
Все запросы к серверу лучше вынести в отдельную папку services/
. Например:
// services/userService.js
import axios from 'axios'
export function fetchUser(userId) {
return axios.get(`/api/users/${userId}`)
}
export function updateUser(userId, data) {
return axios.put(`/api/users/${userId}`, data)
}
Дальше эти функции можно подключать в composables или напрямую в компоненты.
Переход к масштабируемой архитектуре: Feature-based structure
Для больших проектов структура “по слоям” иногда осложняет работу из-за большого числа компонентов и файлов. В таких случаях применяют “feature-based structure”: разделение по функциональным областям.
src/
├─ features/
│ ├─ auth/
│ │ ├─ components/
│ │ ├─ views/
│ │ ├─ composables/
│ │ ├─ store/
│ │ └─ services/
│ ├─ profile/
│ └─ catalog/
├─ shared/
│ ├─ components/
│ ├─ utils/
│ └─ composables/
├─ App.vue
├─ main.js
“Особенности” (features) — модули или секции приложения, каждый со своими компонентами, стором, сервисами. В папку shared/
кладут то, что используется массово по всему приложению.
Импорт путей и абсолютные алиасы
В больших проектах удобно использовать алиасы, чтобы не писать постоянно длинные относительные пути. Обычно в vite.config.js или vue.config.js настраивают:
// vite.config.js
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Теперь @/components/MyButton.vue
всегда отсылает к src/components/MyButton.vue
. Это облегчает навигацию и рефакторинг.
Принципы, которые стоит соблюдать при организации проекта
- Структура должна расти вместе с приложением. Делайте минимально необходимую вложенность, избегайте пустых папок.
- Изолируйте бизнес-логику по областям: повторяющийся функционал выносите в composables или utils.
- Даже в небольших проектах старайтесь придерживаться единого стиля именования.
- Разделяйте внешний и внутренний код: мок-данные, тесты, прототипы удобно держать в отдельных директориях.
- Документируйте проект: что лежит в какой папке, как запускать, как добавлять новые фичи.
Более сложные примеры реальных проектов
Для полноты картины часто помогают реальные схемы, применяемые на крупных проектах:
Пример большого проекта (итеративно расширяемой структуры):
src/
├─ api/ // все сервисы и клиенты (GraphQL, REST)
├─ assets/ // иконки, лого, css, шрифты
├─ components/ // UI- и базовые компоненты
├─ config/ // конфигурационные файлы (переменные, настройки)
├─ constants/ // константы для повторного использования
├─ directives/ // vue-директивы
├─ features/ // фичи и хранилища по областям
├─ hooks/ // композиционные хуки
├─ layouts/ // шаблоны страниц
├─ locales/ // локализация
├─ mixins/ // миксины, если используется Options API
├─ router/ // роутер со структурой
├─ store/ // Vuex/Pinia
├─ styles/ // SCSS/Less и переменные
├─ types/ // определения типов, если используется TypeScript
├─ utils/ // хелперы
├─ views/ // основные страницы
└─ main.ts // точка входа
Такой подход облегчает параллельную разработку, ведение документации и, главное, упрощает масштабирование.
Заключение
Грамотная организация исходных файлов — ключ к гибкости, поддерживаемости и расширяемости вашего Vue-приложения. Не существует универсального шаблона: структура должна исходить из задач, которые вы решаете, и со временем “взрослеть” вместе с проектом. Правильное разделение по слоям и функциональным областям помогает быстрее находить нужный код, тестировать и добавлять новые функции без слома существующей логики.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Как подключать SCSS или глобальные стили к компонентам?
Для глобальных стилей — положите их в src/assets/styles/
, подключайте один раз в main.js (или App.vue).
Пример для main.js:js
import '@/assets/styles/main.scss'
Для SCSS-модулей в отдельных компонентах используйте <style lang="scss" scoped>
. Если нужно подключить общие SCSS-переменные во все компоненты, настройте соответствующий loader (vite.config.js, vue.config.js) — секция css.preprocessorOptions
.
Как структурировать проект с Vue Router при большом количестве маршрутов?
Разбейте конфигурацию роутов на модули.
Пример: создайте папки router/modules/
, куда разместите файлы admin.js
, user.js
, catalog.js
; в router/index.js
импортируйте их и объединяйте в один массив routes.
Можно ли совмещать Option API и Composition API в одном проекте/компоненте?
Да, можно. Например, вы можете использовать <script setup>
вместе с обычными опциями — они будут работать совместно. Постепенно переносите старые компоненты на Composition API, если возникает необходимость.
Как корректно структурировать тесты для компонентов Vue?
Лучше всего держать тесты в соседних папках или как соседние файлы с компонентами.
Пример: components/MyButton/MyButton.spec.js
или __tests__/MyButton.spec.js
.
Что делать, если хочется использовать TypeScript в Vue-проекте?
При создании проекта через Vue CLI или Vite можно выбрать TypeScript.
Все компоненты тогда используют расширение .ts
(или .vue
c <script lang="ts">
).
Выделите отдельную папку src/types/
для глобальных типов, описывайте props и объекты через интерфейсы или типы.
Настройте main.ts аналогично main.js, только заменив синтаксис на TypeScript.