Sourcemaps - Debugando JS e CSS minificados

Sabe quando você está usando uma lib qualquer (jQuery, bootstrap, etc), e abre o console do seu navegador, você percebe uma mensagem de erro 404, pois o browser está tentando baixar um arquivo jquery.js.map ou bootstrap.css.map? Afinal de contas: pra que serve esse arquivo .map? Algum dia na MINHA VIDA eu vou precisar dele?

Vem descobrir! ;)

O problema

É praticamente impossível debugar JS e CSS minificados. Sempre que acontece um erro, e você precisa saber qual a linha de referência de um comando em um arquivo, você “desminifica” o CSS ou JS, e o inclui na sua aplicação para debugar.

Então qual é a solução?

Sourcemaps! Sim, aquele arquivo .map que fica dando erro 404 no seu console é que vai resolver seu problema! :D

Como funcionam os sourcemaps

Quando você tem um código minificado, e adiciona a ele uma referência a um sourcemap, o sourcemap faz uma varredura no arquivo, e gera todas as referências a número de linha, nomes de variáveis e funções, etc., para que você possa debugar no arquivo “desminificado”.

Complicado? Com um exemplo prático vai ficar mais fácil de entender :D

Criando a estrutura de arquivos

Vamos criar a seguinte estrutura para o nosso teste com sourcemaps:

1
2
3
4
5
6
7
8
9
10
.
├── gulpfile.js
├── index.html
├── package.json
├── public
└── src
└── js
├── app.js
└── controllers
└── controller-home.js

No diretório src ficarão nossos fontes, e no public, os arquivos concatenados e minificados.

Vamos utilizar o Gulp para fazer as tarefas de concatenação, minificação e geração do sourcemaps. Se você ainda não conhece o Gulp, e quer saber mais sobre, acesse: https://blog.da2k.com.br/tags/gulpjs/

Instalando os plugins do Gulp necessários

Instale os seguintes plugins:

1
npm i --save-dev gulp gulp-concat gulp-uglify gulp-sourcemaps gulp-load-plugins

Chamando os arquivos na index

Agora precisamos montar nossa index.html para receber o arquivo JS minificado, para que possamos debugá-lo! A index deve ter esse código:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Sourcemaps</title>
</head>
<body>
<a href="http://even.tc/curso-javascript-ninja" data-js="link">
Curso Javascript Ninja!
</a>
<script src="public/js/main.js"></script>
</body>
</html>

Bem básico! Só para que possamos ter o JS incluído :D

Códigos para os JS

Vamos agora colocar um pouco de código nos nossos arquivos JS. Primeiro o main.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;(function( Module, undefined ) {
'use strict';

function app() {
var $public = {};

$public.init = function init() {
Module.controllerHome.init();
};

return $public;
}

app().init();
})( window.Module );

Depois, o controllers/controller-home.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
;(function( win, doc, undefined ) {
'use strict';

function controllerHome() {
var $public = {};
var $private = {};

$public.init = function init() {
$private.initEvents();
};

$private.initEvents = function init() {
var $link = doc.querySelector( '[data-js="link"]' );
$link.addEventListener( 'click', $private.handleClickLink, false );
};

$private.handleClickLink = function handleClickLink( e ) {
e.preventDefault();
Module.redirectService( this.href );
};

return $public;
}

window.Module = window.Module || {};
window.Module.controllerHome = controllerHome();
})( window, document );

E então, vamos configurar nosso Gulpfile.

Configurando Gulpfile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
'use strict';
var gulp = require( 'gulp' );
var g = require( 'gulp-load-plugins' )();

gulp.task( 'default', function() {
gulp.src([
'src/js/controllers/**/*.js',
'src/js/app.js'
])
.pipe( g.concat( 'main.js' ) )
.pipe( g.uglify() )
.pipe( gulp.dest( 'public/js' ) );
});

Esse gulpfile.js vai concatenar e minificar todos os JS em um único arquivo. O gulp-load-plugins vai carregar todos os plugins instalados que tem o prefixo gulp-*, e vai colocar em um objeto, que nós chamamos de g. O padrão para nome dos métodos é remover o prefixo gulp-, remover o traço e usar camelCase.

Por exemplo: o plugin gulp-uglify pode ser chamado com g.uglify(). Se estivéssemos usando CSS aqui, e adicionássemos o plugin gulp-minify-css, ele poderia ser chamado com o comando g.minifyCss().

Executando o comando gulp no terminal, os arquivos serão concatenados, minificados, e colocados nos diretório public/js.

Acessando nossa index.html, já podemos começar a ver como estão os arquivos. Antes de qualquer coisa, abra o console do seu navegador e então, clique no link. Será mostrado o seguinte erro:

Vamos então debugá-lo, clicando no link main.js indicado:

Ops! Como faz pra achar o erro agora? O arquivo está minificado!

É hora de ver como funcionam os sourcemaps! Vamos modificar um pouco nosso Gulpfile, e adicionar o suporte aos sourcemaps no nosso JS:

1
2
3
4
5
6
7
8
9
10
11
gulp.task( 'default', function() {
gulp.src([
'src/js/controllers/**/*.js',
'src/js/app.js'
])
.pipe( g.sourcemaps.init() )
.pipe( g.concat( 'main.js' ) )
.pipe( g.uglify() )
.pipe( g.sourcemaps.write( '.' ) )
.pipe( gulp.dest( 'public/js' ) );
});

Na linha 6, logo após adicionar nossos source files, nós iniciamos o sourcemaps, para que ele tenha a referência de todos os arquivos. Após isso, concatenamos e minificamos o arquivo e, antes de gravá-lo em disco, executamos o método write do sourcemaps para que o arquivo main.js.map seja salvo no mesmo local do main.js.

Os plugins que ficam entre o sourcemaps.init() e sourcemaps.write() devem ter suporte para sourcemaps. Para ver quais são todos os plugins do Gulp suportados, acesse esse link.

Feito isso, vamos gerar novamente nosso arquivo minificado, mas dessa vez, com o arquivo .map. Execute gulp no seu terminal.

Com o comando executado, se você abrir o diretório public/js, verá que o arquivo main.js.map está lá! E abrindo o main.js, você pode ver, no final dele, um comentário com a referência ao seu .map:

1
//# sourceMappingURL=main.js.map

Agora, recarregue a index.html, e clique no link novamente. Vai continuar dando o mesmo erro. Mas perae, agora a referência de arquivo mudou:

O erro apontou para o arquivo controller-home.js! E na linha 19, exatamente onde acontece o erro! :D

Isso acontece porque o sourcemaps guardou a referência desse arquivo, então ele sabe exatamente onde aconteceu o erro :D

Agora, clique no link com o nome do arquivo, e veja a mágica na aba Sources:

Agora chega de sofrer para debugar códigos :D

Lembrando que isso é uma feature do browser, e você pode desabilitá-la a qualquer momento, se não tiver o arquivo .map de alguma lib que está dando erro 404 na sua aplicação. No Chrome, você pode clicar na Engrenagem, que aparece na Dev Tools, e desmarcar a opção Enable Javascript source maps.

Chega por hoje! Amanhã tem mais :D

Ficou com alguma dúvida? Comente!

Sobre o #1postperday: https://blog.da2k.com.br/2014/12/31/um-post-por-dia/

Tem alguma sugestão para os próximos posts do #1postperday? Deixe ela aqui: https://github.com/fdaciuk/fdaciuk.github.io/issues/1