VUE 3
- Архитектура
- Что такое Pinia
- Что такое Nuxt
- Где находится vue-router
- Что такое ConfigProvider
- Что означает app.mount('#app');
- hash vs history
- Где будет #/modules
- Где находится ./modules/
- Что здесь происходит: const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { eager: true, });
- Что выполняется первым main.ts или app.vue
- Где в VUE прописаны роуты?
- что делает конструкция: router.beforeEach((to) => { ... });
- Что означает конструкция: const loadedPaths = new Set<string>();
- Что делает эта строка: const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
- Что такое Vite?
- Что делает эта строка: return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
- Что означает конструкция: badgeVariants?: | 'default' | 'destructive' | 'primary' | 'success' | 'warning' | string;
- Что такое Promise?
- Что означает: type RouteRecordStringComponent<T = string> = Omit< RouteRecordRaw, 'children' | 'component' > & { children?: RouteRecordStringComponent<T>[]; component: T; };
- Что означает: interface GenerateMenuAndRoutesOptions { fetchMenuListAsync?: () => ...
- Что означает: options.routes = cloneDeep(options.routes);
Архитектура
Что такое Pinia
Pinia — что это?
Pinia — это хранилище состояния (state manager) для Vue.
Проще:
Pinia = глобальное хранилище данных приложения
Зачем нужна Pinia
Когда нужно:
-
хранить данные между компонентами
-
не передавать props на 5 уровней вниз
-
иметь единый источник правды
Примеры данных в Pinia:
-
пользователь (user)
-
авторизация
-
корзина
-
настройки
-
данные с API
Pinia vs Vuex
| Vuex | Pinia |
|---|---|
| Старый стандарт | Новый стандарт ✅ |
| Сложный синтаксис | Простой |
| Много boilerplate | Минимум кода |
| Vue 2 / 3 | Vue 3 (рекомендуется) |
👉 Pinia официально заменила Vuex
Простой пример Pinia
1️⃣ Store
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'Alex',
isAuth: false
}),
actions: {
login(name: string) {
this.name = name
this.isAuth = true
}
}
})
2️⃣ Использование в компоненте
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
userStore.login('Ivan')
</script>
<template>
<div>{{ userStore.name }}</div>
</template>
Где подключается Pinia
В main.ts:
import { createPinia } from 'pinia'
const pinia = createPinia()
createApp(App)
.use(pinia)
.mount('#app')
В Nuxt 3
Pinia подключается автоматически:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt']
})
Когда Pinia НЕ нужна
❌ Маленький проект
❌ 2–3 компонента
❌ Нет общего состояния
Коротко
-
Pinia — глобальное состояние
-
Замена Vuex
-
Просто, удобно, официально
-
Используется в Vue 3 и Nuxt 3
Что такое Nuxt
Nuxt — это фреймворк поверх Vue, который упрощает разработку приложений и сайтов.
Если коротко:
Vue — это библиотека
Nuxt — это готовая архитектура + инструменты для Vue
Зачем нужен Nuxt
Nuxt решает типичные проблемы Vue-приложений:
✅ Роутинг из коробки
Не нужно настраивать vue-router.
📁
pages/index.vue → /
pages/about.vue → /about
pages/user/_id.vue → /user/:id
✅ Server-Side Rendering (SSR)
Страницы могут рендериться:
-
на сервере (SEO 👍)
-
на клиенте (SPA)
-
гибридно
Это важно для:
-
SEO
-
быстрой первой загрузки
-
соцсетей (preview)
✅ Автоимпорт
Не нужно вручную импортировать:
-
компоненты
-
composables
-
хуки
<script setup>
const route = useRoute() // работает без import
</script>
✅ Работа с API
Встроенные:
-
server routes
-
middleware
-
env переменные
// server/api/users.ts
export default () => {
return [{ id: 1, name: 'Alex' }]
}
Чем Nuxt отличается от обычного Vue
| Vue | Nuxt |
|---|---|
| Сам настраиваешь проект | Готовая структура |
| Роуты вручную | Роуты по папке pages |
| SPA по умолчанию | SPA / SSR / SSG |
| Больше конфигурации | Меньше рутины |
Когда использовать Nuxt
✔ Лендинги
✔ SEO-проекты
✔ Интернет-магазины
✔ Корпоративные сайты
✔ Fullstack (frontend + backend)
Когда Nuxt не нужен
❌ Маленькие SPA
❌ Админки без SEO
❌ Если важен минимальный размер бандла
Версии
-
Nuxt 2 → Vue 2 (устаревающий)
-
Nuxt 3 → Vue 3 (Composition API, Nitro, Vite)
👉 В 2026 году актуален Nuxt 3
Пример структуры Nuxt 3
pages/
layouts/
components/
composables/
server/
nuxt.config.ts
Где находится vue-router
vue-router не лежит в твоём проекте напрямую — это npm-пакет, который устанавливается как зависимость.
Разберём по шагам 👇
Где физически находится vue-router
После установки он лежит здесь:
node_modules/vue-router/
То есть импорт:
import { createRouter } from 'vue-router'
читается как:
«Возьми пакет
vue-routerизnode_modules»
Откуда он берётся
1️⃣ Он указан в package.json
{
"dependencies": {
"vue": "^3.x.x",
"vue-router": "^4.x.x"
}
}
2️⃣ Устанавливается через npm / yarn / pnpm
npm install vue-router
# или
pnpm add vue-router
Как JS понимает vue-router
Алгоритм такой:
-
Видит импорт
'vue-router' -
Ищет папку:
node_modules/vue-router -
Смотрит
package.jsonвнутри пакета -
Берёт entry point (обычно
dist/vue-router.mjs)
Что внутри vue-router
Примерно так:
node_modules/
vue-router/
dist/
vue-router.mjs
vue-router.cjs
package.json
Именно из dist экспортируются:
-
createRouter -
createWebHashHistory -
createWebHistory -
RouterView -
useRoute -
useRouter
Важный момент 🔥
import './router'
⬆️ это локальный файл
import 'vue-router'
⬆️ это npm-пакет
В Nuxt
Ты не импортируешь vue-router вручную:
-
Nuxt подключает его сам
-
RouterView,useRoute— доступны автоматически
Коротко
vue-router— это пакет изnode_modules, подключённый черезpackage.json
Что такое ConfigProvider
Это корневой шаблон приложения, где оборачивается весь UI и подключаются глобальные настройки. Разберём по слоям, сверху вниз 👇
Общая идея
<ConfigProvider>
<App>
<RouterView />
</App>
</ConfigProvider>
👉 Всё приложение:
-
получает настройки Ant Design
-
живёт внутри layout-компонента
-
меняется в зависимости от маршрута
1️⃣ <ConfigProvider> — глобальная конфигурация Ant Design
<ConfigProvider :locale="antdLocale" :theme="tokenTheme">
Это компонент из Ant Design Vue.
Что он делает:
-
🌍 задаёт локаль (язык, формат дат, текст)
-
🎨 задаёт тему (цвета, токены)
-
⚙️ пробрасывает настройки во все AntD компоненты ниже
Пример:
antdLocale = ruRU
tokenTheme = {
token: { colorPrimary: '#1677ff' }
}
Без него:
-
кнопки, модалки, date-picker — были бы с дефолтными настройками
2️⃣ <App> — layout / оболочка приложения
⚠️ Это не App.vue, а компонент Ant Design Vue:
import { App } from 'ant-design-vue'
Он:
-
управляет message / notification / modal
-
создаёт правильный контекст для AntD
-
нужен для новых API (
App.useApp())
Пример использования:
const { message } = App.useApp()
message.success('Успешно')
📌 По сути — служебный контейнер Ant Design.
3️⃣ <RouterView /> — место для страниц
<RouterView />
Это компонент Vue Router.
Он:
-
рендерит компонент текущего маршрута
-
меняется при переходе по роутам
Пример:
-
/login→Login.vue -
/users→Users.vue
Как это работает вместе
Пусть пользователь открывает:
/users
Тогда Vue:
-
Загружает
App.vue -
Применяет Ant Design настройки
-
Находит маршрут
/users -
Вставляет
Users.vueвнутрь<RouterView /> -
Весь UI:
-
с нужной темой
-
на нужном языке
-
в общем layout
-
Визуально
ConfigProvider (locale, theme)
└─ App (Ant Design context)
└─ RouterView
└─ Users.vue
Коротко
| Элемент | Что делает |
|---|---|
ConfigProvider |
Глобальные настройки AntD |
App |
Контекст AntD (message, modal) |
RouterView |
Отрисовывает текущую страницу |
Частая ошибка ❌
Путать:
-
App.vue(корневой компонент Vue) -
<App>из ant-design-vue
Что означает app.mount('#app');
app.mount('#app') — это момент, когда Vue реально запускается и начинает рисовать интерфейс.
Что именно означает
app.mount('#app')
Vue говорит:
«Найди элемент с id
appв HTML
и вставь туда корневой Vue-компонент»
Откуда берётся #app
В index.html:
<body>
<div id="app"></div>
</body>
#app — это обычный CSS-селектор.
Что происходит при mount
1️⃣ Vue находит <div id="app">
2️⃣ Создаёт экземпляр приложения
3️⃣ Рендерит App.vue
4️⃣ Подключает реактивность
5️⃣ Запускает жизненный цикл компонентов
После этого:
-
начинают работать компоненты
-
выполняется
setup(),mounted() -
появляется UI
До mount
const app = createApp(App)
На этом этапе:
-
приложение ещё не в DOM
-
можно:
-
подключать плагины
-
регистрировать глобальные компоненты
-
настраивать store / router
-
app.use(router)
app.use(pinia)
После mount
❌ Нельзя:
-
подключать плагины
-
менять конфигурацию приложения
app.mount('#app')
// app.use(router) ❌ уже поздно
Если #app не найден
Vue выдаст ошибку:
Failed to mount app: mount target selector "#app" returned null
Аналогия 🧠
-
createApp— собрать мебель -
use(...)— прикрутить детали -
mount('#app')— поставить её в комнату
Коротко
app.mount('#app')
👉 привязывает Vue-приложение к DOM
hash vs history
1️⃣ Hash mode (#/path)
Пример URL:
http://site.com/#/modules
Как работает
-
Всё, что после
#, не отправляется на сервер -
Навигацией полностью управляет Vue Router
-
Серверу вообще не нужно знать о роутинге
Плюсы ✅
✔ Работает без настройки сервера
✔ Можно открыть страницу напрямую
✔ Подходит для GitHub Pages, static hosting
Минусы ❌
✖ URL выглядит «грязно» (#)
✖ Хуже для SEO
Как включается
createWebHashHistory()
2️⃣ History mode (/path)
Пример URL:
http://site.com/modules
Как работает
-
URL реальный
-
При обновлении браузер идёт на сервер
-
Сервер должен вернуть
index.html
Плюсы ✅
✔ Красивые URL
✔ Лучше для SEO
✔ Стандарт для продакшена
Минусы ❌
✖ Нужна настройка сервера
✖ Без неё будет 404 при обновлении
Как включается
createWebHistory()
Главное различие 🔥
| Hash | History | |
|---|---|---|
| URL | #/modules |
/modules |
| Сервер | Не нужен | Нужен |
| SEO | Плохо | Хорошо |
| Обновление страницы | Всегда ок | 404 без конфига |
| Продакшен | Редко | Чаще всего |
Когда что использовать
🔹 Hash — если:
-
нет доступа к серверу
-
хостинг статики
-
быстрый прототип
-
админка без SEO
🔹 History — если:
-
продакшен
-
SEO важно
-
есть сервер (Nginx, Apache, Node)
Пример настройки сервера (Nginx)
location / {
try_files $uri $uri/ /index.html;
}
В Nuxt
-
по умолчанию history
-
hash почти не используется
Коротко
Hash — просто и без сервера
History — красиво и правильно, но требует настройки
Где будет #/modules
#/modules не имеет отношения к папке ./modules в файловой системе.
Это URL-путь, а не путь к файлам.
Что такое #/modules
# в URL означает hash-роутинг.
Пример адреса в браузере:
http://localhost:5173/#/modules
Это значит:
-
используется Vue Router с hash-режимом
-
/modules— маршрут, а не папка
Где это «находится»
👉 Нигде в файловой системе напрямую
#/modules:
-
❌ не папка
-
❌ не файл
-
✅ путь, который обрабатывает vue-router
Как это связано с роутами
Где-то в коде есть маршрут:
{
path: '/modules',
component: ModulesView
}
Или он создаётся динамически, например так 👇
import.meta.glob('./modules/**/*.ts', { eager: true })
Каждый файл может экспортировать роуты, например:
// modules/modules.ts
export default {
path: '/modules',
component: () => import('@/views/Modules.vue')
}
Почему есть #
Если в router используется:
createWebHashHistory()
То URL будет:
#/modules
Если бы было:
createWebHistory()
То было бы:
/modules
Важное различие 🔥
| Что | Пример | Где живёт |
|---|---|---|
| Папка | ./modules |
в проекте |
| Роут | #/modules |
в браузере |
| Компонент | Modules.vue |
src/views |
Коротко
./modules— папка с кодом#/modules— маршрут в браузере
Они могут быть связаны логически, но это разные вещи.
Где находится ./modules/
Тут важно понять относительный путь 👍
import.meta.glob('./modules/**/*.ts')
Где именно находится ./modules
./modules — это путь относительно файла, в котором написана эта строка.
❗ Не от src и не от корня проекта, а от текущего файла.
Пример 1 — файл лежит в router/index.ts
src/
router/
index.ts ← здесь код
modules/
auth.ts
admin.ts
Тогда:
./modules/ → src/router/modules/
Пример 2 — файл в src/index.ts
src/
index.ts ← здесь код
modules/
user.ts
Тогда:
./modules/ → src/modules/
Как быстро проверить в проекте
1️⃣ Найди файл с import.meta.glob
Обычно это:
-
router/index.ts -
routes.ts -
store/index.ts -
modules.ts
2️⃣ Посмотри структуру папок рядом
Папка modules должна быть рядом с этим файлом.
Частая ошибка ❌
Ожидать, что путь считается от src:
// ❌ НЕ от корня проекта
import.meta.glob('/modules/**/*.ts')
Если бы хотели от src
Нужно использовать alias:
import.meta.glob('@/modules/**/*.ts')
(где @ → src)
Итог
./modules— это папка на одном уровне с файлом, в котором написан код.
Что здесь происходит: const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { eager: true, });
Разберём построчно
const dynamicRouteFiles = import.meta.glob(
'./modules/**/*.ts',
{ eager: true }
)
Что это такое в целом
Это Vite-фича (не чистый JS и не Vue), которая:
🔍 находит все
.tsфайлы в папкеmodulesи импортирует их
Подробно
1️⃣ import.meta.glob(...)
Это специальная функция Vite:
-
ищет файлы по glob-шаблону
-
возвращает объект с импортами
import.meta.glob('./modules/**/*.ts')
означает:
-
папка
modules -
любые вложенные папки
** -
все файлы
.ts
📁 Пример структуры:
modules/
auth.ts
admin/users.ts
admin/roles.ts
2️⃣ Что вернёт import.meta.glob
Без eager:
{
'./modules/auth.ts': () => import('./modules/auth.ts'),
'./modules/admin/users.ts': () => import('./modules/admin/users.ts'),
}
⚠️ Файлы не загружаются сразу, а лениво.
3️⃣ { eager: true }
{ eager: true }
означает:
📦 импортировать файлы сразу при запуске
Теперь результат будет:
{
'./modules/auth.ts': { default: ... },
'./modules/admin/users.ts': { default: ... },
}
То есть:
-
все
.tsфайлы выполняются сразу -
ты сразу получаешь их содержимое
Зачем это обычно используют
🔹 Динамическая регистрация
Чаще всего:
-
роутов
-
Pinia stores
-
модулей
-
middleware
Пример с роутами:
const routes = []
Object.values(dynamicRouteFiles).forEach((module: any) => {
routes.push(...module.default)
})
Каждый файл:
// modules/admin.ts
export default [
{ path: '/admin', component: Admin }
]
Почему так делают
✔ Не нужно вручную импортировать каждый файл
✔ Просто добавил файл — он автоматически подключился
✔ Удобно для больших проектов
Важно помнить ⚠️
-
Это работает только в Vite (Vue 3, Nuxt 3)
-
{ eager: true }увеличивает начальный бандл -
Без
eager— ленивая загрузка
Коротко
Эта строка автоматически импортирует все
.tsфайлы изmodulesи подкаталогов при старте приложения.
Что выполняется первым main.ts или app.vue
Коротко:
👉 первым выполняется main.ts, а уже потом — App.vue.
Порядок выполнения во Vue
1️⃣ main.ts — точка входа
Это самый первый файл, с которого стартует приложение.
Пример:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Что здесь происходит:
-
создаётся Vue-приложение
-
подключаются плагины (router, store и т.д.)
-
приложение монтируется
-
Vue говорит: «рендерим
App.vue»
2️⃣ App.vue — корневой компонент
App.vue начинает выполняться после mount().
<script setup>
console.log('App.vue')
</script>
Если смотреть по жизненному циклу
Пример логов:
// main.ts
console.log('main.ts')
createApp(App).mount('#app')
<script setup>
console.log('App.vue setup')
</script>
📌 В консоли будет:
main.ts
App.vue setup
Важно понимать
-
main.tsне имеет жизненного цикла -
App.vue— обычный Vue-компонент:-
setup -
onBeforeMount -
onMounted -
и т.д.
-
Частые случаи
🔹 Где что писать
| Что нужно сделать | Где |
|---|---|
| Подключить router | main.ts |
| Подключить pinia | main.ts |
| Глобальные стили | main.ts |
| Логика UI | App.vue |
<router-view /> |
App.vue |
🔹 В Nuxt
Там нет main.ts:
-
Nuxt сам управляет инициализацией
-
app.vue— корневой компонент -
плагины — в
plugins/
Где в VUE прописаны роуты?
В Vue роуты прописываются не в самом компоненте, а в Vue Router — обычно в отдельном файле.
Где именно смотреть 👇
1️⃣ Vue 2 / Vue 3 (самый частый вариант)
Обычно файл:
src/router/index.js
или
src/router/index.ts
Пример:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
component: () => import('@/views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2️⃣ Где роутер подключается к приложению
Обычно в:
src/main.js
или
src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
3️⃣ Если используется Nuxt
Тогда роуты не прописываются вручную:
pages/
Структура папок = маршруты
Например:
pages/index.vue → /
pages/login.vue → /login
pages/user/_id.vue → /user/:id
4️⃣ Как быстро понять в чужом проекте
-
Ищи папку
router -
Или файл с
createRouter/VueRouter -
Или в
package.jsonпроверь зависимость:
"vue-router"
что делает конструкция: router.beforeEach((to) => { ... });
router.beforeEach((to) => { ... }) — это глобальный навигационный гард (guard) во Vue Router.
Проще:
👉 код, который выполняется ПЕРЕД каждым переходом по роуту
Что именно происходит
router.beforeEach((to) => {
// ...
})
Каждый раз, когда пользователь:
-
переходит по ссылке
-
вызывает
router.push() -
обновляет страницу
Vue Router:
1️⃣ собирается перейти на новый маршрут
2️⃣ вызывает beforeEach
3️⃣ ждёт его результат
4️⃣ либо продолжает переход, либо отменяет / перенаправляет
Параметры
Полная сигнатура:
router.beforeEach((to, from) => {})
-
to— куда идём -
from— откуда идём
Чаще используют to.
Что можно делать внутри
✅ Разрешить переход
return true
или ничего не возвращать
🔁 Перенаправить
return { path: '/login' }
⛔ Запретить
return false
Типичный пример — авторизация 🔐
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuth()) {
return { path: '/login' }
}
})
{
path: '/profile',
component: Profile,
meta: { requiresAuth: true }
}
Когда именно выполняется
⏱ Порядок:
-
beforeEach -
beforeResolve -
afterEach
📌 beforeEach — самый ранний хук
Частые кейсы использования
-
проверка авторизации
-
проверка ролей
-
редиректы
-
логирование
-
смена title страницы
router.beforeEach((to) => {
document.title = to.meta.title || 'App'
})
Важно ⚠️
-
Нельзя делать бесконечные редиректы
-
Если возвращаешь объект → это навигация
-
Если используешь
async— можноawait
router.beforeEach(async (to) => {
await checkAuth()
})
Коротко
router.beforeEach— это фильтр маршрутов, который срабатывает перед каждым переходом
Что означает конструкция: const loadedPaths = new Set<string>();
Эта конструкция означает:
👉 создаётся пустое множество (Set) строк, которое будет хранить уникальные значения.
Разберём по частям 👇
Разбор строки
const loadedPaths = new Set<string>();
1️⃣ Set
Set — это встроенная структура данных в JavaScript.
Она:
-
хранит уникальные значения
-
не допускает дубликатов
-
быстро проверяет наличие элемента
Пример:
const s = new Set();
s.add('/login');
s.add('/login');
console.log(s.size); // 1
2️⃣ <string>
Это TypeScript-тип.
Set<string>
означает:
-
в
Setможно положить только строки -
TypeScript будет ругаться, если положить число или объект
loadedPaths.add('/users'); // ✅
loadedPaths.add(123); // ❌ ошибка TS
3️⃣ new Set()
Создаёт новый пустой Set.
На старте:
loadedPaths.size === 0
Зачем это обычно используют
🔹 Отслеживать уже обработанные маршруты
if (!loadedPaths.has(to.path)) {
loadRoute(to.path)
loadedPaths.add(to.path)
}
🔹 Не выполнять один и тот же код дважды
if (loadedPaths.has(key)) return
🔹 Кешировать результаты
loadedPaths.add('/profile')
Почему не массив (string[])
| Set | Array |
|---|---|
| Гарантирует уникальность | Нужно проверять вручную |
Быстро has() |
Медленно includes() |
| Нет дубликатов | Легко ошибиться |
В контексте vue-router (часто)
const loadedPaths = new Set<string>();
router.beforeEach((to) => {
if (loadedPaths.has(to.path)) return
loadedPaths.add(to.path)
// динамическая загрузка роутов
})
Коротко
const loadedPaths = new Set<string>();
👉 хранилище уникальных строк, обычно для контроля «что уже было обработано»
Что делает эта строка: const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
Это ключевая строка для динамических страниц 👇
const pageMap: ComponentRecordType =
import.meta.glob('../views/**/*.vue');
Коротко
Эта строка находит все
.vueфайлы в папкеviews (на один уровень выше текущего файла)и создаёт карту компонентов,
но не загружает их сразу (ленивая загрузка).
Разбор по частям
1️⃣ import.meta.glob(...)
Это Vite-функция.
Она:
-
ищет файлы по шаблону
-
возвращает объект
-
каждый файл — это функция динамического импорта
2️⃣ '../views/**/*.vue'
Это glob-шаблон:
-
../views— папкаviewsотносительно текущего файла на уровень выше -
**— любые вложенные папки -
*.vue— все Vue-компоненты
📁 Пример:
views/
Home.vue
user/Profile.vue
admin/Users.vue
3️⃣ Что именно вернёт import.meta.glob
Пример результата:
{
'../views/Home.vue': () => import('../views/Home.vue'),
'../views/user/Profile.vue': () => import('../views/user/Profile.vue'),
'../views/admin/Users.vue': () => import('../views/admin/Users.vue'),
}
📌 ВАЖНО:
-
компоненты не загружены
-
загрузка происходит когда ты вызовешь функцию
4️⃣ ComponentRecordType
Это TypeScript-тип.
Обычно выглядит так:
type ComponentRecordType = Record<string, () => Promise<Component>>
Он говорит:
-
ключ — путь к файлу
-
значение — функция, возвращающая компонент
Зачем это используют
🔹 Динамический роутинг
const routes = Object.keys(pageMap).map((path) => {
return {
path: convertToRoute(path),
component: pageMap[path],
}
})
👉 Каждый .vue файл автоматически становится страницей.
🔹 Code splitting
Компонент загрузится только когда пользователь зайдёт на страницу.
Отличие от { eager: true }
| Без eager | С eager |
|---|---|
| Ленивая загрузка | Загрузка сразу |
| Меньше initial bundle | Быстрее первый переход |
| Лучше для больших проектов | Подходит для мелких |
Частая ошибка ❌
Ожидать, что компонент уже доступен:
pageMap['../views/Home.vue'] // ❌ это функция
Правильно:
const comp = await pageMap['../views/Home.vue']()
Коротко
Эта строка создаёт карту Vue-страниц из папки
views
и позволяет лениво загружать компоненты
Что такое Vite?
Vite — это инструмент для разработки и сборки фронтенд-приложений (build tool), чаще всего для Vue, но не только.
Проще всего:
Vite = dev-сервер + сборщик, который делает разработку быстрой.
Зачем нужен Vite
Он решает главную боль старых сборщиков (Webpack):
-
медленный старт проекта
-
долгая пересборка при изменениях
Как работает Vite (идея)
🔹 В режиме разработки
Vite не собирает весь проект.
Он:
-
запускает dev-сервер
-
отдаёт файлы как ES-модули прямо в браузер
-
пересобирает только изменённый файл
👉 поэтому запуск и HMR почти мгновенные.
🔹 В продакшене
Vite:
-
использует Rollup
-
собирает оптимизированный бандл
-
делает code-splitting, tree-shaking и т.п.
Почему Vite такой быстрый
| Старые сборщики | Vite |
|---|---|
| Собирают всё сразу | Сборка «по требованию» |
| Большой initial bundle | ES modules |
| Медленный HMR | Мгновенный HMR |
Что даёт Vite разработчику
✔ Мгновенный запуск (npm run dev)
✔ Быстрый Hot Reload
✔ Простая конфигурация
✔ Отлично работает с Vue 3 и TypeScript
✔ Современный стандарт де-факто
Важная фича Vite (ты уже видел 👇)
import.meta.glob('./modules/**/*.ts')
👉 это фича Vite, не JavaScript и не Vue.
Где используется Vite
-
Vue 3 (по умолчанию)
-
Nuxt 3
-
React
-
Svelte
-
Vanilla JS
Файлы Vite в проекте
vite.config.ts
index.html
src/
Пример:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()]
})
Vite vs Webpack (коротко)
| Vite | Webpack | |
|---|---|---|
| Скорость | ⚡ очень быстро | 🐢 медленно |
| Конфиг | простой | сложный |
| Современность | ES modules | legacy |
| Новый Vue | ✅ стандарт | ❌ почти не используют |
Коротко
Vite — современный инструмент сборки,
который делает разработку быстрой и простой.
Что делает эта строка: return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
Эта строка делает HTTP-запрос к серверу и возвращает результат.
Разберём точно и по слоям 👇
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
Коротко
👉 Отправляет GET-запрос на
/menu/all
👉 Ожидает ответ в виде массива маршрутов
👉 Возвращает Promise с этими данными
Разбор по частям
1️⃣ requestClient
Это HTTP-клиент, чаще всего:
-
axios -
обёртка над
fetch -
кастомный API-клиент
Пример:
const requestClient = axios.create({
baseURL: '/api'
})
2️⃣ .get(...)
Метод:
-
отправляет GET-запрос
-
ничего не изменяет на сервере
-
просто получает данные
requestClient.get('/menu/all')
3️⃣ '/menu/all'
Это endpoint API.
Полный реальный URL может быть:
/api/menu/all
https://example.com/menu/all
Сервер обычно возвращает:
[
{
"path": "/users",
"component": "Users",
"meta": { "title": "Пользователи" }
}
]
4️⃣ <RouteRecordStringComponent[]>
Это TypeScript-тип ответа.
Он означает:
-
сервер вернёт массив
-
каждый элемент — объект типа
RouteRecordStringComponent
Пример такого типа:
type RouteRecordStringComponent = {
path: string
component: string
meta?: Record<string, any>
}
📌 Это только для TypeScript, в рантайме его нет.
5️⃣ return
Функция возвращает Promise.
const res = await getMenu()
или
getMenu().then(res => { ... })
Как это обычно используется (очень типично)
async function loadMenu() {
const routes = await requestClient.get<RouteRecordStringComponent[]>('/menu/all')
addDynamicRoutes(routes)
}
👉 Часто:
-
сервер отдаёт меню
-
фронт на его основе динамически создаёт роуты
Что реально происходит по шагам
1️⃣ Фронт вызывает функцию
2️⃣ Отправляется GET /menu/all
3️⃣ Сервер возвращает JSON
4️⃣ TypeScript проверяет тип
5️⃣ Данные возвращаются вызывающему коду
Коротко
Эта строка получает список маршрутов / меню с сервера
и возвращает его как Promise с типизацией.
Что означает конструкция: badgeVariants?: | 'default' | 'destructive' | 'primary' | 'success' | 'warning' | string;
Это TypeScript-конструкция, которая описывает тип свойства.
По шагам 👇
Что это означает в целом
badgeVariants?:
| 'default'
| 'destructive'
| 'primary'
| 'success'
| 'warning'
| string;
👉 Это описание опционального поля badgeVariants,
которое может принимать одно из перечисленных значений.
Разбор по частям
1️⃣ badgeVariants?
Знак ? означает:
🔹 свойство необязательное
То есть объект может не содержать badgeVariants.
{} // ✅ ок
2️⃣ |
Это union type (объединение типов).
Значение может быть одним из вариантов.
3️⃣ Строковые литералы
'default'
'destructive'
'primary'
'success'
'warning'
Это конкретные допустимые значения строк.
badgeVariants: 'success' // ✅
badgeVariants: 'info' // ❌ (если бы не было string)
4️⃣ | string
Это означает:
🔥 или любая другая строка
То есть тип не ограничивает строго значениями выше,
а лишь подсказывает рекомендуемые варианты.
badgeVariants: 'info' // ✅
badgeVariants: 'my-custom' // ✅
Зачем так делают
🔹 Подсказки + гибкость
-
IDE подсказывает стандартные варианты
-
но не запрещает кастомные
🔹 Часто используется для UI
Например:
-
цвет бейджа
-
вариант кнопки
-
тема компонента
<Badge variant="success" />
<Badge variant="my-theme-variant" />
Важный момент ⚠️
С точки зрения TypeScript:
'default' | 'primary' | string
👉 эквивалентно просто string
НО:
-
IDE всё равно покажет подсказки
-
это делается для автокомплита, не для строгой типизации
Если бы хотели строго ограничить
badgeVariants?:
| 'default'
| 'destructive'
| 'primary'
| 'success'
| 'warning';
Тогда:
badgeVariants: 'info' // ❌ ошибка
Коротко
Это опциональное строковое поле,
которое рекомендует набор значений,
но разрешает любые строки.
Что такое Promise?
Promise — это объект в JavaScript, который представляет результат асинхронной операции
(то есть того, что завершится позже).
Проще:
Promise = обещание, что значение будет, но не сразу
Зачем нужен Promise
В JS много асинхронных вещей:
-
запросы к серверу
-
таймеры
-
чтение файлов
-
динамические импорты
Promise позволяет:
-
дождаться результата
-
обработать успех или ошибку
-
не блокировать основной поток
Как выглядит Promise
const promise = new Promise((resolve, reject) => {
// асинхронная работа
})
У Promise есть 3 состояния:
| Состояние | Что значит |
|---|---|
pending |
в процессе |
fulfilled |
успешно выполнен |
rejected |
ошибка |
Простой пример
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve('Готово!')
}, 1000)
})
Через 1 секунду Promise станет fulfilled.
Как использовать Promise
1️⃣ .then() / .catch()
promise
.then(result => {
console.log(result)
})
.catch(error => {
console.error(error)
})
2️⃣ async / await (современно 👍)
async function run() {
const result = await promise
console.log(result)
}
👉 await:
-
«ставит код на паузу»
-
но не блокирует браузер
Реальный пример (ты уже видел)
requestClient.get('/menu/all')
Возвращает:
Promise<Menu[]>
Использование:
const menu = await getAllMenusApi()
Что происходит под капотом
console.log('1')
fetch('/api')
.then(() => console.log('2'))
console.log('3')
Вывод:
1
3
2
Promise выполняется позже, когда стек освободится.
Promise vs синхронный код
| Синхронный | Асинхронный (Promise) |
|---|---|
| Блокирует выполнение | Не блокирует |
| Сразу возвращает значение | Возвращает Promise |
| Просто | Гибко и эффективно |
Частые ошибки ❌
🔹 Забыли await
const data = getAllMenusApi() // ❌ Promise
🔹 Нужно так:
const data = await getAllMenusApi() // ✅ данные
Коротко
Promise — это механизм работы с асинхронным кодом в JavaScript:
результат будет позже, и JS знает, когда и как его обработать.
Что означает: type RouteRecordStringComponent<T = string> = Omit< RouteRecordRaw, 'children' | 'component' > & { children?: RouteRecordStringComponent<T>[]; component: T; };
Это TypeScript-тип, который переопределяет тип маршрута Vue Router, заменяя поле component на строку (или другой тип).
Общий смысл
RouteRecordStringComponent— это описание маршрута,
гдеcomponent— НЕ Vue-компонент, а строка (обычно имя компонента или путь).
Такой тип часто используют, когда:
-
маршруты приходят с бэкенда
-
компонент определяется позже (динамически)
Разбор кода
type RouteRecordStringComponent<T = string> = ...
1️⃣ T = string
Это generic-тип с дефолтным значением:
-
если тип не передали →
component: string -
если передали →
component: T
Примеры:
RouteRecordStringComponent // component: string
RouteRecordStringComponent<() => Promise<Component>>
2️⃣ Omit<RouteRecordRaw, 'children' | 'component'>
RouteRecordRaw — стандартный тип маршрута из vue-router.
Omit:
❌ убирает поля
childrenиcomponent
То есть берётся всё остальное:
-
path -
name -
meta -
redirect -
и т.д.
3️⃣ & { ... }
Это расширение типа.
Мы добавляем свои версии children и component.
4️⃣ children?: RouteRecordStringComponent<T>[]
-
childrenопциональный -
рекурсивный тип (вложенные маршруты)
-
дети тоже используют строковый component
Пример:
{
path: '/admin',
component: 'BasicLayout',
children: [
{
path: 'users',
component: 'Users'
}
]
}
5️⃣ component: T
Главное отличие:
component: string
вместо:
component: Component
Это позволяет:
-
получать маршруты с сервера
-
маппить строки → реальные Vue-компоненты
Зачем это нужно (практика)
📡 Сервер отдаёт маршруты
{
"path": "/users",
"component": "Users",
"meta": { "title": "Пользователи" }
}
🧠 На фронте
const route: RouteRecordStringComponent = data
🔄 Потом:
route.component = pageMap[`../views/${route.component}.vue`]
Чем отличается от RouteRecordRaw
| Поле | RouteRecordRaw | RouteRecordStringComponent |
|---|---|---|
| component | Vue компонент | string |
| children | RouteRecordRaw[] | RouteRecordStringComponent[] |
| Использование | Обычные роуты | Динамические / backend-routes |
Коротко
Это тип маршрута Vue Router,
гдеcomponent— не компонент, а строка,
чтобы позже заменить её на реальный компонент.
Что означает: interface GenerateMenuAndRoutesOptions { fetchMenuListAsync?: () => ...
interface GenerateMenuAndRoutesOptions {
fetchMenuListAsync?: () => Promise<RouteRecordStringComponent[]>;
forbiddenComponent?: RouteRecordRaw['component'];
layoutMap?: ComponentRecordType;
pageMap?: ComponentRecordType;
roles?: string[];
router: Router;
routes: RouteRecordRaw[];
}
Это TypeScript-интерфейс, который описывает объект с настройками для функции (скорее всего generateAccessible).
Разберём что означает КАЖДАЯ строка и зачем это нужно 👇
Общий смысл
interface GenerateMenuAndRoutesOptions { ... }
👉 Это контракт:
-
какие поля можно / нужно передать
-
какие из них обязательные
-
какие опциональные
-
каких типов они должны быть
Разбор по полям
🔹 fetchMenuListAsync?: () => Promise<RouteRecordStringComponent[]>;
-
?→ необязательное -
тип → функция
-
без аргументов
-
возвращает Promise
-
Promise резолвится в массив
RouteRecordStringComponent
📌 То есть:
fetchMenuListAsync: async () => {
return getAllMenusApi()
}
👉 callback, который:
-
асинхронно получает меню / маршруты
-
обычно с бэкенда
🔹 forbiddenComponent?: RouteRecordRaw['component'];
Это:
«возьми тип поля
componentизRouteRecordRaw»
Фактически:
Component | (() => Promise<Component>)
Используется как:
-
компонент 403 / Forbidden
-
или fallback при отсутствии прав
🔹 layoutMap?: ComponentRecordType;
Карта layout-компонентов:
{
BasicLayout: BasicLayout,
IFrameView: IFrameView,
}
Используется, чтобы:
-
сопоставлять строку
"BasicLayout" -
с реальным Vue-компонентом
🔹 pageMap?: ComponentRecordType;
Карта страниц:
import.meta.glob('../views/**/*.vue')
Используется, чтобы:
-
по строке
"Users" -
найти компонент
Users.vue
🔹 roles?: string[];
Массив ролей пользователя:
['admin', 'editor']
Используется для:
-
фильтрации маршрутов
-
проверки доступа
🔹 router: Router; ✅ (обязательное)
Экземпляр Vue Router:
const router = createRouter(...)
Используется для:
-
router.addRoute() -
управления навигацией
🔹 routes: RouteRecordRaw[]; ✅ (обязательное)
Массив базовых маршрутов:
[
{ path: '/login', component: Login },
{ path: '/404', component: NotFound }
]
Это:
-
статические маршруты
-
которые существуют всегда
Обязательные vs необязательные
| Поле | Обязательное |
|---|---|
router |
✅ |
routes |
✅ |
fetchMenuListAsync |
❌ |
layoutMap |
❌ |
pageMap |
❌ |
roles |
❌ |
forbiddenComponent |
❌ |
Как это используется на практике
generateAccessible(mode, {
router,
routes,
pageMap,
layoutMap,
fetchMenuListAsync,
})
TypeScript:
-
проверяет, что объект корректный
-
подсказывает поля
-
ловит ошибки на этапе разработки
Коротко
Этот интерфейс описывает набор параметров,
которые нужны для генерации меню и маршрутов с доступами
(часто — динамически и с бэкенда).
Что означает: options.routes = cloneDeep(options.routes);
Эта строка означает:
👉 создаётся глубокая копия массива маршрутов, и она записывается обратно в options.routes.
Строка целиком
options.routes = cloneDeep(options.routes);
Что такое cloneDeep
cloneDeep — обычно функция из lodash:
import { cloneDeep } from 'lodash-es';
Она:
-
создаёт полную (глубокую) копию объекта
-
копирует все вложенные объекты и массивы
-
не оставляет ссылок на оригинал
Почему нельзя просто так
❌ Поверхностная копия
const copy = [...options.routes];
Проблема:
-
вложенные объекты (
children,meta) остаются общими по ссылке
copy[0].meta.title = 'New';
// изменится и в original ❌
✅ cloneDeep
const copy = cloneDeep(options.routes);
Теперь:
copy[0].meta.title = 'New';
// original НЕ изменится ✅
Что реально происходит пошагово
1️⃣ Берётся options.routes
2️⃣ Создаётся полный клон всей структуры маршрутов
3️⃣ options.routes теперь указывает на новую копию
4️⃣ Оригинальные маршруты остаются нетронутыми
Зачем это делают (очень важно)
В коде генерации роутов обычно:
-
добавляют
children -
фильтруют по ролям
-
меняют
meta -
добавляют layout
❗ Если менять оригинал:
-
можно сломать повторную генерацию
-
получить дубликаты
-
испортить базовую конфигурацию
Типичный кейс из твоего контекста
options.routes = cloneDeep(options.routes);
// дальше
options.routes.forEach(route => {
route.children?.push(dynamicRoute)
})
👉 изменения безопасны
👉 исходные routes не портятся
Аналогия 🧠
-
Без
cloneDeep— ты редактируешь оригинал -
С
cloneDeep— работаешь с копией документа
Коротко
Эта строка защищает исходные маршруты,
создавая их полную независимую копию перед изменениями.