Миграция на Grunt v0.4

    Предисловие


    18 февраля вышел релиз Grunt v0.4.0, с чем всех и поздравляю. Если вы еще не знакомы с Грантом — прошу пройти на официальный сайт или почитать ознакомительную статью на Хабре. Вкратце, Грант позволяет автоматизировать склеивание и минификацию js-файлов, запуск тестов, проверку кода с помощью JSHint и многое другое.

    Данная статья — история миграции одного приложения с Гранта v0.3.9 на вышедшую v0.4.0. Версии несовместимы и переезд оказался не таким простым делом, как я изначально предполагал. Полная инструкция по миграции на английском находится здесь, она подробнее чем мое описание.

    Зачем я использую Grunt


    Как любому ленивому frontend-разработчику, мне нужен был инструмент, который автоматизирует рутинные задачи, позволяя сосредоточиться непосредственно на разработке. Так я нашел Грант, который делал за меня следующее:

    • компиляция stylus в css;
    • склеивание js-файлов;
    • проверка JavaScript линтером;
    • минификация склеенных js-файлов;
    • запуск unit-тестов (qUnit);
    • отслеживание изменений исходных файлов и автоматический перезапуск вышеперечисленных задач.


    Все эти важные, но скучные задачи выполнялись одной командой:

    → grunt
    Running "stylus:compile" (stylus) task
    File 'css/styles.css' created.
     
    Running "concat:js" (concat) task
    File "project.js" created.
     
    Running "lint:files" (lint) task
    Lint free.
     
    Running "min:js" (min) task
    File "project.min.js" created.
    Uncompressed size: 130468 bytes.
    Compressed size: 20937 bytes gzipped (74246 bytes minified).
     
    Running "qunit:all" (qunit) task
    Testing index.html...............OK
    >> 95 assertions passed (594ms)
     
    Running "watch" task
    Waiting...

    Все задачи описываются в специальном грант-файле: grunt.js. Для вышеприведенного лога он схематично выглядит так:

    module.exports = function(grunt) {
    	grunt.initConfig({
    		stylus: {
    			// Компиляция Stylus в CSS
    			compile: {
    				options: {
    					'compress': true,
    					'paths': ['css/styl/']
    				},
    				files: {
    					'css/styles.css': 'css/styles.styl'
    				}
    			}
    		},
    		concat: {
    			// Склеивание js-файлов
    			js: {
    				src: [
    					/* Здесь большой список файлов */
    				],
    				dest: 'project.js'
    			}
    		},
    		min: {
    			// Минификация
    			js: {
    				src: ['<config:concat.js.dest>'],
    				dest: 'project.min.js'
    			}
    		},
    		jshint: {
    			options: {
    				smarttabs: true
    			}
    		},
    		lint: {
    			// Проверка кода
    			files: ['<config:concat.js.dest>']
    		},
    		watch: {
    			// Перекомпиляция стилей при изменении styl-файлов
    			stylus: {
    				files: ['css/styl/*.styl'],
    				tasks: 'stylus'
    			},
    			// Пересобирание скриптов и запуск lint при изменении исходных js-файлов
    			js: {
    				files: ['src/*.js'],
    				tasks: 'concat lint'
    			}
    		},
    		qunit: {
    			// Запуск написанных qUnit-тестов
    			all: ['../test/index.html']
    		}
    	});
    
    	// Загрузка модуля для компиляции Стилуса
    	grunt.loadNpmTasks('grunt-stylus');
    
    	// Объявление тасков
    	grunt.registerTask('default', 'stylus concat:js lint min:js qunit watch');
    	grunt.registerTask('test', 'qunit');
    };
    

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

    Миграция


    Переустановка модуля

    Ранее установленный глобально модуль grunt (если таковой имеется) удаляем: npm uninstall -g grunt

    И устанавливаем модуль интерфейса командной строки Гранта: npm install -g grunt-cli

    Сам grunt теперь ставится локально в папку проекта: npm install grunt

    Проверяем версии модулей:

    → grunt --version
    grunt-cli v0.1.6
    grunt v0.4.0
    

    Глобально разрешено поставить модуль grunt-init, но в моем приложении он не используется.

    Перед установкой убедитесь, что версия node.js >= 0.8.0.

    Переименование грант-файла

    
    mv grunt.js Gruntfile.js
    

    Грант-файл со старым именем больше не поддерживается, без переименования увидим ошибку:

    Fatal error: Unable to find Gruntfile.

    В новое версии грант-файл можно писать на CoffeeScript: Gruntfile.coffee.

    Установка плагинов

    У обновленного Гранта больше нет встроенных задач, таких как concat, min, watch и др. Их необходимо добавлять в виде отдельных плагинов:

    • concat → grunt-contrib-concat
    • lint → grunt-contrib-jshint
    • min → grunt-contrib-uglify
    • qunit → grunt-contrib-qunit
    • watch → grunt-contrib-watch

    Несложно заметить, что плагины Гранта имеют префикс grunt-contrib-.
    Устанавливаем:

    → npm install grunt-contrib-concat
    → npm install grunt-contrib-jshint
    → npm install grunt-contrib-uglify
    → npm install grunt-contrib-qunit
    → npm install grunt-contrib-watch
    → npm install grunt-contrib-stylus
    

    При установке рекомендуется использовать параметр --save-dev, чтобы автоматически обновлялись зависимости devDependencies в package.json.

    Подключаем плагины в грант-файле:

    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-qunit');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-stylus');
    

    Изменения в грант-файле

    Методу registerTask теперь нельзя передать список задач одной строкой с пробелами-разделителями. Строкой разрешено передать только одну задачу: grunt.registerTask('test', 'qunit');

    Для списка задач обязательно используем массив:

    grunt.registerTask('default', ['stylus', 'concat:js', 'jshint', 'min:js', 'qunit', 'watch']);
    

    Вместо деректив вида <config:concat.js.dest> теперь используем шаблоны: <%= concat.js.dest %>.

    Задача lint с опциями jshint теперь объединена в задачу jshint, где сразу можно указать опции:

    jshint: {
    	options: {
    		smarttabs: true
    	},
    	js: ['project.js']
    }
    

    Задачу min переименовываем в uglify. Вместо объектов src/dest используем объект files:

    uglify: {
    	js: {
    		files: {
    			'project.min.js': ['<%= concat.js.dest %>']
    		}
    	}
    }
    

    В задаче watch перечисляем выполняемые таски в виде массива:

    watch: {
    	js: {
    		files: ['src/*.js'],
    		tasks: ['concat', 'lint']
    	}
    }
    

    На этом мой переезд был завершен, и Грант отработал без ошибок. Все изменения грант-файла собраны в этом шаблоне:

    module.exports = function(grunt) {
    	grunt.initConfig({
    		// Компиляция Stylus в CSS
    		stylus: {
    			compile: {
    				options: {
    					'compress': true,
    					'paths': ['css/styl/']
    				},
    				files: {
    					'css/styles.css': 'css/styles.styl'
    				}
    			}
    		},
    		concat: {
    			// Склеивание js-файлов
    			js: {
    				src: [
    					/* Здесь большой список файлов */
    				],
    				dest: 'project.js'
    			}
    		},
    		uglify: {
    			// Минификация
    			js: {
    				files: {
    					'project.min.js': ['<%= concat.js.dest %>']
    				}
    			}
    		},
    		jshint: {
    			// Проверка кода
    			options: {
    				smarttabs: true
    			},
    			js: ['<%= concat.js.dest %>']
    		},
    		watch: {
    			// Перекомпиляция стилей при изменении styl-файлов
    			stylus: {
    				files: ['css/styl/*.styl'],
    				tasks: 'stylus'
    			},
    			// Пересобирание скриптов и запуск lint при изменении исходных js-файлов
    			js: {
    				files: ['src/*.js'],
    				tasks: ['concat', 'lint']
    			}
    		},
    		qunit: {
    			// Запуск написанных qUnit-тестов
    			all: ['../test/index.html']
    		}
    	});
    
    	grunt.loadNpmTasks('grunt-contrib-concat');
    	grunt.loadNpmTasks('grunt-contrib-jshint');
    	grunt.loadNpmTasks('grunt-contrib-uglify');
    	grunt.loadNpmTasks('grunt-contrib-qunit');
    	grunt.loadNpmTasks('grunt-contrib-watch');
    	grunt.loadNpmTasks('grunt-contrib-stylus');
    
    	// Объявление тасков
    	grunt.registerTask('default', ['stylus', 'concat:js', 'jshint', 'uglify:js', 'qunit', 'watch']);
    	grunt.registerTask('test', 'qunit');
    };
    


    Материалы по теме

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 13
    • +2
      Моё знакомство с Grunt было через проект Yeoman. Весьма удобная вещь.

      Получаешь фреймворк для разработки, в котором установлены основные опции для Grunt и есть интерфейс для создания как скелета проекта так и добавление отдельных файлов.
      Нужен контролллер для приложения AngularJS?

      $ yo angular:controller --coffee

      и получаем скелет файла контроллера, плюс обновлённый файл index.html с подключённым новосозданным файлом.
      • 0
        Генераторы Йомена скоро заменят grunt-init, так что и тут будет возможность добавлять котролеры и прочие ништяки.
      • 0
        А не подскажите как теперь нужно вызывать например 'concat' в своем плагине. Раньше делал так:

        var combined = grunt.helper('concat', src, { separator: '' });

        Как теперь правильно использовать новый grunt-contrib-concat?
        • 0
          Свои плагины я не писал, поэтому могу этим ответом попасть пальцем в небо :-)
          Здесь пишут, что система хэлперов была удалена в пользу нодовского require. Там же отсылают к этому примеру.
          • 0
            В своём плагине я сам делал. В вышеназванном legacyhelpers concat — всего несколько строчек.
            • 0
              Спасибо, а можно ли вообще использовать другие сторонние плагины скажем отсюда в своих собственных?
              • 0
                Некоторые можно. Где какая-то полезная функциональность вынесена в отдельный модуль. Вот так, например. Используется примерно так. Сам пока не пробовал.
                • 0
                  А в 0.5 вообще вот так хотят сделать, можно будет даже не используя Грант подключать задачи для него.
            • +1
              > Сам grunt теперь ставится локально в папку проекта: npm install grunt

              У меня в работе с десяток проектов, у каждого есть ветки… и такой подход мне кажется безумием. Я читал на эту тему эссе разработчика, там есть рациональное зерно, но мне так жить не удобно. Решение проблемы очень простое. Ставим всё глобально (и grunt 0.4, и плагины) и в node_modules/grunt/lib/grunt/task.js подправляем task.loadNpmTasks, заменив
              var root = path.resolve('node_modules');
              
              на
              var root = path.resolve('путь, куда встало всё при глобальной установке', 'node_modules');
              

              Теперь будет работать всё везде, как было в 0.3.

              Подобный патч автору присылали много раз, но он принципиально не хочет так делать.
              • 0
                Можно ещё попробовать использовать npm link.
              • 0
                Хорошая статья. Только надо было упомянуть о package.json. Взял для себя за правило ложить его в папку репозитория для облегчения развертывания фронтенда у других разработчиков. Фактически, им нужно выполнить

                npm install -g grunt-cli
                npm install

                в папке проекта — npm сам выгребет grunt и необходимые для него зависимости, которые указаны в package.json. В вашем случае содержание package.json будет таким:

                {
                    "version": "0.1.0",
                    "devDependencies": {
                        "grunt": "~0.4.0",
                        "grunt-contrib-concat": "",
                        "grunt-contrib-jshint": "",
                        "grunt-contrib-uglify": "",
                        "grunt-contrib-qunit": "",
                        "grunt-contrib-watch": "",
                        "grunt-contrib-stylus": ""
                    }
                }
                
                • 0
                  Статья больше о миграции, нежели о гранте и установке модулей. Упоминание в статье есть здесь:
                  При установке рекомендуется использовать параметр --save-dev, чтобы автоматически обновлялись зависимости devDependencies в package.json.

                  По сути — да, все правильно делаете :)
                  • 0
                    Только не забывайте версии плагинов тоже указывать (npm i grunt-contrib-concat -D), иначе при очередном крупном обновлении Гранта всё сломается.

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