Быстрая разработка на 1С-Битрикс или как я познакомился с gulp



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

Не судите сторого


Так судьба сложилась, что я столкнулся с CMS 1С-Битрикс. Я прекрасно понимаю насколько она громоздкая и «грязная», но приходится иметь дело с тем, что есть. Исходя из этого прошу сильно не критиковать статью, она в первую очередь нацелена на тех, кто работает с этой системой.

Начнем


Первым делом необходимо настроить рабочую среду, а именно заполучить менеджер пакетов npm, который идет вместе с nodejs. Стоп, стоп юные битриксоиды, не разбегаемся, на windows это тоже можно поставить и через консоль работать тоже будет, проверено. А если возникнут вопросы, то google Вам в помощь (обратите внимание на пользовательские переменные среды, пути должны быть правильно прописаны, чтобы команда npm была глобальной).

Установив npm, мы получаем возможность установить все требуемые зависимости. Чтобы это сделать создадим файл:

package.json


{
  "name": "frontend-bitrix",
  "version": "1.0.0",
  "description": "",
  "author": "jmaks",
  "license": "ISC",
  "dependencies": {
    "gulp": "^3.8.11",
    "gulp-autoprefixer": "^2.1.0",
    "gulp-minify-css": "^1.0.0",
    "gulp-rigger": "^0.5.8",
    "gulp-sass": "^1.3.3",
    "gulp-uglify": "^1.1.0",
    "gulp-sourcemaps": "^1.5.0",
    "gulp-watch": "^4.1.1",
    "gulp-imagemin": "^2.2.1",
    "imagemin-pngquant": "^4.0.0",
    "gulp.spritesmith": "latest",
    "gulp-plumber": "latest"
  }
}

Этот файл содержит список всех требуемых пакетов для нашего сборщика, рассмотрим его детально:

  • gulp — сам сборщик
  • gulp-autoprefixer — автоматически добавляет вендорные префиксы к CSS свойствам
  • gulp-minify-css — сжатие CSS кода
  • gulp-rigger — позволяет импортировать один файл в другой простой конструкцией
  • gulp-sass — для компиляции нашего SCSS кода
  • gulp-uglify — сжимать наш JS
  • gulp-sourcemaps — для генерации css sourscemaps, которые будут помогать нам при отладке кода
  • gulp-watch — наблюдение за изменениями файлов
  • gulp-imagemin — сжатие картинок
  • imagemin-pngquant — сжатие картинок | работа с PNG
  • gulp.spritesmith — создание png спрайтов
  • gulp-plumber — ловим ошибки, чтобы не прервался watch

Также нам потребуется такой прекрасный пакетный менеджер как Bower. Устанавливаем сначала его с помощью npm (установка bower идет отдельно от нашей установки зависимостей, так как bower нам надо установить глобально), а потом аналогично создадим файл с настройками:

bower.json


{
  "name": "frontend-bitrix",
  "version": "0.0.1",
  "authors": [
    "jmaks1 <my.mailname@mail.ru>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "jquery": "2.*",
    "bootstrap-sass": "*"
  }
}

Тут все аналогично package.json, есть блок dependencies, который содержит список загружаемых пакетов. Здесь мы загрузим jquery и bootstrap-sass.

Как Вы уже заметили, я использую связку bootstrap'а и css препроцессора sass.
По первому пункту — bootstrap, использую его в виду того, что сам 1С-Битрикс постепенно начинает использовать этот CSS-фреймворк (ну и просто привык уже к нему).
По второму пункту — Sass. Этот препроцессор более функционален чем less, последний я очень любил, но он не смог конкурировать с Sass.

Создадим структуру нового проекта


Создавая новый магазин/портал/«что то еще на битриксе» я устанавливаю на локальном сервере нужную редакцию битрикса. Во время установки выберите любой готовый шаблон из стартовых, а потом просто отмените мастер установки, чтобы не захламить сайт демо данными, в итоге получим такую структуру сайта:
auth
bitrix
upload

Теперь создадим разделы для билда и файлов разработки. Билд — это наш шаблон сайта, в нем будут собираться все файлы, разместить его необходимо в директории /local/. Для тех кто в танке — эта директория равнозначна /bitix/, то есть мы можем в ней создавать компоненты, шаблоны и так далее. Разработку же будем вести в директории /src/, внутри нее создадим простую структуру.

В итоге получим:
auth
bitrix
upload
local
— templates
— — main
src
— fonts
— images
— sprite
— js
— — partials
— styles
— — partials

Тут все тривиально:
Директория local содержит базовый шаблон сайта main.
Директория src содержит:

  • fonts — шрифты
  • images — картинки
  • sprite — иконки для спрайта
  • js — скрипты. В корне этой папки будет файл script.js, который пригодится нам для сборки.
  • styles — стили. Тут так же, в корне template_styles.scss.

Файлы из директории partials в обоих случаях будут дополнительными, их мы просто подключим в главном файле, соответственно в script.js подключим все скрипты, а в template_styles.scss все дополнительные файлы стилей.

Подключать файлы будем конструкцией //= для script.js:

// include jquery
//= ../../bower_components/jquery/dist/jquery.min.js
// include bootstrap.js
//= ../../bower_components/bootstrap-sass/assets/javascripts/bootstrap.min.js

console.log("i'm a live!");

// another scripts
//= partials/app.js

Scss файлы подключим с помощью @import:

// include bootstrap sass
@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap";

// include variables
@import "partials/variables";
// include sprite
@import "partials/sprite";
// include mixin
@import "partials/mixin";
// include fonts
@import "partials/fonts";

Получив структуру проекта, добавим в её корень наши файлы bower.json и package.json. Осталось только создать файл настроек для нашего сборщика — gulpfile.js. Именно этот файл будет отвечать за всю магию.

gulpfile.js


Все зависимости, которые мы прописали в файле package.json подключаем внутри gulpfile.js:

'use strict';

var gulp        = require('gulp'),
    watch       = require('gulp-watch'),
    prefixer    = require('gulp-autoprefixer'), 
    uglify      = require('gulp-uglify'),     
    rigger      = require('gulp-rigger'),     
    sass        = require('gulp-sass'),   
    sourcemaps  = require('gulp-sourcemaps'),
    cssmin      = require('gulp-minify-css'),  
    imagemin    = require('gulp-imagemin'), 
    pngquant    = require('imagemin-pngquant'), 
    spritesmith = require('gulp.spritesmith'),
    plumber     = require('gulp-plumber');

Создадим объект path со всеми путями, чтобы при необходимости легко в одном месте их редактировать. Они будут использоваться нашими таск'ами (task — задача, ставим задачи нашему менеджеру, а он быстро все исполняет).

var path = {
    build: {
        js:            'local/templates/main/',
        css:           'local/templates/main/',
        images:        'local/templates/main/images/',
        fonts:         'local/templates/main/fonts/',
        fontsBootstrap:'local/templates/main/fonts/bootstrap/'
    },
    src: {
        js:            'src/js/script.js',
        styles:        'src/styles/template_styles.scss',
        stylesPartials:'src/styles/partials/',
        spriteTemplate:'src/sass.template.mustache',
        images:        'src/images/**/*.*',
        sprite:        'src/sprite/*.*',
        fonts:         'src/fonts/**/*.*',
        fontsBootstrap:'bower_components/bootstrap-sass/assets/fonts/bootstrap/*.*'
    },
    watch: {
        js:    'src/js/**/*.js',
        styles:'src/styles/**/*.scss',
        images:'src/images/**/*.*',
        sprite:'src/sprite/*.*',
        fonts: 'src/fonts/**/*.*'
    }
};

Подключив все зависимости и написав маршруты создадим таск'и, которые отвечают за всю работу нашего сборщика.

Сборщик js файлов
gulp.task('js:build', function () {
    gulp.src(path.src.js)               // Найдем наш main файл
        .pipe(plumber())
        .pipe(rigger())                 // Прогоним через rigger
        .pipe(sourcemaps.init())        // Инициализируем sourcemap
        .pipe(uglify())                 // Сожмем наш js
        .pipe(sourcemaps.write())       // Пропишем карты
        .pipe(plumber.stop())
        .pipe(gulp.dest(path.build.js)) // Выплюнем готовый файл в build
});

Сборщик css файла
gulp.task('styles:build', function () {
    gulp.src(path.src.styles)            // Выберем наш main.scss
        .pipe(plumber())
        .pipe(sourcemaps.init())         // То же самое что и с js
        .pipe(sass())                    // Скомпилируем
        .pipe(prefixer())                // Добавим вендорные префиксы
        .pipe(cssmin())                  // Сожмем
        .pipe(sourcemaps.write())        // Пропишем карты
        .pipe(plumber.stop())
        .pipe(gulp.dest(path.build.css)) // И в build
});

Таск, отвечающий за сжатие всех картинок
gulp.task('image:build', function () {
    gulp.src(path.src.images) //Выберем наши картинки
        .pipe(plumber())
        .pipe(imagemin({   //Сожмем их
            progressive: true,
            svgoPlugins: [{removeViewBox: false}],
            use: [pngquant()],
            interlaced: true
        }))
        .pipe(plumber.stop())
        .pipe(gulp.dest(path.build.images))
});

Таск собирающий спрайт
gulp.task('sprite:build', function() {
    var spriteData =
        gulp.src(path.src.sprite)
            .pipe(spritesmith({
                imgName: 'sprite.png',
                cssName: 'sprite.scss',
                cssFormat: 'scss',
                algorithm: 'binary-tree',
                padding: 20,
                cssTemplate: path.src.spriteTemplate,
                cssVarMap: function(sprite) {
                    sprite.name = 's-' + sprite.name
                }
            }));

    spriteData.img.pipe(gulp.dest(path.build.images));
    spriteData.css.pipe(gulp.dest(path.src.stylesPartials));
});

Он берет все наши иконки из папки sprite и генерирует спрайт, параллельно на основе файла sass.template.mustache он создает scss файл, в который записываются все размеры и позиции иконок.

sass.template.mustache


{{#items}}
${{name}}: {{px.x}}, {{px.y}}, {{px.offset_x}}, {{px.offset_y}}, {{px.width}}, {{px.height}}, {{px.total_width}}, {{px.total_height}}, '{{{escaped_image}}}';
{{/items}}

Перенос шрифтов в билд
//Переместим шрифт bootstrap'а, для работоспособности иконок 
gulp.task('icons:build', function() {
    gulp.src(path.src.fontsBootstrap)
        .pipe(gulp.dest(path.build.fontsBootstrap));
});
// Переместим шрифты из папки src
gulp.task('fonts:build', function() {
    gulp.src(path.src.fonts)
        .pipe(gulp.dest(path.build.fonts))
});

Создадим последние два таск'а, которые запустят все в работу. Первый требуется для запуска, второй для слежения за изменениями файлов, чтобы нам не требовалось все время перезапускать наш менеджер.

gulp.task('build', [
    'js:build',
    'sprite:build',
    'icons:build',
    'fonts:build',
    'styles:build',
    'image:build',
]);

gulp.task('watch', function(){
    watch([path.watch.js], function(event, cb) {
        gulp.start('js:build');
    });
    watch([path.watch.sprite], function(event, cb) {
        gulp.start('sprite:build');
    });
    watch([path.watch.styles], function(event, cb) {
        gulp.start('styles:build');
    });
    watch([path.watch.fonts], function(event, cb) {
        gulp.start('fonts:build');
    });
    watch([path.watch.images], function(event, cb) {
        gulp.start('image:build');
    });
});

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

В итоге мы получаем в корне проекта три файла конфигураций, исполним их прописав команды в консоли:

  • npm install — установятся все зависимости, которые требует gulp (создастся директория node_modules)
  • bower install — поставим плагины для нашего сайта (создастся директория bower_components)
  • gulp — запустим наш сборщик

профит!

Итог


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

Подводные камни


Перед созданием резервной копии сайта не забудьте поставить исключение на директории: node_modules и bower_components, иначе резервная копия не создасться. Эти папки нужны только для разработки, в «боевой» версии сайта они не понадобятся.



p.s.
Статья берёт начало из статьи «Приятная сборка frontend проекта» – рекомендую ознакомиться.
Если кому то интересно, могу добавить в сборщик генерацию svg спрайтов.
Метки:
Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 18
  • –3
    Но подождите, где здесь 1С?
    • 0
      Вы же понимаете, что такое Битрикс?
      • +1
        Раньше Битрикс назывался просто Битрикс. 1c-Битрикс было придумано как маркетинговый ход — использование партнерской структуры 1С для популяризации Битрикса среди компаний.
        Так что 1С и Битрикс это совершенно разные компании, с разными владельцами и всем остальным.
        Да, в интернет-магазине Битрикса есть средства для синхронизации с 1С (это так, к слову)
        • 0
          Из вики:
          В 2007 году компании 1С и Битрикс объявили о создании совместного предприятия ООО «1С-Битрикс» (доли партнеров равны и составляют 50 %). Новая компания получила право распространения продуктов компании Битрикс на территории Российской Федерации.
      • 0
        Не так давно перешёл на подобную систему, только вместо дерева директорий Битрикса использую немного другое своё (ибо не всегда под Битрикс разрабатываю).

        Тем не менее, хочу ещё вот что посоветовать:
        • gulp-if — использую для расширения задач в случае, когда собираю проект уже для выкладки на сервер (изначально я тестирую вёрстку у себя на локалхосте); например, сжимать постоянно изображения — лишнее время.
        • gulp-concat — склеить все js в один, например. Если их много, неудобно постоянно добавлять риггером их друг в друга.
        • gulp-util — замена пламберу + возможность проверять ключи, переданные галпу + создавать файлы + выкидывать исключения и пр.
        • browser-sync — перезагрузка вёрстки / стилей / скриптов на лету. Удобно тестировать с его помощью всё на локалхосте.
        • gulp-cached — когда вы следите за всеми .* (html/css/js — подставить нужное) сразу, то при изменении одного пересобираются/копируются все по новой. Это не всегда удобно и занимает времени больше обычного. С помощью этого плагина можно создавать кэш и изменять только действительно изменившиеся файлы.
        • gulp-file-include — использую вместо риггера. Плюс в том, что в импортируемый файл можно передать переменные, которые в нём же и использовать. Я верстаю по БЭМу, поэтому когда вставляю одни блоки в другие, вешаю таким образом дополнительные классы.
        • gulp-filter — фильрует файлы по маске. Дабы не порождать несколько задач и вотчеров, можно в одной задаче производить какие-то общие действия (например, сжимать все файлы), а затем по фильтру с одними делать одно, с другими — другое.
        • gulp-rename — прост, как два пальца. Переименовывает, добавляет / удаляет директории в пути, добавляет / удаляет расширения и всё такое. Удобно, например, когда нужно достать файлы из всех подпапок и переместить в одну общую папку не сохраняя дерево директорий.
        • rimraf — rm -rf для ноды. Удобно создать задачку clean, которая будет очищать всё, что нагенерировалось.


        gulp,js
        /* requires */
        
        var gulp		= require('gulp'),
        	ifelse		= require('gulp-if'),
        	util		= require('gulp-util'),
        	browserSync	= require('browser-sync'),
        	cache		= require('gulp-cached'),
        	fileinclude	= require('gulp-file-include'),
        	filter		= require('gulp-filter'),
        	rename		= require('gulp-rename'),
        	url		= require('gulp-css-url-adjuster'),
        	imagemin	= require('gulp-imagemin'),
        	pngquant	= require('imagemin-pngquant'),
        	rimraf		= require('rimraf'),
        
        	compass		= require('gulp-compass'),
        	autoprefixr	= require('autoprefixer-core'),
        	postcss		= require('gulp-postcss'),
        	minifyCSS	= require('gulp-minify-css');
        
        /* paths */
        
        var mask = {
        		html: 'dev/**/*.html',
        		scss: 'dev/**/*.scss',
        		css: 'dev/css/**/*.css',
        		js: 'dev/js/**/*',
        		images: 'dev/blocks/**/*.{jpg,png,gif,svg}',
        		files: 'dev/files/**/*',
        		fonts: 'dev/fonts/**/*.{eot,svg,ttf,woff,woff2}'
        	},
        	input = {
        		css: 'dev/css',
        		scss: 'dev/blocks/styles.scss'
        	},
        	output = {
        		main: 'public',
        		js: 'public/js',
        		css: 'public/css',
        		images: 'public/images',
        		files: 'public/files',
        		fonts: 'public/fonts'
        	},
        	isProduction = (util.env.type == 'production'),
        	isDeploy = (util.env.type == 'deploy');
        
        gulp.task('default', ['build', 'server', 'watch']);
        
        gulp.task('build', ['html', 'scss', 'css', 'js', 'images', 'files', 'fonts']);
        
        gulp.task('html', function() {
        	gulp.src(mask.html)
        		.pipe(fileinclude())
        		.on('error', util.log)
        		.pipe(cache('htmling'))
        		.pipe(filter(['*', '!dev/blocks', '!dev/includes']))
        		.pipe(gulp.dest(output.main))
        		.pipe(browserSync.stream());
        });
        
        gulp.task('scss', function() {
        	/* here if you have more that one styles.scss we need to reload mask.scss not only one file */
        	gulp.src(input.scss)
        		.pipe(compass({
        			config_file: 'config.rb',
        			css: 'dev/_blocks',
        			sass: 'dev/blocks'
        		}))
        		.on('error', util.log)
        		.pipe(gulp.dest(input.css))
        });
        
        gulp.task('css', function() {
        	gulp.src(mask.css)
        		.pipe(cache('cssing'))
        		.pipe(postcss([ autoprefixr({ browsers: [ "> 1%" ] }) ]))
        		.pipe(url({ replace: [/^i-/, '../images/i-'] }))
        		.pipe(url({ replace: [/^f-/, '../fonts/f-'] }))
        		.pipe(ifelse(isProduction, minifyCSS()))
        		.pipe(gulp.dest(output.css))
        		.pipe(browserSync.stream());
        });
        
        gulp.task('images', function() {
        	gulp.src(mask.images)
        		.pipe(cache('imaging'))
        		.pipe(rename({dirname: ''}))
        		.pipe(ifelse(isProduction || isDeploy, imagemin({
        			progressive: true,
        			svgoPlugins: [{removeViewBox: false}],
        			use: [pngquant()],
        			interlaced: true
                })))
        		.pipe(gulp.dest(output.images));
        });
        
        gulp.task('files', function() {
        	gulp.src(mask.files) 
        		.pipe(gulp.dest(output.files));
        });
        
        gulp.task('js', function() {
        	gulp.src(mask.js) 
        		.pipe(gulp.dest(output.js));
        });
        
        gulp.task('fonts', function() {
        	gulp.src(mask.fonts) 
        		.pipe(rename({dirname: ''}))
        		.pipe(gulp.dest(output.fonts));
        });
        
        gulp.task('server', function() {
        	browserSync.init({
        		server: output.main
        	});
        });
        
        gulp.task('watch', function() {
        	gulp.watch(mask.html, ['html']);
        	gulp.watch(mask.scss, ['scss']);
        	gulp.watch(mask.css, ['css']);
        	gulp.watch(mask.js, ['js']);
        	gulp.watch(mask.images, ['images']);
        	gulp.watch(mask.files, ['files']);
        	gulp.watch(mask.fonts, ['fonts']);
        });
        
        gulp.task('clean', function(cb) {
        	rimraf(output.main, cb);
        });
        

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

          Интересно только как тут browser-sync будет сочетаться, ведь если проект запустить на openServer, то плагин становится не рабочим.
        • 0
          Не эксперт в галпе, освоил его пару месяцев назад

          Зачем использовать плагин gulp-watch вместо встроенного gulp.watch?
          По поводу gulp.start, он deprecated
          Зачем в watch таск передавать в качестве аргумента сразу массив, когда можно просто передавать переменную, а в конфиге ее уже делать или строкой или массивом строк?
          • 0
            Каюсь, я тут не стал ничего придумывать и использовал опыт из этой статьи.
            • 0
              Встроенный gulp.watch не использует chokidar (использующий, в свою очередь, библиотеку fsevents), необходимый для отслеживания большого дерева файлов без нагрузки на процессор. Т.е. встроенный gulp.watch тупо проверяет в цикле не изменились ли файлы, что нормально работает на десятке файлов, но если у вас дерево из тысячи файлов, то простая «слежка» сожрёт процентов 50 процессорного времени, просто так. Особенно если работаете на ноуте, то шум вентиляторов вам совсем ни к чему.

              Этого недостатка лишён gulp-watch и его аналог gulp-chokidar. Загрузка проца при слежке — 0%.
              • 0
                И правда, сначала не замечал. Использовал оба модуля и задавался вопросом, почему так тяжело загружаются некоторые проекты, вот и ответ. Спасибо.
                • 0
                  Апдейт: gulp 4 использует chokidar. Алилуя. Плюс много других ништяков: последовательное vs параллельное выполнение, вменяемая обработка ошибок и т.п.

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

              <link href="/bitrix/js/main/core/css/core.css?333257718964" type="text/css"  rel="stylesheet" />
              <link href="/bitrix/js/socialservices/css/ss.css?393394894347" type="text/css"  rel="stylesheet" />
              .....
              <script type="text/javascript" src="/bitrix/js/main/core/core.js?1051185178911"></script>
              <script type="text/javascript" src="/bitrix/js/socialservices/ss.js?138171351419"></script>
              

              даже когда пользователь не авторизован?
              • 0
                Используйте вместо ShowHead отдельные вызовы ShowCSS и т.п., делов-то.
                Но с js файлами там не всё так просто. Если у вас ajax-компоненты, без core.js они работать не будут. При этом весит эта фигня ололо больше 300К.
                • 0
                  Только что проверил, хоть и раньше знал что ShowCSS все равно выводит тоже самое

                  <link href="/bitrix/js/main/core/css/core.css?13933257718964" type="text/css"  rel="stylesheet" />
                  <link href="/bitrix/js/socialservices/css/ss.css?13893394894347" type="text/css"  rel="stylesheet" />
                  

                  • 0
                    Ну так не вызывайте его, если они вам не нужны). Подключайте нужные вам стили и скрипты руками (вы же знаете, какие вам нужны), а меты подключайте через ShowHeadStrings например.
              • 0
                Было бы интересно почитать про генерацию иконочных шрифтов из svg картинок с помощью gulp'а.
                • 0
                  От этого варианта я отказался. Svg в шрифты — это все таки своеобразный хак. Есть некоторые проблемы с отображением в разных браузерах, отсутствует многоцветность. Конечно у иконок можно менять цвет, но это по моему единственный плюс. (Сразу оговорюсь про ие, где с svg-спрайтами не все отлично, для него можно генерировать отдельный png спрайт).

                  А если очень хочется иконочный шрифт, то использую Font-Awesome

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