Categorias
HTML5 JavaScript Tutoriais

localStorage: Armazenando dados com JavaScript

Esse post é um dos posts mais visualizados do blog, foi criado em 2013 mas vou dar uma atualizada no que aconteceu de lá pra cá. Armazenamento local com localStorage é umas das features mais úteis do HTML5 podemos utilizar para uma infinidade de ações agora com Progressive Web Apps esse recursos voltou a ficar evidência.

Ela permite o armazenamento de strings com chave e valor e considerado cookies é uma forma mais segura de armazenamento, mas ela não tem acesso através de Service Worker. Mas é um recurso nativo no browser de implementação simples que não necessita de plugin ou library externa.

Para aplicações mas complexas temos a opção de utilizar o IndexedDB. Mas um dos pontos fortes do localStorage é o suporte comparado a outras APIs ele é suportado por 93% dos browsers:

LocalStorage é um dos recursos do DOM Storage com ele podemos armazenar dados apenas em formato de texto, mas claro podemos contornar essa limitação. Vamos primeiro exemplo básico de armazenamento.

localStorage.setItem("key_da_propriedade","Valor armazenado");

No código acima adicionamos uma variável com nome “key_da_propriedade” que irá armazenar “Valor armazenado”. Quando esse código for armazenado pelo browser o usuário pode sair da aplicação, continuar navegando em outras aplicações quando ele voltar para nossa aplicação o dado continuará lá. Esse recurso será útil caso utilizarmos uma versão offline de nossa aplicação com PWA.

Para visualizar a informação que foi salva, no Google Chrome para abrir o developer Tools vamos em “menu > View > developer > developer tools”, tecla de atalho ctrl+shift+i ou simples clique o com botão direito do mouse e selecionamos a opção “inspect”, com developer tools aberto vamos na aba Application, na sessão storage vemos a opções disponíveis. Quando clicamos em Local Storage vemos as aplicações separadas por url, meu teste estou rodando em localhost, clicando na url da nossa aplicação vemos os dados armazenados:

Agora que sabemos que a informação foi salva vamos resgatar esse valor com o seguinte código:

const minha_propriedade = localStorage.getItem('key_da_propriedade'); alert("Valor:" + minha_propriedade);

No código acima passamos a função getItem e como parâmetro passamos a key da informação que queremos resgatar, na linha seguinte apenas executamos um alert para exibir a informação.

Caso queria remover essa informação do localStorage utilizamos o seguinte código:

localStorage.removeItem('key_da_propriedade');

Agora vamos para um exemplo prático aplicando essas três funções que virmos acima, no caso: salvar, ler e excluir os dados no localStorage.

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>LocalStorage tutorial</title> <script> window.addEventListener('load',() => { if(localStorage.getItem('name')){ sayMyName() }else{ whatsYourName() } }); function whatsYourName(){ document.body.innerHTML = ''; // criando um input para cadastrar o nome; const inputName = document.createElement('input'); inputName.type = 'text'; inputName.placeholder = 'Digite seu nome'; inputName.id = 'nome'; document.body.appendChild(inputName); // criando saveButton const saveButton = document.createElement('button'); saveButton.innerHTML = 'Salvar'; document.body.appendChild(saveButton); // adicionando listener para salvar a informação saveButton.addEventListener('click', saveName); } function sayMyName(){ document.body.innerHTML = ''; // criando mensagem const welcomeMessage = document.createElement('h1'); welcomeMessage.innerText = 'Olá' + localStorage.getItem("name"); // criando removeButton const removeButton = document.createElement('button'); removeButton.innerHTML = 'Excluir'; document.body.appendChild(removeButton); // adicionando o listener para remover informação removeButton.addEventListener('click',removeName); } function removeName(){ if(localStorage.getItem('name')){ localStorage.removeItem('name'); whatsYourName() } } function saveName(){ var nome = document.getElementById('nome').value; localStorage.setItem('name', nome) sayMyName(); } </script> </head> <body> </body> </html>

O Exemplo acima resumindo rapidamente ele verifica se tem o dado com a key “name” no localStorage, caso tenha, exibe o nome. Caso contrário adiciona um input para cadastrar o dado. Quando o nome é exibido também adicionamos um button para chamar a função de excluir os dados. Nos Exemplos anteriores apenas adicionamos e removemos algumas strings, para trabalhar com objetos no localStorage precisamos usar a Classe JSON, com ela é possível fazer a ponte entre textos e objetos.

window.addEventListener('load',() => { if(localStorage.getItem('user')){ const texto = localStorage.getItem("user"); const objeto = JSON.parse(texto); document.body.innerHTML = `nome: <strong>${objeto.name}</strong> email: <strong>${objeto.email}<strong>`; }else{ const user = { name: 'Fellyph', email: 'fellyph@fellyph.com.br' } const userString = JSON.stringify(user); localStorage.setItem('user',userString); document.body.innerHTML = 'dados salvos'; } });

No código acima focamos apenas no javaScript, verificamos se existe um dado com uma key “user”, caso exista ele resgata a informação na variável “texto” em seguida convertemos o texto em objeto utilizando a método parse da classe JSON, assim temos um objeto como retorno e adicionamos o conteúdo no body do nosso arquivo, no código acima utilizamos string literals para concatenar o nosso conteúdo.

Caso o usuário não tenha nenhuma informação salva com a key “user”, o código irá criar um objeto e em seguida converter em texto, assim temos a possibilidade de salvar os dados no em nosso localStorage. Esse exemplo não é muito funcional apenas exemplifica como é salvar um objeto em nosso localStorage.

Agora vamos partir para um exemplo mais funcional, vamos montar uma todo-list utilizando localStorage, desta vez vamos separar o código no arquivo todo-list.html, js/app.js e css/style.css. O exemplo a seguir utiliza ES2015 caso queira dar uma olhada no exemplo em EcmaScript 5 só da uma olhada no gitHub no seguinte link: https://github.com/fellyph/Tutorial-bbUI/tree/master/localstorage

Nosso HTML:

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Todo List</title> <link rel="stylesheet" href="css/style.css"> <script src="js/app.js"></script> </head> <body> <h1>todo-list</h1> <div id="tasks-output"></div> <form id="form-task"> <input type="text" name="descricao" placeholder="adicione uma nova task" required> <input type="submit" value="Salvar"> </form> </body> </html>

Nosso CSS:

#tasks-output li { cursor: pointer; } #form-task { border-top: 1px solid #ddd; padding: 10px 0; margin: 10px 0; } #tasks-output li[data-done="true"] { text-decoration: line-through; }

Detalhe para o ultimo seletor que trata quando o item da lista possui o atributo data-done igual a true, ele vai adicionar uma linha sobre o texto.

let todoList; let todoOutput; function formatDate(date) { // formata a data para o formato DD/MM/YYYY const time = new Date(date); return `${time.getDate()}/${time.getMonth()}/${time.getFullYear()}`; } function showList() { // mostra a lista de todo if (todoList.length > 0) { const htmlTemp = `<ul> ${todoList.map(todoItem => `<li data-id="${todoItem.id}" data-done="${todoItem.done}">${todoItem.descricao} - ${formatDate(todoItem.date)}</li>` )} </ul><button>Limpar tarefas realizadas</button>`; todoOutput.innerHTML = htmlTemp; } else { todoOutput.innerHTML = 'Nenhuma tarefa cadastrada'; } } function saveList() { // converte os dados em string e salva no local storage localStorage.setItem('tasks', JSON.stringify(todoList)); } function clearList() { // varre a lista a procura de tarefas realizadas for (let i = 0; i < todoList.length; i += 1) { if (todoList[i].done === 'true') { // remove 1 elemento na posição i; todoList.splice(i, 1); // voltando o indice no array para validar novamente a lista i = 0; } else { todoList[i].id = i; } } showList(); saveList(); } function clickList(e) { // somente fazer algo quando clicar em um item li if (e.target.localName === 'li') { e.target.dataset.done = !e.target.dataset.done === 'true'; todoList[e.target.dataset.id].done = e.target.dataset.done; saveList(); } else if (e.target.localName === 'button') { clearList(); } } function onSubmit(e) { const task = {}; // pego o valor cadastrado no primeiro input do meu form task.descricao = e.target[0].value; task.date = new Date(); task.id = todoList.length; task.done = 'false'; // adicionando a task na lista todoList.push(task); saveList(); showList(); // utiliza o preventDefault para evitar do form realizar o reload da página e.preventDefault(); } window.addEventListener('load', () => { // guarda em uma variável o elemento tasks-output todoOutput = document.getElementById('tasks-output'); if (localStorage.getItem('tasks')) { todoList = JSON.parse(localStorage.getItem('tasks')); showList(); } else { todoList = []; } if (todoList.length === 0) { todoOutput.innerHTML = 'Nenhuma tarefa cadastrada'; } // adiciona o listener para o evento submit, utilizei form para usar o required do input HTML document.getElementById('form-task').addEventListener('submit', onSubmit); todoOutput.addEventListener('click', clickList); });

Tentei comentar os pontos mais importantes do código, na listagem utilizo um map para montar nossa lista caso não esteja familiarizado só conferir a versão anterior desse código no git ou conferir os posts relacioanos a ecmaScript 2015, mas o código acima o usuário pode cadastrar as tasks que serão vinculadas uma data, quando o usuário clica no item da lista a propriedade “data-done” muda entre true ou false. caso o usuário queira limpar sua listas de task clica no botão “Limpar tarefas…” o código irá varrer da lista os itens com valor da propriedade data-done igual a true e retirar da lista.

Além do LocalStorage temos o SessionStorage, a diferença entre os dois é que o sessionStorage como o nome sugere ele guarda as informações apenas na seção ou seja, quando o aplicação é finalizada a informação é removida.

Fechamos por aqui e até o próximo tutorial, para saber mais como acessar recursos do device confira a página da categoria PWA.

Categorias
JavaScript

Web Share api

Web Share API permite o usuário compartilhar conteúdo através do card de compartilhamento nativo do sistema operacional em que está sendo executado, nos dispositivos Android esse suporte surgiu na versão 61 do Chrome em 2017, já para para iOS o suporte foi incluído no Safari em 2019 na versão 12.2 do iOS.

Para fazer a chamada no card nativo de compartilhamento chamamos a função navigator.share() em nosso browser, esse método faz parte da Web Share API, ele dá o controle ao usuário de como ele quer compartilhar esse conteúdo. Seu uso é baseado em Promise, o método aceito um objeto com as informações de título, texto de descrição e url como podemos ver no código abaixo:

if (navigator.share) { navigator.share({ title: 'Blog Fellyph Cintra', text: 'Como compartilhar conteúdo com web share API', url: 'https://blog.fellyph.com.br/', }) .then(() => console.log('Compartilhamento realizado com sucesso')) .catch((error) => console.log('Erro no compartilhamento', error)); }

No exemplo acima, inicialmente utilizamos uma condicional para verificar se o suporte a web share api existe, caso positivo definimos o conteúdo que será compartilhado e adicionamos um then para tomar uma ação caso o compartilhamento seja realizado com sucesso ou catch em caso de error.

Requisitos

Para invocar o card nativos precisamos de alguns requisitos:

  • A URL deve ser servida por HTTPS
  • O método share deve ser invocado por uma resposta do usuário, por exemplo, um evento de click. Você não pode chamar a share API em um evento de page load.
  • Você só pode compartilhar uma URL dentro do escopo da aplicação
  • Sempre verifique se o usuário tem suporte a essa feature antes de chamar o método

Resultado

No lado esquerdo temos o resultado no iOS e a versao Android no lado direito

Na imagem acima temos dois exemplos de como o card de compartilhamento será exibido no Android e iOS. Para a plataforma iOS o recurso está disponível para Chrome e Safari. Vamos finalizamos por aqui caso tenha alguma dúvida só deixar um comentário.

Categorias
JavaScript Tutoriais

5 maneiras de utilizar o Spread operator

Spread operator parameter foi introduzido no EcmaScript 2015, para um simples recurso no primeiro momento mas utilizados da maneira correta pode se tornar bastante útil, o se você ainda não conhece o spread operator é identificado por “três pontos(…)” com ele podemos desmontar arrays e objetos. Nesse post iremos ver 5 maneiras de utilizar o spread operator.

1. Combinando arrays

Primeiro exemplo podemos ver como podemos combinar dois arrays ou mais, exemplo:

let semana = ['segunda', 'terça', 'quarta', 'quinta', 'sexta']; let semanaCompleta = ['domingo', ... semana, 'sábado']; console.log(semanaCompleta) // output: ['domingo', 'segunda', 'terça' 'quarta', 'quinta', 'sexta', 'sábado'];

2. Copiando array

Um dos problemas de copiar um array para outra variável, caso passamos a referencia diretamente, não estamos fazendo uma cópia do array e sim uma referência ao primeiro array, como no código abaixo:

let lista1 = [1,2,3]; let lista2 = arr; lista2.push(4); console.log(lista1); // output : [1, 2, 3, 4]

Se testarmos o código acima temos como saída do console.log do “lista1” a alteração feita no “lista2”, isso acontece por que temos uma referência para lista1 e não uma copia do array. Para resolver este problema podemos utilizar o spread operator como uma alternativa para copiar a lista como no exemplo abaixo:

let lista1 = [1,2,3]; let lista2 = [...arr]; lista2.push(4); console.log(lista1); // output : [1, 2, 3]

3. Convertendo um nodeList em um array

Outra utilidade de utilizar o spread operator é converter items que parecem um array mas não são. Por exemplo, quando trabalhamos com querySelectorAll temos como retorno uma lista de items mas essa lista não é do tipo array, o retorno é um elemento nodeList outra função que retorna uma lista é getElementsByClassName mas o retorno dessa função é um HTMLCollection, e o qual é o impacto desses dois tipos?

NodeList and HTMLCollection apenas suportam a função forEach, caso queiramos fazer interações como map, filter, reduce ou sort. Funções referentes ao tipo Array teremos um error como retorno, como no exemplo abaixo.

let buttons = document.querySelectorAll('button'); let newList = buttons.map((button) => (button.innerText = button.innerText + ' Copy!'));

No exemplo acima, levamos em conta que nosso HTML tem uma série de elementos button e queremos realizar uma cópia desta lista de elementos, mas nosso código irá disparar um erro pois, map não é função dentro de um nodeList, para isso precisamos converte-lo em um array para isso utilizamos o spread operator, como no exemplo a seguir:

let buttons = [...document.querySelectorAll('button')]; let newList = buttons.map((button) => (button.innerText = button.innerText + ' Copy!'));

Agora sim, conseguimos executar o nosso código e criar uma cópia dos items selecionados, essa ação poderia ser uma ordenação ou um filtro para alterar elementos específicos. Mas é uma boa base para entender a diferença esses dois items claro que nodeList e HTMLCollection tem suas especificidades, eles estão contidos na browser API enquanto Arrays fazem parte da JavaScript API. Caso queria saber mais sobre esses dois items deixe um comentário.

4. Criando novos objetos

Assim como arrays caso queiramos copiar objetos teremos problemas com referência mas nesse caso podemos ir um pouco além, podemos criar novos objetos a partir de um objeto, por exemplo, em um ecommerce temos um objeto endereço, o usuário pode ter múltiplos endereços, quando ele requisita um entrega um novo objeto é criado com endereço que ele selecionou e o seu nome. Como no exemplo abaixo:

const usuario = { nome: 'fellyph cintra', telefone: '0000-0000' }; const endereco = { cidade : 'caruaru', estado: 'pernambuco' }; const entrega = { responsavel: usuario.nome, ...endereco }; console.log(entrega); //output: {responsavel: 'fellyph cintra', cidade: 'caruaru', estado: 'pernambuco'}

No exemplo acima utilizamos a apenas a propriedade nome do usuário, imagine que o objeto entrega precisa de todos os dados do usuário, nesse caso precisaremos combinar os dois objetos então o código ficará da seguinte forma:

const usuario = { nome: 'fellyph cintra', telefone: '00000-0000' }; const endereco = { cidade : 'caruaru', estado: 'pernambuco' }; const entrega = { ...usuario, ...endereco }; console.log(entrega); //output: {responsavel: 'fellyph cintra', telefone: '00000-0000', cidade: 'caruaru', estado: 'pernambuco'}

5. Excluindo propriedades

Voltando para o exemplo anterior imagine que o objeto usuário contém o email do usuário essa informação não será necessária para o objeto entrega imagine, podemos utilizar o spread operator para remover propriedades também, como no código a seguir:

const usuario = { nome: 'fellyph cintra', telefone: '00000-0000', email: 'pindolinha@gmail.com' }; const endereco = { cidade : 'caruaru', estado: 'pernambuco' }; const semEmail = ({ email, ...rest }) => rest; const entrega = { ...semEmail(usuario), ...endereco }; console.log(entrega); //output: {responsavel: 'fellyph cintra', telefone: '00000-0000', cidade: 'caruaru', estado: 'pernambuco'}

No código acima, criarmos uma função chamada semEmail, nela separamos o email do resto do objeto e passamos como retorno o objeto sem a propriedade email.

Existem outros usos para o spread operator, como propriedades default ou propriedades condicionais, esses são os 5 usos mais básicos para spread operator. Caso tenha um uso particular que você gosta comente abaixo!

Para mais tutoriais sobre JavaScript acesse a página da categoria e até o próximo post!

Categorias
JavaScript Tutoriais

Webpack mantendo a qualidade do seu JavaScript

Até o momento vimos dois posts de introdução ao Webpack, nesse post iremos implementar linters em nosso código JavaScript, se você nunca ouviu falar em linting tools, elas são ferramentas de optimização de código, elas nos ajudam a escrever o código organizado e com boas práticas. Mas isso não significa que iremos prevenir bugs, mas a diferença que teremos um código fácil de debugar em em grandes times teremos uma estilo único de desenvolvimento.

Junto ao linters responsáveis pela validação básica podemos integra-los com guias de desenvolvimento, eles utilizam um conjunto padrões de desenvolvimento para realizar uma validação mais restrita, com isso tempos uma melhor manutenção do nosso código. No mercado front-end temos guias específicos para JavaScript e CSS, muitos guias de CSS estão atrelados a guias visuais, mas também podemos encontrar guias para automatização de validação CSS com Webpack e os principais Guias de estilo do mercado são:

Guias JavaScript

Guias CSS/LESS/SASS +HTML

  • Fractal – é uma biblioteca de components com style guide tempos alguns plugins de integração com webpack.
  • Airbnb CSS / SASS Style guide – contém regras de declaração, seletores, formatação, comentários, entre outras regras específicas para SASS
  • Indiomatic CSS Principles – contém regras gerais, whitespace, comentários, formatação e uma série de exemplos práticos.
  • Stylelint Config Standard – essa configuração extends da configuração recomendada e aplica um regra de uma série de guias

Por que utilizar Guia de padrão de desenvolvimento ?

Já abordarmos algumas vantagens mas vale a pena revisar:

  • Ajuda na prevenção de erros
  • Formatação automática em alguns casos
  • Padronização de desenvolvimento
  • Facilidade de manutenção de código
  • Fácil configuração, uma vez definido o estilo você poderá compartilhar a mesma configuração com o seu time
  • Incentivo a boas práticas de desenvolvimento

Ok, Já entendi agora como eu utilizo esses guias com Webpack manter a qualidade do meu código?

Esse tutorial vamos continuar o trabalho dentro do repositório utilizado nos posts anteriores e implementar a validação do nosso código JavaScript, agora vamos criar uma nova branch chamada step-10 para realizarmos os primeiros passos. Inicialmente vamos instalar utilizando nosso terminal o eslint e eslint-loader em nosso projeto, eles serão os pacotes responsáveis pela validação do nosso código EcmaScript:

npm install eslint-loader eslint --save-dev

Após a instalação dos pacotes precisamos configurar o nosso eslint, podemos criar o nosso arquivo de configuração “.eslintrc” manualmente ou utilizar o terminal para instalação, para facilitar o nosso processo vamos utilizar o terminal, para isso executamos o seguinte comando em nosso terminal:

eslint --init

Quando executamos o comando anterior nosso terminal irá realizar uma série de perguntas que serão elas:

  • Como você gostaria de configurar ESLint?
    R. Usando um style guide popular
  • Qual style guide você gostaria de seguir
    R. Airbnb
  • Você usa React?
    R. No
  • Qual o formato que você quer o seu config file?
    R. JavaScript
  • Você gostaria de instalar as dependências necessárias?
    R. Y(sim)

Feito isso, o arquivo .eslintrc será criado com o seguinte código:

module.exports = { "extends": "google" };

Agora que nos temos o eslint instalado e configurado vamos, inclui-lo em nosso webpack.config.js:

const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, 'eslint-loader', ], }, { test: /\.(sa|sc|c)ss$/, exclude: /(node_modules|bower_components)/, use: [ 'style-loader', 'css-loader', 'sass-loader', ], }, { test: /\.(svg|gif|png|jpe?g)$/, loader: 'url-loader', options: { limit: 100, fallback: 'file-loader', publicPath: '/img', outputPath: '/img', }, }, ], }, };

Este é o arquivo de configuração utilizado nos posts anteriores, incluímos o eslint-loader em nossas regras de leitura para arquivos JavaScript ele agora irá trabalhar em conjunto com o babel loader.

Rodando o webpack novamente se tudo ocorrer bem teremos o seguinte retorno:

Nosso lint irá começar a validar o nosso código

Lendo os nossos errors e warnings um ponto de atenção para o document. O eslint não reconhece document porque não especificamos qual ambiente estamos desenvolvendo, para isso vamos fazer uma alteração em nosso arquivo .eslintrc:

module.exports = { "extends": "airbnb-base", "env" : { "browser": true, } };

Com essa nova propriedade “env” definimos browser como true, agora o eslint passará considerar que nosso será escrito para browsers. Podemos também especificar que nosso código irá rodar em servidores node.

Rodando nosso código novamente:

Ainda temos alguns erros e warnings podemos resolver de duas maneiras manualmente ou como um autofix, para realizar a correção automática você pode realizar via terminal ou através de configurações no webpack.config.js adicionando opções ao nosso eslint-loader da seguinte forma:

{ test: /\.js$/, exclude: /(node_modules|bower_components)/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, { loader: 'eslint-loader', options: { fix: true, }, }, ], },
Depois de rodar webpack com autofix temos os seguintes alertas relacionados ao nosso console.log

Podemos remover o console.log do nosso código essa é uma ação recomendada, imagina você enviar um código em produção cheio de console log, no Webpack podemos definir qual será o modo que iremos trabalhar “development” ou “production” partindo desse princípio podemos. Iremos criar uma condicional em nosso webpack.config quando executarmos o nosso código para produção iremos disparar alertas para remover os console.log de nosso código.

Para essa etapa iremos trabalhar na branch step-11, primeira mudança vamos incluir dois scripts em nosso package.json:

{ "name": "webpack-tutorial", "version": "1.0.0", "description": "Webpack tutorial", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev" : "webpack --watch --mode=development", "build" : "webpack --mode=production" }, "repository": { "type": "git", "url": "git+https://github.com/fellyph/webpack-tutorial.git" }, "keywords": [ "tutorial", "webpack" ], "author": "fellyph", "license": "ISC", "bugs": { "url": "https://github.com/fellyph/webpack-tutorial/issues" }, "homepage": "https://github.com/fellyph/webpack-tutorial#readme", "devDependencies": { "@babel/core": "^7.2.2", "@babel/preset-env": "^7.3.1", "babel-loader": "^8.0.5", "css-loader": "^2.1.0", "eslint": "^5.15.1", "eslint-config-airbnb-base": "^13.1.0", "eslint-config-google": "^0.12.0", "eslint-loader": "^2.1.2", "eslint-plugin-import": "^2.16.0", "file-loader": "^3.0.1", "node-sass": "^4.11.0", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", "url-loader": "^1.1.2", "webpack": "^4.29.0", "webpack-cli": "^3.2.1" } }

Agora temos dois novos scripts dev e build, eles incluem o modo que iremos exportar modo de produção irá criar um código minificado sem source map já o código gerado para desenvolvimento terá recursos que irão facilitar o debug de nosso código.

Quando quisermos exportar a versão de desenvolvimento executamos em nosso terminal o comando:

npm run dev

Ele irá executar o comando “webpack –watch –mode=development” assim executaremos o webpack no modo desenvolvimento ainda inclui mais um parâmetro “–watch” para o webpack ficar assistindo todas as mudanças dentro do projeto assim só precisamos rodar o nosso código uma vez. Para exportar nosso script de produção rodamos o comando:

npm run build

Agora com esses dois novos recursos vamos fazer as seguinte alteração em nosso webpack.config:

const path = require('path'); module.exports = (env, options) => ({ entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], }, }, { loader: 'eslint-loader', options: { fix: true, rules: { 'no-console': (options.mode === 'development') ? 'off' : 'warn', }, }, }, ], }, { test: /\.(sa|sc|c)ss$/, exclude: /(node_modules|bower_components)/, use: [ 'style-loader', 'css-loader', 'sass-loader', ], }, { test: /\.(svg|gif|png|jpe?g)$/, loader: 'url-loader', options: { limit: 100, fallback: 'file-loader', publicPath: '/img', outputPath: '/img', }, }, ], }, });

Temos várias maneiras de verificar se estamos criando código para produção ou desenvolvimento, no caso acima converti module.exports de um objeto para uma função para resgatar o mode dentro das opções definidas em nosso código executado no terminal.

Dentro das opções eslint-loader adicionamos uma condicional para saber se vamos desligar o alerta para o console.log, este é um exemplo para uma regra especifica mas podemos colocar qualquer regra dentro dessa condicional todas as regras você pode conferir nesse link

Caso tenha alguma dúvida deixe um comentário ou continue lendo mais tutoriais javascript

Categorias
JavaScript Tutoriais

Introdução Webpack Parte 2: Loaders

No post anterior vimos uma introdução ao Webpack, criamos uma situação básica carregando múltiplos arquivos JavaScript e vimos que Webpack possui 4 elementos chaves: Entry(arquivo de entrada), output(arquivo de saída), loaders e plugins. Nesse tutorial vamos abordar como trabalhamos como loaders, Webpack em seu core nada mais é que JavaScript carregando JavaScript. Nada mais básico que em uma aplicação web termos uma série de assets com diferentes extensões, jpg, css, js, svg… e como o Webpack se trata com essa situações, a resposta é: Loaders

Loaders

Loaders dão Webpack a possibilidade de processar diferentes tipos de arquivos, define como o Webpack irá ler e exportar os assets do seu projeto. Escritos em Node.JS os loaders precisam regras escritas em expressão regular para definir quais extensões serão carregadas pelo tal.

Temos vários loaders e podemos separa-los nas seguintes categorias:

  • Arquivos
  • JSON
  • Transpiling
  • Templating
  • Styling
  • Linting
  • Frameworks

Para nosso tutorial vamos utilizar os seguintes recursos, babel para fazer o transpiling do nosso ECMAScript2015+ e JSX e Sass para fazer o pre-processamento do nosso estilo. Com esse panorama vamos precisar dos seguintes loaders:

  • Arquivos: url-loader, file-loader
  • Transpiling: babel-loader
  • Styling: style-loader, css-loader, sass-loader

Com essa lista vamos seguir um passo-a-passo e falar sobre cada um desses loaders durante a evolução de nosso projeto.

babel-loader

Hoje desenvolvimento com JavaScript moderno é um requisito quase que obrigatório em projetos web, em nossos exemplos anteriores fizemos o uso de EcmaScript 5, versão suportada por todos os browsers do mercado incluindo internet Explorer, mas se a gente quiser fazer o uso de EcmaScript2015+ e não deixar de fora os usuários de browsers mais antigos, reposta será “transpiling”, ele funciona como uma compilação de código, onde temos EcmaScript 2015+ sendo convertido para EcmaScript 5, no mercado temos algumas ferramentas que realizam esse processo atualmente a ferramenta mais usada no mercado é babel.

Webpack é umas da tecnologias compatíveis com babel, assim podemos fazer o processo de transpiling utilizando babel para criar o nosso bundle. Para isso vamos primeiramente instalar babel-loader em nosso projeto, dentro do nosso terminal, na pasta do seu projeto executamos o seguinte comando:

npm install -D babel-loader @babel/core @babel/preset-env

Nesse código temos a instalação do babel-loader, babel core e preset-env, presets são coleções de plugins para o babel suportar uma certa versão, ainda temos presets para cada versão do EcmaScript, react e typescript. Mas a preset-env tem o foco no target environment, ou seja, no ambiente final. Ele suporta as versões mas recentes do ecmaScript, mas o código gerado conseguimos determinar qual será o nível de suporte. Após a instalação dos pacotes nosso package.json ficará da seguinte forma:

{ "name": "webpack-tutorial", "version": "1.0.0", "description": "Webpack tutorial", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/fellyph/webpack-tutorial.git" }, "keywords": [ "tutorial", "webpack" ], "author": "fellyph", "license": "ISC", "bugs": { "url": "https://github.com/fellyph/webpack-tutorial/issues" }, "homepage": "https://github.com/fellyph/webpack-tutorial#readme", "devDependencies": { "@babel/core": "^7.2.2", "@babel/preset-env": "^7.3.1", "babel-loader": "^8.0.5", "webpack": "^4.29.0", "webpack-cli": "^3.2.1" }, "dependencies": { "npm": "^6.7.0" } }

Depois de instalado os pacotes necessários, vamos incluir o loader em nosso webpack.config.js

const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } };

No código acima adicionamos um novo atributo chamado module, ele será responsável por definir as regras de como os nosso módulos serão carregados, passamos um array de regras(rules) contendo os seguintes parâmetros:

  • test: contém uma expressão regular para achar os arquivos que iremos aplicar a regra, na sua maioria utilizamos a extensão que buscamos.
  • exclude: também uma expressão regular, contendo os arquivos ou pastas que iremos ignorar.
  • use: qual o loader iremos utilizar para esse caso.

Por exemplo, em nosso código definimos que todos arquivos que terminam com a extensão .js iremos usar o loader ‘babel-loader‘, dentro do nosso código de entrada adicionamos EcmaScript2015 se conferimos o código gerado temos, por exemplo, a keyword ‘const’ convertida para ‘var’. O exemplo completo você pode conferir na branch step-6.

style-loader e css-loader

Continuando a evolução de nosso webpack.config.js vamos carregar o nosso estilo, para essa tarefa vamos instalar dois loaders css-loader e style-loader, css-loader vai ser responsável por carregar o css junto com webpack e style-loader vai pegar o conteúdo carregado pelo webpack e exportá-lo em uma tag style. Agora em nosso terminal iremos rodar o seguinte código:

npm i -D style-loader css-loader

Completa a instalação vamos adicionar o loader em nosso webpack.config.js

const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.css$/, exclude: /(node_modules|bower_components)/, use: ['style-loader', 'css-loader'], } ] } };

Após adicionarmos a configuração para carregar nosso css o padrão será o mesmo, regex para achar os arquivos css, quais o loaders iremos utilizar com a propriedade use, adicionei um css simples chamado main.css:

:root { --c-text:#28262C; --c-component-background: #F9F5FF; --c-primary: #14248A; --c-secondary: #D4C2FC; --c-ternary: #998FC7; --e-padding: 1em; } .navigation, .main, .header, .footer { padding: var(--e-padding); } .navigation { background: var(--c-primary); } .main { background: var(--c-component-background); } .header { background: var(--c-component-background); } .footer { background: var(--c-ternary); }

Em nosso index.js vamos carregar o css, isso mesmo, faremos o import dentro do nosso arquivo de entrada:

import './css/main.css'; import './js/content'; import './js/header'; import './js/footer';

Quando executarmos o comando webpack em nossa linha de comando teremos a seguinte saída, o arquivo gerado o main.js e os arquivos que serviram de entrada para nosso bundle. O código para esse loader você irá encontrar na branch step-7

Vamos adicionar uma image ao nosso projeto via css, a imagem abaixo será a imagem que iremos trabalhar voce pode salva-la e adicionar na pasta assets/imgs/

Com a image no local definido vamos fazer a seguinte alteracao em nosso CSS:

:root { --c-text:#28262C; --c-component-background: #F9F5FF; --c-primary: #14248A; --c-secondary: #D4C2FC; --c-ternary: #998FC7; --e-padding: 1em; --header-img: url(../../assets/imgs/webpack-logo.png); } .navigation, .main, .header, .footer { padding: var(--e-padding); } .navigation { background: var(--c-primary); } .main { background: var(--c-component-background); } .header { background: var(--header-img); background-repeat: no-repeat; } .footer { background: var(--c-ternary); }

Vamos adicionar a nossa imagem como background do nosso header e tentar compilar o nosso código:

Temos um erro de compilacao do nosso codigo

Isso acontece porque não temos o loader apropriado para a extensão .png, para esse caso podemos trabalhar com loaders:

  • file-loader: irá carregar o arquivo e criar uma copia do arquivo em nossa pasta de output(dist)
  • url-loader: irá carregar o arquivo especificado e irá criar uma copia no formato base64.

Para corrigir o nosso projeto iremos instalar dos dois loaders:

npm i -D file-loader url-loader

Com os loaders instalados vamos combinar os dois loaders em uma solução, arquivos pequenos convertemos em base64 e arquivos pesado criamos uma copia em nossa pasta dist, como podemos ver em nosso webpack.config.js (step-8):

const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.css$/, exclude: /(node_modules|bower_components)/, use: ['style-loader', 'css-loader'], }, { test: /\.(svg|gif|png|jpe?g)$/, loader: 'url-loader', options: { limit: 100, fallback: 'file-loader', publicPath: '/img', outputPath: '/img', }, }, ] } };

Agora temos loaders para nossos arquivos .js .css e images, caso queiramos trabalhar com pre-processadores, por exemplo, SASS ou LESS, parra o nosso tutorial iremos trabalhar com SASS mas o processo é similar para o LESS. Primeiro em nosso terminal iremos instalar os pacotes necessários:

npm install sass-loader node-sass --save-dev

Com tudo pronto vamos alterar a regra de leitura no nosso css para também aceitar arquivos SASS.

const path = require('path'); module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.(sa|sc|c)ss$/, exclude: /(node_modules|bower_components)/, use: [ 'style-loader', 'css-loader', 'sass-loader', ], }, { test: /\.(svg|gif|png|jpe?g)$/, loader: 'url-loader', options: { limit: 100, fallback: 'file-loader', publicPath: '/img', outputPath: '/img', }, }, ] } };

A principal diferença em nosso webpack.config.js é regex que anteriomente procurava arquivos com a extensão .css agora com a regra ‘(sa|sc|c)ss’ procura arquivos .sass .scss ou .css e por fim adicionamento o sass-loader, se trocarmos a extensão do nosso arquivo .css por .scss ele irá carregar normalmente. Exemplo:

_variables.scss

$c-text:#28262C; $c-component-background: #F9F5FF; $c-primary: #14248A; $c-secondary: #D4C2FC; $c-ternary: #998FC7; $e-padding: 1em; $header-img: url(../../assets/imgs/webpack-logo.png);

main.scss

@import 'variables'; .navigation, .main, .header, .footer { padding: $e-padding; } .navigation { background: $c-primary; } .main { background: $c-component-background; } .header { background: $header-img; background-repeat: no-repeat; } .footer { background: $c-ternary; }

O código completo desse tutorial você irá encontrar na branch step-9 E até o próximo tutorial. Veja mais posts sobre webpack aqui.