Олег Марков
Работа с модулями и пакетами в Vue
Введение
В процессе разработки на Vue часто возникает задача грамотно структурировать код, разбить его на отдельные блоки — модули и пакеты. Такой подход позволяет управлять сложностью проекта, улучшает читаемость, облегчает тестирование и переиспользование функций.
В экосистеме Vue модулями принято называть, в первую очередь, отдельные компоненты, Vuex-модули, а также самостоятельные пакеты с логикой и утилитами, которые можно подключать в различные проекты. Пакеты же чаще всего ассоциируются с внешними (или внутренними) npm-библиотеками, которые предоставляют те или иные возможности для вашего приложения.
В этой статье я расскажу:
- Как устроена модульная архитектура в Vue
- Как создавать и использовать модули на разных уровнях (от компонентов до Vuex)
- Как подключать npm-пакеты и управлять зависимостями
- Как организовать собственный пакет для переиспользования в различных проектах
- Лучшие практики — что стоит применять в крупных и небольших приложениях, чтобы ваш код был поддерживаемым
Поддержку модулей и пакетов обеспечивает не только сам Vue, но и экосистема вокруг — Node.js, npm, Vite, webpack и др. Давайте разбираться по порядку.
Организация кода и компонентов в Vue
Почему важна модульность
Vue приложения растут быстро. Если вы пишете всё в одном файле, код превращается в кашу: его сложно найти, изменить, протестировать или вынести повторно используемые решения в отдельные части. Разбиение функционала на модули упрощает навигацию, командную работу и поддержку.
Vue изначально проектируется как фреймворк, ориентированный на компонентность. Каждый компонент можно воспринимать как модуль — он выделяет отдельный фрагмент функционала (кнопку, форму, страницу, диалог и др.).
Что считается модулем в Vue
В контексте Vue модулем часто называют:
- Vue-компонент — файл .vue с шаблоном, скриптом и стилями
- Vuex-модуль — часть хранилища приложения, изолирующая логику работы с определенными сущностями (например, users, cart, products)
- Модуль роутера — группа маршрутов, вынесенная в отдельный файл
- Логическая утилита или сервис — JS/TS-модуль, предоставляющий функции, классы или константы
Всё это структурируется внутри каталогов проекта.
Базовая организация проекта
Вот пример типичной структуры каталога для Vue проекта (предположим, это проект на Vue CLI или Vite):
src/
├── assets/
├── components/
│ ├── BaseButton.vue
│ ├── UserCard.vue
│ └── ...
├── views/
│ ├── HomePage.vue
│ ├── UserProfile.vue
│ └── ...
├── store/
│ ├── index.js
│ ├── modules/
│ │ ├── user.js
│ │ └── products.js
│ └── ...
├── router/
│ └── index.js
├── utils/
│ └── api.js
├── App.vue
└── main.js
Комментарии к структуре:
- components/ — здесь размещаются переиспользуемые компоненты
- views/ — страницы или крупные логические блоки (view-level компоненты)
- store/ — хранилище данных, разделённое на модули
- router/ — маршрутизация, можно делить конфиги на части
- utils/ — вспомогательные функции, сервисы, API-слои
Благодаря модульности, вы легко можете передавать отдельные директории в другие проекты, упрощая разработку.
Импорт компонентов (модулей)
Компоненты можно импортировать как в корневой компонент App.vue
, так и друг в друга:
// Импорт компонента в другом компоненте
import BaseButton from '@/components/BaseButton.vue'
export default {
components: {
BaseButton,
},
// ...
}
// @
— сокращение для src (работает только при соответствующей настройке в сборщике)
Vue сам обрабатывает зависимости между файлами. Такой способ позволяет не тянуть все компоненты в глобальный контекст, а использовать ровно там, где это нужно.
Использование Vuex-модулей
Vuex (до Vue 3 Pinia, в новых проектах используют чаще именно Pinia) — хранилище состояний, поддерживает модульную организацию.
Зачем нужны Vuex-модули
В реальной задаче у вас может быть десятки сущностей: пользователи, товары, заказы, настройки, фильтры. Хранить все состояние и логику в общем объекте store нерационально.
Модули позволяют отделить логику работы с каждой сущностью в отдельный файл:
// store/modules/user.js
export default {
namespaced: true, // Включаем пространство имён для изоляции
state: {
profile: null,
},
mutations: {
setProfile(state, profile) {
state.profile = profile
},
},
actions: {
fetchProfile({ commit }) {
// Имитация запроса данных
commit('setProfile', { name: 'Ivan', age: 28 })
},
},
}
// store/modules/products.js
export default {
namespaced: true,
state: {
list: [],
},
mutations: {
setProducts(state, products) {
state.list = products
},
},
actions: {
fetchProducts({ commit }) {
// Имитация запроса товаров
commit('setProducts', [
{ id: 1, title: 'Телефон' },
{ id: 2, title: 'ПК' },
])
},
},
}
Подключение модулей в store
Для подключения модулей достаточно описать их в store/index.js
:
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import products from './modules/products'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user,
products,
},
})
Теперь обращение к данным идет по схеме: store.state.user.profile
, store.dispatch('user/fetchProfile')
.
В чем польза модульности
- Облегчает тестирование и сопровождение
- Позволяет нескольким разработчикам работать над отдельными областями приложения
- Модули могут быть динамически подключены или отключены
Работа с пакетами npm в проекте Vue
Что такое пакет
Пакет (npm-пакет) — это библиотека с определённой логикой (готовые компоненты, хелперы, стили), которую можно ставить как зависимость через npm или yarn.
Как добавить внешний пакет в проект
Допустим, вам нужна библиотека UI-компонентов element-plus
:
npm install element-plus
или
yarn add element-plus
Затем подключаете её в коде:
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App)
.use(ElementPlus)
.mount('#app')
Теперь все компоненты из Element Plus становятся доступны. Часто сторонние пакеты предоставляют инструкции по интеграции в документации.
Импорт собственных модулей и утилит
Вы можете без npm создавать свои JS/TS-модули и подключать их с помощью относительных путей:
// utils/math.js
export function sum(a, b) {
return a + b
}
// components/SomeComponent.vue
import { sum } from '@/utils/math.js'
export default {
mounted() {
const result = sum(2, 3) // 5
// ...
}
}
Создание собственных npm-пакетов для Vue
Иногда возникает задача — вынести часть функционала или компоненты в переиспользуемый npm-пакет, чтобы подключать его в несколько проектов или делиться им с другими разработчиками.
Простейшая структура собственного пакета
Допустим, вы хотите сделать свой набор компонентов.
my-vue-lib/
├── src/
│ ├── MyButton.vue
│ └── index.js
├── package.json
├── README.md
└── ...
пример src/index.js:
// src/index.js
import MyButton from './MyButton.vue'
export { MyButton } // Named export
export default {
install(app) {
// Регистрация компонента глобально
app.component('MyButton', MyButton)
},
}
package.json
{
"name": "my-vue-lib",
"version": "1.0.0",
"main": "src/index.js",
"peerDependencies": {
"vue": "^3.0.0"
}
}
Локальное подключение собственного пакета
Чтобы попробовать использование пакета в реальном проекте (до публикации на npm), воспользуйтесь npm link
или укажите путь напрямую:
npm install ../my-vue-lib
Теперь компоненты пакета доступны как обычные npm-зависимости.
Публикация пакета в npm
Публиковать пакет можно с помощью нескольких шагов:
- Зарегистрируйтесь на https://npmjs.com
- В терминале:
npm login npm publish
- После публикации ваш пакет становится доступен для установки (
npm install my-vue-lib
)
Упаковка модулей: ESM и CommonJS
Обратите внимание: если вы разрабатываете npm-пакет для Vue 3+, используйте формат ESM (ECMAScript Modules). Пример экспорта:
// src/index.js
export { default as MyButton } from './MyButton.vue'
export { default as MyInput } from './MyInput.vue'
Сборку пакета для публикации проще делать с помощью инструментов вроде Vite, Rollup или webpack.
Ленивая загрузка модулей
Чем больше приложение, тем дольше загружается первая страница. В Vue (при сборке с использованием webpack или Vite) многие модули или компоненты можно загружать «лениво», то есть только тогда, когда действительно требуется.
Пример динамического импорта компонента
export default {
components: {
// Компонент подгружается только когда реально нужен
UserCard: () => import('@/components/UserCard.vue'),
},
}
Если вы используете роутер Vue Router 3+ (или 4+ для Vue 3), ленивая загрузка маршрутов выглядит так:
// router/index.js
const UserProfile = () => import('@/views/UserProfile.vue')
const routes = [
{
path: '/user',
component: UserProfile
},
// ...
]
Что это даёт
- Улучшает время старта (первая страница быстрее)
- Разделяет ваш код на чанки и загружает их по требованию
Автоматизация организации модулей и импортов
В крупных командах часто используют плагины и инструменты, которые помогают автоматизировать импорт компонентов и других модулей:
- unplugin-vue-components — автогенерация импортов компонентов. Не нужно писать import вручную.
- vite-plugin-pages — автогенерация маршрутов на основе структуры файлов.
Пример автоимпорта:
npm install -D unplugin-vue-components
Конфиг для Vite (vite.config.js
):
import Components from 'unplugin-vue-components/vite'
export default {
plugins: [
Components({
dirs: ['src/components'],
// можно указать резолверы для ElementUI, Vuetify и др.
}),
]
}
Стилизация и разделение CSS по модулям
Каждый компонент Vue может содержать секцию <style>
. Добавляя атрибут scoped
, вы делаете стили применимыми только к данному модулю-компоненту:
<template>
<button class="my-button">Кликни меня</button>
</template>
<style scoped>
.my-button {
background: green;
color: white;
}
</style>
Также есть смысл разбивать SCSS или CSS файлы на модули и импортировать необходимые только там, где они требуются.
Лучшие практики модульной архитектуры в Vue
Один компонент — одно назначение
Компонент или модуль должен отвечать за одну задачу.Изоляция зависимостей
Используйте scoped модули там, где возможно (Vuex, стили, утилиты).Плоская структура и короткие пути
Не помещайте слишком много уровней вложенности: иерархия должна быть понятной.Переиспользование — через пакеты
Если вы видите, что компоненты повторяются в разных проектах — оформляйте их в npm-пакеты.Динамические импорты для ленивой загрузки
Используйте ленивая загрузка маршрутов, компонентов, чтобы оптимизировать производительность.Инкапсуляция логики
Выносите “грязную” работу (API-запросы, манипуляции с данными) в отдельные service-модули.Ведите документацию по каждому пакету/модулю
Это упростит работу вашей команды и ускорит онбординг новых участников.
Модули и пакеты — основа любой современной архитектуры на Vue. Такой подход не только делает ваш проект чище, но и позволяет уверенно работать как в небольших, так и в очень крупных приложениях.
Частозадаваемые технические вопросы по теме
Как организовать переиспользуемый модуль API запросов?
Создайте файл api.js
в директории utils/
или services/
:
// utils/api.js
import axios from 'axios'
const apiClient = axios.create({
baseURL: '/api',
timeout: 5000,
})
export default {
getUser(id) {
return apiClient.get(`/user/${id}`)
},
getProducts() {
return apiClient.get('/products')
},
// …
}
Импортировать и использовать можно в любом компоненте.
Как импортировать модуль только когда реально нужен?
Воспользуйтесь динамическим импортом:
const MyDialog = () => import('@/components/MyDialog.vue')
// или в методе: const module = await import('@/utils/heavy.js')
Как подключать приватные npm-пакеты внутри компании?
Опубликуйте пакет с нужным флагом или настройте приватный npm-реестр (например, с помощью Verdaccio или GitHub Packages), затем добавьте ссылку на реестр в .npmrc
и ставьте пакет по названию.
Как сделать алиасы (например, @/
для папки src) в Vite/webpack?
В vite.config.js
:
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: { '@': path.resolve(__dirname, 'src') },
},
})
Для webpack — похожее свойство resolve.alias
в конфиге.
Как протестировать модуль или компонент из пакета локально до публикации?
Используйте npm link
(или yarn link
) в папке пакета, а затем — в вашем проекте. Это создаст символическую ссылку. После любого изменения в пакете перезапустите сборку зависимого проекта, чтобы он увидел обновления.