27 февраля 2013 в 15:21

Миграция на 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');
};


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

Александр @bur
карма
104,0
рейтинг 0,0
Самое читаемое Разработка

Комментарии (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), иначе при очередном крупном обновлении Гранта всё сломается.

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