Este é o segundo post da série sobre Grunt

Todo o código utilizado encontra-se no Github


Até o momento aprendemos o que é Grunt, sobre alguns plugins e como configurar tarefas simples. Neste post iremos aprofundar um pouco mais nas tasks para entender melhor sobre o porque das coisas. :)

Tasks

Configurações

Cada task é na verdade um comando a ser executado, e no Grunt podemos definir configurações para cada comando. Por exemplo, no post anterior quando definimos a task coffee temos uma configuração chamada compile:

...
coffee:{
    compile: {
        files: {
            "main.js": "main.coffee"
        }
    }
},
...

Quando rodamos o comando grunt coffee o resultado é este: Repare na configuração.

Compile coffee

Podemos inclusive definir mais do que uma configuração para algumas tasks (como mostrado abaixo), caso você não especifique nenhuma configuração o Grunt irá rodar todas.

...
coffee:{
    compile: {
        files: {
            "main.js": "main.coffee"
        }
    },
    compileJoin: {
        files: {
            "all.js": "\*.coffee"
        }
    }
},
...

E o resultado do comando grunt coffee:

Grant roda todas as tasks

Se desejar rodar uma task sobre uma configuração específica: grunt coffee:compileJoin.

Opções

Toda task no Grunt possui um atributo chamado options, que serve para definir algumas opções de configuração na execução da task. Por exemplo, no plugin grunt-contrib-coffee temos a opção bare nu que serve para gerar o .js nu (sem o invólucro de segurança de nível superior função.)

Exemplo:

...
coffee:{
    options: {
        bare: true
    },
    compile: {
        files: {
            "main.js": "main.coffee"
        }
    },
...

Desta forma, os arquivos .js gerados não terão uma função wrapper envolvendo todo o javascript. Isto se aplica a todas as configurações.

Options também se aplica às configurações:

...
coffee:{
    options: {
        bare: true
    },
    compile: {
        files: {
            "main.js": "main.coffee"
        }
    },
    compileJoin: {
        options: {
            bare: false
        },
        files: {
            "all.js": "\*.coffee"
        }
    }
},
...

Desta forma as opções definidas em compileJoin irão prevalecer, sobrescrevendo as opções do nível superior.

Files

É importante entender que Grunt foi feito para realizar operações em arquivos, por este motivo temos algumas abstrações como as propriedades src e dest. Por exemplo, nosso compileJoin pode ser escrito da seguinte forma:

...
    compileJoin: {
        options: {
            bare: false
        },
        src: "*.coffee"
        ,dest: "all.js"
    }
},
...

Ou:

files: [
    {src: ['main.coffee', 'js/*.coffee'], dest: 'all.js'},
    {src: ['src/aa1.coffee', 'src/aaa1.coffee'], dest: 'dest/a1.js'},
],

Existem também várias opções de filtros. Mas não entraremos em filtros neste momento.

Refatorando

Agora que entendemos como funcionam as tasks vamos fazer algo divertido :). No decorrer do desenvolvimento, temos pelo menos dois ambientes: O de dev e o de dist “distribuição” (aquela versão que vai para o ar).

É comum que no ambiente de dev tenhamos de executar uma série de tasks, e o mesmo acontece quando vamos publicar uma versão (dist).

Na versão de dev, como dito no post anterior , eu tenho de:

  • Compilar meus arquivos sass e coffee
  • Executar testes
  • Otimizar imagens
  • Compilar templates handlebars

e na versão de dist:

  • Minificar js e css
  • Concatenar
  • Lint
  • Compactar arquivos no formato zip
  • Enviar via FTP para o server

Não vamos fazer todas as tasks acima, mas só algumas pra termos uma noção de como Grunt pode ser útil.

Organizando a estrutura

Primeiramente vamos organizar nossa estrutura de arquivos.

Estrutura de pastas

Versão dev

Compilar meus arquivos coffee

Nosso objetivo é compilar nossos arquivos .coffee para a mesma pasta de origem (mantendo o nome do arquivo). Para isto vamos utilizar o recurso Building the files object dynamically, conforme configuração abaixo:

coffee: {
    dev: {
        expand: true
        ,cwd: "js/"
        ,src: ["**/*.coffee"]
        ,dest: "js/"
        ,ext: ".js"
    }
}

A propriedade expand indica que esta é uma configuração especial. Basicamente estamos dizendo ao Grunt que os arquivos da pasta js proriedade cwd que contém a extensão .coffee e em todos os sub-diretórios proriedade src serão compilados para a extensão .js proriedade ext na mesma pasta de origem proriedade dest.

Para este nosso exemplo não temos sub-diretórios na pasta js, porém serve como dica :).

Otimizar imagens

  1. Primeiramente instale o plugin grunt-contrib-imagemin para otimização de imagens.
  2. Adicione 3 imagens na pasta img.

Configuração

imagemin: {
    options: {
        optimizationLevel: 3
    },
    dev: {
        expand: true
        ,cwd: "img/"
        ,src: ["\*\*/\*.jpg"]
        ,dest: "img/"
        ,ext: ".jpg"
    }
}

Versão dist

Minificar js e concatenar

Os plugins para minificar (grunt-contrib-uglify) e concatenar (grunt-contrib-concat) já estão instalados em nosso projeto, tudo o que temos que fazer é modificar sua configuração para:

uglify: {
    dist: {
        files: {
            'dist/all.js': 'dist/all.js'
        }
    }
},
concat: {
    dist: {
        src: 'js/\*.js',
        dest: 'dist/all.js'
    }
},

Alias

Observe que primeiro devemos concatenar e só então minificar o arquivo js. Então vamos aprender sobre Alias e como executar várias tasks em uma determinada ordem.

Alias nada mais é do que uma lista de tarefas que devem ser executadas em uma determinada ordem. Por exemplo, ao executarmos grunt sem informar nenhuma task veja o que ocorre:

Default task grunt

Observe que grunt tenta rodar uma task default que não existe. Então podemos criar um Alias chamado default e listar as tasks que desejamos que rode caso nenhuma task seja definda:

grunt.registerTask('default', ['coffee:dev', 'imagemin:dev'])

Desta forma, as duas tasks serão executadas nesta ordem caso nenhuma outra seja informada:

Rodando task default caso nenhuma task seja definida

Seguindo esta linha, podemos criar um alias para rodar as tasks relacionadas a dist:

grunt.registerTask('dist', ['concat:dist', 'uglify:dist'])

e então rodar o grunt grunt dist

Rodando task dist (alias) no grunt

No próximo post vamos aprender como criar e customizar estas tasks :)

Todo o código utilizado encontra-se no Github

Referências: