Grunt: система сборки для фронтенд-разработчиков

Артём Сапегин (@sapegin, artem@sapegin.ru)

Grunt

Система сборки для фронтенд-разработчиков

Web Standards Days, 24 ноября 2012 г., Москва

Grunt

Артём Сапегин

Артём Сапегин

Системы сборки

Автоматизируют подготовку проекта к боевому окружению:

Зачем нам это нужно?

Пусть серверные программисты во всём разбираются!

Зачем нам это нужно?

Сборка JS, CSS, картинок и прочего фронтенда — забота технолога.

Почему Grunt?

Ant

<project name="Grunt vs. Ant" default="min" basedir=".">
    <property name="JS" value="src/*.js" />
    <property name="BUILD" value="build/scripts.js" />
    <property name="BUILD_MIN" value="build/scripts.min.js" />
    <target name="concatenate" description="Concatenate JavaScript files">
        <echo message="Concatenation..."/>
        <concat destfile="${BUILD}">
            <fileset dir="." includes="${JS}"/>
        </concat>
    </target>
    <target name="min" depends="concatenate" description="Minify JS...">
        <echo message="Minification..."/>
        <exec executable="uglifyjs">
            <arg line="-o ${BUILD_MIN} ${BUILD}"/>
        </exec>
    </target>
</project>

Ant

Make

JS = src/*.js
BUILD = ./build/scripts.js
BUILD_MIN = ./build/scripts.min.js
­
all: min
­
concat:
    mkdir -p `dirname $(BUILD)`
    cat $(JS) > $(BUILD)
­
min: concat
    uglifyjs -o $(BUILD_MIN) $(BUILD)

Make

Grunt

module.exports = function(grunt) {
    grunt.initConfig({
        concat: {
            main: {
                src: 'src/*.js',
                dest: 'build/scripts.js'
            }
        },
        min: {
            main: {
                src: '<%= concat.main.dest %>',
                dest: 'build/scripts.min.js'
            }
        }
    });
    grunt.registerTask('default', 'concat min');
};

Grunt

Встроенные задачи

Дополнительные задачи (плагины)

Мои плагины

Кто использует

Установка

Сначала Node.js, потом:

$ npm install grunt -g

Gruntfile: grunt.js

module.exports = function(grunt) {
    grunt.initConfig({
        /* ... */
    });
};

Задачи

module.exports = function(grunt) {
    grunt.initConfig({
        concat: {
        }
    });
    grunt.registerTask('default', 'concat');
};

Подзадачи (multi task)

concat: {
    main: {
    },
    second: {
    }
}

Параметры задачи

concat: {
    main: {
        src: 'js/*.js',
        dest: 'build/scripts.js'
    }
}

Списки файлов

'js/main.js'
[ 'js/utils.js', 'js/main.js' ]
[ 'js/libs/*.js', 'js/mylibs/**/*.js' ]

* — любые символы.
/**/ — папка любой вложенности.

Шаблоны

Шаблонизатор из Underscore.

<%= concat.main.dest %>
<%= grunt.template.today("m-d-yyyy") %>

Шаблоны

concat: { main: {
    src: [ 'js/utils.js', 'js/main.js' ],
    dest: 'build/scripts.js'
}},
min: { main: {
    src: '<%= concat.main.dest %>',
    dst: 'build.<%= grunt.template.today("m-d-yyyy") %>.js'
}}

Банеры

meta: {
    banner: '/* © Ivan Pupkin */'
},
min: { main: {
    src: [ '<banner>', 'build/build.js' ], ...
}}

Подключение плагинов

$ npm install grunt-stylus

Подключение плагинов

module.exports = function(grunt) {
    grunt.initConfig({
        stylus: { ... }
    });
    grunt.loadNpmTasks('grunt-stylus');
    grunt.registerTask('default', 'stylus');
};

Запуск

$ grunt
$ grunt concat
$ grunt concat:main
­
$ grunt --debug
­
$ grunt.cmd  # Windows :-)

Конфигурации

    concat: { main: { ... dest: 'build/scripts.js' }},
    min: { main: { src: '<%= concat.main.dest %>',
                   dest: '<%= concat.main.dest %>' }}
    ...
    grunt.registerTask('default', 'concat min');
    grunt.registerTask('debug', 'concat');

$ grunt или $ grunt debug --debug

Собственные задачи

Будем запускать из Гранта imgo
консольный оптимизатор веб-графики.

См. также grunt-imgo, grunt-exec.

Конфиг

imgo: {
    images: {
        files: 'images/**'
    }
}

Задача

grunt.registerMultiTask('imgo', '…', function() {
    // this.data.files ===
    //   <%= imgo.images.files %>
});

Цикл по исходным файлам

var done = this.async();
var files = grunt.file.expandFiles(this.data.files);
grunt.utils.async.forEach(files,
    function(file, next) {
        // Обрабатываем каждый файл
    }
, done);

Обработка файла

function(file, next) {
    grunt.utils.spawn({
        cmd: 'imgo',
        args: [file]
    }, next);
}

Использование

module.exports = function(grunt) {
    grunt.initConfig({
        imgo: {...}
    });
    grunt.registerMultiTask('imgo', ...);
    grunt.registerTask('default', 'imgo');
};

grunt watch

concat: { main: {
    src: [ 'js/utils.js', 'js/main.js' ],
    dest: 'build/scripts.js' }},
watch: { concat: {
    files: '<%= concat.main.src %>',
    tasks: 'concat' }}

grunt server

server: {
    port: 8000,
    base: '.'
}

$ grunt server watchhttp://localhost:8000/

grunt init

Упрощает инициализацию проектов и отдельных файлов (scaffolding).

grunt init

$ grunt init:node
...
$ tree
.
├── LICENSE-MIT
├── README.md
├── grunt.js
├── lib
│   └── grunt-init-node-sample.js
├── package.json
└── test
    └── grunt-init-node-sample_test.js

Реальный мир

...
<script type="text/javascript" src="/Content/js/lib/modernizr.js"></script>       
<script type="text/javascript" src="/Content/js/lib/jquery.js"></script>
<script type="text/javascript" src="/Content/js/lib/json.js"></script>
<script type="text/javascript" src="/Content/js/lib/jquery.selectBox.js"></script>
<script type="text/javascript" src="/Content/js/lib/jquery.fader.js"></script>
<script type="text/javascript" src="/Content/js/lib/jquery.jscrollpane.js"></script>
<script type="text/javascript" src="/Content/js/util.js"></script>
<script type="text/javascript" src="/Content/js/articul/tools.js"></script> 
<script type="text/javascript" src="/Content/js/articul/nav-primary.js"></script>
<script type="text/javascript" src="/Content/js/articul/gallerysis.js"></script>
<script type="text/javascript" src="/Content/js/storejs/store_json2.min.js"></script> 
<script type="text/javascript" src="/Content/js/swfobject/swfobject.js"></script>    
<script type="text/javascript" src="/Content/js/form.js"></script>
<script type="text/javascript" src="/Content/js/articul/widgets/widgets.js"></script>
<script type="text/javascript" src="/Content/js/articul/accessibility.js"></script>
<script type="text/javascript" src="/Content/js/adriver/adriver.core.2.js"></script>
<script type="text/javascript" src='/Content/js/accessibility_sensitive.js'></script>
<script type="text/javascript" src="/Content/js/articul/announce.js" ></script>
<script type="text/javascript" src="/Content/js/lib/countdown.js" ></script>
<script type="text/javascript" src="/bitrix/components2/Articul.Sochi.Components/VideoBlockHomepageComponent/templates/.default/videoblock.js" ></script>
<script type="text/javascript" src="/Content/js/util.js" ></script>
<script type="text/javascript" src="/bitrix/components2/Articul.Sochi.Components/QuoteBlockHomepageComponent/templates/.default/script.js" ></script>
<script type="text/javascript" src="/bitrix/components2/Articul.Sochi.Objects/MapHomepageComponent/templates/.default/script.js" ></script>
<script type="text/javascript" src="/Content/js/lib/hammer.js" ></script>
<script type="text/javascript" src="/Content/js/lib/jquery.specialevent.hammer.js" ></script>
<script type="text/javascript" src="/Content/js/articul/event-scroller.js" ></script>
<script type="text/javascript" src="/Content/js/lib/jquery.jscrollpane.js" ></script>
<script type="text/javascript" src="/Content/js/lib/jquery.mousewheel.js" ></script>
<script type="text/javascript" src="/bitrix/components2/Articul.Sochi.Twitter/TweetListComponent/templates/.default/script.js" ></script>
<script type="text/javascript" src="/Content/js/articul/blogs.js" ></script>
<script type="text/javascript" src="/Content/js/image-centered.js" ></script>
<script type="text/javascript" src="/Content/js/articul/storage.js" ></script>
<script type="text/javascript" src="/Content/js/articul/polls.js" ></script>
<script type="text/javascript" src="/bitrix/components2/Articul.Sochi.Components/InterestingBlockHomepageComponent/templates/.default/script.js" ></script>
...

Главная страница реального проекта.

Не надо так!

Grunt поможет

JavaScript: concat

concat: { main: {
        src: [
            'Content/js/lib/modernizr.js',
            'Content/js/lib/jquery.js',
            'Content/js/articul/**/*.js', ...
        ],
        dest: 'build/scripts.js'
} }

JavaScript: min

min: {
    main: {
        src: [ '<%= concat.main.dest %>' ],
        dest: 'build/scripts.min.js'
    }
}

CSS

concat: { main: {
        src: [
            'Content/css/main.css',
            'Content/css/articul/pit.css', ...
        ],
        dest: 'build/styles.css'
} }

HTML

...
<link rel="stylesheet" href="/build/styles.css">
<script src="/build/scripts.js"></script>
...
Технолог
Пользователь
Серверный программист
Дизайнер
Заказчик

Презентация: sapegin.ru/pres/grunt
Примеры: github.com/sapegin/grunt-talk-examples