Блог Вячеслава Волкова

Тот, кто не может располагать 2/3 дня лично для себя, должен быть назван рабом

— Фридрих Ницше

Сборка backbone+requirejs проекта с помощью grunt

grunt-logoМоей основной задачей в последнее время является создание одностраничных javascript приложений. В основном я использую фреймворк Backbone и requirejs. Обычно публикация приложения была простым действием «svn update» но из-за этого возникала куча проблем, начиная с несжатых файлов стилей, огромного количества js файлов и заканчивая кэшированием файлов браузером.

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

Не буду вдаваться в описание этого сборщика, в Интернете есть достаточное количество уже информации. Расскажу о том, как он помогает решать непосредственно мои задачи.

Сразу расскажу, что я использую при разработке приложений. В качестве IDE — WebStorm с установленным плагином компиляции less файлов. Сейчас уже не представляю как я раньше писал css без less. Это очень удобная штука.

В качестве основного фреймворка JS используется Backbone и загрузчик require.js. Само собой jQuery, underscore и все возможно сторонние библиотеки.

dirsВот так выглядит структура директорий в рабочем проекте.

В папке js находится рабочий js проект. В css — файлы стилей и less файлы. В data — все остальные необходимые данные для проекта. img — хранит изображения.

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

config.dist.txt хранит настройки по умолчанию для проекта.

package.json хранит информацию о проекте и об используемых модулях

Gruntfile.js хранит конфигурацию для grunt. О нем я и буду рассказывать.

Все остальные директории создаются автоматически node.js или grunt при билде проекта.

Первое, что вам необходимо сделать для использования Grunt — это его установить. Информацию по его установке можно найти на этой странице http://gruntjs.com/getting-started. Я устанавливаю его с помощью npm. Поэтому, если у вас нет node.js, то его необходимо сначала скачать и установить. Если у вас уже установлен он, то желательно обновить на новую версию, если она доступна.

Теперь мы переходи в директорию с проектом в консоли. (Не забудьте создать файл package.json. WebStrom умеет их создавать автоматически. Там обязательны только 2 параметра название проекта и версия).

Выполняем следующие команды:

npm install -g grunt-cli

После этой команды grunt будет доступен через консоль.

npm install grunt --save-dev

После этой файл package.json будет обновлен и в файл запишется зависимость использования grunt.
Вот так будет выглядеть файл теперь:

{
  "name": "test-project",
  "version": "0.0.1",
  "devDependencies": {
    "grunt": "^0.4.5"
  }
}

Теперь необходимо создать файл Gruntfile.js и вносить в него компоненты и методы для сборки вашего проекта. Я приведу сразу пример своего файла.

/**
 * Created by VVolkov on 19.05.14.
 */
// Обязательная обёртка
module.exports = function (grunt) {

    grunt.initConfig({
        clean: ["build"],
        requirejs: {
            compile: {
                options: {
                    baseUrl: ".",
                    removeCombined: true,
                    mainConfigFile: "./js/main.js",
                    findNestedDependencies: true,
                    fileExclusionRegExp: /^\./,
                    out: "build/js/app.build.js",
                    name: 'js/main'
                }
            }
        },
        copy: {
            main: {
                files: [
                    {
                        expand: true,
                        src: [
                            'data/*',
                            'js/vendor/*',
                            'config.txt',
                            'favicon.ico',
                            'index.html'
                        ],
                        dest: 'build/'
                    }
                ]
            }
        },
        imagemin: {
            dynamic: {
                files: [{
                    expand: true,
                    cwd: 'img/',
                    src: ['**/*.{png,jpg,gif}'],
                    dest: 'build/img'
                }]
            }
        },
        useminPrepare: {
            html: ['build/index.html'],
            options: {
                root: '.',
                dest: 'build'
            }
        },
        usemin: {
            html: ['build/index.html'],
            options: {
                root: '.',
                dest: 'build'
            }
        },
        replace: {
            appbuild: {
                src: ['build/index.html'],
                overwrite: true,
                replacements: [{
                    from: 'js/main',
                    to: "js/app.build"
                }]
            },
            cssversion: {
                src: ['build/index.html'],
                overwrite: true,
                replacements: [{
                    from: '__VERSION__',
                    to: "<%= grunt.template.today('yyyymmddHHss') %>"
                }]
            },
            csscombinedversion: {
                src: ['build/index.html'],
                overwrite: true,
                replacements: [{
                    from: '.combined.min.css',
                    to: ".combined.min.css?v=<%= grunt.template.today('yyyymmddHHss') %>"
                }]
            },
            requirejsversion: {
                src: ['build/index.html'],
                overwrite: true,
                replacements: [{
                    from: '"bust="+Math.random()',
                    to: '"bust=<%= grunt.template.today(\'yyyymmddHHss\') %>"'
                }]
            }
        },
        htmlmin: {
            dist: {
                options: {
                    removeComments: true,
                    collapseWhitespace: true
                },
                files: {
                    'build/index.html': 'build/index.html'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-requirejs');
    grunt.loadNpmTasks('grunt-contrib-imagemin');
    grunt.loadNpmTasks('grunt-contrib-htmlmin');
    grunt.loadNpmTasks('grunt-text-replace');
    grunt.loadNpmTasks('grunt-usemin');

    grunt.registerTask('default', ['clean', 'requirejs', 'copy', 'imagemin', 'useminPrepare', 'concat', 'cssmin', 'usemin', 'replace', 'htmlmin']);
};

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

Устанавливать любой модуль надо через консоль в директории с проектом. Зависимость сразу же попадет в package.json.

Вот небольшой пример

npm install grunt-contrib-clean --save-dev

Теперь расскажу, что делает каждый модуль.

grunt-contrib-clean — очищает директорию для билда проекта.

grunt-contrib-requirejs — билдит проект через require.js оптимизатор. На выходе получаем 1 файлик. Очень удобно и просто. Код сразу обфусцирован и минифицирован.

grunt-contrib-copy — копирует файлы с которыми ничего делать не надо. Простой перенос.

grunt-contrib-imagemin — Оптимизирует ваши изображения. Хочу сказать, что очень даже хорошо получается. Есть существенная экономия на трафике.

grunt-usemin — Минифицирует ваши файлы и объединяет их. Этому плагину необходимы зависимости grunt-contrib-cssmin и grunt-contrib-concat. Расскажу подробнее об этом модуле. Например в dev проекте у вас есть ссылки на многие css файлы из разных библиотек или ваши собственные файлы. Плагин их объединяет в один используя комментарии html. Например:

    <!-- build:css /css/libs.combined.min.css -->
    <link href="/css/normalize.min.css" rel="stylesheet">
    <link href="/css/bootstrap.css" rel="stylesheet">
    <link href="/css/bootstrap-responsive.css" rel="stylesheet">
    <link href="/css/idangerous.swiper.scrollbar.css" rel="stylesheet">
    <!-- endbuild -->

    <!-- build:css /css/style.combined.min.css -->
    <link href="/css/style.css" rel="stylesheet">
    <!-- endbuild -->

Из этого примера будет создано 2 файла libs.combined.min.css (объединяющий ваши стили библиотек) и style.combined.min.css (объединяющий ваши собственные стили). Само собой файлы будут минифицированы.

grunt-text-replace — Делает замены в ваших файлах. Я это используя для установки версии css файлов или для парметров require.js. Например, на тестовом сервере для require.js я использую такой параметр конфигурации

<script>
    require.config({
        urlArgs: "bust="+Math.random()
    });
</script>

Который необходим что бы файлы js не кэшировались при разработки. При билде проекта я меняю этот параметр на дату и время билда проекта. Также меняю путь к файлы проекта requirejs с «js/main» на «js/app.build». app.build — это собранный проект через оптимизатор require.js.

Ну и в последнюю очередь я использую плагин

grunt-contrib-htmlmin — Этот плагин минифицирует html файл.

Вот на этом собственно и все. Это далеко не полный список всевозможных плагинов. Это только несколько которые использую я. Под себя вы можете настроить grunt как вам угодно и удобно.

Соотвественно последовательность задач устанавливается через

grunt.registerTask('default', ['clean', 'requirejs', 'copy', 'imagemin', 'useminPrepare', 'concat', 'cssmin', 'usemin', 'replace', 'htmlmin']);

На этом все. Остается с помощью консоли в директории с проектом запустить команду grunt и ваш проект будет собран. Хороших билдов вашим проектов!

Что почитать еще?

VeXell

Возможно вам интересно также будет

  • HazArt Labs

    а можно посмотреть пример указания зависимостей в файлах проекта, а то в моем случае вылетает на RangeError: Maximum call stack size exceeded

    • vexell

      Примерно это выглядит так

      requirejs.config({
      baseUrl: ‘/’,
      paths: {

      // Libs
      ‘backbone’: ‘js/libs/backbone-min’,
      ‘jquery’: ‘js/libs/jquery-2.1.0.min’,
      ‘underscore’: ‘js/libs/underscore-min’,
      ‘text’: ‘js/libs/require/text’,
      ‘bootstrap’: ‘js/libs/bootstrap.min’,
      ‘moment’: ‘js/libs/moment-with-langs.min’,
      ‘dotdotdot’: ‘js/libs/jquery.dotdotdot.min’,
      ‘functions’: ‘js/libs/jquery.functions’,
      ‘jquery.nicescroll’: ‘js/libs/jquery.nicescroll.min’,

      // Application
      ‘Application’: ‘js/app/application’,
      ‘App_router’: ‘js/app/router’,

      // Views
      ‘App_View_Body’: ‘js/app/view/body’,
      ‘App_View_HeaderView’: ‘js/app/view/header_view’,

  • Купстас Николай

    А не покажите пример main.js, а то у меня практически на первом этапе сразу всё валится?

  • valexkeepcalm

    Добрый день. После сборки проекта, при загрузке скомпилированного файла:

    Получаю такую ерору:
    app.min.js:110Uncaught TypeError: Cannot read property ‘$’ of undefined(anonymous function) @ app.min.js:110(anonymous function) @ app.min.js:110(anonymous function) @ app.min.js:110