Админка магазина на vue.js. Урок 13. Роутинг

ноябрь 21 , 2019
Метки:
Предыдущая статья Исходники

Роутинг в Single Page Application, что может быть проще? В любом javascript-фреймворке роутинг реализовывается десятью строчками кода. Vue.js, конечно же, тоже это позволяет.

Открываем документацию по vue-router, разбираемся. Вроде все просто. Ставим npm-пакет vue-router, прописываем, по какому урлу какие компоненты отрисовывать, и немного меняем html-шаблон. То есть страница приложения выглядит примерно так

router-link здесь - это компоненты vue, которые преобразуются в ссылки. При переходе на эти ссылки в router-view отрисовываются нужные компоненты. По каким ссылкам какие компоненты отрисовывать - задается в конфиге при создании роутера. Об этом чуть позже, там ничего сложного не будет.

Что могло пойти не так?

Я добросовестно выполнял все инструкции по туториалу vue-router, пока дело не дошло до непосредственно ссылок router-link. Здесь я застрял. Компоненты router-link ни в какую не хотели рендерится в ссылки и тем самым образовывать нормальное меню. То есть в html-разметке вместо такого

Рендерилось вот это

Конечно, это никуда не годилось. При этом, что интересно, сам роутинг работал. То есть когда я вбивал руками урлы /categories и /brands, то нужные страницы отрисовывались. Но со ссылками нужно было что-то делать.

Полез гуглить. На stackoverflow и прочих вопрошающих ресурсах люди дружно говорили, что дело в старых версиях библиотек. Мол, обнови свое старье в package.json и все будет хорошо. В первую очередь говорили обновить webpack до 4-й версии, но большинство советовали обновить все. Ок, попробуем.

Если вам не интересно читать, как я боролся с версиями библиотек и ковырял вебпак, то следующий раздел пропускайте. Переходите к той части, где я пишу непосредственно о внедрении vue-router. Только не забудьте скачать исходники, взять оттуда обновленные package.json, package-lock.json и webpack.config.js. И запустить npm install, чтобы обновить библиотеки. А после можно спокойно работать дальше с новым вебпаком и обновленными конфигами.

Если же интересно поковыряться в библиотеках вместе со мной, то ради бога. Читаем следующий раздел.


Обновляем библиотеки и правим конфиг webpack

Как мы будем обновлять библиотеки? Бегать по всему package.json и обновлять пакеты руками неинтересно. Должны же быть какие-то инструменты, которые автоматизируют это дело. Оказалось, есть такой - npm-пакет npm-check-updates. Ставим его глобально


    $ npm install -g npm-check-updates

Как он работает? Наберите в консоли команду ncu из корневой папки проекта. То есть где package.json. Приложение ncu проанализирует package.json и выведет список текущих версий пакетов и самых актуальных. Примерно так


    $ ncu
    Checking package.json
    [====================] 20/20 100%
    
     babel-loader    7.0.0  →    8.0.6 
     vue-loader    ^14.0.0  →  ^15.7.2 

    Run ncu -u to upgrade package.json

У вас будет список больше, потому что я нужные библиотеки уже обновил. Почему эти 2 остались необновленные - об этом позже.

Пока команда ncu нам только вывела информацию. Но последней строкой идет подсказка - выполнить ncu -u и наш package.json обновится. Пробуем


    $ ncu -u
    Upgrading package.json
    [====================] 20/20 100%
    
     babel-loader    7.0.0  →    8.0.6 
     vue-loader    ^14.0.0  →  ^15.7.2 
    
    Run npm install to install new versions.

И еще одна подсказка в конце - нужно запустить npm install. Все правильно, ведь мы обновили только package.json, а не сами библиотеки. Запускаем npm install и радуемся, что теперь у нас все самое свежее. Действительно, обновился webpack с 3-й версии до 4-й, обновился webpack-dev-server, babel-core, vue-loader - и это я только перечисляю мажорные версии. Вообще обновились все библиотеки, в том числе и vue с vuex-ом. Красота, что ж мы раньше так не делали! Вот теперь можно и код писать.

Давайте запустим нашу админку, как привыкли, через vue-cli - в режиме разработчика


    $ npm run dev

И здесь первое разочарование. Консоль валится с ошибками. Говорит нам, мол, что еще за vue-cli, не знаю такого. Странно, мы вроде бы ставили его вообще глобально, да и раньше как-то работало. Ладно, поставим еще раз, только не глобально, а как dev-зависимость.


    $ npm install --save-dev webpack-cli

Запускаем еще раз сборку


    $ npm run dev
    ...
    ERROR in ./src/main.js
    Module build failed (from ./node_modules/babel-loader/lib/index.js):
    Error: Cannot find module '@babel/core'
     babel-loader@8 requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.

И обламываемся второй раз. Опять ошибка сборки - не хочеть работать babel-core. Суть такая. Когда мы обновляли библиотеки, ncu сказал нам, что babel-loader последняя версия 8-я. А она требует @babel/core 7 версии. У нас пакет другой - babel-core. Я поставил нужный, но дальше повалились еще ошибки уже с babel-loader и vue-loader. В общем, запустилась цепочка.

Возиться с версиями библиотек - самое скучное занятие на свете. Опытным путем я выяснил, что тормозят дело 3 обновленные библиотеки. Я плюнул и решил откатить их на версии пониже, но зато рабочие. И руками поправил вот так


    "babel-core": "^6.26.3",
    "babel-loader": "7.0.0",
    "vue-loader": "^14.0.0",

Возможно, когда вы будете читать статью, то какие-то библиотеки уже обновятся и подружатся между собой, но на ноябрь 2019-го было вот так. Поэтому пусть у нас 3 либы не супер свежие, но зато рабочие. Меня вполне устроило. Пройдет полгодика, попробую обновиться до актуальных, скорее всего, все уже будет нормально.

Еще раз обновляем npm


    $ npm install

И запускаем режим разработчика


    $ npm run dev

Уф, теперь все работает. Но радуемся до тех пор, пока не попробуем собрать для продакшена


    $ npm run build 
    Error: webpack.optimize.UglifyJsPlugin has been removed, please use config.optimization.minimize instead.

Как я понял, новый вебпак не поддерживает webpack.optimize.UglifyJsPlugin. Теперь для сжатия javascript-кода нужно ставить отдельный плагин uglifyjs-webpack-plugin. Что мы и сделаем


    $ npm install --save-dev uglifyjs-webpack-plugin 

И еще нужно его подключить в webpack.config.js

В разделе


    if (process.env.NODE_ENV === 'production') { ... } 

нужно импортировать плагин


    const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

А затем поправить конфиг, вместо


    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),

написать


    new UglifyJsPlugin({
      uglifyOptions: {
        warnings: false,
        ie8: false,
        output: {
          comments: false
        }
      }
    }),

В исходниках все это есть. Теперь npm run build соберется, но с парой ворнингов


    WARNING in configuration
    The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
    You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
    
    WARNING in build.js contains invalid source map

Первый устраняется добавлением в конфиг вебпака строки


    module.exports.mode = 'production';

Ее нужно воткнуть в тот же раздел-условие с production, прямо в самое начало


    if (process.env.NODE_ENV === 'production') { ... } 

Вот теперь приложение собирается и в dev, и в prodcution режиме. Последний ворнинг с "contains invalid source map" я пока не поборол, если сумеете, дайте знать, пожалуйста :-)

А мы пока с конфигами закончили, ну их к черту, заработало и отлично. Теперь можно перейти и к самой важной теме урока - как реализовать роутинг.


Vue Router — официальная библиотека маршрутизации для vue.js

После всего-то пережитого с самим роутингом будет гораздо проще. Сначала ставим сам vue-router


    $ npm install --save vue-router

Далее заведем для настройки роутера отдельный файл - в папке src/ создадим файл router.js с таким содержимым


    import Vue from 'vue';
    import VueRouter from 'vue-router';
    
    import AppProducts from './components/AppProducts';
    import AppCategories from './components/AppCategories';
    import AppBrands from './components/AppBrands';
    
    Vue.use(VueRouter);
    
    let routes = [
        { name: 'products', path: '/', component: AppProducts},
        { name: 'categories', path: '/categories', component: AppCategories },
        { name: 'brands', path: '/brands', component: AppBrands}
    ];
    
    export default new VueRouter({
        routes
    });

Разберем. Сначала импортируем Vue и VueRouter, а также 3 основных компонента-страницы. Ниже применим плагин VueRouter для Vue и создадим коллекцию роутов.

В каждом роуте 3 поля:
1. название - рекомендуется его задавать
2. путь - что будет в урле
3. какой компонент отрисовать по этому урлу

И в конце экспортируем объект роутера. Его мы будем использовать в файле main.js. Переходим туда.

Сначала импортируем роутер


    import router from './router'

А затем добавим его в опции при создании экземпляра Vue, чтобы получилось так


    new Vue({
        el: '#app',
        store,
        router,
        render: h => h(App)
    })

Переходим к компонентам. Сейчас главный vue-компонент App.vue разруливает отображение страниц с помощью свойства currentTab, в котором хранится название текущей вкладки-страницы. Но теперь мы подключили роутер и от самопального решения нужно отказаться. Свойство currentTab и методы, связанные с ним, больше не нужны. То есть удаляем из App.vue следующее:

1. свойство currentTab из data
2. метод changeTab и заодно весь блок methods
3. вызов метода @change-tab="changeTab" в компоненте header

Кроме того, в App.vue больше не нужны компоненты AppProducts, AppCategories и AppBrands. Удалим их из импортов и из components.

Дальше самое интересное. До этого содержимое страницы мы отображали с помощью такой конструкции

Теперь же нет нужды руками вычислять компонент, за нас это сделает vue-router. А чтобы роутер знал, куда подставлять содержимое, вместо кода выше напишем

Напомню, в router-view будут рендириться компоненты, которые мы указали в настройках роутера. То есть все те же AppProducts, AppCategories и AppBrands.

Осталось научить меню выводить нужные ссылки. Для этого посмотрим на массив tabs в data


    tabs: [{
        component: 'AppProducts',
        title: 'Товары'
    }, {
        component: 'AppCategories',
        title: 'Категории'
    }, {
        component: 'AppBrands',
        title: 'Бренды'
    }]

Сейчас нам не нужно знать, какие компоненты привязаны к конкретной вкладке - это знает роутер. Достаточно будет вывести правильные ссылки и все. Давайте вместо поля component сделаем новое поле link


    tabs: [{
        link: '/',
        title: 'Товары'
    }, {
        link: '/categories',
        title: 'Категории'
    }, {
        link: '/brands',
        title: 'Бренды'
    }]

Теперь открываем AppHeader.vue, где и отрисовывалось меню в этом месте

Мы не будем менять внешний вид, пусть остаются кнопки. Только нужно подружить их со специальным компонентом router-link. То есть вот так

Почти то же самое, только тег не button, а router-link, а button мы укажем в атрибуте tag. В качестве ключа :key теперь link, так как component мы из свойств tabs убрали. И не забудем указать свойство to - это сама ссылка. Теперь меню будет отрисовываться, как и раньше, только кнопки будут еще и ссылками.

Все, уже можно переключаться между вкладками, адреса в адресной строке браузера меняются на что-то вроде http://localhost:8080/#/brands. И при перезагрузке страницы мы сразу отрисовываем нужный компонент согласно настройкам роутера.

Добавим чуть красоты. Чтобы по меню было понятно, на какой странице мы сейчас находимся, добавим в AppHeader.vue блок style

Никакой магии, просто класс .router-link-exact-active присваивается выделенным элементам router-link. А мы это использовали, чтобы подсветить активный пункт меню.

И еще один штрих, который нужно сделать - в компонент ProductsFilters.vue добавить такой код


    mounted () {
        this.clear();
    },

Тем самым мы при загрузке страницы сбрасываем все фильтры. Если этого не сделать, то при перезагрузке будут стоять дефолтные цены: максимальная и минимальная - нули. Соответственно список товаров будет пустой. А вызов метода clear установит правильные цены - min и max из всех товаров. Тогда все будет работать правильно.

На этом все, но как и всегда - есть один момент, который стоит обсудить.


Почему вместо красивых урлов /brands используются хэши /#/brands?

Для того чтобы убрать некрасивые /#/, нужно всего лишь в экспорте в файле router.js добавить поле mode: 'history', вот так


    export default new VueRouter({
        mode: 'history',
        routes
    });

Почему я этого не сделал сразу? Давайте посмотрим. Запустим проект в режиме development


    $ npm run dev

Теперь в браузере красивые урлы


    http://localhost:8080
    http://localhost:8080/categories
    http://localhost:8080/brands

Красота же, так намного лучше!

Но теперь пересоберем в режиме production


    $ npm run build

И откроем админку в браузере уже не через localhost. У меня настроен хост w-shop.lc, то есть админка по адресу http://w-shop.lc/admin/vue/

Запускаем, вводим логин/пароль admin/password. Мы же наладили авторизацию пару уроков назад

Ввели логин и пароль, энтер, открывается админка, но почему-то пустая. Хотя меню и подвал есть. Ладно, кликаем по ссылке в меню, например, Товары. Открылся список товаров. Кликаем на категории и бренды - все работает.

Но обратите внимание, как изменился url. Админка у нас находится по адресу http://w-shop.lc/admin/vue/, а сейчас они сменились на такие http://w-shop.lc/brands и http://w-shop.lc/categories. А страница товары вообще стала - http://w-shop.lc. Но это же главная страница самого интернет-магазина!

Проверим, перезагрузим страницу - действительно, открылся каталог интернет-магазина. Попробуем тогда открыть страницу http://w-shop.lc/brands, где мы только что видели, что открывается админка брендов. Жмем F5, а нам в ответ 404 not found. Что за фигня?

А все просто. Vue-router взял под свой контроль работу с адресной строкой браузера и крутит ей, как хочет. Ему нет дела до того, что изначально админка лежит в папке /admin/vue, все пути клиентский роутер заменил на те, что мы задали в конфиге router.js.

А когда мы перезагрузим страницу с адресом http://w-shop.lc/brands, то конечно, веб-сервер не поймет, что это за страница, и отдаст 404. Возможно, есть культурный способ задать корневой урл для vue-router или что-то такое. Или разрулить запросы на nginx, но я в этом мало шарю, а тратить полжизни на это не хочется. Поэтому ищу другие пути.

В development-режиме все отлично, потому что админка работает на корневом домене - localhost:8000. Неплохим решением было бы с самого начала вынести админку как отдельный проект, а не прятать ее в папке интернет-магазина. Часто так и делается, ведь админка и сам магазин - это совершенно разные проекты и связывают их только общая база данных. Поэтому если вы перенесете админку на отдельный домен, то смело включайте mode: 'history' в настройках роутера и наслаждайтесь красивыми адресами. Я же оставлю не совсем красивые хэши, чтобы можно было работать и в development, и в production режиме.

На этом пока все. Урок о роутинге оказался больше уроком о подкапотной жизни vue и vue-cli. Но без таких проблем и поиска их решения не обойтись при изучении чего-то нового. Думаю, мы встретим еще немало подводных камней при изучении vuejs, но также думаю, что будет полезно их преодолевать :-)

Всем удачи и до встречи. Скачивайте исходники и заходите в группу ВК обсудить vuejs и любую другую тему из блога.

Все уроки админки на vue.js

Предыдущая статья Исходники
Метки:
Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!
Понравилась статья? Поделись с другими!