Одностраничный магазин на Phalcon PHP + AngularJS. Работа над ошибками

    image

    Введение

    Всем привет! Не так давно я написал публикацию «Одностраничный магазин с корзиной на Phalcon + AngularJS + Zurb Foundation», которая имела неоднозначный эффект мягко говоря. А точнее получила много отрицательных комментариев, какие-то были объективные и конструктивные, какие-то нет, и они меня заставили задуматься, почему так произошло, ведь я хотел сделать полезный мануал, который пригодиться мне и другим, начинающим писать на AngularJS.

    Исповедь

    Да, мануал был полезен для меня, для меня старого, того, кому в 2009 году отказали в работе в местной веб-студии, и он по сей день ни разу ни работал в команде, ни разу не работал на наёмной работе, а полагался только на себя, и главным критерием эффективности реализации проектов был один — главное, что работает. Но это я — старый, после написания той статьи, и множества комментариев, я впервые решил попробовать сделать всё по правилам хорошего тона, хотя бы ради интереса.

    Список литературы


    Обычно список литературы приводится в конце, но с другой стороны, по ходу написания статьи, будут возникать вопросы, почему была выбрана именно такая реализация. И чтобы не оставлять вас без ответов на ваши вопросы, я буду в скобках [?] указывать на источник, откуда это было взято. Итак, вот и литература, на которую я опирался, исправляя свои ошибки.

    1. Комментарии к статье Одностраничный магазин с корзиной на Phalcon + AngularJS + Zurb Foundation
    2. Смелый стайлгайд по AngularJS для командной разработки [1/2]
    3. Смелый стайлгайд по AngularJS для командной разработки [2/2]
    4. angular js: ng-repeat no longer allowing duplicates
    5. Using Local Storage
    6. AngularJS $http not sending X-Requested-With header
    7. ngRoute preloader example


    Работа над ошибками



    По ходу реализации проекта одностраничного магазина по доставки еды были неумышленно допущены следующие недочёты:

    • Выгрузка сразу всех товаров на одну страницу с помощью php
    • Отсутствие единого стиля написания кода
    • Использование логики в контроллерах AngularJS [1] by EugeneOZ
    • Хранение корзины в объекте js, который после перезагрузки страницы сбрасывался [1] by hVostt
    • Добавление в корзину через вставку инлайн PHP скриптов [1] от steppefox
    • Отзывчивость интерфейса по ходу загрузки новых категорий


    В этой статье я хочу поделиться как я искал решения, и что из этого получилось. А теперь пройдёмся по каждому пункту.

    Выгрузка сразу всех товаров на одну страницу с помощью php


    Товаров в магазине оказалось чуть больше, чем рассчитывалось, и учитывая наш камчатский интернет, мы посчитали:
    100 товаров (изображений), каждая картинка 100-300 кб, в итоге только товары съедали драгоценный трафик наших клиентов в размере 10 мб, плюс почти 10 мб весил сам сайт (картинки, стили, скрипты). Сейчас же мне удалось оптимизировать всё, и сайт весит 3,9 мб во время первой загрузки, и не больше 1 мб в последующие, так как браузер кеширует всё что нужно.

    Такая оптимизация была достигнута за счёт сжатия больших фоновых изображений без потери качества, а так же спрайтов для иконок и мелкой графики, так же были убраны блоки, которые не играли важной роли на странице (отзывы, партнёры, сертификаты, почему мы, открытая форма обратной связи заменена на jivosite). Всё таки скорость загрузки для сайта с едой, гораздо важнее наших лавр и наград, которые нафиг никому не нужны.

    И конечно же оптимизация была за счёт отказа от рендеринга сразу всех товаров на страницу с помощью php, и заменена на ajax подгрузку json данных по клику на ссылку вида /#!/menu/7, да мне наконец довелось изучить как работает роутинг в angularjs, к стыду своему, до этого ни разу не работал с роутингом js.

    И это оказалось не так сложно, как я ожидал, собственно у меня был всего один роут:

    function config($routeProvider, $locationProvider) {
        $routeProvider
            .when('/menu/:id', {templateUrl: '/app/views/products.html', controller: ProductsController});
        $locationProvider.hashPrefix('!');
        $locationProvider.html5Mode(true);
    }
    angular.module('rollShop').config(config);
    


    Он то и подгружал json данные с сервера и рендерил их в шаблоне:

    <div class="large-12 columns" ng-view></div>
    


    Вообще, я слышал что для индексации такого подхода сервер должен возвращать html вместо json, но тогда не понятно как избежать инлайн вставок PHP скриптов? Этот вопрос остался для меня загадкой. И этот подход породил ещё одну проблему, если пользователь перейдя по ссылке /#!/menu/7 нажмёт кнопку «Обновить страницу», то его глаза наполняться кровью от вида json данных он увидит вместо нормальной страницы просто json данные. И тут нам на подмогу приходит Phalcon PHP Framework, на самом деле можно было и без него, но так как я с ним работаю, то и делаю всё в его стиле. Чтобы исправить эту ошибку, я решил что буду отдавать данные json, только при ajax запросах, а при обычных запросах буду перенаправлять пользователя по ссылке /#!/menu/$id.

    public function menuAction($id)
        {
            if($id != 'undefined')
            {
                if ($this->request->isAjax() == true) {
    
                    //Получаем основную категорию
                    $category = Category::findFirst($id);
    
                    //Проверяем есть ли подкатегории
                    $sub_category = Category::find("pid = '" . $id . "'");
    
                    if (count($sub_category) > 0) {
    
                        $products['category'] = $category->name;
    
                        foreach ($sub_category as $key => $val) {
                            $products['subcategory'][$key] = array(
                                'name' => $val->name,
                                'products' => Products::find("category = '" . $val->id . "'")->toArray()
                            );
                        }
                    } else {
                        $products = array(
                            'category' => $category->name,
                            'products' => Products::find("category = '" . $category->id . "'")->toArray()
                        );
                    }
    
                    $this->response->setContent(json_encode($products));
                    return $this->response->send();
    
                } else {
                    $this->response->redirect('/#!/menu/' . $id);
                    return false;
                }
            }
            else
            {
                return false;
            }
    
        }
    


    Проверка на AJAX запрос в Phalcon, осуществляется с помощью условия:

    if ($this->request->isAjax() == true) {
        //This is Ajax
    }
    


    Но есть проблема, AngularJS по-умолчанию не отправляет специальный заголовок X-Requested-With серверу [6], а значит без помощи AngularJS, эта функция не будет работать, поэтому пришлось добавить одну строчку в конфиг ангуляра.

    var app = angular.module('rollShop', ['ngRoute', 'mm.foundation'], function ($httpProvider) {
        $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
    }
    


    Теперь всё работает, и пользователь никогда не увидит голый JSON.

    Кстати, для индексации я подумал может сделать проверку на бота, и боту отдавать чистый html, без js кода, без кнопок, просто товары. Правда не знаю сработает это или правильно это, может кто подскажет в комментариях.

    Добавление в корзину через вставку инлайн PHP скриптов


    Благодаря исправлению предыдущего недочёта, автоматически был исправлен недочёт со вставкой инлайн PHP скриптов в функцию js. Давайте сравним как происходило добавление товара в корзину раньше, а как стало сейчас.

    До (фу):

    <div class="add-cart">
          <input type="number" ng-model="num<?=$p['id']?> value="1" min="1" max="50">
         <button type="button" ng-click="addCart(<?=$p['id']?>, num<?=$p['id']?>, '<?=$p['title']?>', <?=$p['price']?>)"></button>
    </div>
    


    $scope.addCart = function(id, num, title, price)
        {
            var nums = num || 1;
            $scope.carts.push({
                id : id,
                num : nums,
                title : title,
                price : price
            });
        };
    


    После:

    <div class="add-cart">
           <input type="number" ng-model="item.quality" min="1" max="50" placeholder="1">
           <button type="button" ng-click="addCart(item)"></button>
    </div>
    


    $scope.addCart = function (item) {
            CartsService.addCart(item);
    };
    


    Честно, мне такой подход гораздо больше нравится, хоть заказчик этого не оценит, и не заплатит больше, но как-то за себя стало гордо, и на душе приятно.

    Отсутствие единого стиля написания кода


    Пока исправлял предыдущие два недочёта, автоматически пришлось исправить ещё два — «использование логики в контроллерах», как видите теперь всё делает сервис, и «отсутствие единого стиля написания кода».

    Это моя вечная проблема, но опять же если смотреть с точки зрения экономики, и подхода «главное — работает», то здесь всё достаточно эффективно, не заморачиваясь на стайлгайдах, я экономил кучу своего времени, а время, как известно — деньги. К слову бюджеты моих клиентов от 50 до 100 тыс. руб, и им всё равно как написан код. Но это всё оправдания, я ведь хочу исправить ситуацию, и сделать код красивым, читабельным и логичным, в чём мне помогли две статьи на Хабре [2] и [3].

    В итоге у меня вышло 2 контроллера, 3 фабрики, 1 вид и один общий app.js. Выглядит это сейчас примерно так:



    Собственно как и рекомендовалось делать в тех стайлгайдах. А теперь давайте посмотрим на контроллеры, и можете сравнить с прошлой статьей.

    ProductsController.js

    function ProductsController($scope, $routeParams, ProductsService, CartsService)
    {
    
        $scope.items = '';
    
        ProductsService.getData($routeParams.id).success(function(data){
            $scope.items = data;
        });
    
        $scope.addCart = function (item) {
            CartsService.addCart(item);
        };
    
    }
    angular.module('rollShop').controller('ProductsController', ProductsController);
    


    CartsController.js

    function CartController($scope, CartsService) {
    
        $scope.carts = CartsService.getItemsCart();
    
        $scope.total = function(){
            return CartsService.summary($scope.delivery)
        };
    
        $scope.removeItem = function (carts, item) {
            CartsService.removeItem(carts, item);
        };
    
    }
    angular.module('rollShop').controller('CartController', CartController);
    


    Всё круто, как мне кажется, даже самому больше нравится, чем то, что было раньше, точнее даже, что творилось раньше у меня в контроллерах.

    Хранение корзины в объекте js, который после перезагрузки страницы сбрасывался


    Следующий недочет, на который в первую очередь обратили внимание читатели, это хранение товаров корзины в объекте js. Почему я выбрал этот подход, я объяснил в прошлой статье, ведь магазин одностраничный. В любом случае, реализовать хранение в localStorage не составляет труда, да и думаю пригодиться в дальнейшем.

    Я начал искать удобный модуль для AngularJS, который бы работал с localStorage, но в то же время он должен быть простым, и лёгким. Я нашёл несколько вариантов реализации, но они были сложные и большие на мой взгляд, ссылок сейчас к сожалению не помню, и потом наткнулся на тот вариант, что был как раз для меня [5].

    angular.module('ionic.utils', [])
    
    .factory('$localstorage', ['$window', function($window) {
      return {
        set: function(key, value) {
          $window.localStorage[key] = value;
        },
        get: function(key, defaultValue) {
          return $window.localStorage[key] || defaultValue;
        },
        setObject: function(key, value) {
          $window.localStorage[key] = JSON.stringify(value);
        },
        getObject: function(key) {
          return JSON.parse($window.localStorage[key] || '{}');
        }
      }
    }]);
    


    Правда использование его в таком виде не давало 100% исправную работу. При добавлении одного товара в корзину, всё было хорошо, но как только туда попадал второй другой товар или товар из другой категории, ng-repeat переставал выводить товары в корзине, а в консоли святилась ошибка:

    Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys.


    Решение этой ошибки я нашёл в источнике [4]. Проблема была из-за добавления в массив $$hashKey, я так и не разобрался что это, откуда, и зачем, но мне нужно было избавиться от этого, чтобы всё работало, и конечный вариант фабрики работающей с localStorage выглядит так:

    function LocalStorageFactory($window)
    {
        return {
            set: function(key, value) {
                $window.localStorage[key] = value;
            },
            get: function(key, defaultValue) {
                return $window.localStorage[key] || defaultValue;
            },
            setObject: function(key, value) {
                $window.localStorage[key] = JSON.stringify(value, function (key, val) {
                    if (key == '$$hashKey') {
                        return undefined;
                    }
                    return val;
                });
            },
            getObject: function(key) {
                return JSON.parse($window.localStorage[key] || '{}');
            },
            remove: function(key){
                $window.localStorage.removeItem(key);
            },
            clear : function() {
                $window.localStorage.clear();
            }
        }
    }
    
    angular.module('rollShop').factory('LocalStorageFactory', LocalStorageFactory);
    


    Я думаю код небольшой, и найти 2 отличия будет не трудно, проверка на $$hashKey помогла мне исправить ошибка с дубликатами. Теперь сервис корзины работал исправно, и хорошо, запоминая позиции, пересчитывая сумму, и.т.д. Код привожу ниже:

    function CartsService(LocalStorageFactory)
    {
        var CartsService = {};
        var CartData;
    
        if(LocalStorageFactory.getObject('carts').length > 0)
        {
            CartData = LocalStorageFactory.getObject('carts');
        }
        else
        {
            CartData = [];
        }
    
        CartsService.addCart = function (item)
        {
            CartData.push({
                id: item.id,
                num: item.quality || 1,
                title: item.title,
                price: item.price
            });
    
            CartsService.update();
    
        };
    
        CartsService.update = function()
        {
            if(LocalStorageFactory.getObject('carts').length > 0)
            {
                LocalStorageFactory.remove('carts');
            }
    
            LocalStorageFactory.setObject('carts',CartData);
        };
    
        CartsService.getItemsCart = function () {
            return CartData;
        };
    
        CartsService.removeItem = function (items, id) {
            items.splice(id, 1);
            CartsService.update();
        };
    
        CartsService.summary = function(dispatch)
        {
            var total = 0;
            var delivery = 0;
    
            angular.forEach(CartsService.getItemsCart(), function (item) {
                total += item.num * item.price;
            });
    
            //Тут был код высчитывающий стоимость доставки
    
            return total + delivery;
        };
    
        return CartsService;
    }
    angular.module('rollShop').factory('CartsService', CartsService);
    


    Не идеально, но всё же лучше, чем было.

    Отзывчивость интерфейса по ходу загрузки новых категорий


    JSON данные, приходящие с сервера весят не больше 10 кб, плюс время отдачи сервером, плюс время реакции js, в общем в случае великого камчатского интернета с огромнейшим пингом, есть смысл показать пользователю preloader при каждом запросе, да и это в любом случае хороший тон.

    Можно конечно это отдать контроллеру, но хотелось более универсальное решение, и опять же при этом, простое и лёгкое. Сначала я думал использовать плагин nProgress lite для AngularJS, но всё же решил сделать ещё проще, и нашёл пример [7].

    function run($rootScope, $timeout) {
    
        $rootScope.layout = {};
        $rootScope.layout.loading = false;
    
        $rootScope.$on('$routeChangeStart', function () {
            $timeout(function(){
                $rootScope.layout.loading = true;
            });
        });
        $rootScope.$on('$routeChangeSuccess', function () {
            $timeout(function(){
                $rootScope.layout.loading = false;
            }, 500);
        });
        $rootScope.$on('$routeChangeError', function () {
            $timeout(function(){
                $rootScope.layout.loading = false;
            }, 500);
        });
    }
    angular.module('rollShop').run(run);
    


    Теперь когда наступает одно из событий (думаю по названию события всё понятно), показывается / скрывается прелоадер.

    <div class="large-12 columns" ng-hide="!layout.loading">
              <!-- Можно текст, можно gif анимацию, можно на весь экран, в общем что хотите -->   
    </div>
    


    Теперь клиент не будет кликать по 5 раз на категорию, не понимая грузиться она или нет.

    Заключение


    Я постарался исправить недочёты допущенные во время разработки, но основная цель была приучить себя к грамотному использованию технологий, даже не смотря на отсутствие команды. Честно признаюсь, я получил массу удовольствия, хоть и потратил не лишнее рабочее время, и не получил за это какой-либо компенсации. Для себя я заключил, что все последующие проекты я буду делать уже с более грамотным подходом, потому что такой подход заставляет ценить не только деньги полученные за проект, но и знания и опыт полученные во время работы над ним. Опыт и знания дают гордость за своё дело, повышают качество работы, любовь к тому что делаешь. А всё это не оставит тебя без денег в кармане.
    Поделиться публикацией
    Комментарии 55
      +12
      По поводу клиента:
      1. Вы пишете «полезный мануал, который пригодиться мне и другим, начинающим писать на AngularJS»
      angular.codeschool.com/ и github.com/angular/angular-seed в качестве наглядного примера по структурированию.
      2. Почитайте github.com/johnpapa/angularjs-styleguide и откроете для себя много интересного.
      3. Ваш код не будет минимизироваться в рабочий, т.к. он должен быть размечен либо с использованием ngAnnotate, либо через $inject, либо как-нибудь ещё.
      4. Откройте для себя RESTful API уже.
      5. >показать пользователю preloader при каждом запросе, да и это в любом случае хороший тон
      А вот тут можно и поспорить, смотря что за запрос. Если увесистый то да, но если же это обычный запрос, то это будет выглядеть как дерганье экраном.
      6. Честно говоря не понял что за проблемы у вас произошли с localStorage и зачем вы в принципе искали «библиотеку» для работы с localStorage, но то, что angular ругнулся на дубликаты, говорит о неверной структуре сохраняемой информации. Я бы сделал array of objects вида {id: X, title: «XXX», count: X} (title чтобы не подгружать ничего лишний раз, но при вашем условии, когда все товары сразу на странице, можно обойтись и id товаров. Тут конечно можно подискутировать (аля старая информация в кеше и т.д.)
      7. По поводу $$hashKey — stackoverflow.com/questions/18826320/what-is-the-hashkey-added-to-my-json-stringify-result
      8. $locationProvider.hashPrefix('!'); вместе с $locationProvider.html5Mode(true); наводит на мысли что вы пытаетесь использовать history-api вместе с #!.. Надо ли вам объяснять в чем разница между этими двумя подходами?

      По поводу сервера не джедай, но опять же, наблюдаю: SQL-запросы в цикле, необоснованный выбор MySQL, не красивый код и так далее.

      //p.s.: carts — в переводе с инглиша «тележки». Мелочь конечно, а в глаза бросается.
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Все ваши комментарии ниже было интересно, но ужасно неудобно читать. В ответ на один коммент — один коммент же :)
          • НЛО прилетело и опубликовало эту надпись здесь
        • НЛО прилетело и опубликовало эту надпись здесь
          • НЛО прилетело и опубликовало эту надпись здесь
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                angular-seed рекомендуется разработчиками angular js
                • НЛО прилетело и опубликовало эту надпись здесь
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Не вижу где там на amazon слово carts, покажите мне
                  0
                  Правый верхний угол:
                  «Your Shopping Cart is empty.»
                  и рисунок с тележкой =)
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Для древовидной структуры я бы выбрал Mongo
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    Не понял ваш комментарий — причем тут реляционная модель? Она тут как раз и не нужна.
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        А как вы относитесь к тому, что человек в цикле вытягивает дерево из базы многократными запросами?

                        >Если Вы считаете нормальным в заказе хранить копии товаров
                        В заказе как таковом я бы хранил ссылки на товары. В заказе как в транзакции имеет место копирование, разумеется. Модель из вашего примера «с описанием» используется к примеру на AliExpress — купленный товар копируется и при этом остается ссылка на оригинальный товар. Тут зависит уже от самого магазина конечно. Мы же говорим про доставку продуктов. Зачем нам нужно копировать на них описание? Как их хранить — это дело бизнес-процесса. Если допустим обработка идет в виде «1 пицца в соусе сальса, 2 картофеля фри» то удобнее заказ будет хранить в виде копирования списка заголовков с ценами на момент продажи, а также данных клиента для доставки/дозвона, никакой избыточной информации при этом не потребуется. Анализ продаваемости можно хранить при этом либо в отдельных документах готовых отчетов, либо прямо в товарах — вполне будет документ-вей.

                        >Я делал магазины и могу с ответственностью сказать, что реляций там внутри дохренища
                        Ваше личное дело. Я тоже делал магазины и в основном обходился документами чуть менее чем полностью
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            Конечно высказывание
                            Анализ продаваемости можно хранить при этом… прямо в товарах
                            для какого-то абстрактного общего случая не очень хорошее. Для этого лучше юзать map-reduce, скорее всего, но зависит от случая.

                            С таким подходом аналитики будут класть БД каждым запросом.
                            Сиииильно сомневаюсь.
                          0
                          Начнём с того, что в денормализации нет ничего плохого.
                          Ссылка на объект в документарной БД — это тоже нормально. В Mongoose есть удобный populate на этот случай.
                          Выбирать РСУБД стоит только тогда когда это действительно необходимо, например, если вам нужны транзакции и/или контроль целостности данных. В остальных случаях — это дело вкуса.
                          MySQL сама по себе не плохая но и не самая быстрая/удобная/функциональная СУБД.
                          MongoDB шустрая, простая, легко поддаётся горизонтальному масштабированию средствами из коробки. Причём контроль целостности данных можно без труда реализовать в моделях.

                          Да и вообще MongoDB не так просто юзать в PHP как это делается в ноде…
                          • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Ко всему вышеперечисленному выше — я бы вынес загрузку данных в resolve роутов.
                  И тоже не особо понял чем не угодил в данном случае MySQL и почему ngAnnotate не справится с аннотированием. Со стандартными регистрациями angular'овских компонентов (и даже некоторых сторонних плагинов, например resolv'ы ui-router'а) он отлично справляется без дополнительной помощи. В свои компоненты, при использовании например $injector'a, да, иногда надо добавлять /*@ngInject*/
                  +3
                  >if ($this->request->isAjax() == true)
                  WIN, просто WIN

                  Еще можно так:
                  if ( ( ($this->request->isAjax() == true) && ( (!$this->request->isAjax()) == false) ) == true)
                  Чтобы совсем уж наверняка, так сказать :)
                    +2
                    Ну что набросились то, человек же старался… :)
                      0
                      Если уж и сравнивать с true, то только оператором === (https://php.net/manual/ru/language.operators.comparison.php)
                      Иначе совсем уж бредово)
                        0
                        Да отстаньте вы уже от человека…
                      +1
                      Ничего сам не выдумывал, брал из документации к Phalcon

                        0
                        понятно. очень странные доки )
                        –1
                        На самом деле есть один аргумент в пользу такого избыточного синтаксиса — он повышает читабельность кода. Т.к. мозгу проще на ходу сравнить два значения, чем заметить, что объявлено только одно значение и следует его сравнить с true. Возможно это звучит смешно, когда мы говорим об одной строке. Но, когда читаешь много кода «по горизонтали» такие мелочи действительно очень упрощают жизнь.

                        Как вариант еще можно if(true == $this->request->isAjax())

                          0
                          ну тут уже название функции все ясно показывает: if(...isAjax()) = если запрос через аякс, и никаких сравнений не нужно, почти обычный английский язык
                        +2
                        Уже лучше, но не останавливайся.

                        $this->response->setContent(json_encode($products));

                        У нас можно делать так (JSON_UNESCAPED_UNICODE что бы русские буквы в выдаче не корявило):
                        $this->response->setJsonContent($products, JSON_UNESCAPED_UNICODE);
                          0
                          Я не могу понять кое-чего только…
                          Вы несколько раз говорите «стиль написания не важен, не люблю терять на него время». У Вас же PhpStorm стоит? Настройте себе там стиль (там уже есть пачка пресетов популярных Codind Style), а дальше IDE сама всё сделает. Вы потратите ровно секунду на то, чтоб ткнуть Ctrl + Alt + L, а потом Enter и привести весь код в порядок.
                            +2
                            если пользователь перейдя по ссылке /#!/menu/7 нажмёт кнопку «Обновить страницу», он увидит вместо нормальной страницы просто json данные

                            Но как вы этого добились? В SPA сервер при таком запросе должен как всегда вернуть индекс, а дальше уже роутинг в JS решает, что делать. Не понимаю, как у вас вышло показать JSON вместо страницы. Да ещё и решить эту проблему костылями проверки типа запроса с серверной стороны.
                              +1
                              я так понял у человека index отдается phalcon'ом, и роутинг phalcon'а совпадает с роутингом фронта, поэтому так и выходит… Но вообще да, правильно на любой запрос вернуть index…
                                0
                                Но у него же хеш после корня идёт.
                              –3
                              За предыдущую статью поставил минус (без сожалений).
                              За эту плюс (недостатки есть, но прогресс очень приличный) и в карму плюс (за стремление к совершенству).
                                0
                                по 1,5 секунды отдаёт страницу, сколько у вас сейчас нагрузка-то?

                                неосилил почему сайт называется одностраничным, он же перезагружается целиком при клике на пункты меню как минимум?
                                  0
                                  Шутите? Секунд 40 первый раз грузилась главная.
                                  И таки загрузилась, но сайт сделан на OpenCart, похоже версия на angular лежит пока в загашнике и дописывается.
                                    0
                                    Конечно, на текущий сайт не смотрите, его делали не мы, и он скоро уйдёт в небытие.
                                      0
                                      А куда (когда?) смотреть?
                                • НЛО прилетело и опубликовало эту надпись здесь
                                    +1
                                    Gulp еще поможет с base64, сжатием графики, спрайтами, запуском тестов, логами и многим другим. Я свой рабочий процесс без него уже не представляю.
                                      0
                                      а каким образом gulp поможет с логами?
                                          0
                                          Честно говоря не вижу особого профита от использования gutil вне сборки на gulp. Т.к. формат логов приложения как правило отличается от формата логов gulp, в тексте логов ANSI-последовательности для раскраски логов, только мешают. Плюс невозможно кастомизировать логику записи логов. Конечно, можно воспользоваться перехватом stdout, а дальше уже отправлять сообщения в хранилище логов, однако это не очень удобный способ, т.к. в stdout могут писать и сторонние модули.
                                    0
                                    каждая картинка 100-300 кб

                                    Как Вы так умудрились?
                                      0
                                      Вот же:
                                      >Такая оптимизация была достигнута за счёт сжатия больших фоновых изображений без потери качества
                                      Вероятно, и обычные картинки жали с избыточным качеством.

                                      >Сейчас же мне удалось оптимизировать всё, и сайт весит 3,9 мб во время первой загрузки, и не больше 1 мб в последующие, так как браузер кеширует всё что нужно.
                                      А наши пользователи справедливо жалуются на 2,2 мб / 0,4 мб (включая случайные рекламные баннеры), при том что интернет весьма шустрый, так что сайт автору ещё оптимизировать и оптимизировать.
                                      +1
                                      Вот тут:

                                      ProductsService.getData($routeParams.id).success(function(data){
                                      $scope.items = data;
                                      });

                                      Заметил, что вы не словили error колбек. Это важное.
                                      Из не особо важного:
                                      1. Я бы ProductsService здесь назвал с маленькой буквы, т.к. вы уже здесь работаете с инстансом, хотя в JS с объектами и классами все не так однозначно.
                                      2. getData я бы предложил назвать getProducts; data – products; productsService – productsRepository; все три мысли только с одной целью: заставить ваш код говорить более точно всякому, кто его читает. Раз вы пишете мануал – это будет трижды полезно.

                                      Вообще ангуляр позволяет и такие чудеса делать: $scope.items = ProductsService.getData( $routeParams.id ); Но для этого надо научить ваш сервис возвращать обещания, а не прокидывать результат запроса в колбек.
                                        0
                                        Спасибо, с обещаниями ещё до конца не разобрался, хотя уже многое перечитал.
                                        0
                                        Читал и исходную статью, и эту ревью — автор, а зачем использовать такие инструменты, что сайт грузит 4Мб единомоментно после рефакторинга? И это при медленном интернете? И перезагрузка вызывает перекачку 25%? По себе — у меня медленный интернет 3Mbps, и извините, но ждать 15 секунд старта сайта для заказа пиццы я бы не стал. Максимум — позвонил оператору, а скорее убил закладку и ушел к конкуренту. История живо напоминает байку про композер, который жрет оперативку в лучших традициях java-интерпрайза.
                                          0
                                          Насчет первого пункта, а нельзя выгружать все товары, но просто их не рендерить, это избавит от лишних газрузок изображений? Мне кажется это афигенно, скорость поиска и просмотра товаров возрастает в разы. Так, например, контакт делает с друзьями.
                                            0
                                            $sub_category = Category::find("pid = '" . $id . "'");
                                            

                                            Это инъекция 100%.
                                              0
                                              Я сейчас перевожу всё на mongodb, этого бреда больше не будет.

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                            Самое читаемое