У тебя есть сайт с админпанелью и ты используешь или только собираешься использовать Gulp в этом проекте? Хочешь максимально работать с сайтом через админпанель, включая контроль над генератором ресурсов Gulp? Тогда под катом я покажу тебе простой способ управления Gulp'ом с вотчерами на сервере прямо из админпанели.


Это небольшой туториал по настройке и запуску Gulp'а на сервере средствами любой админпанели на PHP. Подразумевается, что запускается Gulp не для единовременной сборки ресурсов, а в режиме с вотчерами. Отлично подходит, если ты работаешь с проектом через Deployment Tools и локально он у тебя не запущен. При этом каждый раз билдить, например, SCSS локально и заливать результат уже надоело.


1. Тонкости хостинга


Так уж получилось, на моём хостинге не было поддержки Node. Пришлось поставить её отдельно в свою папку рядом с сайтами. Какую версию ставить, не принципиально, я использовал Node.js v5.12.0. Для установки просто распакуй архив куда-нибудь к себе на хостинг и добавь NodeJS bin в Path (подробнее ниже).


Обрати внимание, если на твоём хостинге включено изолирование сайтов, то папку с Node нужно добавить в общедоступные. В противном случае, процессу gulp будет отказано в доступе к Node.


2. Настройка Node и Gulp


Тут ты подготовишь всё необходимое для запуска Gulp на сервере. В принципе, им уже можно будет отлично пользоваться через SSH, но основная цель: Настроить управление через админпанель.


2.1 Настраиваем Node


Если ты уже пользовался npm'ом на хостинге и у тебя там уже стоит Node, то пол дела сделано и можешь сразу переходить к пункту 2.2.


Для установки на сервере Gulp и всего необходимого для его запуска тебе нужно будет в корне проекта создать файл package.json. Искренне надеюсь, что корень проекта у тебя лежит чуть выше папки public_html.


Примерное содержимое файла package.json:


{
  "name": "Project Namet",
  "version": "1.0.0",
  "devDependencies": {
    "browser-sync": "^2.14.0",
    "gulp": "~3.9.0",
    "gulp-autoprefixer": "^3.1.0",
    "gulp-browserify": "~0.5.1",
    "gulp-clean-css": "^2.0.12",
    "gulp-concat": "~2.6.0",
    "gulp-notify": "^2.2.0",
    "gulp-plumber": "^1.1.0",
    "gulp-sass": "~2.1.0",
    "gulp-sourcemaps": "^1.6.0",
    "gulp-uglify": "^1.5.1",
    "gulp-util": "^3.0.7"
  },
}

Набор плагинов для Gulp может быть любой, нужно смотреть, что именно ты используешь и зачем тебе это нужно. Что касается BrowserSync, то его запустить так и не удалось. Почему? Читай в самом конце в списке возможных проблем.


Далее, заблаговременно подключившись к серверу по SSH, ты должен настроить Path, чтобы установленная вручную Node попала в директории запуска (это, кстати, вовсе не гарантирует, что процесс сайта будет видеть эту Node или вообще хоть что-то. Там своё окружение со своим Path. Его настроишь уже через PHP.).


Установи Gulp и его компоненты из директории, где лежит package.json:


npm install

У тебя появится в этой директории папка node_modules, в которой будет всё что нужно для запуска Gulp.


Теперь нам нужно сконфигурировать Gulp.


2.2 Настраиваем Gulp под проект


Вообще было бы здорово, если бы ты умел это делать, но в любом случае по настройке конфигурационного файла Gulp есть много статей, я лишь приведу пример из своего проекта.


Для этого создай файл gulpfile.js рядом с файлом package.json с примерным наполнением:


'use strict';

var gulp = require('gulp');
var plumber = require('gulp-plumber');
var notify = require("gulp-notify");
var sass = require('gulp-sass');
var minifyCss = require('gulp-clean-css');
var sourcemaps = require('gulp-sourcemaps');
var autoprefixer = require('gulp-autoprefixer');
var browserSync = require('browser-sync').create();

var errorHandler = {
    errorHandler: notify.onError({
        title: 'Ошибка в плагине <%= error.plugin %>',
        message: "Ошибка: <%= error.message %>"
    })
};

gulp.task('scss', function(){
    gulp.src('./scss/**/*.scss')
        .pipe(plumber(errorHandler))
        .pipe(sourcemaps.init())
        .pipe(sass())
        .pipe(autoprefixer())
        .pipe(minifyCss({compatibility: 'ie8'}))
        .pipe(sourcemaps.write({
            includeContent: false
        }))
        .pipe(gulp.dest('./public_html/css/user'));
});

gulp.task('compiler', [
    'scss'
]);

gulp.task('watch', ['compiler'], function(){
    /*
    browserSync.init({
        host: 'http://mysite.ru',
        online: false,
        files: [
            './public_html/css/user/app.css'
        ]
    });
    */
    gulp.watch('./scss/**/*.scss', ['scss']);
});

gulp.task('default', ['watch']);

Я покажу на примере генерации только CSS, ты же можешь настроить Gulp как хочешь.
Подразумевается, что рядом с gulpfile.js лежит папка scss, в которой полная структура всех SCSS файлов. Если что, то найти и поменять путь к SCSS файлам в gulpfile.js не сложно, их всего два:


  1. Место, куда смотрит таск генерации CSS gulp.src('./scss/**/*.scss')
  2. Место, куда смотрит Watcher gulp.watch('./scss/**/*.scss', ['scss']);

2.3 Проверка готовности инструментов


Если всё сделано правильно, то уже сейчас можно, подключившись по SSH, перейти в папку с gulpfile.js и выполнить там:


gulp

При этом увидим какой-то дебаг от Gulp, либо предупреждения, которые тебе предстоит разрулить до перехода к следующему пункту. Сразу скажу, что проблема может быть в конфигурировании переменной Path для текущего подключенного пользователя, в ней обязательно должна быть папка bin от Node и папка /node_modules/.bin, которую должен устанавливать в Path сам NPM.


Другого рода проблемы, скорее будут относиться к неверной настройке gulpfile.js.


3 Подготовка PHP для работы с Gulp


Для этого тебе сначала нужно понять, как это вообще будет работать, не опираясь на конкретный язык программирования, и лишь потом решить, что же из PHP нужно использовать. Благо вариантов не очень много, да и продумывать почти нечего.


Алгоритм следующий:


  • Нужно при запросе на включение из админпанели запустить процесс Gulp в фоне и сохранить его PID где-нибудь.
  • При запросе на выключение нужно проверить PID и убить работающий под ним процесс Gulp.
  • При запросе статистики можно проверить существование работающего по текущему PID процесса Gulp и вернуть результат.
  • Весь output фонового процесса Gulp стоит сохранять в файл и при желании можно также настроить отправку содержимого этого файла в админпанель по запросу (В статье не рассматривается этот пункт, можешь реализовать его в виде домашнего задания).

Вроде просто, теперь, какие средства PHP тебе понадобятся.


Самое первое и простое, это класс ExecProcess где-то из обсуждения exec на сайте PHP. Я его немного модифицировал и использую вот такой:


<?php

class ExecProcess
{
    private $pid;
    private $command;
    private $root;

    // Массив с полными путями к папкам, которые должны быть в Path (в твоём случае это папка `bin` в Node и `/node_modiles/.bin`)
    private $envList = [];

    // Массив дополнительных команд, которые я использовал для дебага окружения. Тебе может не пригодиться.
    private $additional = [];

    private function runCom()
    {
        $command = "";
        if(!empty($this->envList)){
            $command .= 'export PATH=$PATH:'.implode(":", $this->envList).'; ';
        }
        if(!empty($this->root)){
            $command .= 'cd '.$this->root.'; ';
        }
        if(!empty($this->command)){
            $command .= 'nohup ' . $this->command . ' > /dev/null 2>&1 & echo $!;';
        }
        if(!empty($this->additional)){
            $command .= implode("; ", $this->additional);
        }
        exec($command, $op);
        $this->pid = intval($op[0]);
    }

    public function setPid($pid)
    {
        $this->pid = $pid;
    }

    public function setRoot($root)
    {
        $this->root = $root;
    }

    public function setEnv($envList)
    {
        $this->envList = $envList;
    }

    public function setCommand($commandLine)
    {
        $this->command = $commandLine;
    }

    public function setAdditional($additionalCommands)
    {
        $this->additional = $additionalCommands;
    }

    public function getPid()
    {
        return $this->pid;
    }

    public function status()
    {
        if(empty($this->pid)) return false;
        $command = 'ps -p ' . $this->pid;
        exec($command, $op);
        return isset($op[1]);
    }

    public function start()
    {
        $this->runCom();
    }

    public function stop()
    {
        if(empty($this->pid)) return true;
        $command = 'kill ' . $this->pid;
        exec($command);
        return !$this->status();
    }
}

И класс, который будет запускать именно Gulp и контролировать его состояние, GulpProcess:


<?php

class GulpProcess
{
    // Путь к файлу, который будет хранить PID твоего Gulp процесса
    private $tempPidFile = TEMP_DIR . "/gulpTaskPid.tmp";

    /** @var  ExecProcess */
    private $execProcess;

    public function __construct()
    {
        $this->execProcess = new ExecProcess();
        // Начальная директория для запускаемого нами в итоге скрипта (gulp). У меня так сложилось, что она находится выше на один уровень скрипта index.php. Простыми словами, директория, в которой лежит gulpfile.js
        $this->execProcess->setRoot("../");
        // Те самые директории (абсолютные пути) к Node и node_modules для Path
        $this->execProcess->setEnv([
            NODE_MODULES_BIN,
            NODE_BIN,
        ]);
        /* Тестировал. Может пригодиться)
        $this->execProcess->setAdditional([
            "echo \$PATH",
            "pwd",
            "whoami",
            "ps -ela",
            "id",
        ]);
        */
        $this->execProcess->setCommand('gulp');
    }

    public function start()
    {
        if(!$this->isActive()){
            $this->execProcess->start();
            $this->setPid();
            return $this->checkStatus();
        }
        return true;
    }

    public function stop()
    {
        if($this->isActive()){
            $this->execProcess->stop();
            $this->clearPid();
        }
        return true;
    }

    public function isActive()
    {
        return $this->checkStatus();
    }

    private function getPid()
    {
        if(is_file($this->tempPidFile)){
            $pid = intval(file_get_contents($this->tempPidFile));
            $this->execProcess->setPid($pid);
            return $pid;
        }
        return null;
    }

    private function setPid()
    {
        file_put_contents($this->tempPidFile, $this->execProcess->getPid());
    }

    private function clearPid()
    {
        if(is_file($this->tempPidFile)){
            unlink($this->tempPidFile);
        }
        $this->execProcess->setPid(null);
    }

    private function checkStatus()
    {
        $pid = $this->getPid();
        if(!empty($pid)){
            if($this->execProcess->status()){
                return true;
            }
            $this->clearPid();
            return false;
        }
        return false;
    }
}

Вроде всё лаконично и понятно. Осталось привязать это всё к админпанели.


4. Настройка экшенов админпанели


Какую систему ты используешь и как работаешь с экшенами известно лишь тебе одному. Я приведу пример своих экшенов, но уже всё должно быть очевидно:


<?php

class Admin{

    //...

    public function startGulpProcess()
    {
        $gulpProcess = new GulpProcess();
        return $gulpProcess->start();
    }

    public function stopGulpProcess()
    {
        $gulpProcess = new GulpProcess();
        return $gulpProcess->stop();
    }

    public function getGulpStatus()
    {
        $gulpProcess = new GulpProcess();
        $this->jsonData["is_active"] = $gulpProcess->isActive();
        return true;
    }
}

Как было сказано в пункте 3, можно также получить текущий output от процесса Gulp, но для этого сначала надо модифицировать класс ExecProcess, чтобы писал он весь output не в /dev/null, а в конкретный файл.


Итог


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


Все эти наработки можно красиво обернуть в Composer плагин и подшлифовать для конкретной системы (например, Yii2). Этим я обязательно займусь, когда переведу CMS на неё.


В купе с возможностью править файлы ресурсов на сервере через админпанель ты получаешь мощный инструмент, не требующий от тебя настроенной рабочей машины. Можно срочно поправить мелкие баги по требованию заказчика из любого места, с любого компьютера и т.д.


Возможные проблемы и попытки решения


Отсутствие NodeJS на хостинге

Всё просто, качаем NodeJS в виде архива и заливаем на хостинг. Далее обязательно прописываем его в Path (Это поможет корректно работать NPM и Gulp при запуске через SSH).


Система молча запускается, но процесс неактивен.

После запуска через админпанель может так получиться, что процесс запустится и сразу же выключится. Это связано с какими-то проблемами запуска Gulp, которые, к сожалению, ты не увидишь. Всё ведь посылается в /dev/null. Надо настроить класс ExecProcess так, чтобы output сохранялся в файл, и уже в нём искать проблему.


Система говорит нет прав, при попытке запуска через админпанель

Это ты сможешь узнать только из файла с output'ом процесса Gulp. Это скорее всего означает, что на хостинге включено изолирование сайтов и твой текущий сайт не имеет доступа к папке, где установлен Node. Это решается средствами управления твоего хостинга. Нужно сделать папку с Node общедоступной.


Какие-то ошибки при запуске Gulp

Ну тут ты уже накосячил с gulpfile.js, в теории, пример из статьи должен работать (я его, конечно, немного модифицировал, и он отличается от настоящего, рабочего в проекте, но правки были незначительные), попробуй оставить в нём минимум тасков и логики.


Что-то не работает BrowserSync

Отличная это штука, скажу я тебе, но мне так и не удалось её запустить. Всё дело в том, что она работает на определённых портах, используя Node, но на многих хостингах доступ к портам заблокирован. Если хочешь пользоваться BrowserSync'ом, придётся раскошелиться на VDS.


Я переживаю, что постоянно будут запускаться новые фоновые процессы Gulp

Вообще это контролирует класс GulpProcess, он настраивает класс ExecProcess таким образом, что контроль текущего процесса идёт по его PID. Как только процесс был создан, система получает его PID и дальше всегда работает с ним, при попытке создать новый процесс, при рабочем старом, система этого не позволит.


На крайний случай ты всегда можешь подключиться по SSH и проверить список активных запущенных процессов.


Ищи тот, что называется gulp, это и будет твой фоновый процесс.

Поделиться с друзьями
-->

Комментарии (26)


  1. 0Ilya
    13.12.2016 13:10

    У вас ошибка в примере с установкой зависимостей: «npm imstall»


    1. mrFleshka
      13.12.2016 16:27

      Спасибо, исправил. Лучше в личку в следующий раз)


  1. SerafimArts
    13.12.2016 14:54
    +3

    И самый главный вопрос — зачем? Gulp, Webpack, etc — это те штуки, которые нафиг не упали на проде висеть. Компиляция должна происходить локально и в прод деплоиться уже собранные сырцы.


    А ли я не прав?


    1. obsidok
      13.12.2016 16:26

      Для удобства.

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


    1. mrFleshka
      13.12.2016 16:33

      Отчасти вы несомненно правы, но всё зависит от сложности и важности проекта.
      Мой пример подходит для совсем лайтовых систем, которые не висят на автоматизированных Build & Deploy системах и, скорее всего, поддерживаются одним человеком.

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

      А если привык к хорошему (Gulp), то частичку этого можно и оставить, пусть и таким образом)

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


  1. justboris
    13.12.2016 16:08
    +2

    Не ожидал в 2016 году прочесть статью о новом остроумном способе редактирования файлов на боевом сервере


    1. mrFleshka
      13.12.2016 16:38

      Да ну, бросьте. 2016 не означает, что все вокруг используют для разработки самые современные технологии, в том числе CI и билд сервера. Довольно много сайтов до сих пор поднимаются на коленке и заливаются копированием чего-то с локалки куда-то на хостинг.

      Конечно это не туториал для профессиональных разработчиков мощных систем, но вот помочь связке Программист + Верстальщик, работающим с одним сайтом (каким-нибудь там dev.mysite.ru) это несомненно может.


      1. justboris
        13.12.2016 16:40

        А с git состояние файлов вы потом как синхронизируете? Push прямо с удаленного сервера?


        1. mrFleshka
          13.12.2016 16:46

          Нет, гит стоит только на локальной машине, файлы на хостинге полностью контролируются деплойментом, например, через IDE.

          Прошу, не рассматривайте это как позыв к действию: «Как начать писать свой проект… с галпом через админку», это лишь попытка помочь тем, кто уже использует такой процесс разработки, либо просто не нуждается в чем либо более современном.


          1. justboris
            13.12.2016 16:56

            Тогда почему бы не запускать Gulp локально, а на сервер закидывать собранный результат?


            Сразу отпадает куча проблем по установке ненужной Node.js на сервер и интеграции с php-кодом.


            1. mrFleshka
              13.12.2016 17:37

              Сразу появляется куча проблем, когда:
              — верстальщик правит один файл, а ему нужно заливать другой.
              — верстальщик забывает залить все SCSS файлы на сервер, от этого страдают потом все.
              — верстальщик не в состоянии адекватно настроить генератор на разных машинах (плохой аргумент, но и такое было)
              — программист не хочет настраивать генератор и вообще лезть во фронтенд, но необходимо сделать мелкую правку. Здесь и сейчас…

              Я не считаю интеграцию с PHP или установку NodeJS на сервер проблемой. Это (как и все остальные проблемы) задача, которая требует решения, возникшая во время выполнения другой задачи)


              1. justboris
                13.12.2016 17:49

                Перечисленные вами проблемы — это признак уже не маленького проекта.


                Для такого уже можно и заморочиться с процессом деплоя, хотя бы с локальной машины. Всяко получится лучше, чем изголяться прямо на продакшене, рискуя его уронить.


                1. mrFleshka
                  13.12.2016 18:07

                  Отчего не маленького?

                  Серёжа знает CMSку и готов покодить немного, Юра может чото поверстать, но дико далёк от PHP и системы. Вместе они делают какой-то сайт для подруги жены Серёжы… Я бы не парился там с деплоем, но скрасить время вёрстки для Юры я не откажусь)

                  Можно и заморочиться, но я вижу больше плюсов в таком подходе (читать как «более простом»).
                  Процесс деплоя тянет за собой одеяло с целой кучей проблем, к которым система может быть не готова, всё зависит от изначально выбранного подхода.

                  — Нет деплоя, есть админка, работаешь прямо на сервере? Статья для тебя.
                  — Хочешь автоматизировать деплой и не ставить на сервер инструменты для билда? Ищи другой подход, статья не об этом.

                  Как-то так.

                  Не вижу смысла спорить, всё что вы говорите верно, и, конечно, на много удобнее на крупных проектах с командой, но повторюсь, это пример реализации для конкретного процесса разработки и поддержки сайта (не процесс разработки сайта то обсуждать нужно).


                  1. justboris
                    13.12.2016 18:21

                    Зачем для сайта подруги жены нужен Gulp? Может проще обойтись без препроцессоров, транспиляторов и прочих штук?


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


        1. am-amotion-city
          13.12.2016 17:37

          Мне бы никогда не пришло в голову устанавливать инструменты типа галпа на боевую машину, но уж если — то чем плох git push с удаленного сервера?! Он на то и гит, чтобы хоть из смартфона же.


          1. mrFleshka
            13.12.2016 17:40

            Тем, что на сервер нужно подключаться.
            Пользователь там один, а правки могут делать как программист, так и верстальщик.
            Может ещё можно придумать, но это действительно неудобно. В таком процессе работы может даже и не быть Git'а совсем…

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


  1. ifalur
    13.12.2016 23:47
    +1

    У меня для мелких проектов так: проект выкачивается в netbeans с удалённого сервера на локальную машину, все правки он сам отслеживает и заливает на сервер, включая скомпилированные. По моему этот подход куда проще )


    1. mrFleshka
      14.12.2016 02:20

      Возможно проще, смотря как настроено отслеживание и выкачивание.
      — Можно ли этот процесс легко перенести на другую машину?
      — А что если на самом сервере нужно сделать правки, не имея IDE под рукой?
      — А заливается автоматически (ярый противник автоматического деплоя, особенно при работе с боевым сервером :D)?

      Просто именно с такими трудностями столкнулись я и мои друзья при работе над сайтом при «заливке готовых ресурсов на боевой»…


      1. ifalur
        14.12.2016 02:28

        — На другую машину — почему нет, также выкачал, далее npm install, gulp build.
        — Если без IDE то не получится )
        — Заливается автоматически при сохранении (пока с этим проблем не было)


        1. mrFleshka
          14.12.2016 02:47

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

          Да и чес слово, ну проще же поставить один раз это всё на сервак, чем потом разбираться что на другой машине ещё и ноды нет, а последняя нода не билдит то что нужно и т.д.))

          Жёстких требований к «отсутствию билда на сервере!» не было, поэтому почему бы не попробовать))


          1. SerafimArts
            14.12.2016 06:39

            На другой машине ноды нет? Докер, Вагрант… Нет?


            1. mrFleshka
              14.12.2016 11:36

              Вы серьезно?

              Говорю же, срочно что-то поправить в рабочем сайте.
              При чём тут докер или вагрант?

              Да, на ноутбуке жены в поездке стоит всё окружение и настроен деплой)))))


              1. SerafimArts
                14.12.2016 17:27

                Я пропустил слово "срочно поправить" в треде, перечитал, всё равно не нашёл. Куда смотреть?


                1. mrFleshka
                  15.12.2016 13:11

                  В статье.
                  Даже выделил (см. пункт «Итог»).

                  Но даже не в «срочно» смысл, просто неохота поднимать окружение на всех машинах. Да, и такое бывает. Не переводите всё на промышленные масштабы)


                  1. localkost
                    26.03.2017 19:51

                    Я бы использовал для привода колес дешевые шаговые двигатели 28BYJ-48 вместе с драйвером на UL2003. Шумность нулевая а насчет пластиковых шестеренок в двигателе — время покажет:)


                    1. mrFleshka
                      15.12.2016 14:51

                      У всех разные подходы, у кого как исторически сложилось.
                      У меня так, думаю кому-то будет полезно, если схожая ситуация.