Постановка задачи: что ни сервер, все время приходится настраивать пару удобных утилит любого уважающего себя front-end разработчика — Bower+Grunt. Bower — незаменимый менеджер пакетов. Grunt — замечательное средство для автоматизации рутинных операций со статикой проекта. Ах да, для установки всего этого безобразия нам потребуется Node.js.
0. Обновление системы и пакетов
Чисто чтобы все собралось и взлетело…
dmitry@dev:~$ sudo apt-get update && apt-get upgrade dmitry@dev:~$ sudo apt-get install git-core curl build-essential openssl libssl-dev
1. Установка Node.js и NPM (Node Package Manager)
Замечательная инструкция имеется тут — https://nodejs.org/en/download/package-manager/. Для Debian можно поставить сразу из официального репозитория, а можно собрать пакет ручками.
dmitry@dev:~$ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash - dmitry@dev:~$ sudo apt-get install -y nodejs dmitry@dev:~$ node -v v4.4.5 dmitry@dev:~$ npm -v 2.15.5
Собственно говоря, мы установили npm и node.js. Шутки мощные и полезные, но подробнее о них в следующий раз и в другом контексте.
3. Установка Bower
Bower как менеджер пакетов поставим в системе глобально, используя ключ -g:
dmitry@dev:~$ sudo npm install -g bower
Теперь перейдем в папку проекта и создадим пару файлов .bowerrc и bower.json. Если Вы уже создаввали проект и он хранится где-нибудь в репозитории GitHub, то достаточно просто клонировать репозиторий к себе в папку с проектом. В ином случаем, создадим файлы «с нуля»:
dmitry@dev:~/projects/djangoproject/static$ cat .bowerrc { "directory" : "libraries", "json" : "bower.json", "endpoint" : "https://bower.herokuapp.com" }
Чтобы создать файл конфигурации bower.json можно воспользоваться мастером Bower — bower init или создать/перенести файл самостоятельно:
dmitry@dev:~/projects/djangoproject/static$ cat bower.json { "name": "demon.of.by", "version": "2.2.1", "description": "demon.of.by project", "authors": [ "Dmitry Vl. Rendov" ], "license": "MIT", "private": true, "ignore": [ "**/.*", "node_modules", "bower_components", "libraries", "test", "tests" ], "dependencies": { "jquery": "latest", "bootstrap": "latest", "font-awesome": "latest", "modernizr": "latest", "html5shiv": "latest", "respond": "latest", "SyntaxHighlighter": "latest" } }
Запустим установку выбранных пакетов:
dmitry@dev:~/projects/djangoproject/static$ bower install
В итоге, все пакеты, заявленные в секции dependencies будут найдены, скачены и аккуратно складированы в папке libraries, которая была указана выше в файле .bowerrc.
Примечание: По прошествии времени пакеты можно и обновить:
dmitry@dev:~/projects/djangoproject/static$ bower update
Логично, что bower.json можно допиливать по необходимости. В данном случае я указал минимальный джентельменский набор библиотек, которые используются от проекта к проекту.
4. Установка grunt
Хрю-хрю! Или нет, не так — Grunt-Grunt! И все вдруг стало красиво. Предупреждаю сразу — инструмент вызывает привыкание и является классическим «бесконечным адом перфекциониста«. Чтобы начать пользоваться Grunt-ом, необходимо установить пакет для работы из командной строки grunt-cli всё также глобально, используя ключ -g.
dmitry@dev:~$ sudo npm install -g grunt-cli
Теперь о настройке. Она выполняется в два этапа. Вначале создадим в корневой папке проекта файл package.json, в котором перечислим те пакеты/моудли которые будут выполнять за нас всю грязную, черновую, рутинную работу:
dmitry@dev:~/projects/djangoproject/static$ cat package.json { "name": "demon-of-by", "version": "0.0.1", "description": "demon.of.by project", "main": "package.json", "scripts": { "test": "grunt test" }, "repository": { "type": "git", "url": "git://github.com/DmitryRendov/demon-theme.git" }, "author": "Dmitry Vl. Rendov", "license": { "type": "MIT", "url": "https://github.com/DmitryRendov/demon-theme/blob/master/LICENSE" }, "bugs": { "url": "https://github.com/DmitryRendov/demon-theme/issues" }, "homepage": "http://demon.of.by/", "devDependencies": {} }
Мы поступим довольно скромно и решим только самые насущные проблемы на нашем проекте. Установим модули:
- grunt-contrib-copy — копирования некоторых библиотек/ресурсов из libraries в нужную нам папку;
- grunt-contrib-clean — очистки/удаления файлов;
- grunt-contrib-less — компиляции less в css;
- grunt-contrib-concat — склеивания файлов в один (css и js);
- grunt-contrib-uglify — сжатия css и js;
- grunt-contrib-jshint — проверки соответствия js стандартам ECMA;
- grunt-modernizr — установки custom-пакета modernizr.js;
- grunt-contrib-watch — наблюдения в реальном времени за файлами и их компиляции/валидации/склеивания/сжатия по каждому чиху.
Для этого установим необходимые нам пакеты
dmitry@dev:~$ npm install grunt --save-dev dmitry@dev:~$ npm install grunt-contrib-copy --save-dev dmitry@dev:~$ npm install grunt-contrib-clean --save-dev dmitry@dev:~$ npm install grunt-contrib-less --save-dev dmitry@dev:~$ npm install grunt-contrib-concat --save-dev dmitry@dev:~$ npm install grunt-contrib-uglify --save-dev dmitry@dev:~$ npm install grunt-contrib-jshint --save-dev dmitry@dev:~$ npm install grunt-contrib-watch --save-dev dmitry@dev:~$ npm install grunt-modernizr --save-dev
Примечание: Вам не обязательно устанавливать вручную все пакеты (кроме первого пакета «grunt») командами, которые указаны выше, в случае если они уже указаны в файле package.json в секции devDependencies. В этом случае Вам будет достаточно сделать
dmitry@dev:~$ npm updateи все пакеты загрузятся самостоятельно.
После того, как Grunt и все модули установлены создадим файл с именем Gruntfile.js в корне нашего проекта, рядом с ранее созданным файлом package.json. Это основной файл для конфигурации Grunt, который содержит все наши заявленный ранее задачи и описание их работы. Дабы не разливаться в пояснениях весь файл приведен ниже с нужными комментариями по тексту скрипта.
dmitry@dev:~/vhosts/nanny.of.by/public_html$ vim Gruntfile.js /* * Gruntfile.js * * Copyright (c) 2014 Dmitry Vl. Rendov * Licensed under the MIT license. * https://github.com/DmitryRendov/inv-theme/blob/master/LICENSE */ 'use strict'; module.exports = function(grunt) { var globalConfig = { images : 'images', /* папка для картинок сайта */ styles : 'css', /* папка для готовый файлов css стилей */ fonts : 'fonts', /* папка для шрифтов */ scripts : 'js', /* папка для готовых скриптов js */ src : 'src', /* папка с исходными кодами js, less , etc. */ bower_path : 'libraries' /* папка где хранятся библиотеки jquery, bootstrap, SyntaxHighlighter, etc. */ }; grunt.initConfig({ globalConfig : globalConfig, pkg : grunt.file.readJSON('package.json'), /** * Задача "copy" * * выбрать из библиотек lobalConfig.bower_path = 'libraries' * нужные для проекта файлы и скоипровать их в соответствующие папки */ copy : { main : { files : [{ expand : true, flatten : true, src : '<%= globalConfig.bower_path %>/jquery/dist/jquery.min.js', dest : '<%= globalConfig.scripts %>/', filter : 'isFile' }, { expand : true, flatten : true, src : '<%= globalConfig.bower_path %>/html5shiv/dist/html5shiv.min.js', dest : '<%= globalConfig.scripts %>/', filter : 'isFile' }, { expand : true, flatten : true, src : '<%= globalConfig.bower_path %>/bootstrap/dist/js/bootstrap.min.js', dest : '<%= globalConfig.scripts %>/', filter : 'isFile' }, { expand : true, flatten : true, src : '<%= globalConfig.bower_path %>/bootstrap/dist/css/bootstrap.min.css', dest : '<%= globalConfig.styles %>/', filter : 'isFile' }, { expand : true, flatten : true, src : '<%= globalConfig.bower_path %>/font-awesome/css/font-awesome.min.css', dest : '<%= globalConfig.styles %>/', filter : 'isFile' }, { expand : true, flatten : true, src : '<%= globalConfig.bower_path %>/font-awesome/fonts/*', dest : '<%= globalConfig.fonts %>/', filter : 'isFile' }, { expand : true, flatten : true, src : '<%= globalConfig.bower_path %>/respond/dest/respond.min.js', dest : '<%= globalConfig.scripts %>/', filter : 'isFile' }] } }, /** * Задача "modernizr" * * используя базовую версию библиотеки modernizr.js * выполнить кастомизацию и скопировать в папку scripts проекта */ modernizr : { dist : { // [REQUIRED] Path to the build you're using for development. "devFile" : '<%= globalConfig.bower_path %>/modernizr/modernizr.js', // [REQUIRED] Path to save out the built file. "outputFile" : '<%= globalConfig.scripts %>/modernizr-custom.min.js', // Based on default settings on http://modernizr.com/download/ "extra" : { "shiv" : true, "printshiv" : false, "load" : true, "mq" : false, "cssclasses" : true }, // Based on default settings on http://modernizr.com/download/ "extensibility" : { "addtest" : false, "prefixed" : false, "teststyles" : false, "testprops" : false, "testallprops" : false, "hasevents" : false, "prefixes" : false, "domprefixes" : false }, // By default, source is uglified before saving "uglify" : true, // Define any tests you want to implicitly include. "tests" : [], // By default, this task will crawl your project for references to Modernizr tests. // Set to false to disable. "parseFiles" : true, // When parseFiles = true, this task will crawl all *.js, *.css, *.scss files, except files that are in node_modules/. // You can override this by defining a "files" array below. // "files" : { // "src": [] // }, // When parseFiles = true, matchCommunityTests = true will attempt to // match user-contributed tests. "matchCommunityTests" : false, // Have custom Modernizr tests? Add paths to their location here. "customTests" : [] } }, /** * Задача "clean" * * очистить(удалить) production-файлы перед их повторной "сборкой" */ clean : { js : ['<%= globalConfig.scripts %>/app.js', '<%= globalConfig.scripts %>/app.min.js'], css : ['<%= globalConfig.styles %>/styles.css', '<%= globalConfig.styles %>/styles.min.css'] }, /** * Задача "less" * * преобразовать файлы less в css с последующим сжатием, оптимизацией и * копированием результатов в папку styles проекта */ less : { development : { options : { paths : ["styles"], }, files : { "<%= globalConfig.styles %>/styles.css" : "<%= globalConfig.src %>/less/styles.less" } }, production : { options : { paths : ["styles"], compress : true, yuicompress : true, optimization : 2, cleancss : true }, files : { "<%= globalConfig.styles %>/styles.min.css" : "<%= globalConfig.src %>/less/styles.less" } } }, /** * Задача "watch" * * отслеживать изменения в файлах less и js с последующим сжатием, оптимизацией и * копированием результатов в соответствующие папки проекта */ watch : { styles : { files : ['<%= globalConfig.src %>/less/*.less'], tasks : ['less'], options : { nospawn : true } }, scripts : { files : ['<%= globalConfig.src %>/js/*.js', '!app.js'], tasks : ['js'], options : { nospawn : true } } }, /** * Задача "concat" * * "склеить" все файлы js в один файл, с добавлением "шапки-баннера" */ concat : { dist : { src : ['<%= globalConfig.src %>/js/**/*.js'], dest : '<%= globalConfig.scripts %>/app.js', options : { banner : ";(function( window, undefined ){ \n 'use strict'; \n", footer : "\n}( window ));" } } }, /** * Задача "jshint" * * проверить все файлы js на предмет соответствия стандартам * используя заданные в файле .jshintrc условия верфикации javascript кода */ jshint : { all : ['Gruntfile.js', '<%= globalConfig.src %>/js/**/*.js'], options : { jshintrc : '.jshintrc' } }, /** * Задача "uglify" * * сжать финальный js файл и добавить к нему шапку-баннер */ uglify : { options : { // the banner is inserted at the top of the output banner : '/*! \n * <%= pkg.name %> <%= pkg.version %> (<%= pkg.homepage %>) \n * Copyright <%= grunt.template.today("yyyy") %> Dmitry Vl. Rendov \n * Licensed under MIT (https://github.com/DmitryRendov/inv-theme/blob/master/LICENSE) \n */ \n' }, dist : { files : { '<%= globalConfig.scripts %>/app.min.js' : ['<%= concat.dist.dest %>'] } } } }); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-modernizr'); grunt.loadNpmTasks('grunt-contrib-less'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-clean'); // Default task(s). grunt.registerTask('default', ['copy', 'modernizr', 'clean:css', 'less', 'js']); grunt.registerTask('js', ['clean:js', 'concat', 'jshint', 'uglify']); };
В двух последних строчках вы указали две групповых задачи, причем вторая зачада вложена в первую. В итоге, если выполнить команду grunt без параметров, будут выполнены все задачи, перечисленные в default:
dmitry@dev:~$ grunt Running "copy:main" (copy) task Copied 11 files Running "modernizr:dist" (modernizr) task Enabled Extras >> shiv >> load >> cssclasses Looking for Modernizr references Downloading source files cache modernizr-latest.js cache modernizr.load.1.5.4.js >> Generating a custom Modernizr build >> Uglifying >> Wrote file to js/modernizr-custom.min.js Running "clean:css" (clean) task >> 2 paths cleaned. Running "less:development" (less) task File css/styles.css created: 0 B > 3.48 kB Running "less:production" (less) task File css/styles.min.css created: 3.48 kB > 2.55 kB Running "clean:js" (clean) task >> 2 paths cleaned. Running "concat:dist" (concat) task File js/app.js created. Running "jshint:all" (jshint) task >> 3 files lint free. Running "uglify:dist" (uglify) task >> 1 file created. Done, without errors.
Соответственно, вы можете выполнить отдельно только задачу js:
dmitry@dev:~$ grunt js Running "clean:js" (clean) task >> 2 paths cleaned. Running "concat:dist" (concat) task File js/app.js created. Running "jshint:all" (jshint) task >> 3 files lint free. Running "uglify:dist" (uglify) task >> 1 file created.
И наконец, самое полезное — запустить задачу watch и забыть навсегда про необходимость копировать, компилировать, сжимать файлы less, css, js:
dmitry@dev:~$ grunt watch Running "watch" task Waiting… >> File "src/less/base.less" changed. Running "less:development" (less) task File css/styles.css created: 0 B > 3.48 kB Running "less:production" (less) task File css/styles.min.css created: 3.48 kB > 2.55 kB Running "watch" task Completed in 0.805s at Tue Jan 06 2015 11:46:47 GMT+0300 (FET) - Waiting… >> File "src/js/main.js" changed. Running "clean:js" (clean) task >> 2 paths cleaned. Running "concat:dist" (concat) task File js/app.js created. Running "jshint:all" (jshint) task >> 3 files lint free. Running "uglify:dist" (uglify) task >> 1 file created. Running "watch" task Completed in 0.814s at Tue Jan 06 2015 11:47:26 GMT+0300 (FET) - Waiting…
Засим всё. Пользуйтесь.
Комментарии 2
- 1
Павел — Aug 14, 2015 at 08:13 PM
И еще вопрос , если в bower например какой нибудь слайдер с картинками , примерами картинок и css , как тогда быть? Или если в нем несколько папок/подпапок?
Павел — Aug 14, 2015 at 08:03 PM
Здравствуйте, очень понравилась ваша статья но возникло пару вопросов:
Как в bower указать последовательность склеивания файлов , он ведь может добавить jQuery в конец очереди и половина плагинов работать не будут?
Папка проекта у вас тут? https://github.com/DmitryRendov/demon-theme